Live video on the AT Protocol
at natb/reporting-interface 135 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 }, 50 }; 51}; 52 53/** 54 * System message factory functions for common scenarios 55 */ 56export const SystemMessages = { 57 streamStart: (streamerName: string): ChatMessageViewHydrated => 58 createSystemMessage( 59 SystemMessageType.stream_start, 60 `Now streaming - ${streamerName}`, 61 { 62 streamerName, 63 }, 64 ), 65 66 // technically, streams can't 'end' on Streamplace 67 // possibly we could use deleting or editing streams (`endedAt` param) for this? 68 streamEnd: (duration?: string): ChatMessageViewHydrated => 69 createSystemMessage( 70 SystemMessageType.stream_end, 71 duration ? `Stream has ended. Duration: ${duration}` : "Stream has ended", 72 { duration }, 73 ), 74 75 notification: (message: string): ChatMessageViewHydrated => 76 createSystemMessage(SystemMessageType.notification, message), 77}; 78 79/** 80 * Checks if a message is a system message 81 * @param message The message to check 82 * @returns True if the message is a system message 83 */ 84export const isSystemMessage = (message: ChatMessageViewHydrated): boolean => { 85 return message.author.did === "did:sys:system"; 86}; 87 88/** 89 * Gets the system message type from a message 90 * @param message The message to check 91 * @returns The system message type or null if not a system message 92 */ 93export const getSystemMessageType = ( 94 message: ChatMessageViewHydrated, 95): SystemMessageType | null => { 96 if (!isSystemMessage(message)) { 97 return null; 98 } 99 return message.author.handle as SystemMessageType; 100}; 101 102/** 103 * Parses metadata from a system message based on its type 104 * @param message The system message to parse 105 * @returns The parsed metadata 106 */ 107export const parseSystemMessageMetadata = ( 108 message: ChatMessageViewHydrated, 109): SystemMessageMetadata => { 110 const metadata: SystemMessageMetadata = {}; 111 const type = getSystemMessageType(message); 112 const text = message.record.text; 113 114 if (!type) return metadata; 115 116 switch (type) { 117 case "stream_end": { 118 const durationMatch = text.match(/Duration:\s*(\d+:\d+(?::\d+)?)/); 119 if (durationMatch) { 120 metadata.duration = durationMatch[1]; 121 } 122 break; 123 } 124 125 case "stream_start": { 126 const streamerMatch = text.match(/^(.+?)\s+is now live!/); 127 if (streamerMatch) { 128 metadata.streamerName = streamerMatch[1]; 129 } 130 break; 131 } 132 } 133 134 return metadata; 135};