mirror of
https://github.com/MichMich/MagicMirror.git
synced 2026-04-26 15:52:23 +00:00
`eslint-plugin-import-x` was the last thing blocking the ESLint v10 upgrade - it just got v10 support. So here we go. The upgrade itself is tiny. The rest of the diff is cleanup from issues ESLint v10 now catches: a few `let` declarations with initial values that were immediately overwritten anyway (`no-useless-assignment`), and `Translator` listed in `/* global */` in `main.js` and `module.js`. Working through those `no-useless-assignment` warnings also surfaced a dead default in `openmeteo`: `maxEntries: 5` in the constructor, which was never actually doing anything - `openmeteo` never reads `this.config.maxEntries` anywhere. And `weather.js` already sets that default for all providers, so it was just a redundant duplicate. Removed that too. No runtime behavior changes.
271 lines
6.7 KiB
JavaScript
271 lines
6.7 KiB
JavaScript
const Log = require("logger");
|
|
const HTTPFetcher = require("#http_fetcher");
|
|
|
|
class PirateweatherProvider {
|
|
constructor (config) {
|
|
this.config = {
|
|
apiBase: "https://api.pirateweather.net",
|
|
weatherEndpoint: "/forecast",
|
|
apiKey: "",
|
|
lat: 0,
|
|
lon: 0,
|
|
type: "current",
|
|
updateInterval: 10 * 60 * 1000,
|
|
lang: "en",
|
|
...config
|
|
};
|
|
this.fetcher = null;
|
|
this.onDataCallback = null;
|
|
this.onErrorCallback = null;
|
|
}
|
|
|
|
setCallbacks (onDataCallback, onErrorCallback) {
|
|
this.onDataCallback = onDataCallback;
|
|
this.onErrorCallback = onErrorCallback;
|
|
}
|
|
|
|
initialize () {
|
|
if (!this.config.apiKey) {
|
|
Log.error("[pirateweather] No API key configured");
|
|
if (this.onErrorCallback) {
|
|
this.onErrorCallback({
|
|
message: "API key required",
|
|
translationKey: "MODULE_ERROR_UNSPECIFIED"
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
|
|
this.#initializeFetcher();
|
|
}
|
|
|
|
#initializeFetcher () {
|
|
const url = this.#getUrl();
|
|
|
|
this.fetcher = new HTTPFetcher(url, {
|
|
reloadInterval: this.config.updateInterval,
|
|
headers: {
|
|
"Cache-Control": "no-cache",
|
|
Accept: "application/json"
|
|
},
|
|
logContext: "weatherprovider.pirateweather"
|
|
});
|
|
|
|
this.fetcher.on("response", async (response) => {
|
|
try {
|
|
const data = await response.json();
|
|
this.#handleResponse(data);
|
|
} catch (error) {
|
|
Log.error("[pirateweather] Parse error:", error);
|
|
if (this.onErrorCallback) {
|
|
this.onErrorCallback({
|
|
message: "Failed to parse API response",
|
|
translationKey: "MODULE_ERROR_UNSPECIFIED"
|
|
});
|
|
}
|
|
}
|
|
});
|
|
|
|
this.fetcher.on("error", (errorInfo) => {
|
|
if (this.onErrorCallback) {
|
|
this.onErrorCallback(errorInfo);
|
|
}
|
|
});
|
|
}
|
|
|
|
#handleResponse (data) {
|
|
if (!data || (!data.currently && !data.daily && !data.hourly)) {
|
|
Log.error("[pirateweather] No usable data received");
|
|
if (this.onErrorCallback) {
|
|
this.onErrorCallback({
|
|
message: "No usable data in API response",
|
|
translationKey: "MODULE_ERROR_UNSPECIFIED"
|
|
});
|
|
}
|
|
return;
|
|
}
|
|
|
|
let weatherData;
|
|
|
|
switch (this.config.type) {
|
|
case "current":
|
|
weatherData = this.#generateCurrent(data);
|
|
break;
|
|
case "forecast":
|
|
case "daily":
|
|
weatherData = this.#generateDaily(data);
|
|
break;
|
|
case "hourly":
|
|
weatherData = this.#generateHourly(data);
|
|
break;
|
|
default:
|
|
Log.error(`[pirateweather] Unknown weather type: ${this.config.type}`);
|
|
if (this.onErrorCallback) {
|
|
this.onErrorCallback({
|
|
message: `Unknown weather type: ${this.config.type}`,
|
|
translationKey: "MODULE_ERROR_UNSPECIFIED"
|
|
});
|
|
}
|
|
return;
|
|
|
|
}
|
|
|
|
if (weatherData && this.onDataCallback) {
|
|
this.onDataCallback(weatherData);
|
|
}
|
|
}
|
|
|
|
#generateCurrent (data) {
|
|
if (!data.currently || typeof data.currently.temperature === "undefined") {
|
|
return null;
|
|
}
|
|
|
|
const current = {
|
|
date: new Date(),
|
|
humidity: data.currently.humidity != null ? parseFloat(data.currently.humidity) * 100 : null,
|
|
temperature: parseFloat(data.currently.temperature),
|
|
feelsLikeTemp: data.currently.apparentTemperature != null ? parseFloat(data.currently.apparentTemperature) : null,
|
|
windSpeed: data.currently.windSpeed != null ? parseFloat(data.currently.windSpeed) : null,
|
|
windFromDirection: data.currently.windBearing || null,
|
|
weatherType: this.#convertWeatherType(data.currently.icon),
|
|
sunrise: null,
|
|
sunset: null
|
|
};
|
|
|
|
// Add sunrise/sunset from daily data if available
|
|
if (data.daily && data.daily.data && data.daily.data.length > 0) {
|
|
const today = data.daily.data[0];
|
|
if (today.sunriseTime) {
|
|
current.sunrise = new Date(today.sunriseTime * 1000);
|
|
}
|
|
if (today.sunsetTime) {
|
|
current.sunset = new Date(today.sunsetTime * 1000);
|
|
}
|
|
}
|
|
|
|
return current;
|
|
}
|
|
|
|
#generateDaily (data) {
|
|
if (!data.daily || !data.daily.data || !data.daily.data.length) {
|
|
return [];
|
|
}
|
|
|
|
const days = [];
|
|
|
|
for (const forecast of data.daily.data) {
|
|
const day = {
|
|
date: new Date(forecast.time * 1000),
|
|
minTemperature: forecast.temperatureMin != null ? parseFloat(forecast.temperatureMin) : null,
|
|
maxTemperature: forecast.temperatureMax != null ? parseFloat(forecast.temperatureMax) : null,
|
|
weatherType: this.#convertWeatherType(forecast.icon),
|
|
snow: 0,
|
|
rain: 0,
|
|
precipitationAmount: 0,
|
|
precipitationProbability: forecast.precipProbability != null ? parseFloat(forecast.precipProbability) * 100 : null
|
|
};
|
|
|
|
// Handle precipitation
|
|
let precip = 0;
|
|
if (forecast.hasOwnProperty("precipAccumulation")) {
|
|
precip = forecast.precipAccumulation * 10; // cm to mm
|
|
}
|
|
|
|
day.precipitationAmount = precip;
|
|
|
|
if (forecast.precipType) {
|
|
if (forecast.precipType === "snow") {
|
|
day.snow = precip;
|
|
} else {
|
|
day.rain = precip;
|
|
}
|
|
}
|
|
|
|
days.push(day);
|
|
}
|
|
|
|
return days;
|
|
}
|
|
|
|
#generateHourly (data) {
|
|
if (!data.hourly || !data.hourly.data || !data.hourly.data.length) {
|
|
return [];
|
|
}
|
|
|
|
const hours = [];
|
|
|
|
for (const forecast of data.hourly.data) {
|
|
const hour = {
|
|
date: new Date(forecast.time * 1000),
|
|
temperature: forecast.temperature !== undefined ? parseFloat(forecast.temperature) : null,
|
|
feelsLikeTemp: forecast.apparentTemperature !== undefined ? parseFloat(forecast.apparentTemperature) : null,
|
|
weatherType: this.#convertWeatherType(forecast.icon),
|
|
windSpeed: forecast.windSpeed !== undefined ? parseFloat(forecast.windSpeed) : null,
|
|
windFromDirection: forecast.windBearing || null,
|
|
precipitationProbability: forecast.precipProbability ? parseFloat(forecast.precipProbability) * 100 : null,
|
|
snow: 0,
|
|
rain: 0,
|
|
precipitationAmount: 0
|
|
};
|
|
|
|
// Handle precipitation
|
|
let precip = 0;
|
|
if (forecast.hasOwnProperty("precipAccumulation")) {
|
|
precip = forecast.precipAccumulation * 10; // cm to mm
|
|
}
|
|
|
|
hour.precipitationAmount = precip;
|
|
|
|
if (forecast.precipType) {
|
|
if (forecast.precipType === "snow") {
|
|
hour.snow = precip;
|
|
} else {
|
|
hour.rain = precip;
|
|
}
|
|
}
|
|
|
|
hours.push(hour);
|
|
}
|
|
|
|
return hours;
|
|
}
|
|
|
|
#getUrl () {
|
|
const apiBase = this.config.apiBase || "https://api.pirateweather.net";
|
|
const weatherEndpoint = this.config.weatherEndpoint || "/forecast";
|
|
const lang = this.config.lang || "en";
|
|
return `${apiBase}${weatherEndpoint}/${this.config.apiKey}/${this.config.lat},${this.config.lon}?units=si&lang=${lang}`;
|
|
}
|
|
|
|
#convertWeatherType (weatherType) {
|
|
const weatherTypes = {
|
|
"clear-day": "day-sunny",
|
|
"clear-night": "night-clear",
|
|
rain: "rain",
|
|
snow: "snow",
|
|
sleet: "snow",
|
|
wind: "windy",
|
|
fog: "fog",
|
|
cloudy: "cloudy",
|
|
"partly-cloudy-day": "day-cloudy",
|
|
"partly-cloudy-night": "night-cloudy"
|
|
};
|
|
|
|
return weatherTypes[weatherType] || null;
|
|
}
|
|
|
|
start () {
|
|
if (this.fetcher) {
|
|
this.fetcher.startPeriodicFetch();
|
|
}
|
|
}
|
|
|
|
stop () {
|
|
if (this.fetcher) {
|
|
this.fetcher.clearTimer();
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = PirateweatherProvider;
|