Compare commits
2 Commits
4a260f112f
...
7f45387dac
Author | SHA1 | Date | |
---|---|---|---|
7f45387dac | |||
3b562116fd |
@@ -114,5 +114,11 @@
|
||||
"directory": "audio/voice/cory/",
|
||||
"extension": "flac"
|
||||
}
|
||||
},
|
||||
"weather": {
|
||||
// Provide an OpenWeatherMap API key
|
||||
// https://openweathermap.org/price
|
||||
"key": "not0a0real0key00281f631aef6ad3a1",
|
||||
"city": "chicago"
|
||||
}
|
||||
}
|
||||
|
@@ -4,13 +4,15 @@ import json5 from 'json5';
|
||||
import Sequencer from './sequencer.js';
|
||||
import type {Programs, Segments, Sequences} from './sequencer.js';
|
||||
import type { Voices } from './voice.js';
|
||||
import type { WeatherConfig } from './weather.js';
|
||||
|
||||
|
||||
interface Config {
|
||||
programs: Programs,
|
||||
segments: Segments,
|
||||
sequences: Sequences,
|
||||
voices: Voices
|
||||
voices: Voices,
|
||||
weather: WeatherConfig
|
||||
}
|
||||
|
||||
console.log('morning-report\nCory Sanin 2025\n');
|
||||
|
165
src/voice.ts
165
src/voice.ts
@@ -1,4 +1,4 @@
|
||||
|
||||
import path from 'path';
|
||||
|
||||
interface Voice {
|
||||
directory: string;
|
||||
@@ -7,4 +7,167 @@ interface Voice {
|
||||
|
||||
type Voices = { [name: string]: Voice };
|
||||
|
||||
const LINES = {
|
||||
NEGATIVE: 'negative',
|
||||
POINT: 'point',
|
||||
ZERO: 'zero',
|
||||
ONE: 'one',
|
||||
TWO: 'two',
|
||||
THREE: 'three',
|
||||
FOUR: 'four',
|
||||
FIVE: 'five',
|
||||
SIX: 'six',
|
||||
SEVEN: 'seven',
|
||||
EIGHT: 'eight',
|
||||
NINE: 'nine',
|
||||
TEN: 'ten',
|
||||
ELEVEN: 'eleven',
|
||||
TWELVE: 'twelve',
|
||||
THIRTEEN: 'thirteen',
|
||||
FIFTEEN: 'fifteen',
|
||||
TEEN: 'teen',
|
||||
TWENTY: 'twenty',
|
||||
THIRTY: 'thirty',
|
||||
FORTY: 'forty',
|
||||
FIFTY: 'fifty',
|
||||
SIXTY: 'sixty',
|
||||
SEVENTY: 'seventy',
|
||||
EIGHTY: 'eighty',
|
||||
NINETY: 'ninety',
|
||||
HUNDRED: 'hundred',
|
||||
THOUSAND: 'thousand',
|
||||
MILLION: 'million',
|
||||
BILLION: 'billion',
|
||||
TRILLION: 'trillion'
|
||||
}
|
||||
|
||||
function formatNumber(num: number): string {
|
||||
const parts = num.toString().split(".");
|
||||
parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ",");
|
||||
return parts.join(".");
|
||||
}
|
||||
|
||||
function digitByDigit(str: string): string[] {
|
||||
const tokens: string[] = [];
|
||||
const input = str.replaceAll(',', '');
|
||||
const map = [
|
||||
LINES.ZERO,
|
||||
LINES.ONE,
|
||||
LINES.TWO,
|
||||
LINES.THREE,
|
||||
LINES.FOUR,
|
||||
LINES.FIVE,
|
||||
LINES.SIX,
|
||||
LINES.SEVEN,
|
||||
LINES.EIGHT,
|
||||
LINES.NINE
|
||||
]
|
||||
for (let i = 0; i < input.length; i++) {
|
||||
tokens.push(map[parseInt(input.charAt(i))]);
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
function tens(str: string): string[] {
|
||||
if (str === '0') {
|
||||
return [LINES.ZERO];
|
||||
}
|
||||
const tokens: string[] = [];
|
||||
const num = parseInt(str);
|
||||
const map = {
|
||||
'2': LINES.TWENTY,
|
||||
'3': LINES.THIRTY,
|
||||
'4': LINES.FORTY,
|
||||
'5': LINES.FIFTY,
|
||||
'6': LINES.SIXTY,
|
||||
'7': LINES.SEVENTY,
|
||||
'8': LINES.EIGHTY,
|
||||
'9': LINES.NINETY
|
||||
};
|
||||
const ones = str.charAt(str.length - 1);
|
||||
if (num === 0) {
|
||||
return [];
|
||||
}
|
||||
else if (num >= 20) {
|
||||
tokens.push(map[str.charAt(0)]);
|
||||
if (ones !== '0') {
|
||||
tokens.push(...digitByDigit(ones));
|
||||
}
|
||||
}
|
||||
else if (num < 10) {
|
||||
tokens.push(...digitByDigit(ones));
|
||||
}
|
||||
else {
|
||||
const weirdoNumberMap = [
|
||||
[LINES.TEN],
|
||||
[LINES.ELEVEN],
|
||||
[LINES.TWELVE],
|
||||
[LINES.THIRTEEN],
|
||||
[LINES.FOUR, LINES.TEEN],
|
||||
[LINES.FIFTEEN],
|
||||
[LINES.SIX, LINES.TEEN],
|
||||
[LINES.SEVEN, LINES.TEEN],
|
||||
[LINES.EIGHT, LINES.TEEN],
|
||||
[LINES.NINE, LINES.TEEN],
|
||||
]
|
||||
tokens.push(...weirdoNumberMap[num - 10]);
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
function hundreds(str: string): string[] {
|
||||
const tokens: string[] = [];
|
||||
if (str.length === 3 && str.charAt(0) !== '0') {
|
||||
tokens.push(...digitByDigit(str.charAt(0)));
|
||||
tokens.push(LINES.HUNDRED);
|
||||
str = str.substring(1);
|
||||
}
|
||||
tokens.push(...tens(str));
|
||||
return tokens;
|
||||
}
|
||||
|
||||
function integer(str: string): string[] {
|
||||
const tokens: string[] = [];
|
||||
const numGroups = str.split(',');
|
||||
const seperators = [LINES.TRILLION, LINES.BILLION, LINES.MILLION, LINES.THOUSAND];
|
||||
if (numGroups.length > 5) {
|
||||
return digitByDigit(str);
|
||||
}
|
||||
seperators.splice(0, seperators.length - numGroups.length + 1);
|
||||
numGroups.forEach(g => {
|
||||
if (g !== '000') {
|
||||
tokens.push(...hundreds(g));
|
||||
}
|
||||
if (seperators.length === 0) {
|
||||
return;
|
||||
}
|
||||
const sep = seperators.shift();
|
||||
if (g !== '000') {
|
||||
tokens.push(sep);
|
||||
}
|
||||
});
|
||||
return tokens;
|
||||
}
|
||||
|
||||
function voiceLines(voice: Voice, num: number): string[] {
|
||||
if (Math.abs(num) > 999999999999999 || isNaN(num)) {
|
||||
return [];
|
||||
}
|
||||
const tokens: string[] = [];
|
||||
const str = formatNumber(num);
|
||||
const parts = str.split('.');
|
||||
if (parts[0].startsWith('-')) {
|
||||
tokens.push(LINES.NEGATIVE);
|
||||
parts[0] = parts[0].substring(1);
|
||||
}
|
||||
tokens.push(...integer(parts[0]));
|
||||
if (parts.length > 1) {
|
||||
tokens.push(LINES.POINT);
|
||||
tokens.push(...digitByDigit(parts[1]));
|
||||
}
|
||||
return tokens.map(l => path.join(voice.directory, `${l}.${voice.extension}`));
|
||||
}
|
||||
|
||||
export default voiceLines;
|
||||
export { voiceLines };
|
||||
export type { Voice, Voices };
|
||||
|
101
src/weather.ts
101
src/weather.ts
@@ -0,0 +1,101 @@
|
||||
import OpenWeatherMap from 'openweathermap-ts';
|
||||
import type { ThreeHourResponse, CurrentResponse, CountryCode } from 'openweathermap-ts/dist/types/index.js';
|
||||
|
||||
interface CityName {
|
||||
cityName: string;
|
||||
state: string;
|
||||
countryCode: CountryCode;
|
||||
}
|
||||
|
||||
interface WeatherConfig {
|
||||
key: string;
|
||||
lang?: string;
|
||||
coordinates?: number[] | string;
|
||||
zip?: number;
|
||||
country?: CountryCode;
|
||||
cityid?: number;
|
||||
city?: CityName;
|
||||
}
|
||||
|
||||
type LocationType = 'coordinates' | 'zip' | 'cityid' | 'city' | null;
|
||||
|
||||
function parseCoords(coords: number[] | string): number[] {
|
||||
if (typeof coords == 'string') {
|
||||
return coords.replace(/\s/g, '').split(',').map(Number.parseFloat);
|
||||
}
|
||||
return coords;
|
||||
}
|
||||
|
||||
|
||||
class Weather {
|
||||
private openWeather: OpenWeatherMap.default;
|
||||
private locationType: LocationType;
|
||||
private current: CurrentResponse;
|
||||
private threeDay: ThreeHourResponse;
|
||||
|
||||
constructor(options: WeatherConfig) {
|
||||
this.locationType = this.current = this.threeDay = null;
|
||||
this.openWeather = new OpenWeatherMap.default({
|
||||
apiKey: options.key
|
||||
});
|
||||
if ('city' in options && 'cityName' in options.city && 'state' in options.city && 'countryCode' in options.city) {
|
||||
this.openWeather.setCityName(options.city);
|
||||
this.locationType = 'city';
|
||||
}
|
||||
if ('cityid' in options) {
|
||||
this.openWeather.setCityId(options.cityid);
|
||||
this.locationType = 'cityid';
|
||||
}
|
||||
if ('zip' in options && 'country' in options) {
|
||||
this.openWeather.setZipCode(options.zip, options.country)
|
||||
this.locationType = 'zip';
|
||||
}
|
||||
if ('coordinates' in options) {
|
||||
const coords = parseCoords(options.coordinates);
|
||||
if (coords.length >= 2) {
|
||||
this.openWeather.setGeoCoordinates(coords[0], coords[1]);
|
||||
this.locationType = 'coordinates';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async getCurrentWeather(): Promise<CurrentResponse> {
|
||||
if (this.current) {
|
||||
return this.current;
|
||||
}
|
||||
switch (this.locationType) {
|
||||
case 'city':
|
||||
return this.current = await this.openWeather.getCurrentWeatherByCityName();
|
||||
case 'cityid':
|
||||
return this.current = await this.openWeather.getCurrentWeatherByCityId();
|
||||
case 'zip':
|
||||
return this.current = await this.openWeather.getCurrentWeatherByZipcode();
|
||||
case 'coordinates':
|
||||
return this.current = await this.openWeather.getCurrentWeatherByGeoCoordinates();
|
||||
default:
|
||||
throw new Error(`Can't fetch weather for location type '${this.locationType}'`);
|
||||
}
|
||||
}
|
||||
|
||||
async getThreeHourForecast(): Promise<ThreeHourResponse> {
|
||||
if (this.threeDay) {
|
||||
return this.threeDay;
|
||||
}
|
||||
switch (this.locationType) {
|
||||
case 'city':
|
||||
return this.threeDay = await this.openWeather.getThreeHourForecastByCityName();
|
||||
case 'cityid':
|
||||
return this.threeDay = await this.openWeather.getThreeHourForecastByCityId();
|
||||
case 'zip':
|
||||
return this.threeDay = await this.openWeather.getThreeHourForecastByZipcode();
|
||||
case 'coordinates':
|
||||
return this.threeDay = await this.openWeather.getThreeHourForecastByGeoCoordinates();
|
||||
default:
|
||||
throw new Error(`Can't fetch weather for location type '${this.locationType}'`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Weather;
|
||||
export { Weather };
|
||||
export type { WeatherConfig, CityName };
|
||||
|
Reference in New Issue
Block a user