diff --git a/src/lib/commands/built-in/founders.ts b/src/lib/commands/built-in/founders.ts index 143a1c8b..63f84555 100644 --- a/src/lib/commands/built-in/founders.ts +++ b/src/lib/commands/built-in/founders.ts @@ -1,5 +1,4 @@ import { foundersQuery } from "$lib/graphql/twitch"; -import { SystemMessage } from "$lib/models/message/system-message"; import { defineCommand } from "../util"; export default defineCommand({ @@ -7,8 +6,6 @@ export default defineCommand({ name: "founders", description: "Display a list of founders for this channel", async exec(_, channel) { - const message = new SystemMessage(channel); - const { user } = await channel.client.send(foundersQuery, { id: channel.id }); const founders = @@ -16,12 +13,10 @@ export default defineCommand({ ?.flatMap((founder) => (founder?.user ? [founder.user.displayName] : [])) .toSorted() ?? []; - if (!founders.length) { - message.text = "This channel has no founders."; - } else { - message.text = `Channel founders (${founders.length}): ${founders.join(", ")}`; - } + const text = founders.length + ? `Channel founders (${founders.length}): ${founders.join(", ")}` + : "This channel has no founders."; - channel.chat.addMessage(message); + channel.chat.notice(text); }, }); diff --git a/src/lib/commands/built-in/reload-badges.ts b/src/lib/commands/built-in/reload-badges.ts index 7de470cf..3381df23 100644 --- a/src/lib/commands/built-in/reload-badges.ts +++ b/src/lib/commands/built-in/reload-badges.ts @@ -30,6 +30,6 @@ export default defineCommand({ await Promise.all(promises); } - channel.chat.addSystemMessage("Reloaded badges."); + channel.chat.notice("Reloaded badges."); }, }); diff --git a/src/lib/commands/built-in/reload-cheermotes.ts b/src/lib/commands/built-in/reload-cheermotes.ts index 5e1de312..b0828997 100644 --- a/src/lib/commands/built-in/reload-cheermotes.ts +++ b/src/lib/commands/built-in/reload-cheermotes.ts @@ -7,6 +7,6 @@ export default defineCommand({ async exec(_, channel) { await channel.fetchCheermotes(true); - channel.chat.addSystemMessage("Reloaded cheermotes."); + channel.chat.notice("Reloaded cheermotes."); }, }); diff --git a/src/lib/commands/built-in/reload-emotes.ts b/src/lib/commands/built-in/reload-emotes.ts index 54c4eeb7..83b0368f 100644 --- a/src/lib/commands/built-in/reload-emotes.ts +++ b/src/lib/commands/built-in/reload-emotes.ts @@ -18,6 +18,6 @@ export default defineCommand({ await app.emotes.fetch(includeGlobal); await channel.emotes.fetch(true); - channel.chat.addSystemMessage("Reloaded emotes."); + channel.chat.notice("Reloaded emotes."); }, }); diff --git a/src/lib/commands/built-in/reload-theme.ts b/src/lib/commands/built-in/reload-theme.ts index b19e02c0..4bcc0bdf 100644 --- a/src/lib/commands/built-in/reload-theme.ts +++ b/src/lib/commands/built-in/reload-theme.ts @@ -10,6 +10,6 @@ export default defineCommand({ await loadThemes(settings.state["appearance.theme"]); await injectTheme(settings.state["appearance.theme"]); - channel.chat.addSystemMessage("Reloaded theme."); + channel.chat.notice("Reloaded theme."); }, }); diff --git a/src/lib/commands/twitch/block.ts b/src/lib/commands/twitch/block.ts index 1101e543..bc2ef41f 100644 --- a/src/lib/commands/twitch/block.ts +++ b/src/lib/commands/twitch/block.ts @@ -1,4 +1,5 @@ import { app } from "$lib/app.svelte"; +import BlockStatus from "$lib/components/message/events/BlockStatus.svelte"; import { defineCommand, getTarget } from "../util"; export default defineCommand({ @@ -12,10 +13,6 @@ export default defineCommand({ await app.twitch.users.block(target.id); - channel.chat.addSystemMessage({ - type: "blockStatus", - blocked: true, - user: target.user, - }); + channel.chat.event(BlockStatus, { blocked: true, user: target.user }); }, }); diff --git a/src/lib/commands/twitch/marker.ts b/src/lib/commands/twitch/marker.ts index 7dd49cfe..3b1777ee 100644 --- a/src/lib/commands/twitch/marker.ts +++ b/src/lib/commands/twitch/marker.ts @@ -27,6 +27,6 @@ export default defineCommand({ const echo = description ? `: ${description}` : ""; - channel.chat.addSystemMessage(`Stream marker created at ${duration.format(format) + echo}`); + channel.chat.notice(`Stream marker created at ${duration.format(format) + echo}`); }, }); diff --git a/src/lib/commands/twitch/mods.ts b/src/lib/commands/twitch/mods.ts index d9b4ee64..13a7de4b 100644 --- a/src/lib/commands/twitch/mods.ts +++ b/src/lib/commands/twitch/mods.ts @@ -1,5 +1,4 @@ import { modsQuery } from "$lib/graphql/twitch"; -import { SystemMessage } from "$lib/models/message/system-message"; import { defineCommand } from "../util"; export default defineCommand({ @@ -7,8 +6,6 @@ export default defineCommand({ name: "mods", description: "Display a list of moderators for this channel", async exec(_, channel) { - const message = new SystemMessage(channel); - const { user } = await channel.client.send(modsQuery, { id: channel.id }); const mods = @@ -16,12 +13,10 @@ export default defineCommand({ .flatMap((edge) => (edge.node ? [edge.node.displayName] : [])) .toSorted() ?? []; - if (!mods.length) { - message.text = "This channel has no moderators."; - } else { - message.text = `Channel moderators (${mods.length}): ${mods.join(", ")}`; - } + const text = mods.length + ? `Channel moderators (${mods.length}): ${mods.join(", ")}` + : "This channel has no moderators."; - channel.chat.addMessage(message); + channel.chat.notice(text); }, }); diff --git a/src/lib/commands/twitch/unblock.ts b/src/lib/commands/twitch/unblock.ts index d6973a97..a10e82ff 100644 --- a/src/lib/commands/twitch/unblock.ts +++ b/src/lib/commands/twitch/unblock.ts @@ -1,4 +1,5 @@ import { app } from "$lib/app.svelte"; +import BlockStatus from "$lib/components/message/events/BlockStatus.svelte"; import { defineCommand, getTarget } from "../util"; export default defineCommand({ @@ -11,10 +12,6 @@ export default defineCommand({ await app.twitch.users.unblock(target.id); - channel.chat.addSystemMessage({ - type: "blockStatus", - blocked: false, - user: target.user, - }); + channel.chat.event(BlockStatus, { blocked: false, user: target.user }); }, }); diff --git a/src/lib/commands/twitch/vips.ts b/src/lib/commands/twitch/vips.ts index eeea7af5..df310338 100644 --- a/src/lib/commands/twitch/vips.ts +++ b/src/lib/commands/twitch/vips.ts @@ -1,5 +1,4 @@ import { vipsQuery } from "$lib/graphql/twitch"; -import { SystemMessage } from "$lib/models/message/system-message"; import { defineCommand } from "../util"; export default defineCommand({ @@ -7,8 +6,6 @@ export default defineCommand({ name: "vips", description: "Display a list of VIPs for this channel", async exec(_, channel) { - const message = new SystemMessage(channel); - const { user } = await channel.client.send(vipsQuery, { id: channel.id }); const vips = @@ -16,12 +13,10 @@ export default defineCommand({ ?.flatMap((edge) => (edge.node ? [edge.node.displayName] : [])) .toSorted() ?? []; - if (!vips.length) { - message.text = "This channel has no VIPs."; - } else { - message.text = `Channel VIPs (${vips.length}): ${vips.join(", ")}`; - } + const text = vips.length + ? `Channel VIPs (${vips.length}): ${vips.join(", ")}` + : "This channel has no VIPs."; - channel.chat.addMessage(message); + channel.chat.notice(text); }, }); diff --git a/src/lib/components/chat/Chat.svelte b/src/lib/components/chat/Chat.svelte index 98770cfb..7cbb794f 100644 --- a/src/lib/components/chat/Chat.svelte +++ b/src/lib/components/chat/Chat.svelte @@ -4,8 +4,8 @@ import type { Message } from "$lib/models/message/message"; import { settings } from "$lib/settings"; import AutoMod from "../message/AutoMod.svelte"; + import Event from "../message/Event.svelte"; import Notification from "../message/Notification.svelte"; - import SystemMessage from "../message/SystemMessage.svelte"; import UserMessage from "../message/UserMessage.svelte"; import Separator from "./Separator.svelte"; @@ -101,7 +101,7 @@ bind:this={list} > {#snippet children(message, i)} - {#if message.isSystem() || message.isUser()} + {#if message.isEvent() || message.isUser()} {@const prev = chat.messages[i - 1]} {@const isNewDay = prev && prev.timestamp.getDate() !== message.timestamp.getDate()} @@ -118,8 +118,8 @@ {/if} - {#if message.isSystem()} - + {#if message.isEvent()} + {:else if message.isUser()} {#if message.event} @@ -131,7 +131,7 @@ {/if} {@const next = chat.messages.at(i + 1)} - {@const nextRecent = next && (next.isSystem() || next.isUser()) && next.recent} + {@const nextRecent = next && (next.isEvent() || next.isUser()) && next.recent} {#if message === lastRead && next && settings.state["chat.newSeparator"]} New messages diff --git a/src/lib/components/message/Event.svelte b/src/lib/components/message/Event.svelte new file mode 100644 index 00000000..5adb4d31 --- /dev/null +++ b/src/lib/components/message/Event.svelte @@ -0,0 +1,23 @@ + + +
+ + +

+ +

+
diff --git a/src/lib/components/message/SystemMessage.svelte b/src/lib/components/message/SystemMessage.svelte deleted file mode 100644 index 9866eb1e..00000000 --- a/src/lib/components/message/SystemMessage.svelte +++ /dev/null @@ -1,234 +0,0 @@ - - -
- - -

- {#if ctx} - {#if ctx.type === "blockStatus"} - {ctx.blocked ? "Blocked" : "Unblocked"} {@html colorizeName(ctx.user)} - {:else if ctx.type === "join"} - Joined {@html colorizeName(message.channel.user)} - {:else if ctx.type === "raid"} - {@html colorizeName(ctx.moderator)} is raiding {@html colorizeName(ctx.user)} with {@html ctx.viewers} - viewers. - {:else if ctx.type === "unraid"} - {@html colorizeName(ctx.moderator)} canceled the raid on {@html colorizeName( - ctx.user, - )}. - {:else if ctx.type === "untimeout"} - {@html colorizeName(ctx.moderator)} removed timeout on {@html colorizeName( - ctx.viewer, - )}. - {:else if ctx.type === "warnAck"} - {@html colorizeName(ctx.viewer)} acknowledged their warning. - {:else} - {@render snippetMap[ctx.type](ctx as never)} - {/if} - {:else} - {message.text} - {/if} -

-
- -{#snippet autoMod(ctx: AutoModContext)} - {@const target = colorizeName(ctx.viewer)} - - {#if ctx.status === "expired"} - {@html target}'s message expired and was not shown in chat. - {:else} - {@html colorizeName(ctx.moderator)} {ctx.status} {@html target}'s message. - {/if} -{/snippet} - -{#snippet banned()} - You are permanently banned from {@html colorizeName(message.channel.user)} and cannot send messages. - If you have been unbanned, try - . -{/snippet} - -{#snippet banStatus(ctx: BanStatusContext)} - {@const target = colorizeName(ctx.viewer)} - {@const action = ctx.banned ? "banned" : "unbanned"} - - {#if ctx.moderator} - {@html colorizeName(ctx.moderator)} {action} {@html target} - {:else} - {@html target} has been {action} - {/if}{ctx.reason ? `: ${ctx.reason}` : "."} -{/snippet} - -{#snippet clear(ctx: ClearContext)} - {#if ctx.moderator} - {@html colorizeName(ctx.moderator)} cleared the chat - {:else} - The chat has been cleared - {/if} - - for non-moderator viewers. -{/snippet} - -{#snippet deleteMsg(ctx: DeleteContext)} - {@const target = colorizeName(ctx.user)} - - {#if ctx.moderator} - {@html colorizeName(ctx.moderator)} deleted {@html target}'s message: {ctx.text} - {:else} - {@html target}'s message was deleted: {ctx.text} - {/if} -{/snippet} - -{#snippet emoteSetChange(ctx: EmoteSetChangeContext)} - {@html colorizeName(ctx.actor)} - - {#if ctx.name} - changed the active emote set to - {ctx.name}. - {:else} - disabled the channel's emote set. - {/if} -{/snippet} - -{#snippet emoteSetUpdate(ctx: EmoteSetUpdateContext)} - {@html colorizeName(ctx.actor)} - - {#if ctx.action === "renamed"} - renamed {ctx.oldName} to - {ctx.emote.name} - {:else} - {ctx.action} an emote: - {ctx.emote.name} - {/if} - - -{/snippet} - -{#snippet mode(ctx: ModeContext)} - {@html colorizeName(ctx.moderator)} - {ctx.enabled ? "enabled" : "disabled"} - {Number.isNaN(ctx.seconds) ? "" : formatDuration(ctx.seconds)} - {ctx.mode === "slow" ? "slow mode." : `${ctx.mode} chat.`} -{/snippet} - -{#snippet roleStatus(ctx: RoleStatusContext)} - {@html colorizeName(ctx.moderator)} - {ctx.added ? "added" : "removed"} - {@html colorizeName(ctx.viewer)} as a {ctx.role}. -{/snippet} - -{#snippet streamStatus(ctx: StreamStatusContext)} - {@html colorizeName(message.channel.user)} is now {ctx.online ? "online" : "offline"}. -{/snippet} - -{#snippet suspicionStatus(ctx: SuspicionStatusContext)} - {@html colorizeName(ctx.moderator)} - {ctx.active ? "started" : "stopped"} - {monitored ? "monitoring" : restricted ? "restricting" : ctx.previous} - {@html colorizeName(ctx.viewer)}'s messages. -{/snippet} - -{#snippet term(ctx: TermContext)} - {@const via = ctx.data.from_automod ? " (via AutoMod)" : ""} - - {@html colorizeName(ctx.moderator)} - {ctx.data.action === "add" ? "added" : "removed"} - - {#if ctx.data.terms.length === 1} - a {ctx.data.list} term{via}: {ctx.data.terms[0]} - {:else} - {ctx.data.terms.length} {ctx.data.list} terms{via}: {ctx.data.terms.join(", ")} - {/if} -{/snippet} - -{#snippet timeout(ctx: TimeoutContext)} - {@const target = colorizeName(ctx.viewer)} - {@const duration = formatDuration(ctx.seconds)} - - {#if ctx.moderator} - {@html colorizeName(ctx.moderator)} timed out {@html target} for {duration} - {:else} - {@html target} has been timed out for {duration} - {/if}{ctx.reason ? `: ${ctx.reason}` : "."} -{/snippet} - -{#snippet unbanRequest(ctx: UnbanRequestContext)} - {@const target = colorizeName(ctx.viewer)} - - {#if "status" in ctx.request} - {#if !ctx.moderator} - {@html target}'s unban request was {ctx.request.status}. - {:else} - {@html colorizeName(ctx.moderator)} - {ctx.request.status} - {@html target}'s unban request{ctx.request.resolution_text - ? `: ${ctx.request.resolution_text}` - : "."} - {/if} - {:else} - {@html target} submitted an unban request: {ctx.request.text} - {/if} -{/snippet} - -{#snippet warn(ctx: WarnContext)} - {@const reasons = [ctx.warning.reason, ...(ctx.warning.chat_rules_cited ?? [])] - .filter((r) => r !== null) - .join(", ")} - - {@html colorizeName(ctx.moderator)} warned {@html colorizeName(ctx.viewer)}: {reasons} -{/snippet} diff --git a/src/lib/components/message/events/AutoMod.svelte b/src/lib/components/message/events/AutoMod.svelte new file mode 100644 index 00000000..78b90a85 --- /dev/null +++ b/src/lib/components/message/events/AutoMod.svelte @@ -0,0 +1,21 @@ + + +{#if status === "expired"} + {@html target}'s message expired and was not shown in chat. +{:else} + {@html colorizeName(moderator)} {status} {@html target}'s message. +{/if} diff --git a/src/lib/components/message/events/BanStatus.svelte b/src/lib/components/message/events/BanStatus.svelte new file mode 100644 index 00000000..785ecf42 --- /dev/null +++ b/src/lib/components/message/events/BanStatus.svelte @@ -0,0 +1,22 @@ + + +{#if moderator} + {@html colorizeName(moderator)} {action} {@html target} +{:else} + {@html target} has been {action} +{/if}{reason ? `: ${reason}` : "."} diff --git a/src/lib/components/message/events/Banned.svelte b/src/lib/components/message/events/Banned.svelte new file mode 100644 index 00000000..3a128a71 --- /dev/null +++ b/src/lib/components/message/events/Banned.svelte @@ -0,0 +1,15 @@ + + +You are permanently banned from {@html colorizeName(channel.user)} and cannot send messages. If you have +been unbanned, try +. diff --git a/src/lib/components/message/events/BlockStatus.svelte b/src/lib/components/message/events/BlockStatus.svelte new file mode 100644 index 00000000..5c11e369 --- /dev/null +++ b/src/lib/components/message/events/BlockStatus.svelte @@ -0,0 +1,14 @@ + + +{blocked ? "Blocked" : "Unblocked"} +{@html colorizeName(user)} diff --git a/src/lib/components/message/events/Clear.svelte b/src/lib/components/message/events/Clear.svelte new file mode 100644 index 00000000..7e9183d4 --- /dev/null +++ b/src/lib/components/message/events/Clear.svelte @@ -0,0 +1,18 @@ + + +{#if moderator} + {@html colorizeName(moderator)} cleared the chat +{:else} + The chat has been cleared +{/if} + +for non-moderator viewers. diff --git a/src/lib/components/message/events/Delete.svelte b/src/lib/components/message/events/Delete.svelte new file mode 100644 index 00000000..b1eeff72 --- /dev/null +++ b/src/lib/components/message/events/Delete.svelte @@ -0,0 +1,21 @@ + + +{#if moderator} + {@html colorizeName(moderator)} deleted {@html target}'s message: {text} +{:else} + {@html target}'s message was deleted: {text} +{/if} diff --git a/src/lib/components/message/events/EmoteSetChange.svelte b/src/lib/components/message/events/EmoteSetChange.svelte new file mode 100644 index 00000000..b38cb738 --- /dev/null +++ b/src/lib/components/message/events/EmoteSetChange.svelte @@ -0,0 +1,20 @@ + + +{@html colorizeName(actor)} + +{#if name} + changed the active emote set to + {name}. +{:else} + disabled the channel's emote set. +{/if} diff --git a/src/lib/components/message/events/EmoteSetUpdate.svelte b/src/lib/components/message/events/EmoteSetUpdate.svelte new file mode 100644 index 00000000..39ff966a --- /dev/null +++ b/src/lib/components/message/events/EmoteSetUpdate.svelte @@ -0,0 +1,27 @@ + + +{@html colorizeName(actor)} + +{#if action === "renamed"} + renamed {oldName} to + {emote.name} +{:else} + {action} an emote: + {emote.name} +{/if} + + diff --git a/src/lib/components/message/events/Join.svelte b/src/lib/components/message/events/Join.svelte new file mode 100644 index 00000000..ecaa5506 --- /dev/null +++ b/src/lib/components/message/events/Join.svelte @@ -0,0 +1,12 @@ + + +Joined {@html colorizeName(channel.user)} diff --git a/src/lib/components/message/events/Mode.svelte b/src/lib/components/message/events/Mode.svelte new file mode 100644 index 00000000..a836066b --- /dev/null +++ b/src/lib/components/message/events/Mode.svelte @@ -0,0 +1,18 @@ + + +{@html colorizeName(moderator)} +{enabled ? "enabled" : "disabled"} +{Number.isNaN(seconds) ? "" : formatDuration(seconds)} +{mode === "slow" ? "slow mode." : `${mode} chat.`} diff --git a/src/lib/components/message/events/Notice.svelte b/src/lib/components/message/events/Notice.svelte new file mode 100644 index 00000000..a59c1b90 --- /dev/null +++ b/src/lib/components/message/events/Notice.svelte @@ -0,0 +1,9 @@ + + +{text} diff --git a/src/lib/components/message/events/Raid.svelte b/src/lib/components/message/events/Raid.svelte new file mode 100644 index 00000000..606bde7b --- /dev/null +++ b/src/lib/components/message/events/Raid.svelte @@ -0,0 +1,15 @@ + + +{@html colorizeName(moderator)} is raiding {@html colorizeName(user)} with {viewers} viewers. diff --git a/src/lib/components/message/events/RoleStatus.svelte b/src/lib/components/message/events/RoleStatus.svelte new file mode 100644 index 00000000..18c6c4a3 --- /dev/null +++ b/src/lib/components/message/events/RoleStatus.svelte @@ -0,0 +1,17 @@ + + +{@html colorizeName(moderator)} +{added ? "added" : "removed"} +{@html colorizeName(viewer)} as a {role}. diff --git a/src/lib/components/message/events/StreamStatus.svelte b/src/lib/components/message/events/StreamStatus.svelte new file mode 100644 index 00000000..db7e8775 --- /dev/null +++ b/src/lib/components/message/events/StreamStatus.svelte @@ -0,0 +1,13 @@ + + +{@html colorizeName(channel.user)} is now {online ? "online" : "offline"}. diff --git a/src/lib/components/message/events/SuspicionStatus.svelte b/src/lib/components/message/events/SuspicionStatus.svelte new file mode 100644 index 00000000..a87dd74a --- /dev/null +++ b/src/lib/components/message/events/SuspicionStatus.svelte @@ -0,0 +1,21 @@ + + +{@html colorizeName(moderator)} +{active ? "started" : "stopped"} +{monitored ? "monitoring" : restricted ? "restricting" : previous} +{@html colorizeName(viewer)}'s messages. diff --git a/src/lib/components/message/events/Term.svelte b/src/lib/components/message/events/Term.svelte new file mode 100644 index 00000000..206ebad2 --- /dev/null +++ b/src/lib/components/message/events/Term.svelte @@ -0,0 +1,23 @@ + + +{@html colorizeName(moderator)} +{data.action === "add" ? "added" : "removed"} + +{#if data.terms.length === 1} + a {data.list} term{via}: {data.terms[0]} +{:else} + {data.terms.length} {data.list} terms{via}: {data.terms.join(", ")} +{/if} diff --git a/src/lib/components/message/events/Timeout.svelte b/src/lib/components/message/events/Timeout.svelte new file mode 100644 index 00000000..59a01e81 --- /dev/null +++ b/src/lib/components/message/events/Timeout.svelte @@ -0,0 +1,22 @@ + + +{#if moderator} + {@html colorizeName(moderator)} timed out {@html target} for {duration} +{:else} + {@html target} has been timed out for {duration} +{/if}{reason ? `: ${reason}` : "."} diff --git a/src/lib/components/message/events/UnbanRequest.svelte b/src/lib/components/message/events/UnbanRequest.svelte new file mode 100644 index 00000000..39f44d7c --- /dev/null +++ b/src/lib/components/message/events/UnbanRequest.svelte @@ -0,0 +1,32 @@ + + +{#if "status" in request} + {#if !moderator} + {@html target}'s unban request was {request.status}. + {:else} + {@html colorizeName(moderator)} + {request.status} + {@html target}'s unban request{request.resolution_text + ? `: ${request.resolution_text}` + : "."} + {/if} +{:else} + {@html target} submitted an unban request: {request.text} +{/if} diff --git a/src/lib/components/message/events/Unraid.svelte b/src/lib/components/message/events/Unraid.svelte new file mode 100644 index 00000000..38297c72 --- /dev/null +++ b/src/lib/components/message/events/Unraid.svelte @@ -0,0 +1,14 @@ + + +{@html colorizeName(moderator)} canceled the raid on {@html colorizeName(user)}. diff --git a/src/lib/components/message/events/Untimeout.svelte b/src/lib/components/message/events/Untimeout.svelte new file mode 100644 index 00000000..92a55632 --- /dev/null +++ b/src/lib/components/message/events/Untimeout.svelte @@ -0,0 +1,13 @@ + + +{@html colorizeName(moderator)} removed timeout on {@html colorizeName(viewer)}. diff --git a/src/lib/components/message/events/Warn.svelte b/src/lib/components/message/events/Warn.svelte new file mode 100644 index 00000000..65c0aba9 --- /dev/null +++ b/src/lib/components/message/events/Warn.svelte @@ -0,0 +1,19 @@ + + +{@html colorizeName(moderator)} warned {@html colorizeName(viewer)}: {reasons} diff --git a/src/lib/components/message/events/WarnAck.svelte b/src/lib/components/message/events/WarnAck.svelte new file mode 100644 index 00000000..0667e734 --- /dev/null +++ b/src/lib/components/message/events/WarnAck.svelte @@ -0,0 +1,12 @@ + + +{@html colorizeName(viewer)} acknowledged their warning. diff --git a/src/lib/handlers/eventsub/automod-message-hold.ts b/src/lib/handlers/eventsub/automod-message-hold.ts index baf728a2..108d89df 100644 --- a/src/lib/handlers/eventsub/automod-message-hold.ts +++ b/src/lib/handlers/eventsub/automod-message-hold.ts @@ -48,6 +48,6 @@ export default defineHandler({ boundaries, }; - channel.chat.addMessage(message); + channel.chat.add(message); }, }); diff --git a/src/lib/handlers/eventsub/automod-message-update.ts b/src/lib/handlers/eventsub/automod-message-update.ts index 648b1693..12c8f850 100644 --- a/src/lib/handlers/eventsub/automod-message-update.ts +++ b/src/lib/handlers/eventsub/automod-message-update.ts @@ -1,4 +1,5 @@ import { app } from "$lib/app.svelte"; +import AutoMod from "$lib/components/message/events/AutoMod.svelte"; import type { UserMessage } from "$lib/models/message/user-message"; import { defineHandler } from "../helper"; @@ -17,11 +18,6 @@ export default defineHandler({ if (message) message.deleted = true; - channel.chat.addSystemMessage({ - type: "autoMod", - status: data.status, - viewer, - moderator, - }); + channel.chat.event(AutoMod, { status: data.status, viewer, moderator }); }, }); diff --git a/src/lib/handlers/eventsub/channel-chat-user-message-hold.ts b/src/lib/handlers/eventsub/channel-chat-user-message-hold.ts index fd651cf4..602ac8cd 100644 --- a/src/lib/handlers/eventsub/channel-chat-user-message-hold.ts +++ b/src/lib/handlers/eventsub/channel-chat-user-message-hold.ts @@ -22,6 +22,6 @@ export default defineHandler({ boundaries: [], }; - channel.chat.addMessage(message); + channel.chat.add(message); }, }); diff --git a/src/lib/handlers/eventsub/channel-chat-user-message-update.ts b/src/lib/handlers/eventsub/channel-chat-user-message-update.ts index b4db5dbf..cd20a798 100644 --- a/src/lib/handlers/eventsub/channel-chat-user-message-update.ts +++ b/src/lib/handlers/eventsub/channel-chat-user-message-update.ts @@ -14,6 +14,6 @@ export default defineHandler({ if (message) message.deleted = true; - channel.chat.addSystemMessage(`A moderator ${data.status} your message.`); + channel.chat.notice(`A moderator ${data.status} your message.`); }, }); diff --git a/src/lib/handlers/eventsub/channel-moderate.ts b/src/lib/handlers/eventsub/channel-moderate.ts index 45cfe7ee..456bdde8 100644 --- a/src/lib/handlers/eventsub/channel-moderate.ts +++ b/src/lib/handlers/eventsub/channel-moderate.ts @@ -1,5 +1,15 @@ import { app } from "$lib/app.svelte"; -import { SystemMessage } from "$lib/models/message/system-message"; +import BanStatus from "$lib/components/message/events/BanStatus.svelte"; +import Clear from "$lib/components/message/events/Clear.svelte"; +import Delete from "$lib/components/message/events/Delete.svelte"; +import Mode from "$lib/components/message/events/Mode.svelte"; +import Raid from "$lib/components/message/events/Raid.svelte"; +import RoleStatus from "$lib/components/message/events/RoleStatus.svelte"; +import Term from "$lib/components/message/events/Term.svelte"; +import Timeout from "$lib/components/message/events/Timeout.svelte"; +import Unraid from "$lib/components/message/events/Unraid.svelte"; +import Untimeout from "$lib/components/message/events/Untimeout.svelte"; +import Warn from "$lib/components/message/events/Warn.svelte"; import { defineHandler } from "../helper"; export default defineHandler({ @@ -8,7 +18,7 @@ export default defineHandler({ const channel = app.channels.get(data.broadcaster_user_id); if (!channel) return; - const message = new SystemMessage(channel); + const { chat } = channel; const moderator = await channel.viewers.fetch(data.moderator_user_id); switch (data.action) { @@ -24,48 +34,45 @@ export default defineHandler({ ? "unique-mode" : "subscriber-only"; - message.context = { - type: "mode", + chat.event(Mode, { mode, enabled: !data.action.includes("off"), seconds: Number.NaN, moderator, - }; + }); break; } case "followers": case "followersoff": { - message.context = { - type: "mode", + chat.event(Mode, { mode: "follower-only", enabled: !data.action.includes("off"), seconds: data.followers ? data.followers.follow_duration_minutes * 60 : Number.NaN, moderator, - }; + }); break; } case "slow": case "slowoff": { - message.context = { - type: "mode", + chat.event(Mode, { mode: "slow", enabled: data.slow !== null, seconds: data.slow?.wait_time_seconds ?? Number.NaN, moderator, - }; + }); break; } case "clear": { - channel.chat.deleteMessages(); - message.context = { type: "clear", moderator }; + chat.deleteMessages(); + chat.event(Clear, { moderator }); break; } @@ -75,12 +82,11 @@ export default defineHandler({ const metadata = data.action === "delete" ? data.delete : data.shared_chat_delete; const viewer = await channel.viewers.fetch(metadata.user_id); - message.context = { - type: "delete", + chat.event(Delete, { text: metadata.message_body, user: viewer.user, moderator, - }; + }); break; } @@ -89,19 +95,14 @@ export default defineHandler({ case "add_permitted_term": case "remove_blocked_term": case "remove_permitted_term": { - message.context = { type: "term", data: data.automod_terms, moderator }; + chat.event(Term, { data: data.automod_terms, moderator }); break; } case "warn": { const viewer = await channel.viewers.fetch(data.warn.user_id); - message.context = { - type: "warn", - warning: data.warn, - viewer, - moderator, - }; + chat.event(Warn, { warning: data.warn, viewer, moderator }); break; } @@ -112,18 +113,17 @@ export default defineHandler({ data.action === "timeout" ? data.timeout : data.shared_chat_timeout; const viewer = await channel.viewers.fetch(metadata.user_id); - channel.chat.deleteMessages(metadata.user_id); + chat.deleteMessages(metadata.user_id); const expiration = new Date(metadata.expires_at); - const duration = expiration.getTime() - message.timestamp.getTime(); + const duration = expiration.getTime() - Date.now(); - message.context = { - type: "timeout", + chat.event(Timeout, { seconds: Math.ceil(duration / 1000), reason: metadata.reason, viewer, moderator, - }; + }); break; } @@ -134,11 +134,7 @@ export default defineHandler({ data.action === "untimeout" ? data.untimeout : data.shared_chat_untimeout; const viewer = await channel.viewers.fetch(metadata.user_id); - message.context = { - type: "untimeout", - viewer, - moderator, - }; + chat.event(Untimeout, { viewer, moderator }); break; } @@ -149,16 +145,15 @@ export default defineHandler({ const viewer = await channel.viewers.fetch((isBan ? data.ban : data.unban).user_id); if (isBan) { - channel.chat.deleteMessages(data.ban.user_id); + chat.deleteMessages(data.ban.user_id); } - message.context = { - type: "banStatus", + chat.event(BanStatus, { banned: isBan, reason: isBan ? data.ban.reason : null, viewer, moderator, - }; + }); break; } @@ -171,16 +166,15 @@ export default defineHandler({ ); if (isBan) { - channel.chat.deleteMessages(data.shared_chat_ban.user_id); + chat.deleteMessages(data.shared_chat_ban.user_id); } - message.context = { - type: "banStatus", + chat.event(BanStatus, { banned: isBan, reason: isBan ? data.shared_chat_ban.reason : null, viewer, moderator, - }; + }); break; } @@ -190,13 +184,7 @@ export default defineHandler({ const added = data.action === "mod"; const viewer = await channel.viewers.fetch((added ? data.mod : data.unmod).user_id); - message.context = { - type: "roleStatus", - role: "moderator", - added, - viewer, - moderator, - }; + chat.event(RoleStatus, { role: "moderator", added, viewer, moderator }); break; } @@ -206,13 +194,7 @@ export default defineHandler({ const added = data.action === "vip"; const viewer = await channel.viewers.fetch((added ? data.vip : data.unvip).user_id); - message.context = { - type: "roleStatus", - role: "VIP", - added, - viewer, - moderator, - }; + chat.event(RoleStatus, { role: "VIP", added, viewer, moderator }); break; } @@ -220,12 +202,11 @@ export default defineHandler({ case "raid": { const viewer = await channel.viewers.fetch(data.raid.user_id); - message.context = { - type: "raid", + chat.event(Raid, { viewers: data.raid.viewer_count, user: viewer.user, moderator, - }; + }); break; } @@ -233,11 +214,7 @@ export default defineHandler({ case "unraid": { const viewer = await channel.viewers.fetch(data.unraid.user_id); - message.context = { - type: "unraid", - user: viewer.user, - moderator, - }; + chat.event(Unraid, { user: viewer.user, moderator }); break; } @@ -246,7 +223,5 @@ export default defineHandler({ return; } } - - channel.chat.addMessage(message); }, }); diff --git a/src/lib/handlers/eventsub/channel-subscription-end.ts b/src/lib/handlers/eventsub/channel-subscription-end.ts index 570dc83b..ade9d27b 100644 --- a/src/lib/handlers/eventsub/channel-subscription-end.ts +++ b/src/lib/handlers/eventsub/channel-subscription-end.ts @@ -10,6 +10,6 @@ export default defineHandler({ const tier = data.tier === "Prime" ? "Prime" : `Tier ${data.tier[0]}`; const text = `Your ${data.is_gift ? "gifted" : ""} ${tier} subscription has ended.`; - channel.chat.addSystemMessage(text); + channel.chat.notice(text); }, }); diff --git a/src/lib/handlers/eventsub/channel-suspicious-user-message.ts b/src/lib/handlers/eventsub/channel-suspicious-user-message.ts index 74791251..1a0ff240 100644 --- a/src/lib/handlers/eventsub/channel-suspicious-user-message.ts +++ b/src/lib/handlers/eventsub/channel-suspicious-user-message.ts @@ -20,6 +20,6 @@ export default defineHandler({ message.viewer.banEvasion = data.ban_evasion_evaluation; } - channel.chat.addMessage(message); + channel.chat.add(message); }, }); diff --git a/src/lib/handlers/eventsub/channel-suspicious-user-update.ts b/src/lib/handlers/eventsub/channel-suspicious-user-update.ts index 79dcf192..861a850f 100644 --- a/src/lib/handlers/eventsub/channel-suspicious-user-update.ts +++ b/src/lib/handlers/eventsub/channel-suspicious-user-update.ts @@ -1,4 +1,5 @@ import { app } from "$lib/app.svelte"; +import SuspicionStatus from "$lib/components/message/events/SuspicionStatus.svelte"; import { defineHandler } from "../helper"; export default defineHandler({ @@ -23,8 +24,7 @@ export default defineHandler({ viewer.restricted = status === "restricted"; } - channel.chat.addSystemMessage({ - type: "suspicionStatus", + channel.chat.event(SuspicionStatus, { active: status !== "no_treatment", previous: viewer.monitored ? "monitoring" : viewer.restricted ? "restricting" : null, viewer, diff --git a/src/lib/handlers/eventsub/channel-unban-request-create.ts b/src/lib/handlers/eventsub/channel-unban-request-create.ts index 22b865ca..35a0c94f 100644 --- a/src/lib/handlers/eventsub/channel-unban-request-create.ts +++ b/src/lib/handlers/eventsub/channel-unban-request-create.ts @@ -1,4 +1,5 @@ import { app } from "$lib/app.svelte"; +import UnbanRequest from "$lib/components/message/events/UnbanRequest.svelte"; import { defineHandler } from "../helper"; export default defineHandler({ @@ -9,10 +10,6 @@ export default defineHandler({ const viewer = await channel.viewers.fetch(data.user_id); - channel.chat.addSystemMessage({ - type: "unbanRequest", - request: data, - viewer, - }); + channel.chat.event(UnbanRequest, { request: data, viewer }); }, }); diff --git a/src/lib/handlers/eventsub/channel-unban-request-resolve.ts b/src/lib/handlers/eventsub/channel-unban-request-resolve.ts index 76e63400..774e1800 100644 --- a/src/lib/handlers/eventsub/channel-unban-request-resolve.ts +++ b/src/lib/handlers/eventsub/channel-unban-request-resolve.ts @@ -1,4 +1,5 @@ import { app } from "$lib/app.svelte"; +import UnbanRequest from "$lib/components/message/events/UnbanRequest.svelte"; import type { Viewer } from "$lib/models/viewer.svelte"; import { defineHandler } from "../helper"; @@ -16,11 +17,6 @@ export default defineHandler({ moderator = await channel.viewers.fetch(data.moderator_user_id); } - channel.chat.addSystemMessage({ - type: "unbanRequest", - request: data, - viewer, - moderator, - }); + channel.chat.event(UnbanRequest, { request: data, viewer, moderator }); }, }); diff --git a/src/lib/handlers/eventsub/channel-warning-acknowledge.ts b/src/lib/handlers/eventsub/channel-warning-acknowledge.ts index ecaba79d..54f1a259 100644 --- a/src/lib/handlers/eventsub/channel-warning-acknowledge.ts +++ b/src/lib/handlers/eventsub/channel-warning-acknowledge.ts @@ -1,4 +1,5 @@ import { app } from "$lib/app.svelte"; +import WarnAck from "$lib/components/message/events/WarnAck.svelte"; import { defineHandler } from "../helper"; export default defineHandler({ @@ -9,9 +10,6 @@ export default defineHandler({ const viewer = await channel.viewers.fetch(data.user_id); - channel.chat.addSystemMessage({ - type: "warnAck", - viewer, - }); + channel.chat.event(WarnAck, { viewer }); }, }); diff --git a/src/lib/handlers/eventsub/stream-offline.ts b/src/lib/handlers/eventsub/stream-offline.ts index 97f5f022..88d4a863 100644 --- a/src/lib/handlers/eventsub/stream-offline.ts +++ b/src/lib/handlers/eventsub/stream-offline.ts @@ -1,4 +1,5 @@ import { app } from "$lib/app.svelte"; +import StreamStatus from "$lib/components/message/events/StreamStatus.svelte"; import { defineHandler } from "../helper"; export default defineHandler({ @@ -9,9 +10,6 @@ export default defineHandler({ channel.stream = null; - channel.chat.addSystemMessage({ - type: "streamStatus", - online: false, - }); + channel.chat.event(StreamStatus, { channel, online: false }); }, }); diff --git a/src/lib/handlers/eventsub/stream-online.ts b/src/lib/handlers/eventsub/stream-online.ts index d039939d..b16be937 100644 --- a/src/lib/handlers/eventsub/stream-online.ts +++ b/src/lib/handlers/eventsub/stream-online.ts @@ -1,4 +1,5 @@ import { app } from "$lib/app.svelte"; +import StreamStatus from "$lib/components/message/events/StreamStatus.svelte"; import { defineHandler } from "../helper"; export default defineHandler({ @@ -9,9 +10,6 @@ export default defineHandler({ channel.stream = await channel.fetchStream(); - channel.chat.addSystemMessage({ - type: "streamStatus", - online: true, - }); + channel.chat.event(StreamStatus, { channel, online: true }); }, }); diff --git a/src/lib/handlers/irc/clearchat.ts b/src/lib/handlers/irc/clearchat.ts index 8f9ade27..d8f885e0 100644 --- a/src/lib/handlers/irc/clearchat.ts +++ b/src/lib/handlers/irc/clearchat.ts @@ -1,5 +1,8 @@ import { app } from "$lib/app.svelte"; -import { SystemMessage } from "$lib/models/message/system-message"; +import Banned from "$lib/components/message/events/Banned.svelte"; +import BanStatus from "$lib/components/message/events/BanStatus.svelte"; +import Clear from "$lib/components/message/events/Clear.svelte"; +import Timeout from "$lib/components/message/events/Timeout.svelte"; import { defineHandler } from "../helper"; export default defineHandler({ @@ -14,13 +17,9 @@ export default defineHandler({ return; } - const message = new SystemMessage(channel, data); - if (data.action.type === "clear") { channel.chat.deleteMessages(); - - message.context = { type: "clear" }; - channel.chat.addMessage(message); + channel.chat.event(Clear, {}, data); return; } @@ -29,26 +28,18 @@ export default defineHandler({ channel.chat.deleteMessages(target.id); if (data.action.type === "ban") { - message.context = { - type: "banStatus", - banned: true, - reason: null, - viewer: target, - }; - if (!data.is_recent && target.id === app.user?.id) { app.user.banned.add(channel.id); - message.context = { type: "banned" }; + channel.chat.event(Banned, { channel }, data); + } else { + channel.chat.event(BanStatus, { banned: true, reason: null, viewer: target }, data); } } else { - message.context = { - type: "timeout", - seconds: data.action.duration.secs, - reason: null, - viewer: target, - }; + channel.chat.event( + Timeout, + { seconds: data.action.duration.secs, reason: null, viewer: target }, + data, + ); } - - channel.chat.addMessage(message); }, }); diff --git a/src/lib/handlers/irc/clearmsg.ts b/src/lib/handlers/irc/clearmsg.ts index 452203b1..22370263 100644 --- a/src/lib/handlers/irc/clearmsg.ts +++ b/src/lib/handlers/irc/clearmsg.ts @@ -1,5 +1,5 @@ import { app } from "$lib/app.svelte"; -import { SystemMessage } from "$lib/models/message/system-message"; +import Delete from "$lib/components/message/events/Delete.svelte"; import type { UserMessage } from "$lib/models/message/user-message"; import { defineHandler } from "../helper"; @@ -18,15 +18,7 @@ export default defineHandler({ message.deleted = true; if (data.is_recent || (!data.is_recent && app.user?.moderating.has(channel.id))) { - const sysmsg = new SystemMessage(channel, data); - - sysmsg.context = { - type: "delete", - text: data.message_text, - user: message.author, - }; - - channel.chat.addMessage(sysmsg); + channel.chat.event(Delete, { text: data.message_text, user: message.author }, data); } }, }); diff --git a/src/lib/handlers/irc/join.ts b/src/lib/handlers/irc/join.ts index 934f67ab..d1f50b35 100644 --- a/src/lib/handlers/irc/join.ts +++ b/src/lib/handlers/irc/join.ts @@ -1,4 +1,5 @@ import { app } from "$lib/app.svelte"; +import Join from "$lib/components/message/events/Join.svelte"; import { log } from "$lib/log"; import { sendPresence } from "$lib/seventv"; import { defineHandler } from "../helper"; @@ -21,7 +22,7 @@ export default defineHandler({ viewer.broadcaster = true; viewer.moderator = true; - channel.chat.addSystemMessage({ type: "join" }); + channel.chat.event(Join, { channel }); log.info(`Joined ${channel.user.displayName}`); }, diff --git a/src/lib/handlers/irc/notice.ts b/src/lib/handlers/irc/notice.ts index c0d9ea9a..a8bfd8ff 100644 --- a/src/lib/handlers/irc/notice.ts +++ b/src/lib/handlers/irc/notice.ts @@ -1,5 +1,5 @@ import { app } from "$lib/app.svelte"; -import { SystemMessage } from "$lib/models/message/system-message"; +import Banned from "$lib/components/message/events/Banned.svelte"; import { defineHandler } from "../helper"; export default defineHandler({ @@ -12,11 +12,11 @@ export default defineHandler({ return; } - const message = new SystemMessage(channel, { + const meta = { deleted: data.deleted, is_recent: data.is_recent, server_timestamp: data.recent_timestamp ?? Date.now(), - }); + }; switch (data.message_id) { case "emote_only_on": @@ -34,7 +34,7 @@ export default defineHandler({ .replace("This room", "The chat") .replace("s-only", "-only"); - message.text = text; + channel.chat.notice(text, meta); break; } @@ -43,11 +43,9 @@ export default defineHandler({ app.user.banned.add(channel.id); } - message.context = { type: "banned" }; + channel.chat.event(Banned, { channel }, meta); break; } } - - channel.chat.addMessage(message); }, }); diff --git a/src/lib/handlers/irc/privmsg.ts b/src/lib/handlers/irc/privmsg.ts index a78e1706..5a77eef4 100644 --- a/src/lib/handlers/irc/privmsg.ts +++ b/src/lib/handlers/irc/privmsg.ts @@ -27,6 +27,6 @@ export default defineHandler({ await message.setSource(data.source); } - channel.chat.addMessage(message); + channel.chat.add(message); }, }); diff --git a/src/lib/handlers/irc/usernotice.ts b/src/lib/handlers/irc/usernotice.ts index 8ef1ac8b..637b47b3 100644 --- a/src/lib/handlers/irc/usernotice.ts +++ b/src/lib/handlers/irc/usernotice.ts @@ -22,6 +22,6 @@ export default defineHandler({ await message.setSource(data.source); } - channel.chat.addMessage(message); + channel.chat.add(message); }, }); diff --git a/src/lib/handlers/seventv/emote-set-update.ts b/src/lib/handlers/seventv/emote-set-update.ts index 0cb6441a..488e41a8 100644 --- a/src/lib/handlers/seventv/emote-set-update.ts +++ b/src/lib/handlers/seventv/emote-set-update.ts @@ -1,10 +1,12 @@ import * as cache from "tauri-plugin-cache-api"; import { app } from "$lib/app.svelte"; +import EmoteSetUpdate from "$lib/components/message/events/EmoteSetUpdate.svelte"; import type { Emote } from "$lib/emotes"; -import { SystemMessage } from "$lib/models/message/system-message"; import type { EmoteChange } from "$lib/seventv"; import { defineHandler } from "../helper"; +type EmoteSetUpdateProps = import("svelte").ComponentProps; + function transform(emote: EmoteChange): Emote { let width = 28; let height = 28; @@ -51,17 +53,12 @@ export default defineHandler({ if (!twitch) return; const actor = await channel.viewers.fetch(twitch.id); - const message = new SystemMessage(channel); + let last: EmoteSetUpdateProps | null = null; for (const change of data.pushed ?? []) { const emote = transform(change.value); - message.context = { - type: "emoteSetUpdate", - action: "added", - emote, - actor, - }; + last = { action: "added", emote, actor }; channel.emotes.set(emote.name, emote); } @@ -70,12 +67,7 @@ export default defineHandler({ const emote = channel.emotes.get(change.old_value.name); if (!emote) continue; - message.context = { - type: "emoteSetUpdate", - action: "removed", - emote, - actor, - }; + last = { action: "removed", emote, actor }; channel.emotes.delete(change.old_value.name); } @@ -84,13 +76,7 @@ export default defineHandler({ const emote = channel.emotes.get(change.old_value.name); if (!emote) continue; - message.context = { - type: "emoteSetUpdate", - action: "renamed", - oldName: emote.name, - emote, - actor, - }; + last = { action: "renamed", oldName: emote.name, emote, actor }; emote.name = change.value.name; @@ -98,7 +84,7 @@ export default defineHandler({ channel.emotes.set(change.value.name, emote); } - channel.chat.addMessage(message); + if (last) channel.chat.event(EmoteSetUpdate, last); await cache.remove(`emotes:${channel.id}`); await cache.set(`emotes:${channel.id}`, channel.emotes.values().toArray()); diff --git a/src/lib/handlers/seventv/user-update.ts b/src/lib/handlers/seventv/user-update.ts index 5f2c7504..e7a53ec6 100644 --- a/src/lib/handlers/seventv/user-update.ts +++ b/src/lib/handlers/seventv/user-update.ts @@ -1,6 +1,6 @@ import { invoke } from "@tauri-apps/api/core"; import { app } from "$lib/app.svelte"; -import { SystemMessage } from "$lib/models/message/system-message"; +import EmoteSetChange from "$lib/components/message/events/EmoteSetChange.svelte"; import { defineHandler } from "../helper"; export default defineHandler({ @@ -9,8 +9,6 @@ export default defineHandler({ const channel = app.channels.values().find((c) => c.seventvId === data.id); if (!channel) return; - const message = new SystemMessage(channel); - const root = data.updated?.find((c) => c.key === "connections"); if (!root) return; @@ -28,16 +26,9 @@ export default defineHandler({ channel.emotes.clear("7TV"); if (child.value == null) { - message.context = { - type: "emoteSetChange", - actor, - }; + channel.chat.event(EmoteSetChange, { actor }); } else { - message.context = { - type: "emoteSetChange", - name: child.value.name, - actor, - }; + channel.chat.event(EmoteSetChange, { name: child.value.name, actor }); await channel.emotes.fetch7tv(); await invoke("resub_emote_set", { @@ -45,7 +36,5 @@ export default defineHandler({ setId: channel.emoteSetId, }); } - - channel.chat.addMessage(message); }, }); diff --git a/src/lib/models/chat.svelte.ts b/src/lib/models/chat.svelte.ts index 554b0cd9..d4447fa7 100644 --- a/src/lib/models/chat.svelte.ts +++ b/src/lib/models/chat.svelte.ts @@ -6,11 +6,11 @@ import { settings } from "$lib/settings"; import { sendPresence } from "$lib/seventv"; import type { SentMessage } from "$lib/twitch/api"; import { commands } from "../commands"; +import Notice from "../components/message/events/Notice.svelte"; import type { Channel } from "./channel.svelte"; import { ComponentMessage } from "./message/component-message"; -import type { MessageContext } from "./message/context"; +import { EventMessage, type EventMessageData } from "./message/event-message"; import type { Message } from "./message/message"; -import { SystemMessage } from "./message/system-message"; import { TextualMessage } from "./message/textual-message.svelte"; import type { UserMessage } from "./message/user-message"; import { Viewer } from "./viewer.svelte"; @@ -89,20 +89,12 @@ export class Chat { this.addCommands(commands); } - public addComponent>( - component: C, - props: ComponentProps = {} as never, - ) { - this.messages.push(new ComponentMessage(component, props)); - return this; - } - - public addMessage(message: TextualMessage) { + public add(message: Message) { if (this.messages.some((m) => m.id === message.id)) { return this; } - if (message.recent) { + if (message instanceof TextualMessage && message.recent) { if (this.#lastRecentAt === null) { this.messages.unshift(message); this.#lastRecentAt = 0; @@ -117,17 +109,29 @@ export class Chat { return this; } - public addSystemMessage(content: string | MessageContext) { - let message: SystemMessage; + /** + * Adds a channel event rendered by the given component. + */ + public event>( + component: C, + props: ComponentProps, + data?: Partial, + ) { + return this.add(new EventMessage(this.channel, component, props, data)); + } - if (typeof content === "string") { - message = new SystemMessage(this.channel, content); - } else { - message = new SystemMessage(this.channel); - message.context = content; - } + /** + * Adds a plain text system notice. + */ + public notice(text: string, data?: Partial) { + return this.event(Notice, { text }, data); + } - return this.addMessage(message); + /** + * Adds an arbitrary, chrome-less component to the chat. + */ + public component>(component: C, props: ComponentProps) { + return this.add(new ComponentMessage(component, props)); } public addCommands(commands: Command[]) { @@ -297,7 +301,7 @@ export class Chat { const reason = data.drop_reason.message; log.warn(`Message dropped: ${reason}`); - this.addSystemMessage(reason); + this.notice(reason); } } diff --git a/src/lib/models/message/component-message.ts b/src/lib/models/message/component-message.ts index e7ed86e5..de60d6bb 100644 --- a/src/lib/models/message/component-message.ts +++ b/src/lib/models/message/component-message.ts @@ -2,14 +2,21 @@ import type { Component, ComponentProps } from "svelte"; import { Message } from "./message"; export class ComponentMessage extends Message { - public readonly [Symbol.toStringTag] = "ComponentMessage"; + public override readonly [Symbol.toStringTag] = "ComponentMessage"; - public readonly id = crypto.randomUUID(); - public readonly timestamp = new Date(); + public override readonly id = crypto.randomUUID(); + public override readonly timestamp = new Date(); public constructor( + /** + * The component that renders the message. + */ public readonly component: C, - public readonly props: ComponentProps = {} as never, + + /** + * The props passed to the component. + */ + public readonly props: ComponentProps, ) { super(); } diff --git a/src/lib/models/message/context.ts b/src/lib/models/message/context.ts deleted file mode 100644 index 910db952..00000000 --- a/src/lib/models/message/context.ts +++ /dev/null @@ -1,170 +0,0 @@ -import type { Emote } from "$lib/emotes"; -import type { - AutoModMessageStatus, - AutoModTermsMetadata, - ChannelUnbanRequestCreate, - ChannelUnbanRequestResolve, - WarnMetadata, -} from "$lib/twitch/eventsub"; -import type { User } from "../user.svelte"; -import type { Viewer } from "../viewer.svelte"; - -export interface AutoModContext { - type: "autoMod"; - status: AutoModMessageStatus; - viewer: Viewer; - moderator: Viewer; -} - -export interface BannedContext { - type: "banned"; -} - -export interface BanStatusContext { - type: "banStatus"; - banned: boolean; - reason: string | null; - viewer: Viewer; - moderator?: Viewer; -} - -export interface BlockStatusContext { - type: "blockStatus"; - blocked: boolean; - user: User; -} - -export interface ClearContext { - type: "clear"; - moderator?: Viewer; -} - -export interface DeleteContext { - type: "delete"; - text: string; - user: User; - moderator?: Viewer; - source?: Viewer | null; -} - -export interface EmoteSetChangeContext { - type: "emoteSetChange"; - name?: string; - actor: Viewer; -} - -export interface EmoteSetUpdateContext { - type: "emoteSetUpdate"; - action: "added" | "removed" | "renamed"; - oldName?: string; - emote: Emote; - actor: Viewer; -} - -export interface JoinContext { - type: "join"; -} - -export interface ModeContext { - type: "mode"; - mode: string; - enabled: boolean; - seconds: number; - moderator: Viewer; -} - -export interface RaidContext { - type: "raid"; - viewers: number; - user: User; - moderator: Viewer; -} - -export interface RoleStatusContext { - type: "roleStatus"; - role: string; - added: boolean; - viewer: Viewer; - moderator: Viewer; -} - -export interface StreamStatusContext { - type: "streamStatus"; - online: boolean; -} - -export interface SuspicionStatusContext { - type: "suspicionStatus"; - active: boolean; - previous: "monitoring" | "restricting" | null; - viewer: Viewer; - moderator: Viewer; -} - -export interface TermContext { - type: "term"; - data: AutoModTermsMetadata; - moderator: Viewer; -} - -export interface TimeoutContext { - type: "timeout"; - seconds: number; - reason: string | null; - viewer: Viewer; - moderator?: Viewer; -} - -export interface UnbanRequestContext { - type: "unbanRequest"; - request: ChannelUnbanRequestCreate | ChannelUnbanRequestResolve; - viewer: Viewer; - moderator?: Viewer; -} - -export interface UnraidContext { - type: "unraid"; - user: User; - moderator: Viewer; -} - -export interface UntimeoutContext { - type: "untimeout"; - viewer: Viewer; - moderator: Viewer; -} - -export interface WarnContext { - type: "warn"; - warning: WarnMetadata; - viewer: Viewer; - moderator: Viewer; -} - -export interface WarnAckContext { - type: "warnAck"; - viewer: Viewer; -} - -export type MessageContext = - | AutoModContext - | BannedContext - | BanStatusContext - | BlockStatusContext - | ClearContext - | DeleteContext - | EmoteSetChangeContext - | EmoteSetUpdateContext - | JoinContext - | ModeContext - | RaidContext - | RoleStatusContext - | StreamStatusContext - | SuspicionStatusContext - | TermContext - | TimeoutContext - | UnbanRequestContext - | UnraidContext - | UntimeoutContext - | WarnContext - | WarnAckContext; diff --git a/src/lib/models/message/event-message.ts b/src/lib/models/message/event-message.ts new file mode 100644 index 00000000..f0bf6c70 --- /dev/null +++ b/src/lib/models/message/event-message.ts @@ -0,0 +1,40 @@ +import type { Component, ComponentProps } from "svelte"; +import type { Channel } from "../channel.svelte"; +import { TextualMessage } from "./textual-message.svelte"; + +export interface EventMessageData { + deleted: boolean; + is_recent: boolean; + server_timestamp: number; +} + +/** + * Event messages are constructed internally to relay channel events. + */ +export class EventMessage extends TextualMessage { + public override readonly [Symbol.toStringTag] = "EventMessage"; + + public override readonly id = crypto.randomUUID(); + public override text = ""; + + public constructor( + channel: Channel, + + /** + * The component that renders the event. + */ + public readonly component: C, + + /** + * The props passed to the component. + */ + public readonly props: ComponentProps, + data?: Partial, + ) { + super(channel, { + deleted: data?.deleted ?? false, + is_recent: data?.is_recent ?? false, + server_timestamp: data?.server_timestamp ?? Date.now(), + }); + } +} diff --git a/src/lib/models/message/message.ts b/src/lib/models/message/message.ts index 797e98d9..dfdb6f8e 100644 --- a/src/lib/models/message/message.ts +++ b/src/lib/models/message/message.ts @@ -1,5 +1,5 @@ import type { ComponentMessage } from "./component-message"; -import type { SystemMessage } from "./system-message"; +import type { EventMessage } from "./event-message"; import type { UserMessage } from "./user-message"; export abstract class Message { @@ -19,8 +19,8 @@ export abstract class Message { return this[Symbol.toStringTag] === "ComponentMessage"; } - public isSystem(): this is SystemMessage { - return this[Symbol.toStringTag] === "SystemMessage"; + public isEvent(): this is EventMessage { + return this[Symbol.toStringTag] === "EventMessage"; } public isUser(): this is UserMessage { diff --git a/src/lib/models/message/system-message.ts b/src/lib/models/message/system-message.ts deleted file mode 100644 index f49f0091..00000000 --- a/src/lib/models/message/system-message.ts +++ /dev/null @@ -1,39 +0,0 @@ -import type { Channel } from "../channel.svelte"; -import type { MessageContext } from "./context"; -import { TextualMessage } from "./textual-message.svelte"; - -export interface SystemMessageData { - deleted: boolean; - is_recent: boolean; - server_timestamp: number; -} - -/** - * System messages are messages constructed internally and sent to relay - * information to the user. - */ -export class SystemMessage extends TextualMessage { - public override readonly id = crypto.randomUUID(); - public override text = ""; - - public readonly [Symbol.toStringTag] = "SystemMessage"; - - /** - * The context associated with the message. - */ - public context: MessageContext | null = null; - - public constructor(channel: Channel, data?: string | Partial) { - const args = typeof data === "string" ? undefined : (data ?? {}); - - super(channel, { - deleted: args?.deleted ?? false, - is_recent: args?.is_recent ?? false, - server_timestamp: args?.server_timestamp ?? Date.now(), - }); - - if (typeof data === "string") { - this.text = data; - } - } -} diff --git a/src/lib/models/message/textual-message.svelte.ts b/src/lib/models/message/textual-message.svelte.ts index e7d0324e..8d1f139a 100644 --- a/src/lib/models/message/textual-message.svelte.ts +++ b/src/lib/models/message/textual-message.svelte.ts @@ -1,9 +1,9 @@ import type { BaseUserMessage } from "$lib/twitch/irc"; import type { Channel } from "../channel.svelte"; +import type { EventMessageData } from "./event-message"; import { Message } from "./message"; -import type { SystemMessageData } from "./system-message"; -export type MessageData = BaseUserMessage | SystemMessageData; +export type MessageData = BaseUserMessage | EventMessageData; export abstract class TextualMessage extends Message { public abstract readonly id: string; diff --git a/src/lib/models/message/user-message.ts b/src/lib/models/message/user-message.ts index f809ac1b..6ebafb9d 100644 --- a/src/lib/models/message/user-message.ts +++ b/src/lib/models/message/user-message.ts @@ -44,11 +44,10 @@ function createPartialUser(channel: Channel, sender: BasicUser, color: string) { export class UserMessage extends TextualMessage { #nodes: Node[] = []; + public override readonly [Symbol.toStringTag] = "UserMessage"; public override readonly id: string; public override readonly text: string; - public readonly [Symbol.toStringTag] = "UserMessage"; - /** * The user who sent the message. */ @@ -118,7 +117,7 @@ export class UserMessage extends TextualMessage { // message_text should only be possibly null if it's a USERNOTICE, in // which case we can assume system_message is present - this.text = data.message_text ?? (data as UserNoticeMessage).system_message; + this.text = data.message_text ?? ("system_message" in data ? data.system_message : ""); this.author = viewer?.user ?? createPartialUser(channel, data.sender, data.name_color); this.viewer = viewer ?? null;