From c1bbb11b86cb67a8842be7796564f08e03c3f63d Mon Sep 17 00:00:00 2001 From: Jas Singh Date: Sat, 26 Oct 2024 14:32:40 -0700 Subject: [PATCH] Added the ability to provide a path to a json file for the weather API key. (#374) * Added the ability to provide a path to a json file for the weather API key. * Update weather api key subtitle. * Add jsdoc to methods and consolidate. --- globals/weather.ts | 139 +++++++++++++++++--- widget/settings/pages/config/menus/clock.ts | 2 +- 2 files changed, 120 insertions(+), 21 deletions(-) diff --git a/globals/weather.ts b/globals/weather.ts index 3da7ba1..faaf560 100644 --- a/globals/weather.ts +++ b/globals/weather.ts @@ -4,12 +4,68 @@ import { DEFAULT_WEATHER } from 'lib/types/defaults/weather.js'; import GLib from 'gi://GLib?version=2.0'; import { weatherIcons } from 'modules/icons/weather.js'; +const { EXISTS, IS_REGULAR } = GLib.FileTest; + const { key, interval, location } = options.menus.clock.weather; export const globalWeatherVar = Variable(DEFAULT_WEATHER); let weatherIntervalInstance: null | number = null; +key.connect('changed', () => { + const fetchedKey = getWeatherKey(key.value); + console.log('fetchedKey'); + console.log(fetchedKey); + + weatherApiKey.value = fetchedKey; +}); + +/** + * Retrieves the weather API key from a file if it exists and is valid. + * + * @param apiKey - The path to the file containing the weather API key. + * @returns - The weather API key if found, otherwise the original apiKey. + */ +const getWeatherKey = (apiKey: string): string => { + const weatherKey = apiKey; + + if (GLib.file_test(weatherKey, EXISTS) && GLib.file_test(weatherKey, IS_REGULAR)) { + try { + const fileContentArray = GLib.file_get_contents(weatherKey)[1]; + const fileContent = new TextDecoder().decode(fileContentArray); + + if (!fileContent) { + console.error('File content is empty'); + return ''; + } + + const parsedContent = JSON.parse(fileContent); + + if (parsedContent.weather_api_key !== undefined) { + return parsedContent.weather_api_key; + } else { + console.error('weather_api_key is missing in the JSON content'); + return ''; + } + } catch (error) { + console.error(`Failed to read or parse weather key file: ${error}`); + return ''; + } + } + + return apiKey; +}; + +const fetchedApiKey = getWeatherKey(key.value); +const weatherApiKey = Variable(fetchedApiKey); + +/** + * Sets up a weather update interval function. + * + * @param weatherInterval - The interval in milliseconds at which to fetch weather updates. + * @param loc - The location for which to fetch weather data. + * @param weatherKey - The API key for accessing the weather service. + */ const weatherIntervalFn = (weatherInterval: number, loc: string, weatherKey: string): void => { if (weatherIntervalInstance !== null) { GLib.source_remove(weatherIntervalInstance); @@ -46,22 +102,38 @@ const weatherIntervalFn = (weatherInterval: number, loc: string, weatherKey: str }); }; -Utils.merge([key.bind('value'), interval.bind('value'), location.bind('value')], (weatherKey, weatherInterval, loc) => { - if (!weatherKey) { - return (globalWeatherVar.value = DEFAULT_WEATHER); - } - weatherIntervalFn(weatherInterval, loc, weatherKey); -}); +Utils.merge( + [weatherApiKey.bind('value'), interval.bind('value'), location.bind('value')], + (weatherKey, weatherInterval, loc) => { + if (!weatherKey) { + return (globalWeatherVar.value = DEFAULT_WEATHER); + } + weatherIntervalFn(weatherInterval, loc, weatherKey); + }, +); -export const getTemperature = (wthr: Weather, unt: UnitType): string => { - if (unt === 'imperial') { - return `${Math.ceil(wthr.current.temp_f)}° F`; +/** + * Gets the temperature from the weather data in the specified unit. + * + * @param weatherData - The weather data object. + * @param unitType - The unit type, either 'imperial' or 'metric'. + * @returns - The temperature formatted as a string with the appropriate unit. + */ +export const getTemperature = (weatherData: Weather, unitType: UnitType): string => { + if (unitType === 'imperial') { + return `${Math.ceil(weatherData.current.temp_f)}° F`; } else { - return `${Math.ceil(wthr.current.temp_c)}° C`; + return `${Math.ceil(weatherData.current.temp_c)}° C`; } }; -export const getWeatherIcon = (fahren: number): Record => { +/** + * Returns the appropriate weather icon and color class based on the temperature in Fahrenheit. + * + * @param fahrenheit - The temperature in Fahrenheit. + * @returns - An object containing the weather icon and color class. + */ +export const getWeatherIcon = (fahrenheit: number): Record => { const icons = { 100: '', 75: '', @@ -80,7 +152,7 @@ export const getWeatherIcon = (fahren: number): Record => { type IconKeys = keyof typeof icons; const threshold: IconKeys = - fahren < 0 ? 0 : ([100, 75, 50, 25, 0] as IconKeys[]).find((threshold) => threshold <= fahren) || 0; + fahrenheit < 0 ? 0 : ([100, 75, 50, 25, 0] as IconKeys[]).find((threshold) => threshold <= fahrenheit) || 0; const icon = icons[threshold || 50]; const color = colors[threshold || 50]; @@ -91,23 +163,50 @@ export const getWeatherIcon = (fahren: number): Record => { }; }; -export const getWindConditions = (wthr: Weather, unt: UnitType): string => { - if (unt === 'imperial') { - return `${Math.floor(wthr.current.wind_mph)} mph`; +/** + * Gets the wind conditions from the weather data in the specified unit. + * + * @param weatherData - The weather data object. + * @param unitType - The unit type, either 'imperial' or 'metric'. + * @returns - The wind conditions formatted as a string with the appropriate unit. + */ +export const getWindConditions = (weatherData: Weather, unitType: UnitType): string => { + if (unitType === 'imperial') { + return `${Math.floor(weatherData.current.wind_mph)} mph`; } - return `${Math.floor(wthr.current.wind_kph)} kph`; + return `${Math.floor(weatherData.current.wind_kph)} kph`; }; -export const getRainChance = (wthr: Weather): string => `${wthr.forecast.forecastday[0].day.daily_chance_of_rain}%`; +/** + * Gets the chance of rain from the weather forecast data. + * + * @param weatherData - The weather data object. + * @returns - The chance of rain formatted as a percentage string. + */ +export const getRainChance = (weatherData: Weather): string => + `${weatherData.forecast.forecastday[0].day.daily_chance_of_rain}%`; +/** + * Type Guard + * Checks if the given title is a valid weather icon title. + * + * @param title - The weather icon title to check. + * @returns - True if the title is a valid weather icon title, false otherwise. + */ export const isValidWeatherIconTitle = (title: string): title is WeatherIconTitle => { return title in weatherIcons; }; -export const getWeatherStatusTextIcon = (wthr: Weather): WeatherIcon => { - let iconQuery = wthr.current.condition.text.trim().toLowerCase().replaceAll(' ', '_'); +/** + * Gets the appropriate weather icon based on the weather status text. + * + * @param weatherData - The weather data object. + * @returns - The weather icon corresponding to the weather status text. + */ +export const getWeatherStatusTextIcon = (weatherData: Weather): WeatherIcon => { + let iconQuery = weatherData.current.condition.text.trim().toLowerCase().replaceAll(' ', '_'); - if (!wthr.current.is_day && iconQuery === 'partly_cloudy') { + if (!weatherData.current.is_day && iconQuery === 'partly_cloudy') { iconQuery = 'partly_cloudy_night'; } diff --git a/widget/settings/pages/config/menus/clock.ts b/widget/settings/pages/config/menus/clock.ts index 71d8e18..c6a81af 100644 --- a/widget/settings/pages/config/menus/clock.ts +++ b/widget/settings/pages/config/menus/clock.ts @@ -27,7 +27,7 @@ export const ClockMenuSettings = (): Scrollable => { Option({ opt: options.menus.clock.weather.key, title: 'Weather API Key', - subtitle: 'May require AGS restart. https://weatherapi.com/', + subtitle: "API Key or path to a JSON file that contains a 'weather_api_key' variable.", type: 'string', }), Option({