added .linkfix <old> <new> - bot will automatically reply to any messages containing <old> domain with a new one
This commit is contained in:
parent
4d3bdc2481
commit
55e3a80405
14 changed files with 507 additions and 5 deletions
src/EllieBot
Migrations
PostgreSql
20250323021916_linkfixer.sql20250323022235_init.Designer.cs20250323022235_init.csPostgreSqlContextModelSnapshot.cs
Sqlite
Modules/Utility/LinkFixer
strings
|
@ -0,0 +1,16 @@
|
||||||
|
START TRANSACTION;
|
||||||
|
CREATE TABLE linkfix (
|
||||||
|
id integer GENERATED BY DEFAULT AS IDENTITY,
|
||||||
|
guildid numeric(20,0) NOT NULL,
|
||||||
|
olddomain text NOT NULL,
|
||||||
|
newdomain text NOT NULL,
|
||||||
|
CONSTRAINT pk_linkfix PRIMARY KEY (id)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX ix_linkfix_guildid_olddomain ON linkfix (guildid, olddomain);
|
||||||
|
|
||||||
|
INSERT INTO "__EFMigrationsHistory" (migrationid, productversion)
|
||||||
|
VALUES ('20250323021916_linkfixer', '9.0.1');
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
|
|
@ -12,7 +12,7 @@ using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||||
namespace EllieBot.Migrations.PostgreSql
|
namespace EllieBot.Migrations.PostgreSql
|
||||||
{
|
{
|
||||||
[DbContext(typeof(PostgreSqlContext))]
|
[DbContext(typeof(PostgreSqlContext))]
|
||||||
[Migration("20250319010930_init")]
|
[Migration("20250323022235_init")]
|
||||||
partial class init
|
partial class init
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -1541,6 +1541,39 @@ namespace EllieBot.Migrations.PostgreSql
|
||||||
b.ToTable("imageonlychannels", (string)null);
|
b.ToTable("imageonlychannels", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("EllieBot.Db.Models.LinkFix", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<decimal>("GuildId")
|
||||||
|
.HasColumnType("numeric(20,0)")
|
||||||
|
.HasColumnName("guildid");
|
||||||
|
|
||||||
|
b.Property<string>("NewDomain")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasColumnName("newdomain");
|
||||||
|
|
||||||
|
b.Property<string>("OldDomain")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasColumnName("olddomain");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_linkfix");
|
||||||
|
|
||||||
|
b.HasIndex("GuildId", "OldDomain")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("ix_linkfix_guildid_olddomain");
|
||||||
|
|
||||||
|
b.ToTable("linkfix", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("EllieBot.Db.Models.LiveChannelConfig", b =>
|
modelBuilder.Entity("EllieBot.Db.Models.LiveChannelConfig", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
|
@ -550,6 +550,21 @@ namespace EllieBot.Migrations.PostgreSql
|
||||||
table.PrimaryKey("pk_imageonlychannels", x => x.id);
|
table.PrimaryKey("pk_imageonlychannels", x => x.id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "linkfix",
|
||||||
|
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),
|
||||||
|
olddomain = table.Column<string>(type: "text", nullable: false),
|
||||||
|
newdomain = table.Column<string>(type: "text", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("pk_linkfix", x => x.id);
|
||||||
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "livechannelconfig",
|
name: "livechannelconfig",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
|
@ -1999,6 +2014,12 @@ namespace EllieBot.Migrations.PostgreSql
|
||||||
column: "channelid",
|
column: "channelid",
|
||||||
unique: true);
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "ix_linkfix_guildid_olddomain",
|
||||||
|
table: "linkfix",
|
||||||
|
columns: new[] { "guildid", "olddomain" },
|
||||||
|
unique: true);
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "ix_livechannelconfig_guildid",
|
name: "ix_livechannelconfig_guildid",
|
||||||
table: "livechannelconfig",
|
table: "livechannelconfig",
|
||||||
|
@ -2501,6 +2522,9 @@ namespace EllieBot.Migrations.PostgreSql
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "imageonlychannels");
|
name: "imageonlychannels");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "linkfix");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "livechannelconfig");
|
name: "livechannelconfig");
|
||||||
|
|
|
@ -1538,6 +1538,39 @@ namespace EllieBot.Migrations.PostgreSql
|
||||||
b.ToTable("imageonlychannels", (string)null);
|
b.ToTable("imageonlychannels", (string)null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("EllieBot.Db.Models.LinkFix", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("integer")
|
||||||
|
.HasColumnName("id");
|
||||||
|
|
||||||
|
NpgsqlPropertyBuilderExtensions.UseIdentityByDefaultColumn(b.Property<int>("Id"));
|
||||||
|
|
||||||
|
b.Property<decimal>("GuildId")
|
||||||
|
.HasColumnType("numeric(20,0)")
|
||||||
|
.HasColumnName("guildid");
|
||||||
|
|
||||||
|
b.Property<string>("NewDomain")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasColumnName("newdomain");
|
||||||
|
|
||||||
|
b.Property<string>("OldDomain")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("text")
|
||||||
|
.HasColumnName("olddomain");
|
||||||
|
|
||||||
|
b.HasKey("Id")
|
||||||
|
.HasName("pk_linkfix");
|
||||||
|
|
||||||
|
b.HasIndex("GuildId", "OldDomain")
|
||||||
|
.IsUnique()
|
||||||
|
.HasDatabaseName("ix_linkfix_guildid_olddomain");
|
||||||
|
|
||||||
|
b.ToTable("linkfix", (string)null);
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("EllieBot.Db.Models.LiveChannelConfig", b =>
|
modelBuilder.Entity("EllieBot.Db.Models.LiveChannelConfig", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
|
|
15
src/EllieBot/Migrations/Sqlite/20250323021857_linkfixer.sql
Normal file
15
src/EllieBot/Migrations/Sqlite/20250323021857_linkfixer.sql
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
BEGIN TRANSACTION;
|
||||||
|
CREATE TABLE "LinkFix" (
|
||||||
|
"Id" INTEGER NOT NULL CONSTRAINT "PK_LinkFix" PRIMARY KEY AUTOINCREMENT,
|
||||||
|
"GuildId" INTEGER NOT NULL,
|
||||||
|
"OldDomain" TEXT NOT NULL,
|
||||||
|
"NewDomain" TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE UNIQUE INDEX "IX_LinkFix_GuildId_OldDomain" ON "LinkFix" ("GuildId", "OldDomain");
|
||||||
|
|
||||||
|
INSERT INTO "__EFMigrationsHistory" ("MigrationId", "ProductVersion")
|
||||||
|
VALUES ('20250323021857_linkfixer', '9.0.1');
|
||||||
|
|
||||||
|
COMMIT;
|
||||||
|
|
|
@ -11,7 +11,7 @@ using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||||
namespace EllieBot.Migrations.Sqlite
|
namespace EllieBot.Migrations.Sqlite
|
||||||
{
|
{
|
||||||
[DbContext(typeof(SqliteContext))]
|
[DbContext(typeof(SqliteContext))]
|
||||||
[Migration("20250319010920_init")]
|
[Migration("20250323022218_init")]
|
||||||
partial class init
|
partial class init
|
||||||
{
|
{
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
|
@ -1151,6 +1151,31 @@ namespace EllieBot.Migrations.Sqlite
|
||||||
b.ToTable("ImageOnlyChannels");
|
b.ToTable("ImageOnlyChannels");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("EllieBot.Db.Models.LinkFix", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<ulong>("GuildId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("NewDomain")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("OldDomain")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("GuildId", "OldDomain")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("LinkFix");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("EllieBot.Db.Models.LiveChannelConfig", b =>
|
modelBuilder.Entity("EllieBot.Db.Models.LiveChannelConfig", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
|
@ -550,6 +550,21 @@ namespace EllieBot.Migrations.Sqlite
|
||||||
table.PrimaryKey("PK_ImageOnlyChannels", x => x.Id);
|
table.PrimaryKey("PK_ImageOnlyChannels", x => x.Id);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "LinkFix",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "INTEGER", nullable: false)
|
||||||
|
.Annotation("Sqlite:Autoincrement", true),
|
||||||
|
GuildId = table.Column<ulong>(type: "INTEGER", nullable: false),
|
||||||
|
OldDomain = table.Column<string>(type: "TEXT", nullable: false),
|
||||||
|
NewDomain = table.Column<string>(type: "TEXT", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_LinkFix", x => x.Id);
|
||||||
|
});
|
||||||
|
|
||||||
migrationBuilder.CreateTable(
|
migrationBuilder.CreateTable(
|
||||||
name: "LiveChannelConfig",
|
name: "LiveChannelConfig",
|
||||||
columns: table => new
|
columns: table => new
|
||||||
|
@ -2001,6 +2016,12 @@ namespace EllieBot.Migrations.Sqlite
|
||||||
column: "ChannelId",
|
column: "ChannelId",
|
||||||
unique: true);
|
unique: true);
|
||||||
|
|
||||||
|
migrationBuilder.CreateIndex(
|
||||||
|
name: "IX_LinkFix_GuildId_OldDomain",
|
||||||
|
table: "LinkFix",
|
||||||
|
columns: new[] { "GuildId", "OldDomain" },
|
||||||
|
unique: true);
|
||||||
|
|
||||||
migrationBuilder.CreateIndex(
|
migrationBuilder.CreateIndex(
|
||||||
name: "IX_LiveChannelConfig_GuildId",
|
name: "IX_LiveChannelConfig_GuildId",
|
||||||
table: "LiveChannelConfig",
|
table: "LiveChannelConfig",
|
||||||
|
@ -2503,6 +2524,9 @@ namespace EllieBot.Migrations.Sqlite
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "ImageOnlyChannels");
|
name: "ImageOnlyChannels");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "LinkFix");
|
||||||
|
|
||||||
migrationBuilder.DropTable(
|
migrationBuilder.DropTable(
|
||||||
name: "LiveChannelConfig");
|
name: "LiveChannelConfig");
|
||||||
|
|
|
@ -1148,6 +1148,31 @@ namespace EllieBot.Migrations.Sqlite
|
||||||
b.ToTable("ImageOnlyChannels");
|
b.ToTable("ImageOnlyChannels");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("EllieBot.Db.Models.LinkFix", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<ulong>("GuildId")
|
||||||
|
.HasColumnType("INTEGER");
|
||||||
|
|
||||||
|
b.Property<string>("NewDomain")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.Property<string>("OldDomain")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("TEXT");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.HasIndex("GuildId", "OldDomain")
|
||||||
|
.IsUnique();
|
||||||
|
|
||||||
|
b.ToTable("LinkFix");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("EllieBot.Db.Models.LiveChannelConfig", b =>
|
modelBuilder.Entity("EllieBot.Db.Models.LiveChannelConfig", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
|
|
40
src/EllieBot/Modules/Utility/LinkFixer/LinkFix.cs
Normal file
40
src/EllieBot/Modules/Utility/LinkFixer/LinkFix.cs
Normal file
|
@ -0,0 +1,40 @@
|
||||||
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata.Builders;
|
||||||
|
|
||||||
|
namespace EllieBot.Db.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents a link fix configuration for a guild
|
||||||
|
/// </summary>
|
||||||
|
public class LinkFix
|
||||||
|
{
|
||||||
|
[Key]
|
||||||
|
public int Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// ID of the guild this link fix belongs to
|
||||||
|
/// </summary>
|
||||||
|
public ulong GuildId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The domain to be replaced
|
||||||
|
/// </summary>
|
||||||
|
public string OldDomain { get; set; } = null!;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The domain to replace with
|
||||||
|
/// </summary>
|
||||||
|
public string NewDomain { get; set; } = null!;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Entity configuration for <see cref="LinkFix"/>
|
||||||
|
/// </summary>
|
||||||
|
public class LinkFixConfiguration : IEntityTypeConfiguration<LinkFix>
|
||||||
|
{
|
||||||
|
public void Configure(EntityTypeBuilder<LinkFix> builder)
|
||||||
|
{
|
||||||
|
builder.HasIndex(x => new { x.GuildId, x.OldDomain }).IsUnique();
|
||||||
|
}
|
||||||
|
}
|
100
src/EllieBot/Modules/Utility/LinkFixer/LinkFixerCommands.cs
Normal file
100
src/EllieBot/Modules/Utility/LinkFixer/LinkFixerCommands.cs
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
using DryIoc.ImTools;
|
||||||
|
using EllieBot.Modules.Utility.LinkFixer;
|
||||||
|
|
||||||
|
namespace EllieBot.Modules.Utility;
|
||||||
|
|
||||||
|
public partial class Utility
|
||||||
|
{
|
||||||
|
[Group]
|
||||||
|
public class LinkFixerCommands : EllieModule<LinkFixerService>
|
||||||
|
{
|
||||||
|
[Cmd]
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
|
[UserPerm(GuildPerm.ManageMessages)]
|
||||||
|
public async Task LinkFix(string oldDomain, string? newDomain = null)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrWhiteSpace(newDomain))
|
||||||
|
{
|
||||||
|
var rmSuccess = await _service.RemoveLinkFixAsync(ctx.Guild.Id, oldDomain);
|
||||||
|
|
||||||
|
if (rmSuccess)
|
||||||
|
await Response().Confirm(strs.linkfix_removed(Format.Bold(oldDomain))).SendAsync();
|
||||||
|
else
|
||||||
|
await Response().Error(strs.linkfix_not_found(Format.Bold(oldDomain))).SendAsync();
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
oldDomain = CleanDomain(oldDomain);
|
||||||
|
newDomain = newDomain.Trim();
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(oldDomain) || string.IsNullOrWhiteSpace(newDomain))
|
||||||
|
{
|
||||||
|
await Response().Error(strs.linkfix_invalid_domains).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var success = await _service.AddLinkFixAsync(ctx.Guild.Id, oldDomain, newDomain);
|
||||||
|
if (success)
|
||||||
|
await Response().Confirm(strs.linkfix_added(Format.Bold(oldDomain), Format.Bold(newDomain))).SendAsync();
|
||||||
|
else
|
||||||
|
await Response().Error(strs.linkfix_already_exists(Format.Bold(oldDomain))).SendAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
|
public async Task LinkFixList()
|
||||||
|
{
|
||||||
|
var linkFixes = _service.GetLinkFixes(ctx.Guild.Id);
|
||||||
|
if (linkFixes.Count == 0)
|
||||||
|
{
|
||||||
|
await Response().Confirm(strs.linkfix_list_none).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var items = linkFixes.Select(x => $"{Format.Bold(x.Key)} -> {Format.Bold(x.Value)}").ToList();
|
||||||
|
|
||||||
|
await Response()
|
||||||
|
.Paginated()
|
||||||
|
.Items(items)
|
||||||
|
.PageSize(10)
|
||||||
|
.Page((items, _) =>
|
||||||
|
{
|
||||||
|
var eb = CreateEmbed()
|
||||||
|
.WithTitle(GetText(strs.linkfix_list_title))
|
||||||
|
.WithDescription(string.Join('\n', items))
|
||||||
|
.WithOkColor();
|
||||||
|
|
||||||
|
return eb;
|
||||||
|
})
|
||||||
|
.SendAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes protocol and www. from a domain
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="domain">The domain to clean</param>
|
||||||
|
private static string CleanDomain(string domain)
|
||||||
|
{
|
||||||
|
// Remove protocol if present
|
||||||
|
if (domain.StartsWith("http://", StringComparison.OrdinalIgnoreCase))
|
||||||
|
domain = domain[7..];
|
||||||
|
else if (domain.StartsWith("https://", StringComparison.OrdinalIgnoreCase))
|
||||||
|
domain = domain[8..];
|
||||||
|
|
||||||
|
// Remove www. if present
|
||||||
|
if (domain.StartsWith("www.", StringComparison.OrdinalIgnoreCase))
|
||||||
|
domain = domain[4..];
|
||||||
|
|
||||||
|
// Remove any path or query string
|
||||||
|
var pathIndex = domain.IndexOf('/');
|
||||||
|
if (pathIndex > 0)
|
||||||
|
domain = domain[..pathIndex];
|
||||||
|
|
||||||
|
if (domain.Split('.').Length != 2)
|
||||||
|
return string.Empty;
|
||||||
|
|
||||||
|
return domain.ToLowerInvariant();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
133
src/EllieBot/Modules/Utility/LinkFixer/LinkFixerService.cs
Normal file
133
src/EllieBot/Modules/Utility/LinkFixer/LinkFixerService.cs
Normal file
|
@ -0,0 +1,133 @@
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
|
using LinqToDB;
|
||||||
|
using LinqToDB.EntityFrameworkCore;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using EllieBot.Common.ModuleBehaviors;
|
||||||
|
using EllieBot.Db.Models;
|
||||||
|
|
||||||
|
namespace EllieBot.Modules.Utility.LinkFixer;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Service for managing link fixing functionality
|
||||||
|
/// </summary>
|
||||||
|
public partial class LinkFixerService(DbService db) : IReadyExecutor, IExecNoCommand, IEService
|
||||||
|
{
|
||||||
|
private readonly ConcurrentDictionary<ulong, ConcurrentDictionary<string, string>> _guildLinkFixes = new();
|
||||||
|
|
||||||
|
public async Task OnReadyAsync()
|
||||||
|
{
|
||||||
|
await using var uow = db.GetDbContext();
|
||||||
|
var linkFixes = await uow.GetTable<LinkFix>()
|
||||||
|
.AsNoTracking()
|
||||||
|
.ToListAsyncLinqToDB();
|
||||||
|
|
||||||
|
foreach (var fix in linkFixes)
|
||||||
|
{
|
||||||
|
var guildDict = _guildLinkFixes.GetOrAdd(fix.GuildId, _ => new(StringComparer.InvariantCultureIgnoreCase));
|
||||||
|
guildDict.TryAdd(fix.OldDomain.ToLowerInvariant(), fix.NewDomain);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ExecOnNoCommandAsync(IGuild guild, IUserMessage msg)
|
||||||
|
{
|
||||||
|
if(guild is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var guildId = guild.Id;
|
||||||
|
if (!_guildLinkFixes.TryGetValue(guildId, out var guildDict))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var content = msg.Content;
|
||||||
|
if (string.IsNullOrWhiteSpace(content))
|
||||||
|
return;
|
||||||
|
|
||||||
|
var words = content.Split(' ', StringSplitOptions.RemoveEmptyEntries);
|
||||||
|
foreach (var word in words)
|
||||||
|
{
|
||||||
|
var match = UrlRegex().Match(word);
|
||||||
|
if (!match.Success)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var domain = match.Groups["domain"].Value;
|
||||||
|
if (string.IsNullOrWhiteSpace(domain))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if(!guildDict.TryGetValue(domain, out var newDomain))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var newUrl = match.Groups["prefix"].Value + newDomain + match.Groups["suffix"].Value;
|
||||||
|
await msg.ReplyAsync(newUrl, allowedMentions: AllowedMentions.None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[GeneratedRegex("(?<prefix>https?://(?:www\\.)?)(?<domain>[^/]+)(?<suffix>.*)")]
|
||||||
|
private partial Regex UrlRegex();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Adds a new link fix for a guild
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="guildId">ID of the guild</param>
|
||||||
|
/// <param name="oldDomain">Domain to be replaced</param>
|
||||||
|
/// <param name="newDomain">Domain to replace with</param>
|
||||||
|
/// <returns>True if successfully added, false if already exists</returns>
|
||||||
|
public async Task<bool> AddLinkFixAsync(ulong guildId, string oldDomain, string newDomain)
|
||||||
|
{
|
||||||
|
oldDomain = oldDomain.ToLowerInvariant();
|
||||||
|
|
||||||
|
var guildDict = _guildLinkFixes.GetOrAdd(guildId, _ => new ConcurrentDictionary<string, string>());
|
||||||
|
guildDict[oldDomain] = newDomain;
|
||||||
|
|
||||||
|
await using var uow = db.GetDbContext();
|
||||||
|
await uow.GetTable<LinkFix>()
|
||||||
|
.InsertOrUpdateAsync(() => new LinkFix
|
||||||
|
{
|
||||||
|
GuildId = guildId,
|
||||||
|
OldDomain = oldDomain,
|
||||||
|
NewDomain = newDomain
|
||||||
|
},
|
||||||
|
old => new LinkFix
|
||||||
|
{
|
||||||
|
NewDomain = newDomain
|
||||||
|
},
|
||||||
|
() => new LinkFix
|
||||||
|
{
|
||||||
|
GuildId = guildId,
|
||||||
|
OldDomain = oldDomain,
|
||||||
|
});
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Removes a link fix from a guild
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="guildId">ID of the guild</param>
|
||||||
|
/// <param name="oldDomain">Domain to remove from fixes</param>
|
||||||
|
/// <returns>True if successfully removed, false if not found</returns>
|
||||||
|
public async Task<bool> RemoveLinkFixAsync(ulong guildId, string oldDomain)
|
||||||
|
{
|
||||||
|
oldDomain = oldDomain.ToLowerInvariant();
|
||||||
|
|
||||||
|
if (!_guildLinkFixes.TryGetValue(guildId, out var guildDict) || !guildDict.TryRemove(oldDomain, out _))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
await using var uow = db.GetDbContext();
|
||||||
|
await uow.GetTable<LinkFix>()
|
||||||
|
.DeleteAsync(lf => lf.GuildId == guildId && lf.OldDomain == oldDomain);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets all link fixes for a guild
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="guildId">ID of the guild</param>
|
||||||
|
/// <returns>Dictionary of old domains to new domains</returns>
|
||||||
|
public IReadOnlyDictionary<string, string> GetLinkFixes(ulong guildId)
|
||||||
|
{
|
||||||
|
if (_guildLinkFixes.TryGetValue(guildId, out var guildDict))
|
||||||
|
return guildDict;
|
||||||
|
|
||||||
|
return new Dictionary<string, string>();
|
||||||
|
}
|
||||||
|
}
|
|
@ -1644,4 +1644,10 @@ livechlist:
|
||||||
livechremove:
|
livechremove:
|
||||||
- livechremove
|
- livechremove
|
||||||
- lchd
|
- lchd
|
||||||
- lchrm
|
- lchrm
|
||||||
|
linkfix:
|
||||||
|
- linkfix
|
||||||
|
- lfix
|
||||||
|
linkfixlist:
|
||||||
|
- linkfixlist
|
||||||
|
- lfixlist
|
|
@ -5148,4 +5148,24 @@ livechremove:
|
||||||
- '#general'
|
- '#general'
|
||||||
params:
|
params:
|
||||||
- channel:
|
- channel:
|
||||||
desc: "The channel to remove from live channels."
|
desc: "The channel to remove from live channels."
|
||||||
|
linkfix:
|
||||||
|
desc: |-
|
||||||
|
Configures automatic link fixing from one site to another.
|
||||||
|
When a user posts a link containing the old domain, the bot will automatically fix it to use the new domain.
|
||||||
|
Provide no second domain to disable link fixing.
|
||||||
|
ex:
|
||||||
|
- 'twitter.com vxtwitter.com'
|
||||||
|
- 'x.com'
|
||||||
|
params:
|
||||||
|
- oldDomain:
|
||||||
|
desc: "The domain to be replaced."
|
||||||
|
newDomain:
|
||||||
|
desc: "The domain to replace with."
|
||||||
|
linkfixlist:
|
||||||
|
desc: |-
|
||||||
|
Lists all configured link fixes for the server.
|
||||||
|
ex:
|
||||||
|
- ''
|
||||||
|
params:
|
||||||
|
- { }
|
|
@ -1230,5 +1230,13 @@
|
||||||
"livechannel_list_empty": "No live channels configured for this server.",
|
"livechannel_list_empty": "No live channels configured for this server.",
|
||||||
"livechannel_please_wait": "Please allow up to 10 minutes for the changes to take effect",
|
"livechannel_please_wait": "Please allow up to 10 minutes for the changes to take effect",
|
||||||
"template": "Template",
|
"template": "Template",
|
||||||
"preview": "Preview"
|
"preview": "Preview",
|
||||||
|
"linkfix_invalid_domains": "Both old and new domains must be valid.",
|
||||||
|
"linkfix_invalid_domain": "The domain must be valid.",
|
||||||
|
"linkfix_added": "Links from {0} will now be fixed to {1}.",
|
||||||
|
"linkfix_already_exists": "A link fix for {0} already exists.",
|
||||||
|
"linkfix_list_none": "No link fixes have been configured for this server.",
|
||||||
|
"linkfix_list_title": "Link Fixes",
|
||||||
|
"linkfix_removed": "Link fix for {0} has been removed.",
|
||||||
|
"linkfix_not_found": "No link fix found for {0}."
|
||||||
}
|
}
|
Loading…
Add table
Reference in a new issue