Procedurally generates a radio weather report
at v0.0.4 4.2 kB view raw
1import { OpenWeatherAPI, type DailyWeather } from 'openweather-api-node'; 2import { voiceLines } from './voice.js'; 3import type { Config } from './index.js'; 4import type { Voice } from './voice.js'; 5import crypto from 'crypto'; 6 7type SegmentName = string; 8type SequenceName = string; 9type Programs = SegmentName[][]; 10type Segments = { [segment: SegmentName]: SequenceName[] }; 11type Sequence = { 12 conditions?: string[]; 13 tracks: string[]; 14} 15type Sequences = { [sequence: SequenceName]: Sequence }; 16 17let config: Config = null; 18 19function selectOne<T>(arr: T[]): T { 20 return arr[crypto.randomInt(0, arr.length)]; 21} 22 23function resolveSide(side: string, currentWeather: DailyWeather) { 24 if (!side.startsWith('weather')) { 25 return side.includes('.') ? parseFloat(side) : parseInt(side); 26 } 27 28 const tokens = side.split('.'); 29 let w = currentWeather; 30 tokens.forEach(t => w = w[t]); 31 return typeof w === 'object' ? JSON.stringify(w) : w as (string | number); 32} 33 34function notNotANumber(something: number | string, defaultVal: string) { 35 if (typeof something === 'string' || !isNaN(something)) { 36 return something; 37 } 38 return defaultVal; 39} 40 41function conditionIsMet(condition: string | undefined, currentWeather: DailyWeather): boolean { 42 if (typeof condition !== 'string') { 43 return true; 44 } 45 const [lhs, relational, rhs] = condition.split(' '); 46 if (lhs === undefined || relational === undefined || rhs === undefined) { 47 throw new Error(`Condition "${condition}" is not in the correct format`); 48 } 49 const lhsResolved = notNotANumber(resolveSide(lhs, currentWeather), lhs); 50 const rhsResolved = notNotANumber(resolveSide(rhs, currentWeather), rhs); 51 switch (relational) { 52 case '=': 53 case '==': 54 return lhsResolved == rhsResolved; 55 case '!=': 56 return lhsResolved != rhsResolved; 57 case '<': 58 return lhsResolved < rhsResolved; 59 case '<=': 60 return lhsResolved <= rhsResolved; 61 case '>': 62 return lhsResolved > rhsResolved; 63 case '>=': 64 return lhsResolved >= rhsResolved; 65 default: 66 throw new Error(`Unsupported relational operator: ${relational}`); 67 } 68} 69 70function resolveMacro(str: string, currentWeather: DailyWeather): string[] { 71 if (str.startsWith('%')) { 72 const [profile, subject] = str.substring(1).split(' ', 2); 73 const voiceProfile: Voice = config.voices[profile]; 74 let resolvedSubject: any = currentWeather; 75 subject.split('.').forEach(t => resolvedSubject = resolvedSubject[t]); 76 return voiceLines(voiceProfile, resolvedSubject); 77 } 78 return [str]; 79} 80 81function processSequence(sequence: Sequence, currentWeather: DailyWeather): string[] { 82 const tracks = sequence.tracks; 83 return tracks.map(t => resolveMacro(t, currentWeather)).flat().filter(t => t !== null); 84} 85 86function processSegment(segment: SegmentName, currentWeather: DailyWeather): string[] { 87 if (!(segment in config.segments)) { 88 return (config.sequences[segment].conditions || []).every(c => conditionIsMet(c, currentWeather)) ? processSequence(config.sequences[segment], currentWeather) : []; 89 } 90 const potentialSequences: SequenceName[] = config.segments[segment].filter(s => (config.sequences[s].conditions || []).every(c => conditionIsMet(c, currentWeather))); 91 if (potentialSequences.length === 0) { 92 return []; 93 } 94 return processSequence(config.sequences[selectOne(potentialSequences)], currentWeather); 95} 96 97async function Sequencer(conf: Config): Promise<string[]> { 98 config = conf; 99 const weather = new OpenWeatherAPI(conf.weather); 100 const currentWeather = await weather.getToday(); 101 const sequence: string[] = []; 102 const program: SegmentName[] = selectOne(conf.programs); 103 for (let i = 0; i < program.length; i++) { 104 const segment = program[i]; 105 sequence.push(...(processSegment(segment, currentWeather))); 106 } 107 return sequence; 108} 109 110export default Sequencer; 111export { Sequencer }; 112export type { SegmentName, SequenceName, Programs, Segments, Sequence, Sequences };