geoshell
Version:
A CLI to fetch real-time geo-data from your terminal
162 lines (138 loc) • 4.63 kB
JavaScript
/**
* Weather command implementation
*/
const api = require('../utils/api');
const { LocationNotFound } = require('../utils/errors');
/**
* Get weather information
*
* @param {string} location - City name or coordinates
* @param {Object} options - Options
* @param {number} options.forecast - Number of forecast days (0-7)
* @returns {Promise<Object>} Weather information
*/
async function weather(location, options = {}) {
// Check if we have an API key
const apiKey = process.env.WEATHER_API_KEY;
if (!apiKey) {
// Return mock data if no API key
return getMockWeather(location, options);
}
try {
const url = `${api.BASE_URLS.weather}/weather`;
const params = {
q: location,
appid: apiKey,
units: 'metric'
};
const data = await api.makeRequest(url, { params });
if (!data) {
throw new LocationNotFound(`Location '${location}' not found`);
}
const weatherInfo = {
location: data.name,
temperature: data.main.temp,
condition: data.weather[0].description.charAt(0).toUpperCase() + data.weather[0].description.slice(1),
humidity: data.main.humidity,
windSpeed: data.wind.speed,
pressure: data.main.pressure,
visibility: data.visibility / 1000, // Convert to km
uvIndex: 0 // Would need separate UV API call
};
// Add forecast if requested
if (options.forecast && options.forecast > 0) {
const forecastUrl = `${api.BASE_URLS.weather}/forecast`;
const forecastData = await api.makeRequest(forecastUrl, { params });
if (forecastData && forecastData.list) {
weatherInfo.forecast = processForecast(forecastData.list, options.forecast);
}
}
return weatherInfo;
} catch (error) {
if (error instanceof LocationNotFound) {
throw error;
}
// Return mock data if API fails
return getMockWeather(location, options);
}
}
/**
* Process forecast data
*
* @param {Array} forecastList - List of forecast data points
* @param {number} days - Number of days to include
* @returns {Array} Processed forecast
*/
function processForecast(forecastList, days) {
const forecast = [];
const daysMap = {};
// Group by day
forecastList.forEach(item => {
const date = item.dt_txt.split(' ')[0];
if (!daysMap[date]) {
daysMap[date] = {
date,
temps: [],
conditions: []
};
}
daysMap[date].temps.push(item.main.temp);
daysMap[date].conditions.push(item.weather[0].main);
});
// Process each day
Object.values(daysMap).slice(0, days).forEach(day => {
forecast.push({
date: day.date,
high: Math.max(...day.temps),
low: Math.min(...day.temps),
condition: getMostFrequent(day.conditions)
});
});
return forecast;
}
/**
* Get most frequent item in array
*
* @param {Array} arr - Array of items
* @returns {*} Most frequent item
*/
function getMostFrequent(arr) {
return arr.sort((a, b) =>
arr.filter(v => v === a).length - arr.filter(v => v === b).length
).pop();
}
/**
* Get mock weather data
*
* @param {string} location - Location name
* @param {Object} options - Options
* @returns {Object} Mock weather data
*/
function getMockWeather(location, options = {}) {
const mockWeather = {
location: location,
temperature: Math.round(Math.random() * 30 + 5), // 5-35°C
condition: ['Sunny', 'Cloudy', 'Partly Cloudy', 'Rainy', 'Clear'][Math.floor(Math.random() * 5)],
humidity: Math.round(Math.random() * 40 + 40), // 40-80%
windSpeed: Math.round(Math.random() * 20 + 5), // 5-25 km/h
pressure: Math.round(Math.random() * 50 + 1000), // 1000-1050 hPa
visibility: Math.round(Math.random() * 10 + 5), // 5-15 km
uvIndex: Math.round(Math.random() * 10) // 0-10
};
if (options.forecast && options.forecast > 0) {
const forecast = [];
for (let i = 1; i <= Math.min(options.forecast, 7); i++) {
const date = new Date();
date.setDate(date.getDate() + i);
forecast.push({
date: date.toISOString().split('T')[0],
high: Math.round(Math.random() * 30 + 10),
low: Math.round(Math.random() * 15),
condition: ['Sunny', 'Cloudy', 'Partly Cloudy', 'Rainy', 'Clear'][Math.floor(Math.random() * 5)]
});
}
mockWeather.forecast = forecast;
}
return mockWeather;
}
module.exports = weather;