···27272828export * from "./components/chat/chat";
2929export * from "./components/chat/chat-box";
3030+export * from "./components/chat/system-message";
3131+export * from "./lib/system-messages";
+135
js/components/src/lib/system-messages.ts
···11+import { ChatMessageViewHydrated } from "streamplace";
22+33+export enum SystemMessageType {
44+ stream_start = "stream_start",
55+ stream_end = "stream_end",
66+ notification = "notification",
77+}
88+99+export interface SystemMessageMetadata {
1010+ username?: string;
1111+ action?: string;
1212+ count?: number;
1313+ duration?: string;
1414+ reason?: string;
1515+ streamerName?: string;
1616+}
1717+1818+/**
1919+ * Creates a system message with the proper structure
2020+ * @param type The type of system message
2121+ * @param text The message text
2222+ * @param metadata Optional metadata for the message
2323+ * @returns A properly formatted ChatMessageViewHydrated object
2424+ */
2525+export const createSystemMessage = (
2626+ type: SystemMessageType,
2727+ text: string,
2828+ metadata?: SystemMessageMetadata,
2929+ date: Date = new Date(),
3030+): ChatMessageViewHydrated => {
3131+ const now = date;
3232+3333+ return {
3434+ uri: `at://did:sys:system/place.stream.chat.message/${now.getTime()}`,
3535+ cid: `system-${now.getTime()}`,
3636+ author: {
3737+ did: "did:sys:system",
3838+ handle: type, // Use handle to specify the type of system message
3939+ },
4040+ record: {
4141+ text,
4242+ createdAt: now.toISOString(),
4343+ streamer: "system",
4444+ $type: "place.stream.chat.message",
4545+ },
4646+ indexedAt: now.toISOString(),
4747+ chatProfile: {
4848+ color: { red: 128, green: 128, blue: 128 }, // Gray color for system messages
4949+ },
5050+ };
5151+};
5252+5353+/**
5454+ * System message factory functions for common scenarios
5555+ */
5656+export const SystemMessages = {
5757+ streamStart: (streamerName: string): ChatMessageViewHydrated =>
5858+ createSystemMessage(
5959+ SystemMessageType.stream_start,
6060+ `Now streaming - ${streamerName}`,
6161+ {
6262+ streamerName,
6363+ },
6464+ ),
6565+6666+ // technically, streams can't 'end' on Streamplace
6767+ // possibly we could use deleting or editing streams (`endedAt` param) for this?
6868+ streamEnd: (duration?: string): ChatMessageViewHydrated =>
6969+ createSystemMessage(
7070+ SystemMessageType.stream_end,
7171+ duration ? `Stream has ended. Duration: ${duration}` : "Stream has ended",
7272+ { duration },
7373+ ),
7474+7575+ notification: (message: string): ChatMessageViewHydrated =>
7676+ createSystemMessage(SystemMessageType.notification, message),
7777+};
7878+7979+/**
8080+ * Checks if a message is a system message
8181+ * @param message The message to check
8282+ * @returns True if the message is a system message
8383+ */
8484+export const isSystemMessage = (message: ChatMessageViewHydrated): boolean => {
8585+ return message.author.did === "did:sys:system";
8686+};
8787+8888+/**
8989+ * Gets the system message type from a message
9090+ * @param message The message to check
9191+ * @returns The system message type or null if not a system message
9292+ */
9393+export const getSystemMessageType = (
9494+ message: ChatMessageViewHydrated,
9595+): SystemMessageType | null => {
9696+ if (!isSystemMessage(message)) {
9797+ return null;
9898+ }
9999+ return message.author.handle as SystemMessageType;
100100+};
101101+102102+/**
103103+ * Parses metadata from a system message based on its type
104104+ * @param message The system message to parse
105105+ * @returns The parsed metadata
106106+ */
107107+export const parseSystemMessageMetadata = (
108108+ message: ChatMessageViewHydrated,
109109+): SystemMessageMetadata => {
110110+ const metadata: SystemMessageMetadata = {};
111111+ const type = getSystemMessageType(message);
112112+ const text = message.record.text;
113113+114114+ if (!type) return metadata;
115115+116116+ switch (type) {
117117+ case "stream_end": {
118118+ const durationMatch = text.match(/Duration:\s*(\d+:\d+(?::\d+)?)/);
119119+ if (durationMatch) {
120120+ metadata.duration = durationMatch[1];
121121+ }
122122+ break;
123123+ }
124124+125125+ case "stream_start": {
126126+ const streamerMatch = text.match(/^(.+?)\s+is now live!/);
127127+ if (streamerMatch) {
128128+ metadata.streamerName = streamerMatch[1];
129129+ }
130130+ break;
131131+ }
132132+ }
133133+134134+ return metadata;
135135+};