feat: add text-to-speech option to members (#570)

Merges PluralKit/PluralKit#570
This commit is contained in:
rladenson 2023-08-10 18:03:37 +12:00 committed by Iris System
parent 68cd21fb2f
commit e58b3c7274
16 changed files with 90 additions and 6 deletions

View file

@ -11,5 +11,6 @@ public record ExecuteWebhookRequest
public Sticker[] Stickers { get; init; } public Sticker[] Stickers { get; init; }
public Message.Attachment[] Attachments { get; set; } public Message.Attachment[] Attachments { get; set; }
public AllowedMentions? AllowedMentions { get; init; } public AllowedMentions? AllowedMentions { get; init; }
public bool? Tts { get; init; }
public Message.MessageFlags? Flags { get; set; } public Message.MessageFlags? Flags { get; set; }
} }

View file

@ -54,6 +54,7 @@ public partial class CommandTree
public static Command MemberServerName = new Command("member servername", "member <member> servername [server name]", "Changes a member's display name in the current server"); public static Command MemberServerName = new Command("member servername", "member <member> servername [server name]", "Changes a member's display name in the current server");
public static Command MemberAutoproxy = new Command("member autoproxy", "member <member> autoproxy [on|off]", "Sets whether a member will be autoproxied when autoproxy is set to latch or front mode."); public static Command MemberAutoproxy = new Command("member autoproxy", "member <member> autoproxy [on|off]", "Sets whether a member will be autoproxied when autoproxy is set to latch or front mode.");
public static Command MemberKeepProxy = new Command("member keepproxy", "member <member> keepproxy [on|off]", "Sets whether to include a member's proxy tags when proxying"); public static Command MemberKeepProxy = new Command("member keepproxy", "member <member> keepproxy [on|off]", "Sets whether to include a member's proxy tags when proxying");
public static Command MemberTts = new Command("member text-to-speech", "member <member> text-to-speech [on|off]", "Sets whether to send a member's messages as text-to-speech messages.");
public static Command MemberRandom = new Command("system random", "system [system] random", "Shows the info card of a randomly selected member in a system."); public static Command MemberRandom = new Command("system random", "system [system] random", "Shows the info card of a randomly selected member in a system.");
public static Command MemberId = new Command("member id", "member [member] id", "Prints a member's id."); public static Command MemberId = new Command("member id", "member [member] id", "Prints a member's id.");
public static Command MemberPrivacy = new Command("member privacy", "member <member> privacy <name|description|birthday|pronouns|metadata|visibility|all> <public|private>", "Changes a members's privacy settings"); public static Command MemberPrivacy = new Command("member privacy", "member <member> privacy <name|description|birthday|pronouns|metadata|visibility|all> <public|private>", "Changes a members's privacy settings");
@ -116,7 +117,7 @@ public partial class CommandTree
public static Command[] MemberCommands = public static Command[] MemberCommands =
{ {
MemberInfo, MemberNew, MemberRename, MemberDisplayName, MemberServerName, MemberDesc, MemberPronouns, MemberInfo, MemberNew, MemberRename, MemberDisplayName, MemberServerName, MemberDesc, MemberPronouns,
MemberColor, MemberBirthday, MemberProxy, MemberAutoproxy, MemberKeepProxy, MemberGroups, MemberGroupAdd, MemberColor, MemberBirthday, MemberProxy, MemberAutoproxy, MemberKeepProxy, MemberTts, MemberGroups, MemberGroupAdd,
MemberGroupRemove, MemberDelete, MemberAvatar, MemberServerAvatar, MemberBannerImage, MemberPrivacy, MemberGroupRemove, MemberDelete, MemberAvatar, MemberServerAvatar, MemberBannerImage, MemberPrivacy,
MemberRandom MemberRandom
}; };

View file

@ -336,6 +336,8 @@ public partial class CommandTree
await ctx.Execute<MemberEdit>(MemberAutoproxy, m => m.MemberAutoproxy(ctx, target)); await ctx.Execute<MemberEdit>(MemberAutoproxy, m => m.MemberAutoproxy(ctx, target));
else if (ctx.Match("keepproxy", "keeptags", "showtags", "kp")) else if (ctx.Match("keepproxy", "keeptags", "showtags", "kp"))
await ctx.Execute<MemberEdit>(MemberKeepProxy, m => m.KeepProxy(ctx, target)); await ctx.Execute<MemberEdit>(MemberKeepProxy, m => m.KeepProxy(ctx, target));
else if (ctx.Match("texttospeech", "text-to-speech", "tts"))
await ctx.Execute<MemberEdit>(MemberTts, m => m.Tts(ctx, target));
else if (ctx.Match("id")) else if (ctx.Match("id"))
await ctx.Execute<Member>(MemberId, m => m.DisplayId(ctx, target)); await ctx.Execute<Member>(MemberId, m => m.DisplayId(ctx, target));
else if (ctx.Match("privacy")) else if (ctx.Match("privacy"))

View file

@ -532,6 +532,47 @@ public class MemberEdit
$"{Emojis.Success} Member proxy tags will now not be included in the resulting message when proxying."); $"{Emojis.Success} Member proxy tags will now not be included in the resulting message when proxying.");
} }
public async Task Tts(Context ctx, PKMember target)
{
ctx.CheckSystem().CheckOwnMember(target);
bool newValue;
if (ctx.Match("on", "enabled", "true", "yes"))
{
newValue = true;
}
else if (ctx.Match("off", "disabled", "false", "no"))
{
newValue = false;
}
else if (ctx.HasNext())
{
throw new PKSyntaxError("You must pass either \"on\" or \"off\".");
}
else
{
if (target.Tts)
await ctx.Reply(
"This member has text-to-speech **enabled**, which means their messages **will be** sent as text-to-speech messages.");
else
await ctx.Reply(
"This member has text-to-speech **disabled**, which means their messages **will not** be sent as text-to-speech messages.");
return;
}
;
var patch = new MemberPatch { Tts = Partial<bool>.Present(newValue) };
await ctx.Repository.UpdateMember(target.Id, patch);
if (newValue)
await ctx.Reply(
$"{Emojis.Success} Member's messages will now be sent as text-to-speech messages.");
else
await ctx.Reply(
$"{Emojis.Success} Member messages will no longer be sent as text-to-speech messages.");
}
public async Task MemberAutoproxy(Context ctx, PKMember target) public async Task MemberAutoproxy(Context ctx, PKMember target)
{ {
if (ctx.System == null) throw Errors.NoSystemError; if (ctx.System == null) throw Errors.NoSystemError;

View file

@ -230,6 +230,11 @@ public class ProxyService
var rootChannel = await _cache.GetRootChannel(trigger.ChannelId); var rootChannel = await _cache.GetRootChannel(trigger.ChannelId);
var threadId = messageChannel.IsThread() ? messageChannel.Id : (ulong?)null; var threadId = messageChannel.IsThread() ? messageChannel.Id : (ulong?)null;
var guild = await _cache.GetGuild(trigger.GuildId.Value); var guild = await _cache.GetGuild(trigger.GuildId.Value);
var guildMember = await _rest.GetGuildMember(trigger.GuildId!.Value, trigger.Author.Id);
//If the member is a text-to-speech member and the user can send text-to-speech messages in that channel, turn text-to-speech on
var senderPermissions = PermissionExtensions.PermissionsFor(guild, messageChannel, trigger.Author.Id, guildMember);
var tts = match.Member.Tts && senderPermissions.HasFlag(PermissionSet.SendTtsMessages);
var proxyMessage = await _webhookExecutor.ExecuteWebhook(new ProxyRequest var proxyMessage = await _webhookExecutor.ExecuteWebhook(new ProxyRequest
{ {
@ -245,6 +250,7 @@ public class ProxyService
Stickers = trigger.StickerItems, Stickers = trigger.StickerItems,
AllowEveryone = allowEveryone, AllowEveryone = allowEveryone,
Flags = trigger.Flags.HasFlag(Message.MessageFlags.VoiceMessage) ? Message.MessageFlags.VoiceMessage : null, Flags = trigger.Flags.HasFlag(Message.MessageFlags.VoiceMessage) ? Message.MessageFlags.VoiceMessage : null,
Tts = tts,
}); });
await HandleProxyExecutedActions(ctx, autoproxySettings, trigger, proxyMessage, match); await HandleProxyExecutedActions(ctx, autoproxySettings, trigger, proxyMessage, match);
} }
@ -291,6 +297,9 @@ public class ProxyService
if (!senderPermissions.HasFlag(PermissionSet.SendMessages)) if (!senderPermissions.HasFlag(PermissionSet.SendMessages))
throw new PKError("You don't have permission to send messages in the channel that message is in."); throw new PKError("You don't have permission to send messages in the channel that message is in.");
//If the member is a text-to-speech member and the user can send text-to-speech messages in that channel, turn text-to-speech on
var tts = member.Tts && senderPermissions.HasFlag(PermissionSet.SendTtsMessages);
// Mangle embeds (for reply embed color changing) // Mangle embeds (for reply embed color changing)
var mangledEmbeds = originalMsg.Embeds!.Select(embed => MangleReproxyEmbed(embed, member)).Where(embed => embed != null).ToArray(); var mangledEmbeds = originalMsg.Embeds!.Select(embed => MangleReproxyEmbed(embed, member)).Where(embed => embed != null).ToArray();
@ -309,6 +318,7 @@ public class ProxyService
Stickers = originalMsg.StickerItems!, Stickers = originalMsg.StickerItems!,
AllowEveryone = allowEveryone, AllowEveryone = allowEveryone,
Flags = originalMsg.Flags.HasFlag(Message.MessageFlags.VoiceMessage) ? Message.MessageFlags.VoiceMessage : null, Flags = originalMsg.Flags.HasFlag(Message.MessageFlags.VoiceMessage) ? Message.MessageFlags.VoiceMessage : null,
Tts = tts,
}); });

View file

@ -44,6 +44,7 @@ public record ProxyRequest
public Sticker[] Stickers { get; init; } public Sticker[] Stickers { get; init; }
public bool AllowEveryone { get; init; } public bool AllowEveryone { get; init; }
public Message.MessageFlags? Flags { get; init; } public Message.MessageFlags? Flags { get; init; }
public bool Tts { get; init; }
} }
public class WebhookExecutorService public class WebhookExecutorService
@ -131,6 +132,7 @@ public class WebhookExecutorService
Embeds = req.Embeds, Embeds = req.Embeds,
Stickers = req.Stickers, Stickers = req.Stickers,
Flags = req.Flags, Flags = req.Flags,
Tts = req.Tts,
}; };
MultipartFile[] files = null; MultipartFile[] files = null;

View file

@ -17,6 +17,7 @@ public class ProxyMember
public MemberId Id { get; } public MemberId Id { get; }
public IReadOnlyCollection<ProxyTag> ProxyTags { get; } = new ProxyTag[0]; public IReadOnlyCollection<ProxyTag> ProxyTags { get; } = new ProxyTag[0];
public bool KeepProxy { get; } public bool KeepProxy { get; }
public bool Tts { get; }
public string? ServerName { get; } public string? ServerName { get; }
public string? DisplayName { get; } public string? DisplayName { get; }

View file

@ -1,4 +1,4 @@
create function message_context(account_id bigint, guild_id bigint, channel_id bigint, thread_id bigint) create function message_context(account_id bigint, guild_id bigint, channel_id bigint, thread_id bigint)
returns table ( returns table (
system_id int, system_id int,
log_channel bigint, log_channel bigint,
@ -68,6 +68,7 @@ create function proxy_members(account_id bigint, guild_id bigint)
id int, id int,
proxy_tags proxy_tag[], proxy_tags proxy_tag[],
keep_proxy bool, keep_proxy bool,
tts bool,
server_name text, server_name text,
display_name text, display_name text,
@ -87,6 +88,7 @@ as $$
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,
members.tts as tts,
-- Name info -- Name info
member_guild.display_name as server_name, member_guild.display_name as server_name,

View file

@ -0,0 +1,6 @@
-- database version 38
-- add proxy tag privacy
alter table members add column tts boolean not null default false;
update info set schema_version = 38;

View file

@ -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 = 37; private const int TargetSchemaVersion = 38;
private readonly ILogger _logger; private readonly ILogger _logger;
public DatabaseMigrator(ILogger logger) public DatabaseMigrator(ILogger logger)

View file

@ -49,6 +49,7 @@ public class PKMember
public string Description { get; private set; } public string Description { get; private set; }
public ICollection<ProxyTag> ProxyTags { get; private set; } public ICollection<ProxyTag> ProxyTags { get; private set; }
public bool KeepProxy { get; private set; } public bool KeepProxy { get; private set; }
public bool Tts { get; private set; }
public Instant Created { get; private set; } public Instant Created { get; private set; }
public int MessageCount { get; private set; } public int MessageCount { get; private set; }
public Instant? LastMessageTimestamp { get; private set; } public Instant? LastMessageTimestamp { get; private set; }
@ -137,6 +138,7 @@ public static class PKMemberExt
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());
o.Add("keep_proxy", member.KeepProxy); o.Add("keep_proxy", member.KeepProxy);
o.Add("tts", member.Tts);
o.Add("autoproxy_enabled", ctx == LookupContext.ByOwner ? member.AllowAutoproxy : null); o.Add("autoproxy_enabled", ctx == LookupContext.ByOwner ? member.AllowAutoproxy : null);

View file

@ -21,6 +21,7 @@ public class MemberPatch: PatchObject
public Partial<string?> Description { get; set; } public Partial<string?> Description { get; set; }
public Partial<ProxyTag[]> ProxyTags { get; set; } public Partial<ProxyTag[]> ProxyTags { get; set; }
public Partial<bool> KeepProxy { get; set; } public Partial<bool> KeepProxy { get; set; }
public Partial<bool> Tts { get; set; }
public Partial<int> MessageCount { get; set; } public Partial<int> MessageCount { get; set; }
public Partial<bool> AllowAutoproxy { get; set; } public Partial<bool> AllowAutoproxy { get; set; }
public Partial<PrivacyLevel> Visibility { get; set; } public Partial<PrivacyLevel> Visibility { get; set; }
@ -46,6 +47,7 @@ public class MemberPatch: PatchObject
.With("description", Description) .With("description", Description)
.With("proxy_tags", ProxyTags) .With("proxy_tags", ProxyTags)
.With("keep_proxy", KeepProxy) .With("keep_proxy", KeepProxy)
.With("tts", Tts)
.With("message_count", MessageCount) .With("message_count", MessageCount)
.With("allow_autoproxy", AllowAutoproxy) .With("allow_autoproxy", AllowAutoproxy)
.With("member_visibility", Visibility) .With("member_visibility", Visibility)
@ -126,6 +128,7 @@ public class MemberPatch: PatchObject
if (o.ContainsKey("pronouns")) patch.Pronouns = o.Value<string>("pronouns").NullIfEmpty(); if (o.ContainsKey("pronouns")) patch.Pronouns = o.Value<string>("pronouns").NullIfEmpty();
if (o.ContainsKey("description")) patch.Description = o.Value<string>("description").NullIfEmpty(); if (o.ContainsKey("description")) patch.Description = o.Value<string>("description").NullIfEmpty();
if (o.ContainsKey("keep_proxy")) patch.KeepProxy = o.Value<bool>("keep_proxy"); if (o.ContainsKey("keep_proxy")) patch.KeepProxy = o.Value<bool>("keep_proxy");
if (o.ContainsKey("tts")) patch.Tts = o.Value<bool>("tts");
if (isImport) if (isImport)
{ {
@ -223,6 +226,9 @@ public class MemberPatch: PatchObject
if (KeepProxy.IsPresent) if (KeepProxy.IsPresent)
o.Add("keep_proxy", KeepProxy.Value); o.Add("keep_proxy", KeepProxy.Value);
if (Tts.IsPresent)
o.Add("tts", Tts.Value);
if ( if (
Visibility.IsPresent Visibility.IsPresent
|| NamePrivacy.IsPresent || NamePrivacy.IsPresent

View file

@ -61,10 +61,11 @@ export interface Member {
banner?: string; banner?: string;
description?: string; description?: string;
created?: string; created?: string;
keep_proxy?: boolean keep_proxy?: boolean;
tts?: boolean;
system?: string; system?: string;
proxy_tags?: Array<proxytag>; proxy_tags?: Array<proxytag>;
privacy?: MemberPrivacy privacy?: MemberPrivacy;
} }
export interface GroupPrivacy { export interface GroupPrivacy {

View file

@ -52,6 +52,7 @@ Every PluralKit entity has two IDs: a short (5-character) ID and a longer UUID.
|created|?datetime|| |created|?datetime||
|proxy_tags|array of [ProxyTag objects](#proxytag-object)| |proxy_tags|array of [ProxyTag objects](#proxytag-object)|
|keep_proxy|boolean|| |keep_proxy|boolean||
|tts|boolean||
|autoproxy_enabled|?boolean|| |autoproxy_enabled|?boolean||
|message_count|?int|| |message_count|?int||
|last_message_timestamp|?datetime|| |last_message_timestamp|?datetime||

View file

@ -92,6 +92,7 @@ You can have a space after `pk;`, e.g. `pk;system` and `pk; system` will do the
- `pk;member <member> proxy remove [tags]` - Removes a proxy tag from a member. - `pk;member <member> proxy remove [tags]` - Removes a proxy tag from a member.
- `pk;member <member> autoproxy [on|off]` - Sets whether a member will be autoproxied when autoproxy is set to latch or front mode. - `pk;member <member> autoproxy [on|off]` - Sets whether a member will be autoproxied when autoproxy is set to latch or front mode.
- `pk;member <member> keepproxy [on|off]` - Sets whether to include a member's proxy tags in the proxied message. - `pk;member <member> keepproxy [on|off]` - Sets whether to include a member's proxy tags in the proxied message.
- `pk;member <member> tts [on|off]` - Sets whether to send a member's messages as text-to-speech messages.
- `pk;member <member> pronouns [pronouns]` - Changes the pronouns of a member. - `pk;member <member> pronouns [pronouns]` - Changes the pronouns of a member.
- `pk;member <member> color [color]` - Changes the color of a member. - `pk;member <member> color [color]` - Changes the color of a member.
- `pk;member <member> birthdate [birthdate|today]` - Changes the birthday of a member. - `pk;member <member> birthdate [birthdate|today]` - Changes the birthday of a member.

View file

@ -358,6 +358,13 @@ The practical effect of this is:
* **Keep proxy tags on:** `[Message goes here]` typed -> `[Message goes here]` displayed * **Keep proxy tags on:** `[Message goes here]` typed -> `[Message goes here]` displayed
* **Keep proxy tags off:** `[Message goes here]` typed -> `Message goes here` displayed * **Keep proxy tags off:** `[Message goes here]` typed -> `Message goes here` displayed
### Sending text-to-speech messages
If you'd like your proxied messages to be sent as text-to-speech messages (read off out loud to anyone who has the channel focused) you can enable the text-to-speech option for a given member, like so:
pk;member John text-to-speech on
Turning the option off is similar - replace "on" with "off" in the command. The default value for every member is off. If you are not allowed to send text-to-speech messages in a server, this feature will not work.
### Disabling proxying on a per-server basis ### Disabling proxying on a per-server basis
If you need to disable or re-enable proxying messages for your system entirely in a specific server (for example, if you'd like to If you need to disable or re-enable proxying messages for your system entirely in a specific server (for example, if you'd like to
use a different proxy bot there), you can use the commands: use a different proxy bot there), you can use the commands: