commit 4a260f112f364e323ff25618af8b2794329f61ae Author: Cory Sanin Date: Fri Aug 22 01:04:31 2025 -0500 Initial commit diff --git a/.github/workflows/npm-audit.yml b/.github/workflows/npm-audit.yml new file mode 100644 index 0000000..7af3253 --- /dev/null +++ b/.github/workflows/npm-audit.yml @@ -0,0 +1,27 @@ +name: NPM Audit Check + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + schedule: + - cron: '15 16 * * 5' + +jobs: + + npm_audit: + name: Check NPM audit + runs-on: ubuntu-latest + timeout-minutes: 20 + strategy: + fail-fast: true + permissions: + contents: read + + steps: + - name: Checkout repository + uses: https://github.com/actions/checkout@v4 + + - name: NPM Audit + run: npm audit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2e3619 --- /dev/null +++ b/.gitignore @@ -0,0 +1,104 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +lerna-debug.log* + +# Diagnostic reports (https://nodejs.org/api/report.html) +report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage +*.lcov + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (https://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# TypeScript v1 declaration files +typings/ + +# TypeScript cache +*.tsbuildinfo + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Microbundle cache +.rpt2_cache/ +.rts2_cache_cjs/ +.rts2_cache_es/ +.rts2_cache_umd/ + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env +.env.test + +# parcel-bundler cache (https://parceljs.org/) +.cache + +# next.js build output +.next + +# nuxt.js build output +.nuxt + +# gatsby files +.cache/ +public + +# vuepress build output +.vuepress/dist + +# Serverless directories +.serverless/ + +# FuseBox cache +.fusebox/ + +# DynamoDB Local files +.dynamodb/ + + +# custom .gitignore +config/config.json5 +distribution/ +.env diff --git a/README.md b/README.md new file mode 100644 index 0000000..86b3c4c --- /dev/null +++ b/README.md @@ -0,0 +1,12 @@ +# morning-report + +randomly generate a radio show-style weather report according to a flexible config file. + +## Usage + +``` +npm install +npm run build +npm start +``` + diff --git a/config/config.example.json5 b/config/config.example.json5 new file mode 100644 index 0000000..9712f30 --- /dev/null +++ b/config/config.example.json5 @@ -0,0 +1,118 @@ +{ + "programs": [ + [ + "intro music", + "intro", + "hi's and low's", + "notable weather", + "signoff" + ] + ], + "segments": { + "intro": [ + "intro 1", + "intro 2" + ], + "hi's and low's": [ + "hilo 1", + "hilo 2" + ], + "notable weather": [ + "rain 1", + "rain 2", + "storm 1", + "storm 2", + "snow 1", + "hail 1" + ], + "signoff": [ + "signoff 1", + "signoff 2" + ] + }, + "sequences": { + "intro music": { + "tracks": [ + "audio/intro_music.flac" + ] + }, + "intro 1": { + "tracks": [ + "audio/intro_01.flac" + ] + }, + "intro 2": { + "tracks": [ + "audio/intro_02.flac" + ] + }, + "hilo 1": { + "tracks": [ + "audio/hi_01.flac", + "%cory hi", + "audio/lo_01.flac", + "%cory lo" + ] + }, + "hilo 2": { + "tracks": [ + "audio/hi_02.flac", + "%cory hi", + "audio/lo_02.flac", + "%cory lo" + ] + }, + "rain 1": { + "condition": "weather == rain", + "tracks": [ + "audio/rain1.flac" + ] + }, + "rain 2": { + "condition": "weather == rain", + "tracks": [ + "audio/rain2.flac" + ] + }, + "storm 1": { + "condition": "weather == storm", + "tracks": [ + "audio/storm1.flac" + ] + }, + "storm 2": { + "condition": "weather == storm", + "tracks": [ + "audio/storm2.flac" + ] + }, + "snow 1": { + "condition": "weather == snow", + "tracks": [ + "audio/snow1.flac" + ] + }, + "hail 1": { + "condition": "weather == hail", + "tracks": [ + "audio/hail1.flac" + ] + }, + "signoff 1": { + "tracks": [ + "audio/signoff_01.flac" + ] + }, + "signoff 2": { + "tracks": [ + "audio/signoff_02.flac" + ] + } + }, + "voices": { + "cory": { + "directory": "audio/voice/cory/", + "extension": "flac" + } + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..8f18d99 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,115 @@ +{ + "name": "morning-report", + "version": "0.0.1", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "morning-report", + "version": "0.0.1", + "license": "MIT", + "dependencies": { + "json5": "2.2.3", + "openweathermap-ts": "1.2.10" + }, + "devDependencies": { + "@types/node": "24.3.0", + "typescript": "5.9.2" + } + }, + "node_modules/@types/node": { + "version": "24.3.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz", + "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.10.0" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "license": "MIT", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/openweathermap-ts": { + "version": "1.2.10", + "resolved": "https://registry.npmjs.org/openweathermap-ts/-/openweathermap-ts-1.2.10.tgz", + "integrity": "sha512-Zckv2aXN8ENSeAeroces2jJciLWb6aLNXEmvG6pmF+BcIMw2kwRo6++/AKUNoU5suOp47UWA6lllDV0TNm//OA==", + "license": "MIT", + "dependencies": { + "node-fetch": "^2.6.0" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/typescript": { + "version": "5.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", + "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/undici-types": { + "version": "7.10.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", + "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", + "dev": true, + "license": "MIT" + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..be37d86 --- /dev/null +++ b/package.json @@ -0,0 +1,34 @@ +{ + "name": "morning-report", + "version": "0.0.1", + "description": "Procedurally generates a radio weather report", + "keywords": [ + "weather", + "radio", + "audio" + ], + "repository": { + "type": "git", + "url": "ssh://git@git.sanin.dev:3689/corysanin/morning-report.git" + }, + "license": "MIT", + "author": { + "name": "Cory Sanin", + "email": "corysanin@outlook.com", + "url": "https://sanin.dev/" + }, + "type": "module", + "main": "distribution/index.js", + "scripts": { + "build": "npx tsc", + "start": "node distribution/index.js" + }, + "dependencies": { + "json5": "2.2.3", + "openweathermap-ts": "1.2.10" + }, + "devDependencies": { + "typescript": "5.9.2", + "@types/node": "24.3.0" + } +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..0387df2 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,23 @@ +import path from 'path'; +import fsp from 'fs/promises'; +import json5 from 'json5'; +import Sequencer from './sequencer.js'; +import type {Programs, Segments, Sequences} from './sequencer.js'; +import type { Voices } from './voice.js'; + + +interface Config { + programs: Programs, + segments: Segments, + sequences: Sequences, + voices: Voices +} + +console.log('morning-report\nCory Sanin 2025\n'); + +const config: Config = json5.parse(await fsp.readFile(process.env['CONFIG'] || path.join('config', 'config.json5'), { encoding: 'utf-8' })); +const sequence = Sequencer(config); +console.log(sequence.join('\n')); + + +export type { Config }; diff --git a/src/sequencer.ts b/src/sequencer.ts new file mode 100644 index 0000000..f87ff5d --- /dev/null +++ b/src/sequencer.ts @@ -0,0 +1,54 @@ +import type { Config } from './index.js'; + +type SegmentName = string; +type SequenceName = string; +type Programs = SegmentName[][]; +type Segments = { [segment: SegmentName]: SequenceName[] }; +type Sequence = { + condition?: string; + tracks: string[]; +} +type Sequences = { [sequence: SequenceName]: Sequence }; + +let config: Config = null; + +function selectOne(arr: T[]): T { + return arr[Math.floor(Math.random() * arr.length)]; +} + +function conditionIsMet(condition: string | undefined = undefined): boolean { + if (typeof condition !== 'string') { + return true; + } + // TODO: parse condition, return bool + return false; +} + +function processSequence(sequence: Sequence): string[] { + const tracks = sequence.tracks; + // TODO: process voice macros + return tracks; +} + +function processSegment(segment: SegmentName): string[] { + if (!(segment in config.segments)) { + return processSequence(config.sequences[segment]); + } + const potentialSequences: SequenceName[] = config.segments[segment].filter(s => conditionIsMet(config.sequences[s].condition)); + if (potentialSequences.length === 0) { + return []; + } + return processSequence(config.sequences[selectOne(potentialSequences)]); +} + +function Sequencer(conf: Config): string[] { + config = conf; + const sequence: string[] = []; + const program: SegmentName[] = selectOne(conf.programs); + program.forEach(segment => sequence.push(...processSegment(segment))); + return sequence; +} + +export default Sequencer; +export { Sequencer }; +export type { SegmentName, SequenceName, Programs, Segments, Sequence, Sequences }; diff --git a/src/stitcher.ts b/src/stitcher.ts new file mode 100644 index 0000000..e69de29 diff --git a/src/voice.ts b/src/voice.ts new file mode 100644 index 0000000..c012d9a --- /dev/null +++ b/src/voice.ts @@ -0,0 +1,10 @@ + + +interface Voice { + directory: string; + extension: string; +} + +type Voices = { [name: string]: Voice }; + +export type { Voice, Voices }; diff --git a/src/weather.ts b/src/weather.ts new file mode 100644 index 0000000..e69de29 diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..12618a8 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "lib": ["ESNext"], + "module": "NodeNext", + "target": "ESNext", + + "esModuleInterop": true, + "skipLibCheck": true, + "moduleResolution": "node16", + "sourceMap": true, + "inlineSources": true, + "rootDir": "./src", + "outDir": "./distribution" + }, + "include": ["src/**/*"], + "exclude": ["**/*.spec.ts"] +}