Live video on the AT Protocol
at eli/fix-context-recursion 136 lines 3.7 kB view raw
1import { ChatMessageViewHydrated } from "streamplace"; 2 3export enum SystemMessageType { 4 stream_start = "stream_start", 5 stream_end = "stream_end", 6 notification = "notification", 7} 8 9export interface SystemMessageMetadata { 10 username?: string; 11 action?: string; 12 count?: number; 13 duration?: string; 14 reason?: string; 15 streamerName?: string; 16} 17 18/** 19 * Creates a system message with the proper structure 20 * @param type The type of system message 21 * @param text The message text 22 * @param metadata Optional metadata for the message 23 * @returns A properly formatted ChatMessageViewHydrated object 24 */ 25export const createSystemMessage = ( 26 type: SystemMessageType, 27 text: string, 28 metadata?: SystemMessageMetadata, 29 date: Date = new Date(), 30): ChatMessageViewHydrated => { 31 const now = date; 32 33 return { 34 uri: `at://did:sys:system/place.stream.chat.message/${now.getTime()}`, 35 cid: `system-${now.getTime()}`, 36 author: { 37 did: "did:sys:system", 38 handle: type, // Use handle to specify the type of system message 39 }, 40 record: { 41 text, 42 createdAt: now.toISOString(), 43 streamer: "system", 44 $type: "place.stream.chat.message", 45 }, 46 indexedAt: now.toISOString(), 47 chatProfile: { 48 color: { red: 128, green: 128, blue: 128 }, // Gray color for system messages 49 $type: "place.stream.chat.profile", 50 }, 51 }; 52}; 53 54/** 55 * System message factory functions for common scenarios 56 */ 57export const SystemMessages = { 58 streamStart: (streamerName: string): ChatMessageViewHydrated => 59 createSystemMessage( 60 SystemMessageType.stream_start, 61 `Now streaming - ${streamerName}`, 62 { 63 streamerName, 64 }, 65 ), 66 67 // technically, streams can't 'end' on Streamplace 68 // possibly we could use deleting or editing streams (`endedAt` param) for this? 69 streamEnd: (duration?: string): ChatMessageViewHydrated => 70 createSystemMessage( 71 SystemMessageType.stream_end, 72 duration ? `Stream has ended. Duration: ${duration}` : "Stream has ended", 73 { duration }, 74 ), 75 76 notification: (message: string): ChatMessageViewHydrated => 77 createSystemMessage(SystemMessageType.notification, message), 78}; 79 80/** 81 * Checks if a message is a system message 82 * @param message The message to check 83 * @returns True if the message is a system message 84 */ 85export const isSystemMessage = (message: ChatMessageViewHydrated): boolean => { 86 return message.author.did === "did:sys:system"; 87}; 88 89/** 90 * Gets the system message type from a message 91 * @param message The message to check 92 * @returns The system message type or null if not a system message 93 */ 94export const getSystemMessageType = ( 95 message: ChatMessageViewHydrated, 96): SystemMessageType | null => { 97 if (!isSystemMessage(message)) { 98 return null; 99 } 100 return message.author.handle as SystemMessageType; 101}; 102 103/** 104 * Parses metadata from a system message based on its type 105 * @param message The system message to parse 106 * @returns The parsed metadata 107 */ 108export const parseSystemMessageMetadata = ( 109 message: ChatMessageViewHydrated, 110): SystemMessageMetadata => { 111 const metadata: SystemMessageMetadata = {}; 112 const type = getSystemMessageType(message); 113 const text = message.record.text; 114 115 if (!type) return metadata; 116 117 switch (type) { 118 case "stream_end": { 119 const durationMatch = text.match(/Duration:\s*(\d+:\d+(?::\d+)?)/); 120 if (durationMatch) { 121 metadata.duration = durationMatch[1]; 122 } 123 break; 124 } 125 126 case "stream_start": { 127 const streamerMatch = text.match(/^(.+?)\s+is now live!/); 128 if (streamerMatch) { 129 metadata.streamerName = streamerMatch[1]; 130 } 131 break; 132 } 133 } 134 135 return metadata; 136};