test_sample.mjs
1 import * as events from "node:events" 2 import * as util from "./util.mjs" 3 import * as twitch from "./twitch.mjs" 4 import * as irc from "./irc.mjs" 5 6 const TARGET_SAMPLES = 100 7 const SAMPLE_LENGTH = 60*60e3 8 const MAX_ACTIVE_SAMPLES = 50 9 const MAX_VIEWERS = 300 10 11 const SeenChannels = new Set() 12 13 function makeNewSample(stream) { 14 return Object.assign({}, stream, { 15 timeoutMs: SAMPLE_LENGTH, 16 failedIRCAuthRetries: 0, // Not sure if we even keep this... 17 uniqueChatters: new Set(), 18 validQuestions: new Array(), 19 totalMessages: 0, 20 totalBits: 0, 21 collectionStart: null, 22 collectionEnd: null, 23 viewers: [stream.viewers], 24 targetViewers: MAX_VIEWERS, 25 }) 26 } 27 28 function reportSampleProgress(update) { 29 const { userName, viewers, bits, chatters, messages, questions } = update 30 31 if ((viewers + bits + chatters + messages + questions) == 0) return 32 33 const newChatters = `${chatters > 0 ? "+":""}${chatters}`.padEnd(8, " ") 34 , newQuestions = `${questions > 0 ? "+":""}${questions}`.padEnd(8, " ") 35 , newMessages = `${messages > 0 ? "+":""}${messages}`.padEnd(8, " ") 36 , newBits = `${bits > 0 ? "+":""}${bits}`.padEnd(8, " ") 37 , newViewers = `${viewers > 0 ? "+":""}${viewers}`.padEnd(8, " ") 38 39 let logLine = `\x1b[33m${userName}\x1b[0m `.padStart(31) 40 41 logLine += `\x1b[36m${newViewers}\x1b[0m ` 42 logLine += `\x1b[31m${newChatters}\x1b[0m ` 43 logLine += `\x1b[93m${newQuestions}\x1b[0m ` 44 logLine += `\x1b[32m${newMessages}\x1b[0m ` 45 logLine += `\x1b[95m${newBits}\x1b[0m` 46 47 console.log(logLine) 48 } 49 50 async function main() { 51 const ActiveSamples = new Map() 52 , CompletedSamples = [] 53 54 const SampleCollector = new events.EventEmitter() 55 56 SampleCollector.on("cancel", async (sample, type) => { 57 const {channelId, userName } = sample 58 const {status, desc } = type 59 console.log(`-> CANCEL: ${userName} (${desc})`) 60 61 if (sample.collectionEnd - sample.collectionStart >= 5*60e3) { 62 CompletedSamples.push(ActiveSamples.get(channelId)) 63 } 64 65 ActiveSamples.delete(channelId) 66 67 if ((ActiveSamples.size < MAX_ACTIVE_SAMPLES) && (ActiveSamples.size + CompletedSamples.length) < TARGET_SAMPLES) { 68 const [nextStream] = await twitch.GetStreams(MAX_VIEWERS, 1, SeenChannels) 69 , nextSample = makeNewSample(nextStream) 70 ActiveSamples.set(nextSample.channelId, nextSample) 71 irc.SampleTwitchChat(nextSample, SampleCollector) 72 } 73 }) 74 75 SampleCollector.on("complete", async ({channelId, userName}) => { 76 console.log(`-> COMPLETE: ${userName}`) 77 CompletedSamples.push(ActiveSamples.get(channelId)) 78 ActiveSamples.delete(channelId) 79 80 if ((ActiveSamples.size < MAX_ACTIVE_SAMPLES) && (ActiveSamples.size + CompletedSamples.length) < TARGET_SAMPLES) { 81 const [nextStream] = await twitch.GetStreams(MAX_VIEWERS, 1, SeenChannels) 82 , nextSample = makeNewSample(nextStream) 83 ActiveSamples.set(nextSample.channelId, nextSample) 84 irc.SampleTwitchChat(nextSample, SampleCollector) 85 } 86 }) 87 88 SampleCollector.on("update", reportSampleProgress) 89 90 // Initialize ActiveSamples 91 const streams = await twitch.GetStreams(MAX_VIEWERS, TARGET_SAMPLES, SeenChannels) 92 93 for (const stream of streams) { 94 const sample = makeNewSample(stream) 95 ActiveSamples.set(stream.channelId, sample) 96 irc.SampleTwitchChat(sample, SampleCollector) 97 98 console.log(`\x1b[33m${sample.userName}\x1b[0m`.padStart(30) + ` \x1b[36m${(""+sample.viewers[sample.viewers.length-1]).padEnd(8, " ")}\x1b[0m`) 99 } 100 101 for (;;) { 102 await util.sleep(5e3) 103 104 if (ActiveSamples.size == 0 && CompletedSamples.length >= TARGET_SAMPLES) { 105 const questions = new Array() 106 , summaries = new Array() 107 108 for (const sample of CompletedSamples) { 109 questions.push(...sample.validQuestions) 110 summaries.push({ 111 channelId: sample.channelId, 112 userName: sample.userName, 113 topic: sample.topic, 114 title: sample.title, 115 start: sample.start, 116 duration: Math.floor((sample.collectionEnd - sample.collectionStart)/60e3), 117 bits: sample.totalBits, 118 msgs: sample.totalMessages, 119 qs: sample.validQuestions.length, 120 chatters: sample.uniqueChatters.size, 121 qpm: Math.floor(sample.validQuestions.length/Math.floor((sample.collectionEnd - sample.collectionStart)/60e3)) || 0, 122 avgViewers: Math.floor(sample.viewers.reduce((acc,vc) => acc+vc) / sample.viewers.length) 123 }) 124 } 125 126 // write CSVs 127 if (summaries.length) { 128 await util.writeCSV(summaries, "samples") 129 } 130 if (questions.length) { 131 await util.writeCSV(questions, "questions") 132 } 133 134 break 135 } 136 } 137 } 138 139 main()