/ support / ebsSupport / test_sample.mjs
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()