Compare commits
4 commits
4d3bdc2481
...
7246c982df
Author | SHA1 | Date | |
---|---|---|---|
7246c982df | |||
d26efb3c8c | |||
16025b74e3 | |||
55e3a80405 |
21 changed files with 693 additions and 37 deletions
CHANGELOG.md
src/EllieBot
EllieBot.csproj
Migrations
PostgreSql
20250323021916_linkfixer.sql20250323022235_init.Designer.cs20250323022235_init.csPostgreSqlContextModelSnapshot.cs
Sqlite
Modules
Administration/Role
Gambling/Shop
Utility
data
strings
17
CHANGELOG.md
17
CHANGELOG.md
|
@ -2,6 +2,23 @@
|
||||||
|
|
||||||
*a,c,f,r,o*
|
*a,c,f,r,o*
|
||||||
|
|
||||||
|
## [6.0.13] - 23.03.2025
|
||||||
|
|
||||||
|
### Added
|
||||||
|
- Added `.linkfix <old> <new>` command
|
||||||
|
- If bot sees a message with the old link, it will reply to the message with a fixed (new) link
|
||||||
|
- ex: `.linkfix twitter.com vxtwitter.com`
|
||||||
|
- Added `.roleicon role <icon_url / server_emoji>` command to set the icon of a role
|
||||||
|
- Added a captcha option for `.fish`
|
||||||
|
|
||||||
|
### Fixed
|
||||||
|
- Fixed youtube stream notifications in case invalid channel was provided
|
||||||
|
- `.lcha` (live channel) will now let you override an existing channel template even if you're at the limit
|
||||||
|
- Fixed `.shop` commands
|
||||||
|
|
||||||
|
### Removed
|
||||||
|
- removed `.xpglb` as it is no longer used
|
||||||
|
|
||||||
## [6.0.12] - 20.03.2025
|
## [6.0.12] - 20.03.2025
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
<ImplicitUsings>true</ImplicitUsings>
|
<ImplicitUsings>true</ImplicitUsings>
|
||||||
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
<SatelliteResourceLanguages>en</SatelliteResourceLanguages>
|
||||||
<Version>6.0.12</Version>
|
<Version>6.0.13</Version>
|
||||||
|
|
||||||
<!-- Output/build -->
|
<!-- Output/build -->
|
||||||
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
|
<RunWorkingDirectory>$(MSBuildProjectDirectory)</RunWorkingDirectory>
|
||||||
|
|
|
@ -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")
|
||||||
|
|
|
@ -48,9 +48,9 @@ public partial class Administration
|
||||||
});
|
});
|
||||||
|
|
||||||
await Response()
|
await Response()
|
||||||
.Confirm(strs.setrole(Format.Bold(roleToAdd.Name),
|
.Confirm(strs.setrole(Format.Bold(roleToAdd.Name),
|
||||||
Format.Bold(targetUser.ToString())))
|
Format.Bold(targetUser.ToString())))
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
@ -73,9 +73,9 @@ public partial class Administration
|
||||||
{
|
{
|
||||||
await targetUser.RemoveRoleAsync(roleToRemove);
|
await targetUser.RemoveRoleAsync(roleToRemove);
|
||||||
await Response()
|
await Response()
|
||||||
.Confirm(strs.remrole(Format.Bold(roleToRemove.Name),
|
.Confirm(strs.remrole(Format.Bold(roleToRemove.Name),
|
||||||
Format.Bold(targetUser.ToString())))
|
Format.Bold(targetUser.ToString())))
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
@ -226,8 +226,8 @@ public partial class Administration
|
||||||
if (!await CheckRoleHierarchy(role))
|
if (!await CheckRoleHierarchy(role))
|
||||||
{
|
{
|
||||||
await Response()
|
await Response()
|
||||||
.Error(strs.hierarchy)
|
.Error(strs.hierarchy)
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -236,10 +236,83 @@ public partial class Administration
|
||||||
|
|
||||||
|
|
||||||
await Response()
|
await Response()
|
||||||
.Confirm(strs.temp_role_added(user.Mention,
|
.Confirm(strs.temp_role_added(user.Mention,
|
||||||
Format.Bold(role.Name),
|
Format.Bold(role.Name),
|
||||||
TimestampTag.FromDateTime(DateTime.UtcNow.Add(timespan.Time), TimestampTagStyles.Relative)))
|
TimestampTag.FromDateTime(DateTime.UtcNow.Add(timespan.Time), TimestampTagStyles.Relative)))
|
||||||
.SendAsync();
|
.SendAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
|
[UserPerm(GuildPerm.ManageRoles)]
|
||||||
|
[BotPerm(GuildPerm.ManageRoles)]
|
||||||
|
public Task RoleIcon(IRole role, Emote emote)
|
||||||
|
=> RoleIcon(role, emote.Url);
|
||||||
|
|
||||||
|
[Cmd]
|
||||||
|
[RequireContext(ContextType.Guild)]
|
||||||
|
[UserPerm(GuildPerm.ManageRoles)]
|
||||||
|
[BotPerm(GuildPerm.ManageRoles)]
|
||||||
|
public async Task RoleIcon(IRole role, [Leftover] string iconUrl)
|
||||||
|
{
|
||||||
|
if (!await CheckRoleHierarchy(role))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (string.IsNullOrWhiteSpace(iconUrl))
|
||||||
|
{
|
||||||
|
await Response().Error(strs.userrole_icon_invalid).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate the URL format
|
||||||
|
if (!Uri.TryCreate(iconUrl, UriKind.Absolute, out var uri))
|
||||||
|
{
|
||||||
|
await Response().Error(strs.userrole_icon_invalid).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Download the image
|
||||||
|
using var httpClient = new HttpClient();
|
||||||
|
using var response = await httpClient.GetAsync(uri, HttpCompletionOption.ResponseHeadersRead);
|
||||||
|
|
||||||
|
// Check if the response is successful
|
||||||
|
if (!response.IsSuccessStatusCode)
|
||||||
|
{
|
||||||
|
await Response().Error(strs.userrole_icon_fail).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check content type - must be image/png or image/jpeg
|
||||||
|
var contentType = response.Content.Headers.ContentType?.MediaType?.ToLower();
|
||||||
|
if (contentType != "image/png"
|
||||||
|
&& contentType != "image/jpeg"
|
||||||
|
&& contentType != "image/webp")
|
||||||
|
{
|
||||||
|
await Response().Error(strs.userrole_icon_fail).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check file size - Discord limit is 256KB
|
||||||
|
var contentLength = response.Content.Headers.ContentLength;
|
||||||
|
if (contentLength is > 256 * 1024)
|
||||||
|
{
|
||||||
|
await Response().Error(strs.userrole_icon_fail).SendAsync();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the image to a memory stream
|
||||||
|
await using var stream = await response.Content.ReadAsStreamAsync();
|
||||||
|
await using var memoryStream = new MemoryStream();
|
||||||
|
await stream.CopyToAsync(memoryStream);
|
||||||
|
memoryStream.Position = 0;
|
||||||
|
|
||||||
|
// Create Discord image from stream
|
||||||
|
using var discordImage = new Image(memoryStream);
|
||||||
|
|
||||||
|
// Upload the image to Discord
|
||||||
|
await role.ModifyAsync(r => r.Icon = discordImage);
|
||||||
|
|
||||||
|
await Response().Confirm(strs.userrole_icon_success(Format.Bold(role.Name))).SendAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -332,17 +332,18 @@ public partial class Gambling
|
||||||
Type = ShopEntryType.Role,
|
Type = ShopEntryType.Role,
|
||||||
AuthorId = ctx.User.Id,
|
AuthorId = ctx.User.Id,
|
||||||
RoleId = role.Id,
|
RoleId = role.Id,
|
||||||
RoleName = role.Name
|
RoleName = role.Name,
|
||||||
|
GuildId = ctx.Guild.Id,
|
||||||
};
|
};
|
||||||
await using (var uow = _db.GetDbContext())
|
await using (var uow = _db.GetDbContext())
|
||||||
{
|
{
|
||||||
var entries = new IndexedCollection<ShopEntry>(await uow.Set<ShopEntry>()
|
var entries = new IndexedCollection<ShopEntry>(await uow.Set<ShopEntry>()
|
||||||
.Where(x => x.GuildId == ctx.Guild.Id)
|
.Where(x => x.GuildId == ctx.Guild.Id)
|
||||||
.Include(x => x.Items)
|
.Include(x => x.Items)
|
||||||
.ToListAsyncEF())
|
.ToListAsyncEF());
|
||||||
{
|
|
||||||
entry
|
entries.Add(entry);
|
||||||
};
|
uow.Add(entry);
|
||||||
await uow.SaveChangesAsync();
|
await uow.SaveChangesAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -363,7 +364,8 @@ public partial class Gambling
|
||||||
Price = price,
|
Price = price,
|
||||||
Type = ShopEntryType.List,
|
Type = ShopEntryType.List,
|
||||||
AuthorId = ctx.User.Id,
|
AuthorId = ctx.User.Id,
|
||||||
Items = new()
|
Items = new(),
|
||||||
|
GuildId = ctx.Guild.Id
|
||||||
};
|
};
|
||||||
await using (var uow = _db.GetDbContext())
|
await using (var uow = _db.GetDbContext())
|
||||||
{
|
{
|
||||||
|
@ -440,7 +442,7 @@ public partial class Gambling
|
||||||
var items = await uow.Set<ShopEntry>()
|
var items = await uow.Set<ShopEntry>()
|
||||||
.Where(x => x.GuildId == ctx.Guild.Id)
|
.Where(x => x.GuildId == ctx.Guild.Id)
|
||||||
.Include(x => x.Items)
|
.Include(x => x.Items)
|
||||||
.ToListAsyncLinqToDB();
|
.ToListAsyncEF();
|
||||||
|
|
||||||
var entries = new IndexedCollection<ShopEntry>(items);
|
var entries = new IndexedCollection<ShopEntry>(items);
|
||||||
removed = entries.ElementAtOrDefault(index);
|
removed = entries.ElementAtOrDefault(index);
|
||||||
|
|
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>();
|
||||||
|
}
|
||||||
|
}
|
|
@ -154,7 +154,7 @@ public sealed class UserRoleService : IUserRoleService, IEService
|
||||||
memoryStream.Position = 0;
|
memoryStream.Position = 0;
|
||||||
|
|
||||||
// Create Discord image from stream
|
// Create Discord image from stream
|
||||||
var discordImage = new Image(memoryStream);
|
using var discordImage = new Image(memoryStream);
|
||||||
|
|
||||||
// Upload the image to Discord
|
// Upload the image to Discord
|
||||||
var discordSuccess = await _discordRoleManager.ModifyRoleAsync(
|
var discordSuccess = await _discordRoleManager.ModifyRoleAsync(
|
||||||
|
|
|
@ -1606,6 +1606,21 @@
|
||||||
"Administrator Server Permission"
|
"Administrator Server Permission"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".roleicon"
|
||||||
|
],
|
||||||
|
"Description": "Changes the icon of a role.",
|
||||||
|
"Usage": [
|
||||||
|
".roleicon @Role :server_emoji_here:"
|
||||||
|
],
|
||||||
|
"Submodule": "RoleCommands",
|
||||||
|
"Module": "Administration",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": [
|
||||||
|
"ManageRoles Server Permission"
|
||||||
|
]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".iam"
|
".iam"
|
||||||
|
@ -7384,6 +7399,37 @@
|
||||||
"ManageChannels Channel Permission"
|
"ManageChannels Channel Permission"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".linkfix",
|
||||||
|
".lfix"
|
||||||
|
],
|
||||||
|
"Description": "Configures automatic link fixing from one site to another.\nWhen a user posts a link containing the old domain, the bot will automatically fix it to use the new domain.\nProvide no second domain to disable link fixing.",
|
||||||
|
"Usage": [
|
||||||
|
".linkfix twitter.com vxtwitter.com",
|
||||||
|
".linkfix x.com"
|
||||||
|
],
|
||||||
|
"Submodule": "LinkFixerCommands",
|
||||||
|
"Module": "Utility",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": [
|
||||||
|
"ManageMessages Server Permission"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Aliases": [
|
||||||
|
".linkfixlist",
|
||||||
|
".lfixlist"
|
||||||
|
],
|
||||||
|
"Description": "Lists all configured link fixes for the server.",
|
||||||
|
"Usage": [
|
||||||
|
".linkfixlist"
|
||||||
|
],
|
||||||
|
"Submodule": "LinkFixerCommands",
|
||||||
|
"Module": "Utility",
|
||||||
|
"Options": null,
|
||||||
|
"Requirements": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"Aliases": [
|
"Aliases": [
|
||||||
".livechadd",
|
".livechadd",
|
||||||
|
@ -8218,7 +8264,7 @@
|
||||||
],
|
],
|
||||||
"Description": "Changes the icon of your assigned role.",
|
"Description": "Changes the icon of your assigned role.",
|
||||||
"Usage": [
|
"Usage": [
|
||||||
".userroleicon @Role :thumbsup:"
|
".userroleicon @Role :server_emoji_here:"
|
||||||
],
|
],
|
||||||
"Submodule": "UserRoleCommands",
|
"Submodule": "UserRoleCommands",
|
||||||
"Module": "Utility",
|
"Module": "Utility",
|
||||||
|
|
|
@ -21,24 +21,24 @@ chance:
|
||||||
fish:
|
fish:
|
||||||
- id: 0
|
- id: 0
|
||||||
name: Bass
|
name: Bass
|
||||||
weather:
|
weather:
|
||||||
spot:
|
spot:
|
||||||
time:
|
time:
|
||||||
chance: 100
|
chance: 100
|
||||||
stars: 4
|
stars: 4
|
||||||
fluff: Very common.
|
fluff: Very common.
|
||||||
condition:
|
condition:
|
||||||
image: https://cdn.nadeko.bot/fish/bass.png
|
image: https://cdn.nadeko.bot/fish/bass.png
|
||||||
emoji: <:bass:1328520376892002386>
|
emoji: <:bass:1328520376892002386>
|
||||||
trash:
|
trash:
|
||||||
- id: 1002
|
- id: 1002
|
||||||
name: Plastic Bag
|
name: Plastic Bag
|
||||||
weather:
|
weather:
|
||||||
spot:
|
spot:
|
||||||
time:
|
time:
|
||||||
chance: 50
|
chance: 50
|
||||||
stars: 4
|
stars: 4
|
||||||
fluff: Trophy of your contribution to the environment.
|
fluff: Trophy of your contribution to the environment.
|
||||||
condition:
|
condition:
|
||||||
image: https://cdn.nadeko.bot/fish/plasticbag.png
|
image: https://cdn.nadeko.bot/fish/plasticbag.png
|
||||||
emoji: <:plasticbag:1328520895454515211>
|
emoji: <:plasticbag:1328520895454515211>
|
||||||
|
|
|
@ -165,6 +165,8 @@ deleterole:
|
||||||
rolecolor:
|
rolecolor:
|
||||||
- rolecolor
|
- rolecolor
|
||||||
- roleclr
|
- roleclr
|
||||||
|
roleicon:
|
||||||
|
- roleicon
|
||||||
ban:
|
ban:
|
||||||
- ban
|
- ban
|
||||||
- b
|
- b
|
||||||
|
@ -1644,4 +1646,10 @@ livechlist:
|
||||||
livechremove:
|
livechremove:
|
||||||
- livechremove
|
- livechremove
|
||||||
- lchd
|
- lchd
|
||||||
- lchrm
|
- lchrm
|
||||||
|
linkfix:
|
||||||
|
- linkfix
|
||||||
|
- lfix
|
||||||
|
linkfixlist:
|
||||||
|
- linkfixlist
|
||||||
|
- lfixlist
|
|
@ -599,6 +599,20 @@ rolehoist:
|
||||||
params:
|
params:
|
||||||
- role:
|
- role:
|
||||||
desc: "The role that determines the visibility of the sidebar."
|
desc: "The role that determines the visibility of the sidebar."
|
||||||
|
roleicon:
|
||||||
|
desc: |-
|
||||||
|
Changes the icon of a role.
|
||||||
|
ex:
|
||||||
|
- '@Role :server_emoji_here:'
|
||||||
|
params:
|
||||||
|
- role:
|
||||||
|
desc: 'The role to change the icon of.'
|
||||||
|
imageUrl:
|
||||||
|
desc: 'The image url to be used as a new icon for the role.'
|
||||||
|
- role:
|
||||||
|
desc: 'The role to change the icon of.'
|
||||||
|
serverEmoji:
|
||||||
|
desc: 'The server emoji to be used as a new icon for the role.'
|
||||||
createrole:
|
createrole:
|
||||||
desc: Creates a role with a given name.
|
desc: Creates a role with a given name.
|
||||||
ex:
|
ex:
|
||||||
|
@ -5054,7 +5068,7 @@ userroleicon:
|
||||||
desc: |-
|
desc: |-
|
||||||
Changes the icon of your assigned role.
|
Changes the icon of your assigned role.
|
||||||
ex:
|
ex:
|
||||||
- '@Role :thumbsup:'
|
- '@Role :server_emoji_here:'
|
||||||
params:
|
params:
|
||||||
- role:
|
- role:
|
||||||
desc: 'The assigned role to change the icon of.'
|
desc: 'The assigned role to change the icon of.'
|
||||||
|
@ -5148,4 +5162,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