Merge branch 'main' into rust-api
This commit is contained in:
commit
f382d30693
@ -304,9 +304,11 @@ public partial class CommandTree
|
|||||||
await ctx.Execute<MemberEdit>(MemberDelete, m => m.Delete(ctx, target));
|
await ctx.Execute<MemberEdit>(MemberDelete, m => m.Delete(ctx, target));
|
||||||
else if (ctx.Match("avatar", "profile", "picture", "icon", "image", "pfp", "pic"))
|
else if (ctx.Match("avatar", "profile", "picture", "icon", "image", "pfp", "pic"))
|
||||||
await ctx.Execute<MemberAvatar>(MemberAvatar, m => m.Avatar(ctx, target));
|
await ctx.Execute<MemberAvatar>(MemberAvatar, m => m.Avatar(ctx, target));
|
||||||
|
else if (ctx.Match("proxyavatar", "proxypfp", "webhookavatar", "webhookpfp", "pa"))
|
||||||
|
await ctx.Execute<MemberAvatar>(MemberAvatar, m => m.WebhookAvatar(ctx, target));
|
||||||
else if (ctx.Match("banner", "splash", "cover"))
|
else if (ctx.Match("banner", "splash", "cover"))
|
||||||
await ctx.Execute<MemberEdit>(MemberBannerImage, m => m.BannerImage(ctx, target));
|
await ctx.Execute<MemberEdit>(MemberBannerImage, m => m.BannerImage(ctx, target));
|
||||||
else if (ctx.Match("group", "groups"))
|
else if (ctx.Match("group", "groups", "g"))
|
||||||
if (ctx.Match("add", "a"))
|
if (ctx.Match("add", "a"))
|
||||||
await ctx.Execute<GroupMember>(MemberGroupAdd,
|
await ctx.Execute<GroupMember>(MemberGroupAdd,
|
||||||
m => m.AddRemoveGroups(ctx, target, Groups.AddRemoveOperation.Add));
|
m => m.AddRemoveGroups(ctx, target, Groups.AddRemoveOperation.Add));
|
||||||
@ -406,9 +408,9 @@ public partial class CommandTree
|
|||||||
{
|
{
|
||||||
if (ctx.Match("out"))
|
if (ctx.Match("out"))
|
||||||
await ctx.Execute<Switch>(SwitchOut, m => m.SwitchOut(ctx));
|
await ctx.Execute<Switch>(SwitchOut, m => m.SwitchOut(ctx));
|
||||||
else if (ctx.Match("move", "shift", "offset"))
|
else if (ctx.Match("move", "m", "shift", "offset"))
|
||||||
await ctx.Execute<Switch>(SwitchMove, m => m.SwitchMove(ctx));
|
await ctx.Execute<Switch>(SwitchMove, m => m.SwitchMove(ctx));
|
||||||
else if (ctx.Match("edit", "replace"))
|
else if (ctx.Match("edit", "e", "replace"))
|
||||||
if (ctx.Match("out"))
|
if (ctx.Match("out"))
|
||||||
await ctx.Execute<Switch>(SwitchEditOut, m => m.SwitchEditOut(ctx));
|
await ctx.Execute<Switch>(SwitchEditOut, m => m.SwitchEditOut(ctx));
|
||||||
else
|
else
|
||||||
|
@ -11,7 +11,7 @@ public class Help
|
|||||||
{
|
{
|
||||||
Title = "PluralKit",
|
Title = "PluralKit",
|
||||||
Description = "PluralKit is a bot designed for plural communities on Discord, and is open for anyone to use. It allows you to register systems, maintain system information, set up message proxying, log switches, and more.",
|
Description = "PluralKit is a bot designed for plural communities on Discord, and is open for anyone to use. It allows you to register systems, maintain system information, set up message proxying, log switches, and more.",
|
||||||
Footer = new("By @Ske#6201 | Myriad by @Layl#8888 | GitHub: https://github.com/PluralKit/PluralKit/ | Website: https://pluralkit.me/"),
|
Footer = new("By @Ske#6201 | Myriad design by @Layl#8888, art by https://twitter.com/sillyvizion | GitHub: https://github.com/PluralKit/PluralKit/ | Website: https://pluralkit.me/"),
|
||||||
Color = DiscordUtils.Blue,
|
Color = DiscordUtils.Blue,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -47,6 +47,9 @@ public class ImportExport
|
|||||||
var response = await _client.GetAsync(url);
|
var response = await _client.GetAsync(url);
|
||||||
if (!response.IsSuccessStatusCode)
|
if (!response.IsSuccessStatusCode)
|
||||||
throw Errors.InvalidImportFile;
|
throw Errors.InvalidImportFile;
|
||||||
|
// hacky fix for discord api returning nonsense charsets sometimes
|
||||||
|
response.Content.Headers.Remove("content-type");
|
||||||
|
response.Content.Headers.Add("content-type", "application/json; charset=UTF-8");
|
||||||
data = JsonConvert.DeserializeObject<JObject>(
|
data = JsonConvert.DeserializeObject<JObject>(
|
||||||
await response.Content.ReadAsStringAsync(),
|
await response.Content.ReadAsStringAsync(),
|
||||||
_settings
|
_settings
|
||||||
|
@ -15,10 +15,10 @@ public class MemberAvatar
|
|||||||
_client = client;
|
_client = client;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task AvatarClear(AvatarLocation location, Context ctx, PKMember target, MemberGuildSettings? mgs)
|
private async Task AvatarClear(MemberAvatarLocation location, Context ctx, PKMember target, MemberGuildSettings? mgs)
|
||||||
{
|
{
|
||||||
await UpdateAvatar(location, ctx, target, null);
|
await UpdateAvatar(location, ctx, target, null);
|
||||||
if (location == AvatarLocation.Server)
|
if (location == MemberAvatarLocation.Server)
|
||||||
{
|
{
|
||||||
if (target.AvatarUrl != null)
|
if (target.AvatarUrl != null)
|
||||||
await ctx.Reply(
|
await ctx.Reply(
|
||||||
@ -26,6 +26,14 @@ public class MemberAvatar
|
|||||||
else
|
else
|
||||||
await ctx.Reply($"{Emojis.Success} Member server avatar cleared. This member now has no avatar.");
|
await ctx.Reply($"{Emojis.Success} Member server avatar cleared. This member now has no avatar.");
|
||||||
}
|
}
|
||||||
|
else if (location == MemberAvatarLocation.MemberWebhook)
|
||||||
|
{
|
||||||
|
if (mgs?.AvatarUrl != null)
|
||||||
|
await ctx.Reply(
|
||||||
|
$"{Emojis.Success} Member proxy avatar cleared. Note that this member has a server-specific avatar set here, type `pk;member {target.Reference(ctx)} serveravatar clear` if you wish to clear that too.");
|
||||||
|
else
|
||||||
|
await ctx.Reply($"{Emojis.Success} Member proxy avatar cleared. This member will now use the main avatar for proxied messages.");
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (mgs?.AvatarUrl != null)
|
if (mgs?.AvatarUrl != null)
|
||||||
@ -36,18 +44,26 @@ public class MemberAvatar
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task AvatarShow(AvatarLocation location, Context ctx, PKMember target,
|
private async Task AvatarShow(MemberAvatarLocation location, Context ctx, PKMember target,
|
||||||
MemberGuildSettings? guildData)
|
MemberGuildSettings? guildData)
|
||||||
{
|
{
|
||||||
// todo: this privacy code is really confusing
|
// todo: this privacy code is really confusing
|
||||||
// for now, we skip privacy flag/config parsing for this, but it would be good to fix that at some point
|
// for now, we skip privacy flag/config parsing for this, but it would be good to fix that at some point
|
||||||
|
|
||||||
var currentValue = location == AvatarLocation.Member ? target.AvatarUrl : guildData?.AvatarUrl;
|
var currentValue = location switch
|
||||||
var canAccess = location != AvatarLocation.Member ||
|
{
|
||||||
|
MemberAvatarLocation.Server => guildData?.AvatarUrl,
|
||||||
|
MemberAvatarLocation.MemberWebhook => target.WebhookAvatarUrl,
|
||||||
|
MemberAvatarLocation.Member => target.AvatarUrl,
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(location))
|
||||||
|
};
|
||||||
|
|
||||||
|
var canAccess = location == MemberAvatarLocation.Server ||
|
||||||
target.AvatarPrivacy.CanAccess(ctx.DirectLookupContextFor(target.System));
|
target.AvatarPrivacy.CanAccess(ctx.DirectLookupContextFor(target.System));
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(currentValue) || !canAccess)
|
if (string.IsNullOrEmpty(currentValue) || !canAccess)
|
||||||
{
|
{
|
||||||
if (location == AvatarLocation.Member)
|
if (location == MemberAvatarLocation.Member)
|
||||||
{
|
{
|
||||||
if (target.System == ctx.System?.Id)
|
if (target.System == ctx.System?.Id)
|
||||||
throw new PKSyntaxError(
|
throw new PKSyntaxError(
|
||||||
@ -55,19 +71,24 @@ public class MemberAvatar
|
|||||||
throw new PKError("This member does not have an avatar set.");
|
throw new PKError("This member does not have an avatar set.");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (location == AvatarLocation.Server)
|
if (location == MemberAvatarLocation.MemberWebhook)
|
||||||
|
throw new PKError(
|
||||||
|
$"This member does not have a proxy avatar set. Type `pk;member {target.Reference(ctx)} avatar` to see their global avatar.");
|
||||||
|
|
||||||
|
if (location == MemberAvatarLocation.Server)
|
||||||
throw new PKError(
|
throw new PKError(
|
||||||
$"This member does not have a server avatar set. Type `pk;member {target.Reference(ctx)} avatar` to see their global avatar.");
|
$"This member does not have a server avatar set. Type `pk;member {target.Reference(ctx)} avatar` to see their global avatar.");
|
||||||
}
|
}
|
||||||
|
|
||||||
var field = location == AvatarLocation.Server ? $"server avatar (for {ctx.Guild.Name})" : "avatar";
|
var field = location.Name();
|
||||||
var cmd = location == AvatarLocation.Server ? "serveravatar" : "avatar";
|
if (location == MemberAvatarLocation.Server)
|
||||||
|
field += $" (for {ctx.Guild.Name})";
|
||||||
|
|
||||||
var eb = new EmbedBuilder()
|
var eb = new EmbedBuilder()
|
||||||
.Title($"{target.NameFor(ctx)}'s {field}")
|
.Title($"{target.NameFor(ctx)}'s {field}")
|
||||||
.Image(new Embed.EmbedImage(currentValue?.TryGetCleanCdnUrl()));
|
.Image(new Embed.EmbedImage(currentValue?.TryGetCleanCdnUrl()));
|
||||||
if (target.System == ctx.System?.Id)
|
if (target.System == ctx.System?.Id)
|
||||||
eb.Description($"To clear, use `pk;member {target.Reference(ctx)} {cmd} clear`.");
|
eb.Description($"To clear, use `pk;member {target.Reference(ctx)} {location.Command()} clear`.");
|
||||||
await ctx.Reply(embed: eb.Build());
|
await ctx.Reply(embed: eb.Build());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -75,7 +96,7 @@ public class MemberAvatar
|
|||||||
{
|
{
|
||||||
ctx.CheckGuildContext();
|
ctx.CheckGuildContext();
|
||||||
var guildData = await ctx.Repository.GetMemberGuild(ctx.Guild.Id, target.Id);
|
var guildData = await ctx.Repository.GetMemberGuild(ctx.Guild.Id, target.Id);
|
||||||
await AvatarCommandTree(AvatarLocation.Server, ctx, target, guildData);
|
await AvatarCommandTree(MemberAvatarLocation.Server, ctx, target, guildData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Avatar(Context ctx, PKMember target)
|
public async Task Avatar(Context ctx, PKMember target)
|
||||||
@ -84,16 +105,23 @@ public class MemberAvatar
|
|||||||
? await ctx.Repository.GetMemberGuild(ctx.Guild.Id, target.Id)
|
? await ctx.Repository.GetMemberGuild(ctx.Guild.Id, target.Id)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
await AvatarCommandTree(AvatarLocation.Member, ctx, target, guildData);
|
await AvatarCommandTree(MemberAvatarLocation.Member, ctx, target, guildData);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task AvatarCommandTree(AvatarLocation location, Context ctx, PKMember target,
|
public async Task WebhookAvatar(Context ctx, PKMember target)
|
||||||
|
{
|
||||||
|
var guildData = ctx.Guild != null
|
||||||
|
? await ctx.Repository.GetMemberGuild(ctx.Guild.Id, target.Id)
|
||||||
|
: null;
|
||||||
|
|
||||||
|
await AvatarCommandTree(MemberAvatarLocation.MemberWebhook, ctx, target, guildData);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task AvatarCommandTree(MemberAvatarLocation location, Context ctx, PKMember target,
|
||||||
MemberGuildSettings? guildData)
|
MemberGuildSettings? guildData)
|
||||||
{
|
{
|
||||||
// First, see if we need to *clear*
|
// First, see if we need to *clear*
|
||||||
if (ctx.MatchClear() && await ctx.ConfirmClear(location == AvatarLocation.Server
|
if (ctx.MatchClear() && await ctx.ConfirmClear("this member's " + location.Name()))
|
||||||
? "this member's server avatar"
|
|
||||||
: "this member's avatar"))
|
|
||||||
{
|
{
|
||||||
ctx.CheckSystem().CheckOwnMember(target);
|
ctx.CheckSystem().CheckOwnMember(target);
|
||||||
await AvatarClear(location, ctx, target, guildData);
|
await AvatarClear(location, ctx, target, guildData);
|
||||||
@ -115,33 +143,30 @@ public class MemberAvatar
|
|||||||
await PrintResponse(location, ctx, target, avatarArg.Value, guildData);
|
await PrintResponse(location, ctx, target, avatarArg.Value, guildData);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task PrintResponse(AvatarLocation location, Context ctx, PKMember target, ParsedImage avatar,
|
private Task PrintResponse(MemberAvatarLocation location, Context ctx, PKMember target, ParsedImage avatar,
|
||||||
MemberGuildSettings? targetGuildData)
|
MemberGuildSettings? targetGuildData)
|
||||||
{
|
{
|
||||||
var typeFrag = location switch
|
|
||||||
{
|
|
||||||
AvatarLocation.Server => "server avatar",
|
|
||||||
AvatarLocation.Member => "avatar",
|
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(location))
|
|
||||||
};
|
|
||||||
|
|
||||||
var serverFrag = location switch
|
var serverFrag = location switch
|
||||||
{
|
{
|
||||||
AvatarLocation.Server =>
|
MemberAvatarLocation.Server =>
|
||||||
$" This avatar will now be used when proxying in this server (**{ctx.Guild.Name}**).",
|
$" This avatar will now be used when proxying in this server (**{ctx.Guild.Name}**).",
|
||||||
AvatarLocation.Member when targetGuildData?.AvatarUrl != null =>
|
MemberAvatarLocation.Member when targetGuildData?.AvatarUrl != null =>
|
||||||
$"\n{Emojis.Note} Note that this member *also* has a server-specific avatar set in this server (**{ctx.Guild.Name}**), and thus changing the global avatar will have no effect here.",
|
$"\n{Emojis.Note} Note that this member *also* has a server-specific avatar set in this server (**{ctx.Guild.Name}**), and thus changing the global avatar will have no effect here.",
|
||||||
|
MemberAvatarLocation.MemberWebhook when targetGuildData?.AvatarUrl != null =>
|
||||||
|
$" This avatar will now be used for this member's proxied messages, instead of their main avatar.\n{Emojis.Note} Note that this member *also* has a server-specific avatar set in this server (**{ctx.Guild.Name}**), and thus changing the global avatar will have no effect here.",
|
||||||
|
MemberAvatarLocation.MemberWebhook =>
|
||||||
|
$" This avatar will now be used for this member's proxied messages, instead of their main avatar.",
|
||||||
_ => ""
|
_ => ""
|
||||||
};
|
};
|
||||||
|
|
||||||
var msg = avatar.Source switch
|
var msg = avatar.Source switch
|
||||||
{
|
{
|
||||||
AvatarSource.User =>
|
AvatarSource.User =>
|
||||||
$"{Emojis.Success} Member {typeFrag} changed to {avatar.SourceUser?.Username}'s avatar!{serverFrag}\n{Emojis.Warn} If {avatar.SourceUser?.Username} changes their avatar, the member's avatar will need to be re-set.",
|
$"{Emojis.Success} Member {location.Name()} changed to {avatar.SourceUser?.Username}'s avatar!{serverFrag}\n{Emojis.Warn} If {avatar.SourceUser?.Username} changes their avatar, the member's avatar will need to be re-set.",
|
||||||
AvatarSource.Url =>
|
AvatarSource.Url =>
|
||||||
$"{Emojis.Success} Member {typeFrag} changed to the image at the given URL.{serverFrag}",
|
$"{Emojis.Success} Member {location.Name()} changed to the image at the given URL.{serverFrag}",
|
||||||
AvatarSource.Attachment =>
|
AvatarSource.Attachment =>
|
||||||
$"{Emojis.Success} Member {typeFrag} changed to attached image.{serverFrag}\n{Emojis.Warn} If you delete the message containing the attachment, the avatar will stop working.",
|
$"{Emojis.Success} Member {location.Name()} changed to attached image.{serverFrag}\n{Emojis.Warn} If you delete the message containing the attachment, the avatar will stop working.",
|
||||||
_ => throw new ArgumentOutOfRangeException()
|
_ => throw new ArgumentOutOfRangeException()
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -152,18 +177,49 @@ public class MemberAvatar
|
|||||||
: ctx.Reply(msg);
|
: ctx.Reply(msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task UpdateAvatar(AvatarLocation location, Context ctx, PKMember target, string? url)
|
private Task UpdateAvatar(MemberAvatarLocation location, Context ctx, PKMember target, string? url)
|
||||||
{
|
{
|
||||||
switch (location)
|
switch (location)
|
||||||
{
|
{
|
||||||
case AvatarLocation.Server:
|
case MemberAvatarLocation.Server:
|
||||||
return ctx.Repository.UpdateMemberGuild(target.Id, ctx.Guild.Id, new MemberGuildPatch { AvatarUrl = url });
|
return ctx.Repository.UpdateMemberGuild(target.Id, ctx.Guild.Id, new MemberGuildPatch { AvatarUrl = url });
|
||||||
case AvatarLocation.Member:
|
case MemberAvatarLocation.Member:
|
||||||
return ctx.Repository.UpdateMember(target.Id, new MemberPatch { AvatarUrl = url });
|
return ctx.Repository.UpdateMember(target.Id, new MemberPatch { AvatarUrl = url });
|
||||||
|
case MemberAvatarLocation.MemberWebhook:
|
||||||
|
return ctx.Repository.UpdateMember(target.Id, new MemberPatch { WebhookAvatarUrl = url });
|
||||||
default:
|
default:
|
||||||
throw new ArgumentOutOfRangeException($"Unknown avatar location {location}");
|
throw new ArgumentOutOfRangeException($"Unknown avatar location {location}");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
private enum AvatarLocation { Member, Server }
|
internal enum MemberAvatarLocation
|
||||||
|
{
|
||||||
|
Member,
|
||||||
|
MemberWebhook,
|
||||||
|
Server,
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static class MemberAvatarLocationExt
|
||||||
|
{
|
||||||
|
public static string Name(this MemberAvatarLocation location)
|
||||||
|
{
|
||||||
|
return location switch
|
||||||
|
{
|
||||||
|
MemberAvatarLocation.Server => "server avatar",
|
||||||
|
MemberAvatarLocation.MemberWebhook => "proxy avatar",
|
||||||
|
MemberAvatarLocation.Member => "avatar",
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(location))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public static string Command(this MemberAvatarLocation location)
|
||||||
|
{
|
||||||
|
return location switch
|
||||||
|
{
|
||||||
|
MemberAvatarLocation.Server => "serveravatar",
|
||||||
|
MemberAvatarLocation.MemberWebhook => "proxyavatar",
|
||||||
|
MemberAvatarLocation.Member => "avatar",
|
||||||
|
_ => throw new ArgumentOutOfRangeException(nameof(location))
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
@ -228,7 +228,7 @@ public class ProxyService
|
|||||||
var guildMember = await _rest.GetGuildMember(msg.Guild!.Value, trigger.Author.Id);
|
var guildMember = await _rest.GetGuildMember(msg.Guild!.Value, trigger.Author.Id);
|
||||||
|
|
||||||
// Grab user permissions
|
// Grab user permissions
|
||||||
var senderPermissions = PermissionExtensions.PermissionsFor(guild, rootChannel, trigger.Author.Id, guildMember);
|
var senderPermissions = PermissionExtensions.PermissionsFor(guild, messageChannel, trigger.Author.Id, guildMember);
|
||||||
var allowEveryone = senderPermissions.HasFlag(PermissionSet.MentionEveryone);
|
var allowEveryone = senderPermissions.HasFlag(PermissionSet.MentionEveryone);
|
||||||
|
|
||||||
// Make sure user has permissions to send messages
|
// Make sure user has permissions to send messages
|
||||||
|
@ -135,7 +135,7 @@ public class EmbedService
|
|||||||
// sometimes Discord will just... not return the avatar hash with webhook messages
|
// sometimes Discord will just... not return the avatar hash with webhook messages
|
||||||
var avatar = proxiedMessage.Author.Avatar != null
|
var avatar = proxiedMessage.Author.Avatar != null
|
||||||
? proxiedMessage.Author.AvatarUrl()
|
? proxiedMessage.Author.AvatarUrl()
|
||||||
: member.AvatarFor(LookupContext.ByNonOwner);
|
: member.WebhookAvatarFor(LookupContext.ByNonOwner);
|
||||||
var embed = new EmbedBuilder()
|
var embed = new EmbedBuilder()
|
||||||
.Author(new Embed.EmbedAuthor($"#{channelName}: {name}", IconUrl: avatar))
|
.Author(new Embed.EmbedAuthor($"#{channelName}: {name}", IconUrl: avatar))
|
||||||
.Thumbnail(new Embed.EmbedThumbnail(avatar))
|
.Thumbnail(new Embed.EmbedThumbnail(avatar))
|
||||||
@ -175,6 +175,7 @@ public class EmbedService
|
|||||||
|
|
||||||
var guildSettings = guild != null ? await _repo.GetMemberGuild(guild.Id, member.Id) : null;
|
var guildSettings = guild != null ? await _repo.GetMemberGuild(guild.Id, member.Id) : null;
|
||||||
var guildDisplayName = guildSettings?.DisplayName;
|
var guildDisplayName = guildSettings?.DisplayName;
|
||||||
|
var webhook_avatar = guildSettings?.AvatarUrl ?? member.WebhookAvatarFor(ctx) ?? member.AvatarFor(ctx);
|
||||||
var avatar = guildSettings?.AvatarUrl ?? member.AvatarFor(ctx);
|
var avatar = guildSettings?.AvatarUrl ?? member.AvatarFor(ctx);
|
||||||
|
|
||||||
var groups = await _repo.GetMemberGroups(member.Id)
|
var groups = await _repo.GetMemberGroups(member.Id)
|
||||||
@ -183,7 +184,7 @@ public class EmbedService
|
|||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
var eb = new EmbedBuilder()
|
var eb = new EmbedBuilder()
|
||||||
.Author(new Embed.EmbedAuthor(name, IconUrl: avatar.TryGetCleanCdnUrl(), Url: $"https://dash.pluralkit.me/profile/m/{member.Hid}"))
|
.Author(new Embed.EmbedAuthor(name, IconUrl: webhook_avatar.TryGetCleanCdnUrl(), Url: $"https://dash.pluralkit.me/profile/m/{member.Hid}"))
|
||||||
// .WithColor(member.ColorPrivacy.CanAccess(ctx) ? color : DiscordUtils.Gray)
|
// .WithColor(member.ColorPrivacy.CanAccess(ctx) ? color : DiscordUtils.Gray)
|
||||||
.Color(color)
|
.Color(color)
|
||||||
.Footer(new Embed.EmbedFooter(
|
.Footer(new Embed.EmbedFooter(
|
||||||
|
@ -23,9 +23,9 @@ public class ProxyMember
|
|||||||
public string Name { get; } = "";
|
public string Name { get; } = "";
|
||||||
|
|
||||||
public string? ServerAvatar { get; }
|
public string? ServerAvatar { get; }
|
||||||
|
public string? WebhookAvatar { get; }
|
||||||
public string? Avatar { get; }
|
public string? Avatar { get; }
|
||||||
|
|
||||||
|
|
||||||
public bool AllowAutoproxy { get; }
|
public bool AllowAutoproxy { get; }
|
||||||
public string? Color { get; }
|
public string? Color { get; }
|
||||||
|
|
||||||
@ -42,5 +42,5 @@ public class ProxyMember
|
|||||||
return memberName;
|
return memberName;
|
||||||
}
|
}
|
||||||
|
|
||||||
public string? ProxyAvatar(MessageContext ctx) => ServerAvatar ?? Avatar ?? ctx.SystemAvatar;
|
public string? ProxyAvatar(MessageContext ctx) => ServerAvatar ?? WebhookAvatar ?? Avatar ?? ctx.SystemAvatar;
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
create function message_context(account_id bigint, guild_id bigint, channel_id bigint)
|
create function message_context(account_id bigint, guild_id bigint, channel_id bigint)
|
||||||
returns table (
|
returns table (
|
||||||
system_id int,
|
system_id int,
|
||||||
log_channel bigint,
|
log_channel bigint,
|
||||||
@ -67,6 +67,7 @@ create function proxy_members(account_id bigint, guild_id bigint)
|
|||||||
name text,
|
name text,
|
||||||
|
|
||||||
server_avatar text,
|
server_avatar text,
|
||||||
|
webhook_avatar text,
|
||||||
avatar text,
|
avatar text,
|
||||||
|
|
||||||
color char(6),
|
color char(6),
|
||||||
@ -76,22 +77,23 @@ create function proxy_members(account_id bigint, guild_id bigint)
|
|||||||
as $$
|
as $$
|
||||||
select
|
select
|
||||||
-- Basic data
|
-- Basic data
|
||||||
members.id as id,
|
members.id as id,
|
||||||
members.proxy_tags as proxy_tags,
|
members.proxy_tags as proxy_tags,
|
||||||
members.keep_proxy as keep_proxy,
|
members.keep_proxy as keep_proxy,
|
||||||
|
|
||||||
-- Name info
|
-- Name info
|
||||||
member_guild.display_name as server_name,
|
member_guild.display_name as server_name,
|
||||||
members.display_name as display_name,
|
members.display_name as display_name,
|
||||||
members.name as name,
|
members.name as name,
|
||||||
|
|
||||||
-- Avatar info
|
-- Avatar info
|
||||||
member_guild.avatar_url as server_avatar,
|
member_guild.avatar_url as server_avatar,
|
||||||
members.avatar_url as avatar,
|
members.webhook_avatar_url as webhook_avatar,
|
||||||
|
members.avatar_url as avatar,
|
||||||
|
|
||||||
members.color as color,
|
members.color as color,
|
||||||
|
|
||||||
members.allow_autoproxy as allow_autoproxy
|
members.allow_autoproxy as allow_autoproxy
|
||||||
from accounts
|
from accounts
|
||||||
inner join systems on systems.id = accounts.system
|
inner join systems on systems.id = accounts.system
|
||||||
inner join members on members.system = systems.id
|
inner join members on members.system = systems.id
|
||||||
|
6
PluralKit.Core/Database/Migrations/33.sql
Normal file
6
PluralKit.Core/Database/Migrations/33.sql
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
-- database version 33
|
||||||
|
-- add webhook_avatar_url to system members
|
||||||
|
|
||||||
|
alter table members add column webhook_avatar_url text;
|
||||||
|
|
||||||
|
update info set schema_version = 33;
|
@ -9,7 +9,7 @@ namespace PluralKit.Core;
|
|||||||
internal class DatabaseMigrator
|
internal class DatabaseMigrator
|
||||||
{
|
{
|
||||||
private const string RootPath = "PluralKit.Core.Database"; // "resource path" root for SQL files
|
private const string RootPath = "PluralKit.Core.Database"; // "resource path" root for SQL files
|
||||||
private const int TargetSchemaVersion = 32;
|
private const int TargetSchemaVersion = 33;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
||||||
public DatabaseMigrator(ILogger logger)
|
public DatabaseMigrator(ILogger logger)
|
||||||
|
@ -39,6 +39,7 @@ public class PKMember
|
|||||||
public Guid Uuid { get; private set; }
|
public Guid Uuid { get; private set; }
|
||||||
public SystemId System { get; private set; }
|
public SystemId System { get; private set; }
|
||||||
public string Color { get; private set; }
|
public string Color { get; private set; }
|
||||||
|
public string WebhookAvatarUrl { get; private set; }
|
||||||
public string AvatarUrl { get; private set; }
|
public string AvatarUrl { get; private set; }
|
||||||
public string BannerImage { get; private set; }
|
public string BannerImage { get; private set; }
|
||||||
public string Name { get; private set; }
|
public string Name { get; private set; }
|
||||||
@ -90,6 +91,9 @@ public static class PKMemberExt
|
|||||||
public static string AvatarFor(this PKMember member, LookupContext ctx) =>
|
public static string AvatarFor(this PKMember member, LookupContext ctx) =>
|
||||||
member.AvatarPrivacy.Get(ctx, member.AvatarUrl.TryGetCleanCdnUrl());
|
member.AvatarPrivacy.Get(ctx, member.AvatarUrl.TryGetCleanCdnUrl());
|
||||||
|
|
||||||
|
public static string WebhookAvatarFor(this PKMember member, LookupContext ctx) =>
|
||||||
|
member.AvatarPrivacy.Get(ctx, (member.WebhookAvatarUrl ?? member.AvatarUrl).TryGetCleanCdnUrl());
|
||||||
|
|
||||||
public static string DescriptionFor(this PKMember member, LookupContext ctx) =>
|
public static string DescriptionFor(this PKMember member, LookupContext ctx) =>
|
||||||
member.DescriptionPrivacy.Get(ctx, member.Description);
|
member.DescriptionPrivacy.Get(ctx, member.Description);
|
||||||
|
|
||||||
@ -128,6 +132,7 @@ public static class PKMemberExt
|
|||||||
o.Add("birthday", member.BirthdayFor(ctx)?.FormatExport());
|
o.Add("birthday", member.BirthdayFor(ctx)?.FormatExport());
|
||||||
o.Add("pronouns", member.PronounsFor(ctx));
|
o.Add("pronouns", member.PronounsFor(ctx));
|
||||||
o.Add("avatar_url", member.AvatarFor(ctx).TryGetCleanCdnUrl());
|
o.Add("avatar_url", member.AvatarFor(ctx).TryGetCleanCdnUrl());
|
||||||
|
o.Add("webhook_avatar_url", member.WebhookAvatarFor(ctx).TryGetCleanCdnUrl());
|
||||||
o.Add("banner", member.DescriptionPrivacy.Get(ctx, member.BannerImage).TryGetCleanCdnUrl());
|
o.Add("banner", member.DescriptionPrivacy.Get(ctx, member.BannerImage).TryGetCleanCdnUrl());
|
||||||
o.Add("description", member.DescriptionFor(ctx));
|
o.Add("description", member.DescriptionFor(ctx));
|
||||||
o.Add("created", member.CreatedFor(ctx)?.FormatExport());
|
o.Add("created", member.CreatedFor(ctx)?.FormatExport());
|
||||||
|
@ -12,6 +12,7 @@ public class MemberPatch: PatchObject
|
|||||||
public Partial<string> Name { get; set; }
|
public Partial<string> Name { get; set; }
|
||||||
public Partial<string> Hid { get; set; }
|
public Partial<string> Hid { get; set; }
|
||||||
public Partial<string?> DisplayName { get; set; }
|
public Partial<string?> DisplayName { get; set; }
|
||||||
|
public Partial<string?> WebhookAvatarUrl { get; set; }
|
||||||
public Partial<string?> AvatarUrl { get; set; }
|
public Partial<string?> AvatarUrl { get; set; }
|
||||||
public Partial<string?> BannerImage { get; set; }
|
public Partial<string?> BannerImage { get; set; }
|
||||||
public Partial<string?> Color { get; set; }
|
public Partial<string?> Color { get; set; }
|
||||||
@ -34,6 +35,7 @@ public class MemberPatch: PatchObject
|
|||||||
.With("name", Name)
|
.With("name", Name)
|
||||||
.With("hid", Hid)
|
.With("hid", Hid)
|
||||||
.With("display_name", DisplayName)
|
.With("display_name", DisplayName)
|
||||||
|
.With("webhook_avatar_url", WebhookAvatarUrl)
|
||||||
.With("avatar_url", AvatarUrl)
|
.With("avatar_url", AvatarUrl)
|
||||||
.With("banner_image", BannerImage)
|
.With("banner_image", BannerImage)
|
||||||
.With("color", Color)
|
.With("color", Color)
|
||||||
@ -62,6 +64,9 @@ public class MemberPatch: PatchObject
|
|||||||
if (AvatarUrl.Value != null)
|
if (AvatarUrl.Value != null)
|
||||||
AssertValid(AvatarUrl.Value, "avatar_url", Limits.MaxUriLength,
|
AssertValid(AvatarUrl.Value, "avatar_url", Limits.MaxUriLength,
|
||||||
s => MiscUtils.TryMatchUri(s, out var avatarUri));
|
s => MiscUtils.TryMatchUri(s, out var avatarUri));
|
||||||
|
if (WebhookAvatarUrl.Value != null)
|
||||||
|
AssertValid(WebhookAvatarUrl.Value, "webhook_avatar_url", Limits.MaxUriLength,
|
||||||
|
s => MiscUtils.TryMatchUri(s, out var webhookAvatarUri));
|
||||||
if (BannerImage.Value != null)
|
if (BannerImage.Value != null)
|
||||||
AssertValid(BannerImage.Value, "banner", Limits.MaxUriLength,
|
AssertValid(BannerImage.Value, "banner", Limits.MaxUriLength,
|
||||||
s => MiscUtils.TryMatchUri(s, out var bannerUri));
|
s => MiscUtils.TryMatchUri(s, out var bannerUri));
|
||||||
@ -93,6 +98,7 @@ public class MemberPatch: PatchObject
|
|||||||
if (o.ContainsKey("name")) patch.Name = o.Value<string>("name");
|
if (o.ContainsKey("name")) patch.Name = o.Value<string>("name");
|
||||||
if (o.ContainsKey("color")) patch.Color = o.Value<string>("color").NullIfEmpty()?.ToLower();
|
if (o.ContainsKey("color")) patch.Color = o.Value<string>("color").NullIfEmpty()?.ToLower();
|
||||||
if (o.ContainsKey("display_name")) patch.DisplayName = o.Value<string>("display_name").NullIfEmpty();
|
if (o.ContainsKey("display_name")) patch.DisplayName = o.Value<string>("display_name").NullIfEmpty();
|
||||||
|
if (o.ContainsKey("webhook_avatar_url")) patch.WebhookAvatarUrl = o.Value<string>("webhook_avatar_url").NullIfEmpty();
|
||||||
if (o.ContainsKey("avatar_url")) patch.AvatarUrl = o.Value<string>("avatar_url").NullIfEmpty();
|
if (o.ContainsKey("avatar_url")) patch.AvatarUrl = o.Value<string>("avatar_url").NullIfEmpty();
|
||||||
if (o.ContainsKey("banner")) patch.BannerImage = o.Value<string>("banner").NullIfEmpty();
|
if (o.ContainsKey("banner")) patch.BannerImage = o.Value<string>("banner").NullIfEmpty();
|
||||||
|
|
||||||
@ -177,6 +183,8 @@ public class MemberPatch: PatchObject
|
|||||||
o.Add("display_name", DisplayName.Value);
|
o.Add("display_name", DisplayName.Value);
|
||||||
if (AvatarUrl.IsPresent)
|
if (AvatarUrl.IsPresent)
|
||||||
o.Add("avatar_url", AvatarUrl.Value);
|
o.Add("avatar_url", AvatarUrl.Value);
|
||||||
|
if (WebhookAvatarUrl.IsPresent)
|
||||||
|
o.Add("webhook_avatar_url", WebhookAvatarUrl.Value);
|
||||||
if (BannerImage.IsPresent)
|
if (BannerImage.IsPresent)
|
||||||
o.Add("banner", BannerImage.Value);
|
o.Add("banner", BannerImage.Value);
|
||||||
if (Color.IsPresent)
|
if (Color.IsPresent)
|
||||||
|
@ -26,8 +26,9 @@
|
|||||||
"bootstrap": "^5.1.3",
|
"bootstrap": "^5.1.3",
|
||||||
"bootstrap-dark-5": "^1.1.3",
|
"bootstrap-dark-5": "^1.1.3",
|
||||||
"core-js-pure": "^3.23.4",
|
"core-js-pure": "^3.23.4",
|
||||||
"discord-markdown": "^2.5.1",
|
"discord-markdown": "https://github.com/repository/discord-markdown#b9608feef6856c9baa68f96c932a25c1d2bc55c2",
|
||||||
"gh-pages": "^3.2.3",
|
"gh-pages": "^3.2.3",
|
||||||
|
"highlight.js": "^11.7.0",
|
||||||
"import": "^0.0.6",
|
"import": "^0.0.6",
|
||||||
"moment": "^2.29.1",
|
"moment": "^2.29.1",
|
||||||
"sass": "^1.52.2",
|
"sass": "^1.52.2",
|
||||||
|
421
dashboard/src/api/parse-markdown.ts
Normal file
421
dashboard/src/api/parse-markdown.ts
Normal file
@ -0,0 +1,421 @@
|
|||||||
|
import { toHTML } from 'discord-markdown';
|
||||||
|
import hljs from 'highlight.js/lib/core';
|
||||||
|
import parseTimestamps from './parse-timestamps';
|
||||||
|
|
||||||
|
const languages: Record<string, () => Promise<typeof import("highlight.js/lib/languages/*")>> = {
|
||||||
|
"1c": () => import("highlight.js/lib/languages/1c"),
|
||||||
|
"abnf": () => import("highlight.js/lib/languages/abnf"),
|
||||||
|
"accesslog": () => import("highlight.js/lib/languages/accesslog"),
|
||||||
|
"actionscript": () => import("highlight.js/lib/languages/actionscript"),
|
||||||
|
"ada": () => import("highlight.js/lib/languages/ada"),
|
||||||
|
"angelscript": () => import("highlight.js/lib/languages/angelscript"),
|
||||||
|
"apache": () => import("highlight.js/lib/languages/apache"),
|
||||||
|
"applescript": () => import("highlight.js/lib/languages/applescript"),
|
||||||
|
"arcade": () => import("highlight.js/lib/languages/arcade"),
|
||||||
|
"arduino": () => import("highlight.js/lib/languages/arduino"),
|
||||||
|
"armasm": () => import("highlight.js/lib/languages/armasm"),
|
||||||
|
"xml": () => import("highlight.js/lib/languages/xml"),
|
||||||
|
"asciidoc": () => import("highlight.js/lib/languages/asciidoc"),
|
||||||
|
"aspectj": () => import("highlight.js/lib/languages/aspectj"),
|
||||||
|
"autohotkey": () => import("highlight.js/lib/languages/autohotkey"),
|
||||||
|
"autoit": () => import("highlight.js/lib/languages/autoit"),
|
||||||
|
"avrasm": () => import("highlight.js/lib/languages/avrasm"),
|
||||||
|
"awk": () => import("highlight.js/lib/languages/awk"),
|
||||||
|
"axapta": () => import("highlight.js/lib/languages/axapta"),
|
||||||
|
"bash": () => import("highlight.js/lib/languages/bash"),
|
||||||
|
"basic": () => import("highlight.js/lib/languages/basic"),
|
||||||
|
"bnf": () => import("highlight.js/lib/languages/bnf"),
|
||||||
|
"brainfuck": () => import("highlight.js/lib/languages/brainfuck"),
|
||||||
|
"c": () => import("highlight.js/lib/languages/c"),
|
||||||
|
"cal": () => import("highlight.js/lib/languages/cal"),
|
||||||
|
"capnproto": () => import("highlight.js/lib/languages/capnproto"),
|
||||||
|
"ceylon": () => import("highlight.js/lib/languages/ceylon"),
|
||||||
|
"clean": () => import("highlight.js/lib/languages/clean"),
|
||||||
|
"clojure": () => import("highlight.js/lib/languages/clojure"),
|
||||||
|
"clojure-repl": () => import("highlight.js/lib/languages/clojure-repl"),
|
||||||
|
"cmake": () => import("highlight.js/lib/languages/cmake"),
|
||||||
|
"coffeescript": () => import("highlight.js/lib/languages/coffeescript"),
|
||||||
|
"coq": () => import("highlight.js/lib/languages/coq"),
|
||||||
|
"cos": () => import("highlight.js/lib/languages/cos"),
|
||||||
|
"cpp": () => import("highlight.js/lib/languages/cpp"),
|
||||||
|
"crmsh": () => import("highlight.js/lib/languages/crmsh"),
|
||||||
|
"crystal": () => import("highlight.js/lib/languages/crystal"),
|
||||||
|
"csharp": () => import("highlight.js/lib/languages/csharp"),
|
||||||
|
"csp": () => import("highlight.js/lib/languages/csp"),
|
||||||
|
"css": () => import("highlight.js/lib/languages/css"),
|
||||||
|
"d": () => import("highlight.js/lib/languages/d"),
|
||||||
|
"markdown": () => import("highlight.js/lib/languages/markdown"),
|
||||||
|
"dart": () => import("highlight.js/lib/languages/dart"),
|
||||||
|
"delphi": () => import("highlight.js/lib/languages/delphi"),
|
||||||
|
"diff": () => import("highlight.js/lib/languages/diff"),
|
||||||
|
"django": () => import("highlight.js/lib/languages/django"),
|
||||||
|
"dns": () => import("highlight.js/lib/languages/dns"),
|
||||||
|
"dockerfile": () => import("highlight.js/lib/languages/dockerfile"),
|
||||||
|
"dos": () => import("highlight.js/lib/languages/dos"),
|
||||||
|
"dsconfig": () => import("highlight.js/lib/languages/dsconfig"),
|
||||||
|
"dts": () => import("highlight.js/lib/languages/dts"),
|
||||||
|
"dust": () => import("highlight.js/lib/languages/dust"),
|
||||||
|
"ebnf": () => import("highlight.js/lib/languages/ebnf"),
|
||||||
|
"elixir": () => import("highlight.js/lib/languages/elixir"),
|
||||||
|
"elm": () => import("highlight.js/lib/languages/elm"),
|
||||||
|
"ruby": () => import("highlight.js/lib/languages/ruby"),
|
||||||
|
"erb": () => import("highlight.js/lib/languages/erb"),
|
||||||
|
"erlang-repl": () => import("highlight.js/lib/languages/erlang-repl"),
|
||||||
|
"erlang": () => import("highlight.js/lib/languages/erlang"),
|
||||||
|
"excel": () => import("highlight.js/lib/languages/excel"),
|
||||||
|
"fix": () => import("highlight.js/lib/languages/fix"),
|
||||||
|
"flix": () => import("highlight.js/lib/languages/flix"),
|
||||||
|
"fortran": () => import("highlight.js/lib/languages/fortran"),
|
||||||
|
"fsharp": () => import("highlight.js/lib/languages/fsharp"),
|
||||||
|
"gams": () => import("highlight.js/lib/languages/gams"),
|
||||||
|
"gauss": () => import("highlight.js/lib/languages/gauss"),
|
||||||
|
"gcode": () => import("highlight.js/lib/languages/gcode"),
|
||||||
|
"gherkin": () => import("highlight.js/lib/languages/gherkin"),
|
||||||
|
"glsl": () => import("highlight.js/lib/languages/glsl"),
|
||||||
|
"gml": () => import("highlight.js/lib/languages/gml"),
|
||||||
|
"go": () => import("highlight.js/lib/languages/go"),
|
||||||
|
"golo": () => import("highlight.js/lib/languages/golo"),
|
||||||
|
"gradle": () => import("highlight.js/lib/languages/gradle"),
|
||||||
|
"graphql": () => import("highlight.js/lib/languages/graphql"),
|
||||||
|
"groovy": () => import("highlight.js/lib/languages/groovy"),
|
||||||
|
"haml": () => import("highlight.js/lib/languages/haml"),
|
||||||
|
"handlebars": () => import("highlight.js/lib/languages/handlebars"),
|
||||||
|
"haskell": () => import("highlight.js/lib/languages/haskell"),
|
||||||
|
"haxe": () => import("highlight.js/lib/languages/haxe"),
|
||||||
|
"hsp": () => import("highlight.js/lib/languages/hsp"),
|
||||||
|
"http": () => import("highlight.js/lib/languages/http"),
|
||||||
|
"hy": () => import("highlight.js/lib/languages/hy"),
|
||||||
|
"inform7": () => import("highlight.js/lib/languages/inform7"),
|
||||||
|
"ini": () => import("highlight.js/lib/languages/ini"),
|
||||||
|
"irpf90": () => import("highlight.js/lib/languages/irpf90"),
|
||||||
|
"isbl": () => import("highlight.js/lib/languages/isbl"),
|
||||||
|
"java": () => import("highlight.js/lib/languages/java"),
|
||||||
|
"javascript": () => import("highlight.js/lib/languages/javascript"),
|
||||||
|
"jboss-cli": () => import("highlight.js/lib/languages/jboss-cli"),
|
||||||
|
"json": () => import("highlight.js/lib/languages/json"),
|
||||||
|
"julia": () => import("highlight.js/lib/languages/julia"),
|
||||||
|
"julia-repl": () => import("highlight.js/lib/languages/julia-repl"),
|
||||||
|
"kotlin": () => import("highlight.js/lib/languages/kotlin"),
|
||||||
|
"lasso": () => import("highlight.js/lib/languages/lasso"),
|
||||||
|
"latex": () => import("highlight.js/lib/languages/latex"),
|
||||||
|
"ldif": () => import("highlight.js/lib/languages/ldif"),
|
||||||
|
"leaf": () => import("highlight.js/lib/languages/leaf"),
|
||||||
|
"less": () => import("highlight.js/lib/languages/less"),
|
||||||
|
"lisp": () => import("highlight.js/lib/languages/lisp"),
|
||||||
|
"livecodeserver": () => import("highlight.js/lib/languages/livecodeserver"),
|
||||||
|
"livescript": () => import("highlight.js/lib/languages/livescript"),
|
||||||
|
"llvm": () => import("highlight.js/lib/languages/llvm"),
|
||||||
|
"lsl": () => import("highlight.js/lib/languages/lsl"),
|
||||||
|
"lua": () => import("highlight.js/lib/languages/lua"),
|
||||||
|
"makefile": () => import("highlight.js/lib/languages/makefile"),
|
||||||
|
"mathematica": () => import("highlight.js/lib/languages/mathematica"),
|
||||||
|
"matlab": () => import("highlight.js/lib/languages/matlab"),
|
||||||
|
"maxima": () => import("highlight.js/lib/languages/maxima"),
|
||||||
|
"mel": () => import("highlight.js/lib/languages/mel"),
|
||||||
|
"mercury": () => import("highlight.js/lib/languages/mercury"),
|
||||||
|
"mipsasm": () => import("highlight.js/lib/languages/mipsasm"),
|
||||||
|
"mizar": () => import("highlight.js/lib/languages/mizar"),
|
||||||
|
"perl": () => import("highlight.js/lib/languages/perl"),
|
||||||
|
"mojolicious": () => import("highlight.js/lib/languages/mojolicious"),
|
||||||
|
"monkey": () => import("highlight.js/lib/languages/monkey"),
|
||||||
|
"moonscript": () => import("highlight.js/lib/languages/moonscript"),
|
||||||
|
"n1ql": () => import("highlight.js/lib/languages/n1ql"),
|
||||||
|
"nestedtext": () => import("highlight.js/lib/languages/nestedtext"),
|
||||||
|
"nginx": () => import("highlight.js/lib/languages/nginx"),
|
||||||
|
"nim": () => import("highlight.js/lib/languages/nim"),
|
||||||
|
"nix": () => import("highlight.js/lib/languages/nix"),
|
||||||
|
"node-repl": () => import("highlight.js/lib/languages/node-repl"),
|
||||||
|
"nsis": () => import("highlight.js/lib/languages/nsis"),
|
||||||
|
"objectivec": () => import("highlight.js/lib/languages/objectivec"),
|
||||||
|
"ocaml": () => import("highlight.js/lib/languages/ocaml"),
|
||||||
|
"openscad": () => import("highlight.js/lib/languages/openscad"),
|
||||||
|
"oxygene": () => import("highlight.js/lib/languages/oxygene"),
|
||||||
|
"parser3": () => import("highlight.js/lib/languages/parser3"),
|
||||||
|
"pf": () => import("highlight.js/lib/languages/pf"),
|
||||||
|
"pgsql": () => import("highlight.js/lib/languages/pgsql"),
|
||||||
|
"php": () => import("highlight.js/lib/languages/php"),
|
||||||
|
"php-template": () => import("highlight.js/lib/languages/php-template"),
|
||||||
|
"plaintext": () => import("highlight.js/lib/languages/plaintext"),
|
||||||
|
"pony": () => import("highlight.js/lib/languages/pony"),
|
||||||
|
"powershell": () => import("highlight.js/lib/languages/powershell"),
|
||||||
|
"processing": () => import("highlight.js/lib/languages/processing"),
|
||||||
|
"profile": () => import("highlight.js/lib/languages/profile"),
|
||||||
|
"prolog": () => import("highlight.js/lib/languages/prolog"),
|
||||||
|
"properties": () => import("highlight.js/lib/languages/properties"),
|
||||||
|
"protobuf": () => import("highlight.js/lib/languages/protobuf"),
|
||||||
|
"puppet": () => import("highlight.js/lib/languages/puppet"),
|
||||||
|
"purebasic": () => import("highlight.js/lib/languages/purebasic"),
|
||||||
|
"python": () => import("highlight.js/lib/languages/python"),
|
||||||
|
"python-repl": () => import("highlight.js/lib/languages/python-repl"),
|
||||||
|
"q": () => import("highlight.js/lib/languages/q"),
|
||||||
|
"qml": () => import("highlight.js/lib/languages/qml"),
|
||||||
|
"r": () => import("highlight.js/lib/languages/r"),
|
||||||
|
"reasonml": () => import("highlight.js/lib/languages/reasonml"),
|
||||||
|
"rib": () => import("highlight.js/lib/languages/rib"),
|
||||||
|
"roboconf": () => import("highlight.js/lib/languages/roboconf"),
|
||||||
|
"routeros": () => import("highlight.js/lib/languages/routeros"),
|
||||||
|
"rsl": () => import("highlight.js/lib/languages/rsl"),
|
||||||
|
"ruleslanguage": () => import("highlight.js/lib/languages/ruleslanguage"),
|
||||||
|
"rust": () => import("highlight.js/lib/languages/rust"),
|
||||||
|
"sas": () => import("highlight.js/lib/languages/sas"),
|
||||||
|
"scala": () => import("highlight.js/lib/languages/scala"),
|
||||||
|
"scheme": () => import("highlight.js/lib/languages/scheme"),
|
||||||
|
"scilab": () => import("highlight.js/lib/languages/scilab"),
|
||||||
|
"scss": () => import("highlight.js/lib/languages/scss"),
|
||||||
|
"shell": () => import("highlight.js/lib/languages/shell"),
|
||||||
|
"smali": () => import("highlight.js/lib/languages/smali"),
|
||||||
|
"smalltalk": () => import("highlight.js/lib/languages/smalltalk"),
|
||||||
|
"sml": () => import("highlight.js/lib/languages/sml"),
|
||||||
|
"sqf": () => import("highlight.js/lib/languages/sqf"),
|
||||||
|
"sql": () => import("highlight.js/lib/languages/sql"),
|
||||||
|
"stan": () => import("highlight.js/lib/languages/stan"),
|
||||||
|
"stata": () => import("highlight.js/lib/languages/stata"),
|
||||||
|
"step21": () => import("highlight.js/lib/languages/step21"),
|
||||||
|
"stylus": () => import("highlight.js/lib/languages/stylus"),
|
||||||
|
"subunit": () => import("highlight.js/lib/languages/subunit"),
|
||||||
|
"swift": () => import("highlight.js/lib/languages/swift"),
|
||||||
|
"taggerscript": () => import("highlight.js/lib/languages/taggerscript"),
|
||||||
|
"yaml": () => import("highlight.js/lib/languages/yaml"),
|
||||||
|
"tap": () => import("highlight.js/lib/languages/tap"),
|
||||||
|
"tcl": () => import("highlight.js/lib/languages/tcl"),
|
||||||
|
"thrift": () => import("highlight.js/lib/languages/thrift"),
|
||||||
|
"tp": () => import("highlight.js/lib/languages/tp"),
|
||||||
|
"twig": () => import("highlight.js/lib/languages/twig"),
|
||||||
|
"typescript": () => import("highlight.js/lib/languages/typescript"),
|
||||||
|
"vala": () => import("highlight.js/lib/languages/vala"),
|
||||||
|
"vbnet": () => import("highlight.js/lib/languages/vbnet"),
|
||||||
|
"vbscript": () => import("highlight.js/lib/languages/vbscript"),
|
||||||
|
"vbscript-html": () => import("highlight.js/lib/languages/vbscript-html"),
|
||||||
|
"verilog": () => import("highlight.js/lib/languages/verilog"),
|
||||||
|
"vhdl": () => import("highlight.js/lib/languages/vhdl"),
|
||||||
|
"vim": () => import("highlight.js/lib/languages/vim"),
|
||||||
|
"wasm": () => import("highlight.js/lib/languages/wasm"),
|
||||||
|
"wren": () => import("highlight.js/lib/languages/wren"),
|
||||||
|
"x86asm": () => import("highlight.js/lib/languages/x86asm"),
|
||||||
|
"xl": () => import("highlight.js/lib/languages/xl"),
|
||||||
|
"xquery": () => import("highlight.js/lib/languages/xquery"),
|
||||||
|
"zephir": () => import("highlight.js/lib/languages/zephir"),
|
||||||
|
}
|
||||||
|
|
||||||
|
// hljs.listLanguages().map(l => ([l, hljs.getLanguage(l).aliases])).filter(([, b]) => b).map(([n, a]) => a.map(al => ([al, n]))).flat().map(([a, n]) => `"${a}": languages["${n}"]`).join(",\n")
|
||||||
|
const aliases: Record<string, typeof languages[keyof typeof languages]> = {
|
||||||
|
"as": languages["actionscript"],
|
||||||
|
"asc": languages["angelscript"],
|
||||||
|
"apacheconf": languages["apache"],
|
||||||
|
"osascript": languages["applescript"],
|
||||||
|
"ino": languages["arduino"],
|
||||||
|
"arm": languages["armasm"],
|
||||||
|
"html": languages["xml"],
|
||||||
|
"xhtml": languages["xml"],
|
||||||
|
"rss": languages["xml"],
|
||||||
|
"atom": languages["xml"],
|
||||||
|
"xjb": languages["xml"],
|
||||||
|
"xsd": languages["xml"],
|
||||||
|
"xsl": languages["xml"],
|
||||||
|
"plist": languages["xml"],
|
||||||
|
"wsf": languages["xml"],
|
||||||
|
"svg": languages["xml"],
|
||||||
|
"adoc": languages["asciidoc"],
|
||||||
|
"ahk": languages["autohotkey"],
|
||||||
|
"x++": languages["axapta"],
|
||||||
|
"sh": languages["bash"],
|
||||||
|
"bf": languages["brainfuck"],
|
||||||
|
"h": languages["c"],
|
||||||
|
"capnp": languages["capnproto"],
|
||||||
|
"icl": languages["clean"],
|
||||||
|
"dcl": languages["clean"],
|
||||||
|
"clj": languages["clojure"],
|
||||||
|
"edn": languages["clojure"],
|
||||||
|
"cmake.in": languages["cmake"],
|
||||||
|
"coffee": languages["coffeescript"],
|
||||||
|
"cson": languages["coffeescript"],
|
||||||
|
"iced": languages["coffeescript"],
|
||||||
|
"cls": languages["cos"],
|
||||||
|
"cc": languages["cpp"],
|
||||||
|
"c++": languages["cpp"],
|
||||||
|
"h++": languages["cpp"],
|
||||||
|
"hpp": languages["cpp"],
|
||||||
|
"hh": languages["cpp"],
|
||||||
|
"hxx": languages["cpp"],
|
||||||
|
"cxx": languages["cpp"],
|
||||||
|
"crm": languages["crmsh"],
|
||||||
|
"pcmk": languages["crmsh"],
|
||||||
|
"cr": languages["crystal"],
|
||||||
|
"cs": languages["csharp"],
|
||||||
|
"c#": languages["csharp"],
|
||||||
|
"md": languages["markdown"],
|
||||||
|
"mkdown": languages["markdown"],
|
||||||
|
"mkd": languages["markdown"],
|
||||||
|
"dpr": languages["delphi"],
|
||||||
|
"dfm": languages["delphi"],
|
||||||
|
"pas": languages["delphi"],
|
||||||
|
"pascal": languages["delphi"],
|
||||||
|
"patch": languages["diff"],
|
||||||
|
"jinja": languages["django"],
|
||||||
|
"bind": languages["dns"],
|
||||||
|
"zone": languages["dns"],
|
||||||
|
"docker": languages["dockerfile"],
|
||||||
|
"bat": languages["dos"],
|
||||||
|
"cmd": languages["dos"],
|
||||||
|
"dst": languages["dust"],
|
||||||
|
"ex": languages["elixir"],
|
||||||
|
"exs": languages["elixir"],
|
||||||
|
"rb": languages["ruby"],
|
||||||
|
"gemspec": languages["ruby"],
|
||||||
|
"podspec": languages["ruby"],
|
||||||
|
"thor": languages["ruby"],
|
||||||
|
"irb": languages["ruby"],
|
||||||
|
"erl": languages["erlang"],
|
||||||
|
"xlsx": languages["excel"],
|
||||||
|
"xls": languages["excel"],
|
||||||
|
"f90": languages["fortran"],
|
||||||
|
"f95": languages["fortran"],
|
||||||
|
"fs": languages["fsharp"],
|
||||||
|
"f#": languages["fsharp"],
|
||||||
|
"gms": languages["gams"],
|
||||||
|
"gss": languages["gauss"],
|
||||||
|
"nc": languages["gcode"],
|
||||||
|
"feature": languages["gherkin"],
|
||||||
|
"golang": languages["go"],
|
||||||
|
"gql": languages["graphql"],
|
||||||
|
"hbs": languages["handlebars"],
|
||||||
|
"html.hbs": languages["handlebars"],
|
||||||
|
"html.handlebars": languages["handlebars"],
|
||||||
|
"htmlbars": languages["handlebars"],
|
||||||
|
"hs": languages["haskell"],
|
||||||
|
"hx": languages["haxe"],
|
||||||
|
"https": languages["http"],
|
||||||
|
"hylang": languages["hy"],
|
||||||
|
"i7": languages["inform7"],
|
||||||
|
"toml": languages["ini"],
|
||||||
|
"jsp": languages["java"],
|
||||||
|
"js": languages["javascript"],
|
||||||
|
"jsx": languages["javascript"],
|
||||||
|
"mjs": languages["javascript"],
|
||||||
|
"cjs": languages["javascript"],
|
||||||
|
"wildfly-cli": languages["jboss-cli"],
|
||||||
|
"jldoctest": languages["julia-repl"],
|
||||||
|
"kt": languages["kotlin"],
|
||||||
|
"kts": languages["kotlin"],
|
||||||
|
"ls": languages["lasso"],
|
||||||
|
"lassoscript": languages["lasso"],
|
||||||
|
"tex": languages["latex"],
|
||||||
|
"mk": languages["makefile"],
|
||||||
|
"mak": languages["makefile"],
|
||||||
|
"make": languages["makefile"],
|
||||||
|
"mma": languages["mathematica"],
|
||||||
|
"wl": languages["mathematica"],
|
||||||
|
"m": languages["mercury"],
|
||||||
|
"moo": languages["mercury"],
|
||||||
|
"mips": languages["mipsasm"],
|
||||||
|
"pl": languages["perl"],
|
||||||
|
"pm": languages["perl"],
|
||||||
|
"moon": languages["moonscript"],
|
||||||
|
"nt": languages["nestedtext"],
|
||||||
|
"nginxconf": languages["nginx"],
|
||||||
|
"nixos": languages["nix"],
|
||||||
|
"mm": languages["objectivec"],
|
||||||
|
"objc": languages["objectivec"],
|
||||||
|
"obj-c": languages["objectivec"],
|
||||||
|
"obj-c++": languages["objectivec"],
|
||||||
|
"objective-c++": languages["objectivec"],
|
||||||
|
"ml": languages["ocaml"],
|
||||||
|
"scad": languages["openscad"],
|
||||||
|
"pf.conf": languages["pf"],
|
||||||
|
"postgres": languages["pgsql"],
|
||||||
|
"postgresql": languages["pgsql"],
|
||||||
|
"text": languages["plaintext"],
|
||||||
|
"txt": languages["plaintext"],
|
||||||
|
"pwsh": languages["powershell"],
|
||||||
|
"ps": languages["powershell"],
|
||||||
|
"ps1": languages["powershell"],
|
||||||
|
"pde": languages["processing"],
|
||||||
|
"pp": languages["puppet"],
|
||||||
|
"pb": languages["purebasic"],
|
||||||
|
"pbi": languages["purebasic"],
|
||||||
|
"py": languages["python"],
|
||||||
|
"gyp": languages["python"],
|
||||||
|
"ipython": languages["python"],
|
||||||
|
"pycon": languages["python-repl"],
|
||||||
|
"k": languages["q"],
|
||||||
|
"kdb": languages["q"],
|
||||||
|
"qt": languages["qml"],
|
||||||
|
"re": languages["reasonml"],
|
||||||
|
"graph": languages["roboconf"],
|
||||||
|
"instances": languages["roboconf"],
|
||||||
|
"mikrotik": languages["routeros"],
|
||||||
|
"rs": languages["rust"],
|
||||||
|
"scm": languages["scheme"],
|
||||||
|
"sci": languages["scilab"],
|
||||||
|
"console": languages["shell"],
|
||||||
|
"shellsession": languages["shell"],
|
||||||
|
"st": languages["smalltalk"],
|
||||||
|
"stanfuncs": languages["stan"],
|
||||||
|
"do": languages["stata"],
|
||||||
|
"ado": languages["stata"],
|
||||||
|
"p21": languages["step21"],
|
||||||
|
"step": languages["step21"],
|
||||||
|
"stp": languages["step21"],
|
||||||
|
"styl": languages["stylus"],
|
||||||
|
"yml": languages["yaml"],
|
||||||
|
"tk": languages["tcl"],
|
||||||
|
"craftcms": languages["twig"],
|
||||||
|
"ts": languages["typescript"],
|
||||||
|
"tsx": languages["typescript"],
|
||||||
|
"vb": languages["vbnet"],
|
||||||
|
"vbs": languages["vbscript"],
|
||||||
|
"v": languages["verilog"],
|
||||||
|
"sv": languages["verilog"],
|
||||||
|
"svh": languages["verilog"],
|
||||||
|
"tao": languages["xl"],
|
||||||
|
"xpath": languages["xquery"],
|
||||||
|
"xq": languages["xquery"],
|
||||||
|
"zep": languages["zephir"]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ParseMarkdownOptions {
|
||||||
|
parseTimestamps?: boolean;
|
||||||
|
embed?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parseMarkdown = async (raw: string, opts?: ParseMarkdownOptions) => {
|
||||||
|
if (opts?.parseTimestamps) {
|
||||||
|
raw = parseTimestamps(raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
const markdownUnparsed = toHTML(raw, { embed: opts?.embed });
|
||||||
|
const markdownUnparsedDom = new DOMParser().parseFromString(markdownUnparsed, "text/html");
|
||||||
|
|
||||||
|
const codeBlocks = markdownUnparsedDom.querySelectorAll("pre code[data-code]");
|
||||||
|
|
||||||
|
const promies = Array.from(codeBlocks).map(async (codeBlock) => {
|
||||||
|
let code: string = window.atob(codeBlock.getAttribute("data-code"));
|
||||||
|
|
||||||
|
codeBlock.classList.add("hljs");
|
||||||
|
|
||||||
|
const specifiedLanguage = codeBlock.getAttribute("data-code-language");
|
||||||
|
const languageImportFn = languages[specifiedLanguage] ?? aliases[specifiedLanguage];
|
||||||
|
|
||||||
|
if (languageImportFn) {
|
||||||
|
if (!hljs.getLanguage(specifiedLanguage)) {
|
||||||
|
const languageImport = await languageImportFn();
|
||||||
|
|
||||||
|
hljs.registerLanguage(specifiedLanguage, languageImport.default);
|
||||||
|
}
|
||||||
|
|
||||||
|
codeBlock.classList.add(specifiedLanguage);
|
||||||
|
codeBlock.innerHTML = hljs.highlight(code, {language: specifiedLanguage}).value;
|
||||||
|
} else {
|
||||||
|
codeBlock.textContent = code;
|
||||||
|
}
|
||||||
|
|
||||||
|
codeBlock.removeAttribute("data-code");
|
||||||
|
codeBlock.removeAttribute("data-code-language");
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(promies);
|
||||||
|
|
||||||
|
return markdownUnparsedDom.body.innerHTML;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default parseMarkdown;
|
11
dashboard/src/components/common/AwaitHtml.svelte
Normal file
11
dashboard/src/components/common/AwaitHtml.svelte
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
<script lang="ts">
|
||||||
|
export let htmlPromise: Promise<string> = Promise.resolve("");
|
||||||
|
</script>
|
||||||
|
|
||||||
|
{#await htmlPromise}
|
||||||
|
(loading...)
|
||||||
|
{:then html}
|
||||||
|
{@html html ?? ""}
|
||||||
|
{:catch error}
|
||||||
|
(failed to parse: {error?.message ?? String(error)})
|
||||||
|
{/await}
|
@ -1,23 +1,24 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { tick } from 'svelte';
|
import { tick } from 'svelte';
|
||||||
import { Modal, CardTitle} from 'sveltestrap';
|
import { Modal, CardTitle} from 'sveltestrap';
|
||||||
|
import AwaitHtml from './AwaitHtml.svelte';
|
||||||
import default_avatar from '../../assets/default_avatar.png';
|
import default_avatar from '../../assets/default_avatar.png';
|
||||||
import resizeMedia from '../../api/resize-media';
|
import resizeMedia from '../../api/resize-media';
|
||||||
import { toHTML } from 'discord-markdown';
|
import parseMarkdown from '../../api/parse-markdown';
|
||||||
import twemoji from 'twemoji';
|
import twemoji from 'twemoji';
|
||||||
|
|
||||||
export let item: any;
|
export let item: any;
|
||||||
export let searchBy: string = null;
|
export let searchBy: string = null;
|
||||||
export let sortBy: string = null;
|
export let sortBy: string = null;
|
||||||
|
|
||||||
let htmlName: string;
|
let htmlNamePromise: Promise<string>;
|
||||||
let nameElement: any;
|
let nameElement: any;
|
||||||
let settings = JSON.parse(localStorage.getItem("pk-settings"));
|
let settings = JSON.parse(localStorage.getItem("pk-settings"));
|
||||||
|
|
||||||
$: if (item.name) {
|
$: if (item.name) {
|
||||||
if ((searchBy === "display_name" || sortBy === "display_name") && item.display_name) htmlName = toHTML(item.display_name);
|
if ((searchBy === "display_name" || sortBy === "display_name") && item.display_name) htmlNamePromise = parseMarkdown(item.display_name);
|
||||||
else htmlName = toHTML(item.name);
|
else htmlNamePromise = parseMarkdown(item.name);
|
||||||
} else htmlName = "";
|
}
|
||||||
|
|
||||||
$: if (settings && settings.appearance.twemoji) {
|
$: if (settings && settings.appearance.twemoji) {
|
||||||
if (nameElement) twemoji.parse(nameElement, { base: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/' });
|
if (nameElement) twemoji.parse(nameElement, { base: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/' });
|
||||||
@ -47,7 +48,7 @@
|
|||||||
<div class="icon d-inline-block">
|
<div class="icon d-inline-block">
|
||||||
<slot name="icon" />
|
<slot name="icon" />
|
||||||
</div>
|
</div>
|
||||||
<span bind:this={nameElement} style="vertical-align: middle;">{@html htmlName} ({item.id})</span>
|
<span bind:this={nameElement} style="vertical-align: middle;"><AwaitHtml htmlPromise={htmlNamePromise} /> ({item.id})</span>
|
||||||
</div>
|
</div>
|
||||||
<div style="margin-left: auto;">
|
<div style="margin-left: auto;">
|
||||||
{#if item && (item.avatar_url || item.icon)}
|
{#if item && (item.avatar_url || item.icon)}
|
||||||
|
@ -2,8 +2,7 @@
|
|||||||
import { tick } from 'svelte';
|
import { tick } from 'svelte';
|
||||||
import { Row, Col, Modal, Image, Button, CardBody, ModalHeader, ModalBody, ModalFooter, Spinner } from 'sveltestrap';
|
import { Row, Col, Modal, Image, Button, CardBody, ModalHeader, ModalBody, ModalFooter, Spinner } from 'sveltestrap';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { toHTML } from 'discord-markdown';
|
import parseMarkdown from '../../api/parse-markdown';
|
||||||
import parseTimestamps from '../../api/parse-timestamps';
|
|
||||||
import resizeMedia from '../../api/resize-media';
|
import resizeMedia from '../../api/resize-media';
|
||||||
import Edit from './Edit.svelte';
|
import Edit from './Edit.svelte';
|
||||||
import twemoji from 'twemoji';
|
import twemoji from 'twemoji';
|
||||||
@ -12,6 +11,7 @@
|
|||||||
import { Link, useLocation } from 'svelte-navigator';
|
import { Link, useLocation } from 'svelte-navigator';
|
||||||
|
|
||||||
import type { Member, Group } from '../../api/types';
|
import type { Member, Group } from '../../api/types';
|
||||||
|
import AwaitHtml from '../common/AwaitHtml.svelte';
|
||||||
|
|
||||||
export let group: Group;
|
export let group: Group;
|
||||||
let editMode: boolean = false;
|
let editMode: boolean = false;
|
||||||
@ -21,14 +21,13 @@
|
|||||||
export let isMainDash = true;
|
export let isMainDash = true;
|
||||||
export let isPage = false;
|
export let isPage = false;
|
||||||
|
|
||||||
let htmlDescription: string;
|
let htmlDescriptionPromise: Promise<string>;
|
||||||
$: if (group.description) {
|
$: if (group.description) {
|
||||||
htmlDescription = toHTML(parseTimestamps(group.description), {embed: true});
|
htmlDescriptionPromise = parseMarkdown(group.description, { parseTimestamps: true, embed: true });
|
||||||
} else {
|
|
||||||
htmlDescription = "(no description)";
|
|
||||||
}
|
}
|
||||||
let htmlDisplayName: string;
|
|
||||||
$: if (group.display_name) htmlDisplayName = toHTML(group.display_name)
|
let htmlDisplayNamePromise: Promise<string>;
|
||||||
|
$: if (group.display_name) htmlDisplayNamePromise = parseMarkdown(group.display_name, { parseTimestamps: true, embed: true });
|
||||||
|
|
||||||
let settings = JSON.parse(localStorage.getItem("pk-settings"));
|
let settings = JSON.parse(localStorage.getItem("pk-settings"));
|
||||||
let descriptionElement: any;
|
let descriptionElement: any;
|
||||||
@ -83,7 +82,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{#if group.display_name}
|
{#if group.display_name}
|
||||||
<Col xs={12} lg={4} class="mb-2">
|
<Col xs={12} lg={4} class="mb-2">
|
||||||
<b>Display Name:</b> <span bind:this={displayNameElement}>{@html htmlDisplayName}</span>
|
<b>Display Name:</b> <span bind:this={displayNameElement}><AwaitHtml htmlPromise={htmlDisplayNamePromise} /></span>
|
||||||
</Col>
|
</Col>
|
||||||
{/if}
|
{/if}
|
||||||
{#if group.created && !isPublic}
|
{#if group.created && !isPublic}
|
||||||
@ -122,7 +121,7 @@
|
|||||||
</Row>
|
</Row>
|
||||||
<div class="mt-2 mb-3 description" bind:this={descriptionElement}>
|
<div class="mt-2 mb-3 description" bind:this={descriptionElement}>
|
||||||
<b>Description:</b><br />
|
<b>Description:</b><br />
|
||||||
{@html htmlDescription && htmlDescription}
|
<AwaitHtml htmlPromise={htmlDescriptionPromise} />
|
||||||
</div>
|
</div>
|
||||||
{#if (group.banner && ((settings && settings.appearance.banner_bottom) || !settings))}
|
{#if (group.banner && ((settings && settings.appearance.banner_bottom) || !settings))}
|
||||||
<img on:click={toggleBannerModal} src={resizeMedia(group.banner, [1200, 480])} alt="group banner" class="w-100 mb-3 rounded" style="max-height: 13em; object-fit: cover; cursor: pointer"/>
|
<img on:click={toggleBannerModal} src={resizeMedia(group.banner, [1200, 480])} alt="group banner" class="w-100 mb-3 rounded" style="max-height: 13em; object-fit: cover; cursor: pointer"/>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import { Card, CardHeader, CardTitle, Modal, Button, ListGroup, ListGroupItem, Label, Input, Alert, Tooltip, Row, Col } from 'sveltestrap';
|
import { Card, CardHeader, CardTitle, Modal, Button, ListGroup, ListGroupItem, Label, Input, Alert, Tooltip, Row, Col } from 'sveltestrap';
|
||||||
import { toHTML } from 'discord-markdown';
|
import parseMarkdown from '../../api/parse-markdown';
|
||||||
import twemoji from 'twemoji';
|
import twemoji from 'twemoji';
|
||||||
import { Link } from 'svelte-navigator';
|
import { Link } from 'svelte-navigator';
|
||||||
import { autoresize } from 'svelte-textarea-autoresize';
|
import { autoresize } from 'svelte-textarea-autoresize';
|
||||||
@ -16,6 +16,7 @@
|
|||||||
import api from '../../api';
|
import api from '../../api';
|
||||||
import default_avatar from '../../assets/default_avatar.png';
|
import default_avatar from '../../assets/default_avatar.png';
|
||||||
import resizeMedia from '../../api/resize-media';
|
import resizeMedia from '../../api/resize-media';
|
||||||
|
import AwaitHtml from '../common/AwaitHtml.svelte';
|
||||||
|
|
||||||
export let group: Group;
|
export let group: Group;
|
||||||
export let searchBy: string;
|
export let searchBy: string;
|
||||||
@ -33,17 +34,17 @@
|
|||||||
|
|
||||||
let settings = JSON.parse(localStorage.getItem("pk-settings"));
|
let settings = JSON.parse(localStorage.getItem("pk-settings"));
|
||||||
|
|
||||||
let htmlName: string;
|
let htmlNamePromise: Promise<string>;
|
||||||
$: htmlDesc = group.description && toHTML(group.description, { embed: true}) || "(no description)";
|
$: htmlDescPromise = group.description ? parseMarkdown(group.description, { embed: true }) : Promise.resolve("(no description)");
|
||||||
$: htmlDisplayName = group.display_name && toHTML(group.display_name);
|
$: htmlDisplayNamePromise = group.display_name ? parseMarkdown(group.display_name, { embed: true }) : undefined;
|
||||||
|
|
||||||
let nameElement: any;
|
let nameElement: any;
|
||||||
let descElement: any;
|
let descElement: any;
|
||||||
let dnElement: any;
|
let dnElement: any;
|
||||||
|
|
||||||
$: if (group.name) {
|
$: if (group.name) {
|
||||||
if ((searchBy === "display name" || sortBy === "display name") && group.display_name) htmlName = toHTML(group.display_name);
|
if ((searchBy === "display name" || sortBy === "display name") && group.display_name) htmlNamePromise = parseMarkdown(group.display_name);
|
||||||
else htmlName = toHTML(group.name);
|
else htmlNamePromise = parseMarkdown(group.name);
|
||||||
}
|
}
|
||||||
if (settings && settings.appearance.twemoji) {
|
if (settings && settings.appearance.twemoji) {
|
||||||
if (nameElement) twemoji.parse(nameElement, { base: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/' });
|
if (nameElement) twemoji.parse(nameElement, { base: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/' });
|
||||||
@ -111,7 +112,7 @@
|
|||||||
<div class="icon d-inline-block">
|
<div class="icon d-inline-block">
|
||||||
<slot name="icon" />
|
<slot name="icon" />
|
||||||
</div>
|
</div>
|
||||||
<span bind:this={nameElement} style="vertical-align: middle; margin-bottom: 0;">{@html htmlName} ({group.id})</span>
|
<span bind:this={nameElement} style="vertical-align: middle; margin-bottom: 0;">{@html htmlNamePromise} ({group.id})</span>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<div class="card-body d-block hide-scrollbar" style="flex: 1; overflow: auto;">
|
<div class="card-body d-block hide-scrollbar" style="flex: 1; overflow: auto;">
|
||||||
@ -123,11 +124,11 @@
|
|||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
{#if group.display_name}
|
{#if group.display_name}
|
||||||
<div class="text-center" bind:this={dnElement}><b>{@html htmlDisplayName}</b></div>
|
<div class="text-center" bind:this={dnElement}><b><AwaitHtml htmlPromise={htmlDisplayNamePromise} /></b></div>
|
||||||
{/if}
|
{/if}
|
||||||
<hr style="min-height: 1px;"/>
|
<hr style="min-height: 1px;"/>
|
||||||
<div bind:this={descElement}>
|
<div bind:this={descElement}>
|
||||||
{@html htmlDesc}
|
<AwaitHtml htmlPromise={htmlDescPromise} />
|
||||||
</div>
|
</div>
|
||||||
<hr style="min-height: 1px;"/>
|
<hr style="min-height: 1px;"/>
|
||||||
<Row>
|
<Row>
|
||||||
@ -150,7 +151,7 @@
|
|||||||
<hr style="min-height: 1px"/>
|
<hr style="min-height: 1px"/>
|
||||||
<ListGroup>
|
<ListGroup>
|
||||||
{#each memberList as member, index (member.id)}
|
{#each memberList as member, index (member.id)}
|
||||||
<ListGroupItem class="d-flex"><span bind:this={listGroupElements[index]}><span><b>{@html toHTML(member.name)}</b> (<code>{member.id}</code>)</span></ListGroupItem>
|
<ListGroupItem class="d-flex"><span bind:this={listGroupElements[index]}><span><b><AwaitHtml htmlPromise={parseMarkdown(member.name)} /></b> (<code>{member.id}</code>)</span></ListGroupItem>
|
||||||
{/each}
|
{/each}
|
||||||
</ListGroup>
|
</ListGroup>
|
||||||
{:else}
|
{:else}
|
||||||
|
@ -144,7 +144,7 @@
|
|||||||
{:else}<Button style="flex: 0" color="primary" disabled aria-label="submit edits"><Spinner size="sm"/></Button> <Button style="flex: 0" color="secondary" disabled aria-label="cancel edits">Back</Button><Button style="flex: 0; float: right;" color="danger" disabled aria-label="delete group">Delete</Button>{/if}
|
{:else}<Button style="flex: 0" color="primary" disabled aria-label="submit edits"><Spinner size="sm"/></Button> <Button style="flex: 0" color="secondary" disabled aria-label="cancel edits">Back</Button><Button style="flex: 0; float: right;" color="danger" disabled aria-label="delete group">Delete</Button>{/if}
|
||||||
<Modal size="lg" isOpen={deleteOpen} toggle={toggleDeleteModal}>
|
<Modal size="lg" isOpen={deleteOpen} toggle={toggleDeleteModal}>
|
||||||
<ModalHeader toggle={toggleDeleteModal}>
|
<ModalHeader toggle={toggleDeleteModal}>
|
||||||
Delete member
|
Delete group
|
||||||
</ModalHeader>
|
</ModalHeader>
|
||||||
<ModalBody>
|
<ModalBody>
|
||||||
{#if deleteErr}<Alert color="danger">{deleteErr}</Alert>{/if}
|
{#if deleteErr}<Alert color="danger">{deleteErr}</Alert>{/if}
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { tick } from 'svelte';
|
import { tick } from 'svelte';
|
||||||
import { Row, Col, Modal, Image, Button, CardBody, ModalHeader, ModalBody } from 'sveltestrap';
|
import { Row, Col, Modal, Button, CardBody, ModalHeader, ModalBody } from 'sveltestrap';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { toHTML } from 'discord-markdown';
|
import parseMarkdown from '../../api/parse-markdown';
|
||||||
import parseTimestamps from '../../api/parse-timestamps';
|
|
||||||
import resizeMedia from '../../api/resize-media';
|
import resizeMedia from '../../api/resize-media';
|
||||||
import twemoji from 'twemoji';
|
import twemoji from 'twemoji';
|
||||||
|
|
||||||
@ -14,6 +13,7 @@
|
|||||||
|
|
||||||
import type { Member, Group } from '../../api/types';
|
import type { Member, Group } from '../../api/types';
|
||||||
import { Link, useLocation } from 'svelte-navigator';
|
import { Link, useLocation } from 'svelte-navigator';
|
||||||
|
import AwaitHtml from '../common/AwaitHtml.svelte';
|
||||||
|
|
||||||
export let groups: Group[] = [];
|
export let groups: Group[] = [];
|
||||||
export let member: Member;
|
export let member: Member;
|
||||||
@ -24,16 +24,14 @@
|
|||||||
let editMode: boolean = false;
|
let editMode: boolean = false;
|
||||||
let groupMode: boolean = false;
|
let groupMode: boolean = false;
|
||||||
|
|
||||||
let htmlDescription: string;
|
let htmlDescriptionPromise: Promise<string> = Promise.resolve("(no description)");
|
||||||
$: if (member.description) {
|
$: if (member.description) {
|
||||||
htmlDescription = toHTML(parseTimestamps(member.description), {embed: true});
|
htmlDescriptionPromise = parseMarkdown(member.description, { parseTimestamps: true, embed: true });
|
||||||
} else {
|
|
||||||
htmlDescription = "(no description)";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let htmlPronouns: string;
|
let htmlPronounsPromise: Promise<string>;
|
||||||
$: if (member.pronouns) {
|
$: if (member.pronouns) {
|
||||||
htmlPronouns = toHTML(parseTimestamps(member.pronouns), {embed: true});
|
htmlPronounsPromise = parseMarkdown(member.pronouns, { parseTimestamps: true, embed: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
let settings = JSON.parse(localStorage.getItem("pk-settings"));
|
let settings = JSON.parse(localStorage.getItem("pk-settings"));
|
||||||
@ -115,7 +113,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{#if member.pronouns}
|
{#if member.pronouns}
|
||||||
<Col xs={12} lg={4} class="mb-2">
|
<Col xs={12} lg={4} class="mb-2">
|
||||||
<b>Pronouns:</b> <span bind:this={pronounElement}>{@html htmlPronouns}</span>
|
<b>Pronouns:</b> <span bind:this={pronounElement}><AwaitHtml htmlPromise={htmlPronounsPromise} /></span>
|
||||||
</Col>
|
</Col>
|
||||||
{/if}
|
{/if}
|
||||||
{#if member.birthday}
|
{#if member.birthday}
|
||||||
@ -172,7 +170,7 @@
|
|||||||
</Row>
|
</Row>
|
||||||
<div class="my-2 mb-3 description" bind:this={descriptionElement}>
|
<div class="my-2 mb-3 description" bind:this={descriptionElement}>
|
||||||
<b>Description:</b><br />
|
<b>Description:</b><br />
|
||||||
{@html htmlDescription && htmlDescription}
|
<AwaitHtml htmlPromise={htmlDescriptionPromise} />
|
||||||
</div>
|
</div>
|
||||||
{#if (member.banner && ((settings && settings.appearance.banner_bottom) || !settings))}
|
{#if (member.banner && ((settings && settings.appearance.banner_bottom) || !settings))}
|
||||||
<img on:click={toggleBannerModal} src={resizeMedia(member.banner, [1200, 480])} alt="member banner" class="w-100 mb-3 rounded" style="max-height: 13em; object-fit: cover; cursor: pointer"/>
|
<img on:click={toggleBannerModal} src={resizeMedia(member.banner, [1200, 480])} alt="member banner" class="w-100 mb-3 rounded" style="max-height: 13em; object-fit: cover; cursor: pointer"/>
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { createEventDispatcher } from 'svelte';
|
import { createEventDispatcher } from 'svelte';
|
||||||
import { Card, CardHeader, CardTitle, Modal, Button, ListGroup, ListGroupItem, Input, Alert, Label, Spinner, Row, Col, Tooltip } from 'sveltestrap';
|
import { Card, CardHeader, CardTitle, Modal, Button, ListGroup, ListGroupItem, Input, Alert, Label, Spinner, Row, Col, Tooltip } from 'sveltestrap';
|
||||||
import { toHTML } from 'discord-markdown';
|
import AwaitHtml from '../common/AwaitHtml.svelte';
|
||||||
|
import parseMarkdown from '../../api/parse-markdown';
|
||||||
import twemoji from 'twemoji';
|
import twemoji from 'twemoji';
|
||||||
import { Link } from 'svelte-navigator';
|
import { Link } from 'svelte-navigator';
|
||||||
import { autoresize } from 'svelte-textarea-autoresize';
|
import { autoresize } from 'svelte-textarea-autoresize';
|
||||||
@ -34,10 +35,10 @@
|
|||||||
|
|
||||||
let settings = JSON.parse(localStorage.getItem("pk-settings"));
|
let settings = JSON.parse(localStorage.getItem("pk-settings"));
|
||||||
|
|
||||||
let htmlName: string;
|
let htmlNamePromise: Promise<string>;
|
||||||
$: htmlDesc = member.description && toHTML(member.description, { embed: true}) || "(no description)";
|
$: htmlDescPromise = member.description ? parseMarkdown(member.description, { embed: true }) : Promise.resolve("(no description)");
|
||||||
$: htmlDisplayName = member.display_name && toHTML(member.display_name);
|
$: htmlDisplayNamePromise = member.display_name ? parseMarkdown(member.display_name) : undefined;
|
||||||
$: htmlPronouns = member.pronouns && toHTML(member.pronouns, {embed: true});
|
$: htmlPronounsPromise = member.pronouns ? parseMarkdown(member.pronouns, {embed: true}) : undefined;
|
||||||
|
|
||||||
let nameElement: any;
|
let nameElement: any;
|
||||||
let descElement: any;
|
let descElement: any;
|
||||||
@ -45,8 +46,8 @@
|
|||||||
let prnsElement: any;
|
let prnsElement: any;
|
||||||
|
|
||||||
$: if (member.name) {
|
$: if (member.name) {
|
||||||
if ((searchBy === "display name" || sortBy === "display name") && member.display_name) htmlName = toHTML(member.display_name);
|
if ((searchBy === "display name" || sortBy === "display name") && member.display_name) htmlNamePromise = parseMarkdown(member.display_name);
|
||||||
else htmlName = toHTML(member.name);
|
else htmlNamePromise = parseMarkdown(member.name);
|
||||||
}
|
}
|
||||||
if (settings && settings.appearance.twemoji) {
|
if (settings && settings.appearance.twemoji) {
|
||||||
if (nameElement) twemoji.parse(nameElement, { base: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/' });
|
if (nameElement) twemoji.parse(nameElement, { base: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/' });
|
||||||
@ -117,7 +118,7 @@
|
|||||||
<div class="icon d-inline-block">
|
<div class="icon d-inline-block">
|
||||||
<slot name="icon" />
|
<slot name="icon" />
|
||||||
</div>
|
</div>
|
||||||
<span bind:this={nameElement} style="vertical-align: middle; margin-bottom: 0;">{@html htmlName} ({member.id})</span>
|
<span bind:this={nameElement} style="vertical-align: middle; margin-bottom: 0;"><AwaitHtml htmlPromise={htmlNamePromise} /> ({member.id})</span>
|
||||||
</CardTitle>
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<div class="card-body d-block hide-scrollbar" style="flex: 1; overflow: auto;">
|
<div class="card-body d-block hide-scrollbar" style="flex: 1; overflow: auto;">
|
||||||
@ -129,14 +130,14 @@
|
|||||||
</div>
|
</div>
|
||||||
</Modal>
|
</Modal>
|
||||||
{#if member.display_name}
|
{#if member.display_name}
|
||||||
<div class="text-center" bind:this={dnElement}><b>{@html htmlDisplayName}</b></div>
|
<div class="text-center" bind:this={dnElement}><b><AwaitHtml htmlPromise={htmlDisplayNamePromise} /></b></div>
|
||||||
{/if}
|
{/if}
|
||||||
{#if member.pronouns}
|
{#if member.pronouns}
|
||||||
<div class="text-center" bind:this={prnsElement}>{@html htmlPronouns}</div>
|
<div class="text-center" bind:this={prnsElement}><AwaitHtml htmlPromise={htmlPronounsPromise} /></div>
|
||||||
{/if}
|
{/if}
|
||||||
<hr style="min-height: 1px;"/>
|
<hr style="min-height: 1px;"/>
|
||||||
<div bind:this={descElement}>
|
<div bind:this={descElement}>
|
||||||
{@html htmlDesc}
|
<AwaitHtml htmlPromise={htmlDescPromise} />
|
||||||
</div>
|
</div>
|
||||||
<hr style="min-height: 1px;"/>
|
<hr style="min-height: 1px;"/>
|
||||||
<Row>
|
<Row>
|
||||||
@ -159,7 +160,7 @@
|
|||||||
<hr style="min-height: 1px"/>
|
<hr style="min-height: 1px"/>
|
||||||
<ListGroup>
|
<ListGroup>
|
||||||
{#each groupList as group, index (group.id)}
|
{#each groupList as group, index (group.id)}
|
||||||
<ListGroupItem class="d-flex"><span bind:this={listGroupElements[index]}><span><b>{@html toHTML(group.name)}</b> (<code>{group.id}</code>)</span></ListGroupItem>
|
<ListGroupItem class="d-flex"><span bind:this={listGroupElements[index]}><span><b><AwaitHtml htmlPromise={parseMarkdown(group.name)} /></b> (<code>{group.id}</code>)</span></ListGroupItem>
|
||||||
{/each}
|
{/each}
|
||||||
</ListGroup>
|
</ListGroup>
|
||||||
{:else}
|
{:else}
|
||||||
|
@ -3,7 +3,8 @@
|
|||||||
import ListPagination from "../common/ListPagination.svelte";
|
import ListPagination from "../common/ListPagination.svelte";
|
||||||
import twemoji from "twemoji";
|
import twemoji from "twemoji";
|
||||||
import Svelecte, { addFormatter } from 'svelecte';
|
import Svelecte, { addFormatter } from 'svelecte';
|
||||||
import { toHTML } from 'discord-markdown';
|
import AwaitHtml from '../common/AwaitHtml.svelte';
|
||||||
|
import parseMarkdown from '../../api/parse-markdown';
|
||||||
|
|
||||||
import FaFolderOpen from 'svelte-icons/fa/FaFolderOpen.svelte'
|
import FaFolderOpen from 'svelte-icons/fa/FaFolderOpen.svelte'
|
||||||
import FaFolderPlus from 'svelte-icons/fa/FaFolderPlus.svelte'
|
import FaFolderPlus from 'svelte-icons/fa/FaFolderPlus.svelte'
|
||||||
@ -115,7 +116,7 @@
|
|||||||
{#if finalGroupsList && finalGroupsList.length > 0}
|
{#if finalGroupsList && finalGroupsList.length > 0}
|
||||||
<ListGroup>
|
<ListGroup>
|
||||||
{#each finalGroupsList as group, index (group.id)}
|
{#each finalGroupsList as group, index (group.id)}
|
||||||
<ListGroupItem class="d-flex"><span bind:this={listGroupElements[index]} class="d-flex justify-content-between flex-grow-1"><span><b>{group.name}</b> (<code>{group.id}</code>)</span> <span>{@html group.display_name ? `${toHTML(group.display_name)}` : ""}</span></span></ListGroupItem>
|
<ListGroupItem class="d-flex"><span bind:this={listGroupElements[index]} class="d-flex justify-content-between flex-grow-1"><span><b>{group.name}</b> (<code>{group.id}</code>)</span> <span><AwaitHtml htmlPromise={parseMarkdown(group.display_name)} /></span></span></ListGroupItem>
|
||||||
{/each}
|
{/each}
|
||||||
</ListGroup>
|
</ListGroup>
|
||||||
{:else}
|
{:else}
|
||||||
|
@ -1,33 +1,33 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Row, Col, Modal, Image, Button } from 'sveltestrap';
|
import { Row, Col, Modal, Image, Button } from 'sveltestrap';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { toHTML } from 'discord-markdown';
|
import parseMarkdown from '../../api/parse-markdown';
|
||||||
import parseTimestamps from '../../api/parse-timestamps';
|
|
||||||
import resizeMedia from '../../api/resize-media';
|
import resizeMedia from '../../api/resize-media';
|
||||||
import twemoji from 'twemoji';
|
import twemoji from 'twemoji';
|
||||||
|
|
||||||
import type { System } from '../../api/types';
|
import type { System } from '../../api/types';
|
||||||
|
import AwaitHtml from '../common/AwaitHtml.svelte';
|
||||||
|
|
||||||
export let user: System;
|
export let user: System;
|
||||||
export let editMode: boolean;
|
export let editMode: boolean;
|
||||||
export let isPublic: boolean;
|
export let isPublic: boolean;
|
||||||
|
|
||||||
let htmlDescription: string;
|
let htmlDescriptionPromise: Promise<string>;
|
||||||
let htmlName: string;
|
let htmlNamePromise: Promise<string>;
|
||||||
let htmlPronouns: string;
|
let htmlPronounsPromise: Promise<string>;
|
||||||
|
|
||||||
if (user.description) {
|
if (user.description) {
|
||||||
htmlDescription = toHTML(parseTimestamps(user.description), {embed: true});
|
htmlDescriptionPromise = parseMarkdown(user.description, { embed: true, parseTimestamps: true });
|
||||||
} else {
|
} else {
|
||||||
htmlDescription = "(no description)";
|
htmlDescriptionPromise = Promise.resolve("(no description)");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.name) {
|
if (user.name) {
|
||||||
htmlName = toHTML(user.name);
|
htmlNamePromise = parseMarkdown(user.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (user.pronouns) {
|
if (user.pronouns) {
|
||||||
htmlPronouns = toHTML(user.pronouns);
|
htmlPronounsPromise = parseMarkdown(user.pronouns);
|
||||||
}
|
}
|
||||||
|
|
||||||
let created = moment(user.created).format("MMM D, YYYY");
|
let created = moment(user.created).format("MMM D, YYYY");
|
||||||
@ -58,7 +58,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{#if user.name}
|
{#if user.name}
|
||||||
<Col xs={12} lg={4} class="mb-2">
|
<Col xs={12} lg={4} class="mb-2">
|
||||||
<span bind:this={nameElement}><b>Name:</b> {@html htmlName}</span>
|
<span bind:this={nameElement}><b>Name:</b> <AwaitHtml htmlPromise={htmlNamePromise} /></span>
|
||||||
</Col>
|
</Col>
|
||||||
{/if}
|
{/if}
|
||||||
{#if user.tag}
|
{#if user.tag}
|
||||||
@ -68,7 +68,7 @@
|
|||||||
{/if}
|
{/if}
|
||||||
{#if user.pronouns}
|
{#if user.pronouns}
|
||||||
<Col xs={12} lg={4} class="mb-2">
|
<Col xs={12} lg={4} class="mb-2">
|
||||||
<span bind:this={pronounElement}><b>Pronouns:</b> {@html htmlPronouns}</span>
|
<span bind:this={pronounElement}><b>Pronouns:</b> <AwaitHtml htmlPromise={htmlPronounsPromise} /></span>
|
||||||
</Col>
|
</Col>
|
||||||
{/if}
|
{/if}
|
||||||
{#if user.created && !isPublic}
|
{#if user.created && !isPublic}
|
||||||
@ -99,7 +99,7 @@
|
|||||||
</Row>
|
</Row>
|
||||||
<div class="my-2 description" bind:this={descriptionElement}>
|
<div class="my-2 description" bind:this={descriptionElement}>
|
||||||
<b>Description:</b><br />
|
<b>Description:</b><br />
|
||||||
{@html htmlDescription}
|
<AwaitHtml htmlPromise={htmlDescriptionPromise} />
|
||||||
</div>
|
</div>
|
||||||
{#if (user.banner && ((settings && settings.appearance.banner_bottom) || !settings))}
|
{#if (user.banner && ((settings && settings.appearance.banner_bottom) || !settings))}
|
||||||
<img on:click={toggleBannerModal} src={resizeMedia(user.banner, [1200, 480])} alt="system banner" class="w-100 mb-3 rounded" style="max-height: 13em; object-fit: cover; cursor: pointer;"/>
|
<img on:click={toggleBannerModal} src={resizeMedia(user.banner, [1200, 480])} alt="system banner" class="w-100 mb-3 rounded" style="max-height: 13em; object-fit: cover; cursor: pointer;"/>
|
||||||
|
@ -2,7 +2,7 @@ import * as Sentry from "@sentry/browser";
|
|||||||
import { Integrations } from "@sentry/tracing";
|
import { Integrations } from "@sentry/tracing";
|
||||||
|
|
||||||
// polyfill for replaceAll
|
// polyfill for replaceAll
|
||||||
import * as replaceAll from 'core-js-pure/es/string/virtual/replace-all.js';
|
import replaceAll from 'core-js-pure/es/string/virtual/replace-all.js';
|
||||||
if (!String.prototype.replaceAll)
|
if (!String.prototype.replaceAll)
|
||||||
String.prototype.replaceAll = replaceAll;
|
String.prototype.replaceAll = replaceAll;
|
||||||
|
|
||||||
|
@ -6,10 +6,11 @@
|
|||||||
import { loggedIn, currentUser } from '../stores';
|
import { loggedIn, currentUser } from '../stores';
|
||||||
import { Link } from 'svelte-navigator';
|
import { Link } from 'svelte-navigator';
|
||||||
import twemoji from 'twemoji';
|
import twemoji from 'twemoji';
|
||||||
import { toHTML } from 'discord-markdown';
|
import parseMarkdown from '../api/parse-markdown';
|
||||||
|
|
||||||
import type { System } from '../api/types';
|
import type { System } from '../api/types';
|
||||||
import api from '../api';
|
import api from '../api';
|
||||||
|
import AwaitHtml from '../components/common/AwaitHtml.svelte';
|
||||||
|
|
||||||
let loading = false;
|
let loading = false;
|
||||||
let err: string;
|
let err: string;
|
||||||
@ -70,9 +71,9 @@
|
|||||||
|
|
||||||
let settings = JSON.parse(localStorage.getItem("pk-settings"));
|
let settings = JSON.parse(localStorage.getItem("pk-settings"));
|
||||||
let welcomeElement: any;
|
let welcomeElement: any;
|
||||||
let htmlName: string;
|
let htmlNamePromise: Promise<string>;
|
||||||
$: if (user && user.name) {
|
$: if (user && user.name) {
|
||||||
htmlName = toHTML(user.name);
|
htmlNamePromise = parseMarkdown(user.name);
|
||||||
}
|
}
|
||||||
$: if (settings && settings.appearance.twemoji) {
|
$: if (settings && settings.appearance.twemoji) {
|
||||||
if (welcomeElement) twemoji.parse(welcomeElement, { base: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/' });
|
if (welcomeElement) twemoji.parse(welcomeElement, { base: 'https://cdn.jsdelivr.net/gh/twitter/twemoji@14.0.2/assets/' });
|
||||||
@ -103,7 +104,7 @@
|
|||||||
verifying login...
|
verifying login...
|
||||||
{:else if isLoggedIn}
|
{:else if isLoggedIn}
|
||||||
{#if user && user.name}
|
{#if user && user.name}
|
||||||
<p bind:this={welcomeElement}>Welcome, <b>{@html htmlName}</b>!</p>
|
<p bind:this={welcomeElement}>Welcome, <b><AwaitHtml htmlPromise={htmlNamePromise} /></b>!</p>
|
||||||
{:else}
|
{:else}
|
||||||
<p>Welcome!</p>
|
<p>Welcome!</p>
|
||||||
{/if}
|
{/if}
|
||||||
|
@ -2,12 +2,15 @@
|
|||||||
import { Card, CardHeader, CardBody, Container, Row, Col, CardTitle, Tooltip, Button } from 'sveltestrap';
|
import { Card, CardHeader, CardBody, Container, Row, Col, CardTitle, Tooltip, Button } from 'sveltestrap';
|
||||||
import Toggle from 'svelte-toggle';
|
import Toggle from 'svelte-toggle';
|
||||||
import { autoresize } from 'svelte-textarea-autoresize';
|
import { autoresize } from 'svelte-textarea-autoresize';
|
||||||
|
import FaAmbulance from 'svelte-icons/fa/FaAmbulance.svelte'
|
||||||
import FaCogs from 'svelte-icons/fa/FaCogs.svelte'
|
import FaCogs from 'svelte-icons/fa/FaCogs.svelte'
|
||||||
import type { Config } from '../../api/types';
|
import type { Config } from '../../api/types';
|
||||||
import api from '../../api';
|
import api from '../../api';
|
||||||
|
|
||||||
let savedSettings = JSON.parse(localStorage.getItem("pk-settings"));
|
let savedSettings = JSON.parse(localStorage.getItem("pk-settings"));
|
||||||
let apiConfig: Config = JSON.parse(localStorage.getItem("pk-config"));
|
let apiConfig: Config = JSON.parse(localStorage.getItem("pk-config"));
|
||||||
|
let token = localStorage.getItem("pk-token");
|
||||||
|
let showToken = false;
|
||||||
|
|
||||||
let settings = {
|
let settings = {
|
||||||
appearance: {
|
appearance: {
|
||||||
@ -40,6 +43,7 @@
|
|||||||
else document.getElementById("app").classList.remove("dyslexic");
|
else document.getElementById("app").classList.remove("dyslexic");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const revealToken = () => showToken = !showToken;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<Container>
|
<Container>
|
||||||
@ -123,6 +127,36 @@
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
{/if}
|
{/if}
|
||||||
|
{#if token}
|
||||||
|
<Row>
|
||||||
|
<Col class="mx-auto" xs={12} lg={11} xl={10}>
|
||||||
|
<Card class="mb-4">
|
||||||
|
<CardHeader>
|
||||||
|
<CardTitle style="margin-top: 8px; outline: none;">
|
||||||
|
<div class="icon d-inline-block">
|
||||||
|
<FaAmbulance />
|
||||||
|
</div>Recovery
|
||||||
|
</CardTitle>
|
||||||
|
</CardHeader>
|
||||||
|
<CardBody>
|
||||||
|
<p>If you've lost access to your discord account, you can retrieve your token here.</p>
|
||||||
|
<p>Send a direct message to a staff member (a helper, moderator or developer <a href="https://discord.gg/PczBt78">in the support server</a>), they can recover your system with this token.</p>
|
||||||
|
<Button color="danger" on:click={() => revealToken()}>Reveal token</Button>
|
||||||
|
{#if showToken}
|
||||||
|
<Row>
|
||||||
|
<Col xs={12} md={9}>
|
||||||
|
<span class="mt-2 form-control">{token}</span>
|
||||||
|
</Col>
|
||||||
|
<Col xs={12} md={3}>
|
||||||
|
<Button color="primary" class="w-100 mt-2" on:click={() => navigator.clipboard.writeText(token)}>Copy</Button>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
{/if}
|
||||||
|
</CardBody>
|
||||||
|
</Card>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
{/if}
|
||||||
</Container>
|
</Container>
|
||||||
|
|
||||||
<svelte:head>
|
<svelte:head>
|
||||||
|
@ -15,8 +15,11 @@ export default defineConfig({
|
|||||||
if (filename.length < 2) return 'index';
|
if (filename.length < 2) return 'index';
|
||||||
else filename = filename[1];
|
else filename = filename[1];
|
||||||
|
|
||||||
// this is really big and makes the map size go over the sentry file cache limit
|
if (filename.startsWith("/highlight.js/es/languages/")) {
|
||||||
if (filename.includes("highlight.js")) return 'vendor-0';
|
const lang = filename.split("/").pop().split(".").shift();
|
||||||
|
|
||||||
|
return `vendor_hljs-${lang}`;
|
||||||
|
}
|
||||||
|
|
||||||
return 'vendor-1';
|
return 'vendor-1';
|
||||||
// return `vendor-${filename.charCodeAt(1) % 2}`;
|
// return `vendor-${filename.charCodeAt(1) % 2}`;
|
||||||
|
@ -225,6 +225,11 @@ balanced-match@^1.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
|
||||||
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
|
integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
|
||||||
|
|
||||||
|
base-64@^1.0.0:
|
||||||
|
version "1.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/base-64/-/base-64-1.0.0.tgz#09d0f2084e32a3fd08c2475b973788eee6ae8f4a"
|
||||||
|
integrity sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg==
|
||||||
|
|
||||||
binary-extensions@^2.0.0:
|
binary-extensions@^2.0.0:
|
||||||
version "2.2.0"
|
version "2.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
|
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d"
|
||||||
@ -341,12 +346,11 @@ detect-indent@^6.0.0:
|
|||||||
resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6"
|
resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-6.1.0.tgz#592485ebbbf6b3b1ab2be175c8393d04ca0d57e6"
|
||||||
integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==
|
integrity sha512-reYkTUJAZb9gUuZ2RvVCNhVHdg62RHnJ7WJl8ftMi4diZ6NWlciOzQN88pUhSELEwflJht4oQDv0F0BMlwaYtA==
|
||||||
|
|
||||||
discord-markdown@^2.5.1:
|
"discord-markdown@https://github.com/repository/discord-markdown#b9608feef6856c9baa68f96c932a25c1d2bc55c2":
|
||||||
version "2.5.1"
|
version "2.5.1"
|
||||||
resolved "https://registry.yarnpkg.com/discord-markdown/-/discord-markdown-2.5.1.tgz#d18773c6e3cff8df90f305654ecbbc5e38c507eb"
|
resolved "https://github.com/repository/discord-markdown#b9608feef6856c9baa68f96c932a25c1d2bc55c2"
|
||||||
integrity sha512-SGNlL1Y8NYjY2MA5Vj1SI5+Ue5GUW2HkkDAq5jPQ6fI5j/rwOB814lFNhfs2AJMT72Jij8usTEqWZfdU8C3uag==
|
|
||||||
dependencies:
|
dependencies:
|
||||||
highlight.js "^11.2.0"
|
base-64 "^1.0.0"
|
||||||
simple-markdown "^0.7.3"
|
simple-markdown "^0.7.3"
|
||||||
|
|
||||||
email-addresses@^3.0.1:
|
email-addresses@^3.0.1:
|
||||||
@ -653,10 +657,10 @@ has@^1.0.3:
|
|||||||
dependencies:
|
dependencies:
|
||||||
function-bind "^1.1.1"
|
function-bind "^1.1.1"
|
||||||
|
|
||||||
highlight.js@^11.2.0:
|
highlight.js@^11.7.0:
|
||||||
version "11.3.1"
|
version "11.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.3.1.tgz#813078ef3aa519c61700f84fe9047231c5dc3291"
|
resolved "https://registry.yarnpkg.com/highlight.js/-/highlight.js-11.7.0.tgz#3ff0165bc843f8c9bce1fd89e2fda9143d24b11e"
|
||||||
integrity sha512-PUhCRnPjLtiLHZAQ5A/Dt5F8cWZeMyj9KRsACsWT+OD6OP0x6dp5OmT5jdx0JgEyPxPZZIPQpRN2TciUT7occw==
|
integrity sha512-1rRqesRFhMO/PRF+G86evnyJkCgaZFOI+Z6kdj15TA18funfoqJXvgPCLSf0SWq3SRfg1j3HlDs8o4s3EGq1oQ==
|
||||||
|
|
||||||
immutable@^4.0.0:
|
immutable@^4.0.0:
|
||||||
version "4.1.0"
|
version "4.1.0"
|
||||||
|
@ -24,6 +24,7 @@ services:
|
|||||||
command: ["bin/PluralKit.API.dll"]
|
command: ["bin/PluralKit.API.dll"]
|
||||||
environment:
|
environment:
|
||||||
- "PluralKit:Database=Host=db;Username=postgres;Password=postgres;Database=postgres;Maximum Pool Size=1000"
|
- "PluralKit:Database=Host=db;Username=postgres;Password=postgres;Database=postgres;Maximum Pool Size=1000"
|
||||||
|
- "PluralKit:RedisAddr=redis"
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:2838:5000"
|
- "127.0.0.1:2838:5000"
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
|
@ -31,8 +31,9 @@ When something goes wrong, the API will send back a 4xx HTTP status code, along
|
|||||||
|code|HTTP response code|meaning|
|
|code|HTTP response code|meaning|
|
||||||
|---|---|---|
|
|---|---|---|
|
||||||
|0|500|Internal server error, try again later|
|
|0|500|Internal server error, try again later|
|
||||||
|0|400|Bad Request (usually invalid JSON)|
|
|0|400|Invalid JSON, or invalid request format (check `error` key in the response body)|
|
||||||
|0|401|Missing or invalid Authorization header|
|
|0|401|Missing or invalid Authorization header|
|
||||||
|
|0|403|Your access to the API is blocked - please contact us in the support server|
|
||||||
|20001|404|System not found.|
|
|20001|404|System not found.|
|
||||||
|20002|404|Member not found.|
|
|20002|404|Member not found.|
|
||||||
|20003|404|Member '{memberRef}' not found.|
|
|20003|404|Member '{memberRef}' not found.|
|
||||||
|
@ -46,6 +46,7 @@ Every PluralKit entity has two IDs: a short (5-character) ID and a longer UUID.
|
|||||||
|birthday|?string|`YYYY-MM-DD` format, 0004 hides the year|
|
|birthday|?string|`YYYY-MM-DD` format, 0004 hides the year|
|
||||||
|pronouns|?string|100-character-limit|
|
|pronouns|?string|100-character-limit|
|
||||||
|avatar_url|?string|256-character limit, must be a publicly-accessible URL|
|
|avatar_url|?string|256-character limit, must be a publicly-accessible URL|
|
||||||
|
|webhook_avatar_url|?string|256-character limit, must be a publicly-accessible URL|
|
||||||
|banner|?string|256-character limit, must be a publicly-accessible URL|
|
|banner|?string|256-character limit, must be a publicly-accessible URL|
|
||||||
|description|?string|1000-character limit|
|
|description|?string|1000-character limit|
|
||||||
|created|?datetime||
|
|created|?datetime||
|
||||||
|
@ -21,7 +21,14 @@ For models that have them, the keys `id`, `uuid` and `created` are **not** user-
|
|||||||
|
|
||||||
Endpoints taking JSON bodies (eg. most `PATCH` and `PUT` endpoints) require the `Content-Type: application/json` header set.
|
Endpoints taking JSON bodies (eg. most `PATCH` and `PUT` endpoints) require the `Content-Type: application/json` header set.
|
||||||
|
|
||||||
|
## User agent
|
||||||
|
|
||||||
|
The API requires the `User-Agent` header to be set to a non-empty string. Not doing so will return a `400 Bad Request` with a JSON body.
|
||||||
|
|
||||||
|
If you are developing an application exposed to the public, we would appreciate if your `User-Agent` uniquely identifies your application, and (if possible) provides some contact information for the developers - so that we are able to contact you if we notice your application doing something it shouldn't.
|
||||||
|
|
||||||
## Authentication
|
## Authentication
|
||||||
|
|
||||||
Authentication is done with a simple "system token". You can get your system token by running `pk;token` using the
|
Authentication is done with a simple "system token". You can get your system token by running `pk;token` using the
|
||||||
Discord bot, either in a channel with the bot or in DMs. Then, pass this token in the `Authorization` HTTP header
|
Discord bot, either in a channel with the bot or in DMs. Then, pass this token in the `Authorization` HTTP header
|
||||||
on requests that require it. Failure to do so on endpoints that require authentication will return a `401 Unauthorized`.
|
on requests that require it. Failure to do so on endpoints that require authentication will return a `401 Unauthorized`.
|
||||||
@ -59,7 +66,7 @@ The following API libraries have been created by members of our community. Pleas
|
|||||||
|
|
||||||
- **Python:** *PluralKit.py* ([PyPI](https://pypi.org/project/pluralkit/) | [Docs](https://pluralkit.readthedocs.io/en/latest/source/quickstart.html) | [Source code](https://github.com/almonds0166/pluralkit.py))
|
- **Python:** *PluralKit.py* ([PyPI](https://pypi.org/project/pluralkit/) | [Docs](https://pluralkit.readthedocs.io/en/latest/source/quickstart.html) | [Source code](https://github.com/almonds0166/pluralkit.py))
|
||||||
- **JavaScript:** *pkapi.js* ([npmjs](https://npmjs.com/package/pkapi.js) | [Docs](https://github.com/greysdawn/pk.js/wiki) | [Source code](https://github.com/greysdawn/pk.js))
|
- **JavaScript:** *pkapi.js* ([npmjs](https://npmjs.com/package/pkapi.js) | [Docs](https://github.com/greysdawn/pk.js/wiki) | [Source code](https://github.com/greysdawn/pk.js))
|
||||||
- **Golang:** *pkgo* (install: `go get github.com/starshine-sys/pkgo` | [Docs (godoc)](https://godocs.io/github.com/starshine-sys/pkgo) | [Docs (pkg.go.dev)](https://pkg.go.dev/github.com/starshine-sys/pkgo) | [Source code](https://github.com/starshine-sys/pkgo))
|
- **Golang:** *pkgo* (install: `go get github.com/starshine-sys/pkgo/v2` | [Docs (godoc)](https://godocs.io/github.com/starshine-sys/pkgo/v2) | [Docs (pkg.go.dev)](https://pkg.go.dev/github.com/starshine-sys/pkgo/v2) | [Source code](https://github.com/starshine-sys/pkgo))
|
||||||
- **Kotlin:** *Plural.kt* ([Maven Repository](https://maven.proxyfox.dev/dev/proxyfox/pluralkt) | [Source code](https://github.com/The-ProxyFox-Group/Plural.kt))
|
- **Kotlin:** *Plural.kt* ([Maven Repository](https://maven.proxyfox.dev/dev/proxyfox/pluralkt) | [Source code](https://github.com/The-ProxyFox-Group/Plural.kt))
|
||||||
|
|
||||||
Do let us know in the support server if you made a new library and would like to see it listed here!
|
Do let us know in the support server if you made a new library and would like to see it listed here!
|
||||||
|
@ -65,6 +65,7 @@ Some arguments indicate the use of specific Discord features. These include:
|
|||||||
- `pk;member <member> servername <new server name>` - Changes the display name of a member, only in the current server.
|
- `pk;member <member> servername <new server name>` - Changes the display name of a member, only in the current server.
|
||||||
- `pk;member <member> description [description]` - Changes the description of a member.
|
- `pk;member <member> description [description]` - Changes the description of a member.
|
||||||
- `pk;member <member> avatar [avatar url|@mention|upload]` - Changes the avatar of a member.
|
- `pk;member <member> avatar [avatar url|@mention|upload]` - Changes the avatar of a member.
|
||||||
|
- `pk;member <member> proxyavatar [avatar url|@mention|upload]` - Changes the avatar used for proxied messages sent by a member.
|
||||||
- `pk;member <member> serveravatar [avatar url|@mention|upload]` - Changes the avatar of a member in a specific server.
|
- `pk;member <member> serveravatar [avatar url|@mention|upload]` - Changes the avatar of a member in a specific server.
|
||||||
- `pk;member <name> banner [image url|upload]` - Changes the banner image of a member.
|
- `pk;member <name> banner [image url|upload]` - Changes the banner image of a member.
|
||||||
- `pk;member <member> privacy` - Displays a members current privacy settings.
|
- `pk;member <member> privacy` - Displays a members current privacy settings.
|
||||||
|
@ -229,6 +229,13 @@ To preview the current avatar (if one is set), use the command with no arguments
|
|||||||
|
|
||||||
To clear your avatar, use the subcommand `avatar clear` (eg. `pk;member John avatar clear`).
|
To clear your avatar, use the subcommand `avatar clear` (eg. `pk;member John avatar clear`).
|
||||||
|
|
||||||
|
### Member proxy avatar
|
||||||
|
If you want your member to have a different avatar for proxies messages than the one displayed on the member card, you can set a proxy avatar. To do so, use the `pk;member proxyavatar` command, in the same way as the normal avatar command above:
|
||||||
|
|
||||||
|
pk;member John avatar
|
||||||
|
pk;member John proxyavatar http://placebeard.it/512.jpg
|
||||||
|
pk;member "Craig Johnson" proxyavatar (with an attached image)
|
||||||
|
|
||||||
### Member server avatar
|
### Member server avatar
|
||||||
You can also set an avatar for a specific server. This will "override" the normal avatar, and will be used when proxying messages and looking up member cards in that server. To do so, use the `pk;member serveravatar` command, in the same way as the normal avatar command above:
|
You can also set an avatar for a specific server. This will "override" the normal avatar, and will be used when proxying messages and looking up member cards in that server. To do so, use the `pk;member serveravatar` command, in the same way as the normal avatar command above:
|
||||||
|
|
||||||
|
@ -25,20 +25,15 @@ func proxyTo(host string) *httputil.ReverseProxy {
|
|||||||
|
|
||||||
// todo: this shouldn't be in this repo
|
// todo: this shouldn't be in this repo
|
||||||
var remotes = map[string]*httputil.ReverseProxy{
|
var remotes = map[string]*httputil.ReverseProxy{
|
||||||
"api.pluralkit.me": proxyTo("[fdaa:0:ae33:a7b:8dd7:0:a:202]:5000"),
|
"api.pluralkit.me": proxyTo("[fdaa:0:ae33:a7b:8dd7:0:a:902]:5000"),
|
||||||
"dash.pluralkit.me": proxyTo("[fdaa:0:ae33:a7b:8dd7:0:a:202]:8080"),
|
"dash.pluralkit.me": proxyTo("[fdaa:0:ae33:a7b:8dd7:0:a:902]:8080"),
|
||||||
"sentry.pluralkit.me": proxyTo("[fdaa:0:ae33:a7b:8dd7:0:a:202]:9000"),
|
"sentry.pluralkit.me": proxyTo("[fdaa:0:ae33:a7b:8dd7:0:a:902]:9000"),
|
||||||
|
"grafana.pluralkit.me": proxyTo("[fdaa:0:ae33:a7b:8dd7:0:a:802]:3000"),
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProxyHandler struct{}
|
type ProxyHandler struct{}
|
||||||
|
|
||||||
func (p ProxyHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
func (p ProxyHandler) ServeHTTP(rw http.ResponseWriter, r *http.Request) {
|
||||||
if r.Header.Get("User-Agent") == "" {
|
|
||||||
// please set a valid user-agent
|
|
||||||
rw.WriteHeader(403)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
remote, ok := remotes[r.Host]
|
remote, ok := remotes[r.Host]
|
||||||
if !ok {
|
if !ok {
|
||||||
// unknown domains redirect to landing page
|
// unknown domains redirect to landing page
|
||||||
|
Loading…
Reference in New Issue
Block a user