diff --git a/Myriad/Rest/Types/Requests/ExecuteWebhookRequest.cs b/Myriad/Rest/Types/Requests/ExecuteWebhookRequest.cs index 28316e36..9378650e 100644 --- a/Myriad/Rest/Types/Requests/ExecuteWebhookRequest.cs +++ b/Myriad/Rest/Types/Requests/ExecuteWebhookRequest.cs @@ -11,5 +11,6 @@ public record ExecuteWebhookRequest public Sticker[] Stickers { get; init; } public Message.Attachment[] Attachments { get; set; } public AllowedMentions? AllowedMentions { get; init; } + public bool? Tts { get; init; } public Message.MessageFlags? Flags { get; set; } } \ No newline at end of file diff --git a/PluralKit.Bot/CommandMeta/CommandHelp.cs b/PluralKit.Bot/CommandMeta/CommandHelp.cs index badd9d41..3938a0d9 100644 --- a/PluralKit.Bot/CommandMeta/CommandHelp.cs +++ b/PluralKit.Bot/CommandMeta/CommandHelp.cs @@ -54,6 +54,7 @@ public partial class CommandTree public static Command MemberServerName = new Command("member servername", "member servername [server name]", "Changes a member's display name in the current server"); public static Command MemberAutoproxy = new Command("member autoproxy", "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 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 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 MemberId = new Command("member id", "member [member] id", "Prints a member's id."); public static Command MemberPrivacy = new Command("member privacy", "member privacy ", "Changes a members's privacy settings"); @@ -116,7 +117,7 @@ public partial class CommandTree public static Command[] MemberCommands = { 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, MemberRandom }; diff --git a/PluralKit.Bot/CommandMeta/CommandTree.cs b/PluralKit.Bot/CommandMeta/CommandTree.cs index b5d4cce9..c09447ea 100644 --- a/PluralKit.Bot/CommandMeta/CommandTree.cs +++ b/PluralKit.Bot/CommandMeta/CommandTree.cs @@ -336,6 +336,8 @@ public partial class CommandTree await ctx.Execute(MemberAutoproxy, m => m.MemberAutoproxy(ctx, target)); else if (ctx.Match("keepproxy", "keeptags", "showtags", "kp")) await ctx.Execute(MemberKeepProxy, m => m.KeepProxy(ctx, target)); + else if (ctx.Match("texttospeech", "text-to-speech", "tts")) + await ctx.Execute(MemberTts, m => m.Tts(ctx, target)); else if (ctx.Match("id")) await ctx.Execute(MemberId, m => m.DisplayId(ctx, target)); else if (ctx.Match("privacy")) diff --git a/PluralKit.Bot/Commands/MemberEdit.cs b/PluralKit.Bot/Commands/MemberEdit.cs index 611c7913..51589f3c 100644 --- a/PluralKit.Bot/Commands/MemberEdit.cs +++ b/PluralKit.Bot/Commands/MemberEdit.cs @@ -532,6 +532,47 @@ public class MemberEdit $"{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.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) { if (ctx.System == null) throw Errors.NoSystemError; diff --git a/PluralKit.Bot/Proxy/ProxyService.cs b/PluralKit.Bot/Proxy/ProxyService.cs index 2d83ea1a..33a28f2f 100644 --- a/PluralKit.Bot/Proxy/ProxyService.cs +++ b/PluralKit.Bot/Proxy/ProxyService.cs @@ -230,6 +230,11 @@ public class ProxyService var rootChannel = await _cache.GetRootChannel(trigger.ChannelId); var threadId = messageChannel.IsThread() ? messageChannel.Id : (ulong?)null; 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 { @@ -245,6 +250,7 @@ public class ProxyService Stickers = trigger.StickerItems, AllowEveryone = allowEveryone, Flags = trigger.Flags.HasFlag(Message.MessageFlags.VoiceMessage) ? Message.MessageFlags.VoiceMessage : null, + Tts = tts, }); await HandleProxyExecutedActions(ctx, autoproxySettings, trigger, proxyMessage, match); } @@ -291,6 +297,9 @@ public class ProxyService if (!senderPermissions.HasFlag(PermissionSet.SendMessages)) 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) var mangledEmbeds = originalMsg.Embeds!.Select(embed => MangleReproxyEmbed(embed, member)).Where(embed => embed != null).ToArray(); @@ -309,6 +318,7 @@ public class ProxyService Stickers = originalMsg.StickerItems!, AllowEveryone = allowEveryone, Flags = originalMsg.Flags.HasFlag(Message.MessageFlags.VoiceMessage) ? Message.MessageFlags.VoiceMessage : null, + Tts = tts, }); diff --git a/PluralKit.Bot/Services/WebhookExecutorService.cs b/PluralKit.Bot/Services/WebhookExecutorService.cs index ac97b23e..fa38d22f 100644 --- a/PluralKit.Bot/Services/WebhookExecutorService.cs +++ b/PluralKit.Bot/Services/WebhookExecutorService.cs @@ -44,6 +44,7 @@ public record ProxyRequest public Sticker[] Stickers { get; init; } public bool AllowEveryone { get; init; } public Message.MessageFlags? Flags { get; init; } + public bool Tts { get; init; } } public class WebhookExecutorService @@ -131,6 +132,7 @@ public class WebhookExecutorService Embeds = req.Embeds, Stickers = req.Stickers, Flags = req.Flags, + Tts = req.Tts, }; MultipartFile[] files = null; diff --git a/PluralKit.Core/Database/Functions/ProxyMember.cs b/PluralKit.Core/Database/Functions/ProxyMember.cs index bb79bb2f..d42a865e 100644 --- a/PluralKit.Core/Database/Functions/ProxyMember.cs +++ b/PluralKit.Core/Database/Functions/ProxyMember.cs @@ -17,6 +17,7 @@ public class ProxyMember public MemberId Id { get; } public IReadOnlyCollection ProxyTags { get; } = new ProxyTag[0]; public bool KeepProxy { get; } + public bool Tts { get; } public string? ServerName { get; } public string? DisplayName { get; } diff --git a/PluralKit.Core/Database/Functions/functions.sql b/PluralKit.Core/Database/Functions/functions.sql index 61ae9255..e1a2e827 100644 --- a/PluralKit.Core/Database/Functions/functions.sql +++ b/PluralKit.Core/Database/Functions/functions.sql @@ -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 ( system_id int, log_channel bigint, @@ -68,6 +68,7 @@ create function proxy_members(account_id bigint, guild_id bigint) id int, proxy_tags proxy_tag[], keep_proxy bool, + tts bool, server_name text, display_name text, @@ -87,6 +88,7 @@ as $$ members.id as id, members.proxy_tags as proxy_tags, members.keep_proxy as keep_proxy, + members.tts as tts, -- Name info member_guild.display_name as server_name, diff --git a/PluralKit.Core/Database/Migrations/38.sql b/PluralKit.Core/Database/Migrations/38.sql new file mode 100644 index 00000000..4570d2d9 --- /dev/null +++ b/PluralKit.Core/Database/Migrations/38.sql @@ -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; \ No newline at end of file diff --git a/PluralKit.Core/Database/Utils/DatabaseMigrator.cs b/PluralKit.Core/Database/Utils/DatabaseMigrator.cs index 09837cfb..9b0a39c0 100644 --- a/PluralKit.Core/Database/Utils/DatabaseMigrator.cs +++ b/PluralKit.Core/Database/Utils/DatabaseMigrator.cs @@ -9,7 +9,7 @@ namespace PluralKit.Core; internal class DatabaseMigrator { 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; public DatabaseMigrator(ILogger logger) @@ -78,4 +78,4 @@ internal class DatabaseMigrator // Then, migration 1 gets executed, which creates the info table and sets version to 1 return -1; } -} \ No newline at end of file +} diff --git a/PluralKit.Core/Models/PKMember.cs b/PluralKit.Core/Models/PKMember.cs index 5a69053b..72ec3d07 100644 --- a/PluralKit.Core/Models/PKMember.cs +++ b/PluralKit.Core/Models/PKMember.cs @@ -49,6 +49,7 @@ public class PKMember public string Description { get; private set; } public ICollection ProxyTags { get; private set; } public bool KeepProxy { get; private set; } + public bool Tts { get; private set; } public Instant Created { get; private set; } public int MessageCount { 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("created", member.CreatedFor(ctx)?.FormatExport()); o.Add("keep_proxy", member.KeepProxy); + o.Add("tts", member.Tts); o.Add("autoproxy_enabled", ctx == LookupContext.ByOwner ? member.AllowAutoproxy : null); diff --git a/PluralKit.Core/Models/Patch/MemberPatch.cs b/PluralKit.Core/Models/Patch/MemberPatch.cs index 19a7a502..c4f40597 100644 --- a/PluralKit.Core/Models/Patch/MemberPatch.cs +++ b/PluralKit.Core/Models/Patch/MemberPatch.cs @@ -21,6 +21,7 @@ public class MemberPatch: PatchObject public Partial Description { get; set; } public Partial ProxyTags { get; set; } public Partial KeepProxy { get; set; } + public Partial Tts { get; set; } public Partial MessageCount { get; set; } public Partial AllowAutoproxy { get; set; } public Partial Visibility { get; set; } @@ -46,6 +47,7 @@ public class MemberPatch: PatchObject .With("description", Description) .With("proxy_tags", ProxyTags) .With("keep_proxy", KeepProxy) + .With("tts", Tts) .With("message_count", MessageCount) .With("allow_autoproxy", AllowAutoproxy) .With("member_visibility", Visibility) @@ -126,6 +128,7 @@ public class MemberPatch: PatchObject if (o.ContainsKey("pronouns")) patch.Pronouns = o.Value("pronouns").NullIfEmpty(); if (o.ContainsKey("description")) patch.Description = o.Value("description").NullIfEmpty(); if (o.ContainsKey("keep_proxy")) patch.KeepProxy = o.Value("keep_proxy"); + if (o.ContainsKey("tts")) patch.Tts = o.Value("tts"); if (isImport) { @@ -223,6 +226,9 @@ public class MemberPatch: PatchObject if (KeepProxy.IsPresent) o.Add("keep_proxy", KeepProxy.Value); + if (Tts.IsPresent) + o.Add("tts", Tts.Value); + if ( Visibility.IsPresent || NamePrivacy.IsPresent diff --git a/dashboard/src/api/types.ts b/dashboard/src/api/types.ts index 8ac50f13..f4f3ecb4 100644 --- a/dashboard/src/api/types.ts +++ b/dashboard/src/api/types.ts @@ -61,10 +61,11 @@ export interface Member { banner?: string; description?: string; created?: string; - keep_proxy?: boolean + keep_proxy?: boolean; + tts?: boolean; system?: string; proxy_tags?: Array; - privacy?: MemberPrivacy + privacy?: MemberPrivacy; } export interface GroupPrivacy { diff --git a/docs/content/api/models.md b/docs/content/api/models.md index c1d9c0e9..2d935895 100644 --- a/docs/content/api/models.md +++ b/docs/content/api/models.md @@ -52,6 +52,7 @@ Every PluralKit entity has two IDs: a short (5-character) ID and a longer UUID. |created|?datetime|| |proxy_tags|array of [ProxyTag objects](#proxytag-object)| |keep_proxy|boolean|| +|tts|boolean|| |autoproxy_enabled|?boolean|| |message_count|?int|| |last_message_timestamp|?datetime|| diff --git a/docs/content/command-list.md b/docs/content/command-list.md index 84156057..c03234ee 100644 --- a/docs/content/command-list.md +++ b/docs/content/command-list.md @@ -92,6 +92,7 @@ You can have a space after `pk;`, e.g. `pk;system` and `pk; system` will do the - `pk;member proxy remove [tags]` - Removes a proxy tag from a member. - `pk;member autoproxy [on|off]` - Sets whether a member will be autoproxied when autoproxy is set to latch or front mode. - `pk;member keepproxy [on|off]` - Sets whether to include a member's proxy tags in the proxied message. +- `pk;member tts [on|off]` - Sets whether to send a member's messages as text-to-speech messages. - `pk;member pronouns [pronouns]` - Changes the pronouns of a member. - `pk;member color [color]` - Changes the color of a member. - `pk;member birthdate [birthdate|today]` - Changes the birthday of a member. diff --git a/docs/content/user-guide.md b/docs/content/user-guide.md index aa01718b..c1ce1392 100644 --- a/docs/content/user-guide.md +++ b/docs/content/user-guide.md @@ -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 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 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: