├── .github └── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── .gitignore ├── .npmignore ├── LICENSE ├── README.md ├── examples ├── air-pollution.js ├── daily-example.js ├── historical-data.js ├── simple.js └── using-options.js ├── jest.config.ts ├── media ├── favicon.ico ├── logo.png └── style.css ├── package-lock.json ├── package.json ├── src ├── constants.ts ├── index.ts ├── parsers │ ├── air-pollution │ │ ├── list-parser.ts │ │ └── single-parser.ts │ ├── onecall │ │ ├── current-parser.ts │ │ ├── daily-parser.ts │ │ ├── history-parser.ts │ │ ├── hourly-parser.ts │ │ └── minutely-parser.ts │ └── weather │ │ ├── current-parser.ts │ │ └── forecast-parser.ts ├── request.ts └── types │ ├── air-pollution.ts │ ├── index.ts │ ├── options.ts │ └── weather │ ├── current.ts │ ├── daily.ts │ ├── forecast.ts │ ├── historical.ts │ ├── hourly.ts │ ├── index.ts │ └── minutely.ts ├── test ├── errors.test.ts ├── getter.test.ts └── options.test.ts └── tsconfig.json /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Please complete the following information:** 27 | Node version: x.x.x 28 | openweather-api-node version: x.x.x 29 | 30 | **Additional context** 31 | Add any other context about the problem here. 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | docs/ 3 | dist/ 4 | temp.* 5 | key*.txt 6 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | src/ 3 | media/ 4 | docs/ 5 | .github/ 6 | temp.* 7 | key*.txt 8 | tsconfig.json 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) loloToster 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |
3 |

4 | logo 5 |

6 |
7 |

8 | Simple package that makes it easy to work with OpenWeather API. 9 |

10 |
11 |

12 | 13 | Version 14 | 15 | 16 | Downloads 17 | 18 | 19 | Issues 20 | 21 | 22 | License 23 | 24 | 25 | GitHub Stars 26 | 27 |

28 | 29 | # About 30 | 31 | This package is a wrapper for OpenWeather API. If you want to learn how to use this package check out examples in [*examples* directory](https://github.com/loloToster/openweather-api-node/tree/master/examples). The only thing that you need to get started is API key if you don't have one go to [OpenWeatherMap website](https://openweathermap.org/) and get it. For now this package supports only a part of the API but we are planning on adding more features like: triggers, maps and all the other stuff that is available for free in OpenWeatherMap API. 32 | 33 | Currently Supported APIs: 34 | * Current Weather - get **current weather** 35 | * Forecast - get weather **forecast for up to 5 days** with 3-hour step 36 | * OneCall - get **current weather** and weather **forecast for up to 7 days** 37 | * Geocoding - get location **latitude and longitude from its name** and vice versa 38 | * Historical - get weather data for **more that 40 years back** 39 | * Air pollution - get **current, forecasted and historical data about air pollution** 40 | 41 | # Installation 42 | ``` 43 | npm i openweather-api-node 44 | ``` 45 | 46 | # Examples 47 | ## JS: 48 | ```js 49 | const { OpenWeatherAPI } = require("openweather-api-node") 50 | 51 | let weather = new OpenWeatherAPI({ 52 | key: "put-key-here", 53 | locationName: "New York", 54 | units: "imperial" 55 | }) 56 | 57 | /* 58 | you can use setters as well: 59 | weather.setKey("put-key-here") 60 | weather.setLocationByName("New York") 61 | ... 62 | */ 63 | 64 | weather.getCurrent().then(data => { 65 | console.log(`Current temperature in New York is: ${data.weather.temp.cur}\u00B0F`) 66 | }) 67 | ``` 68 | 69 | ## TS: 70 | ```ts 71 | import OpenWeatherAPI from "openweather-api-node" 72 | 73 | let weather = new OpenWeatherAPI({ 74 | key: "put-key-here", 75 | locationName: "New York", 76 | units: "imperial" 77 | }) 78 | 79 | /* 80 | you can use setters as well: 81 | weather.setKey("put-key-here") 82 | weather.setLocationByName("New York") 83 | ... 84 | */ 85 | 86 | weather.getCurrent().then(data => { 87 | console.log(`Current temperature in New York is: ${data.weather.temp.cur}\u00B0F`) 88 | }) 89 | ``` 90 | 91 | # [Documentation for v2 & v3](https://lolotoster.github.io/openweather-api-node/) 92 | 93 | [Documentation for older versions](https://github.com/loloToster/openweather-api-node/blob/829077b6653ffcde4736f3c7aec259e222c9d395/README.md) 94 | 95 | --- 96 | 97 | #### Old OneCall API support 98 | 99 | This package partially uses the OneCall 3.0 api endpoint. If you want to use older version of the OneCall api you can install the latest version of the package that supported it: 100 | 101 | ``` 102 | npm i openweather-api-node@2.1.2 103 | ``` 104 | -------------------------------------------------------------------------------- /examples/air-pollution.js: -------------------------------------------------------------------------------- 1 | const { OpenWeatherAPI } = require("..") 2 | 3 | const location = "Paris" 4 | 5 | // Set global key, location 6 | let weather = new OpenWeatherAPI({ 7 | key: "put-key-here", 8 | locationName: location 9 | }) 10 | 11 | // getCurrentAirPollution is asynchronous so we can use .then() 12 | weather.getCurrentAirPollution().then(data => { 13 | console.log(`Currently air quality in ${location} is ${data.aqiName.toLowerCase()}.`) 14 | }) 15 | 16 | // We set the limit to 24 to get only 24 hours out of 5 days 17 | weather.getForecastedAirPollution(24).then(data => { 18 | console.log("The amount of each component for each hour of upcoming 24 hours:") 19 | console.table( 20 | data.map(v => { 21 | return { Time: v.dt.toLocaleString(), ...v.components } 22 | }) 23 | ) 24 | }) 25 | 26 | let currentDate = new Date() 27 | let dateFrom12HoursAgo = new Date().setHours(currentDate.getHours() - 12) 28 | 29 | weather.getHistoryAirPollution(dateFrom12HoursAgo, currentDate).then(data => { 30 | console.log(`Air quality in ${location} 12 hours ago was ${data[0].aqiName.toLowerCase()}.`) 31 | }) 32 | -------------------------------------------------------------------------------- /examples/daily-example.js: -------------------------------------------------------------------------------- 1 | const { OpenWeatherAPI } = require("..") 2 | 3 | // Set global key, location and units 4 | let weather = new OpenWeatherAPI({ 5 | key: "put-key-here", 6 | locationName: "Sydney", 7 | units: "imperial" 8 | }) 9 | 10 | const days = [ 11 | "Monday", 12 | "Thuesday", 13 | "Wednesday", 14 | "Thursday", 15 | "Friday", 16 | "Saturday", 17 | "Sunday" 18 | ] 19 | 20 | console.log("Daily forecast in Sydney:") 21 | weather.getDailyForecast().then(data => { 22 | let table = [] 23 | let today = (new Date()).getDay() 24 | data.forEach((e, i) => { 25 | let newE = {} 26 | newE.day = days[today + i > 6 ? today + i - 7 : today + i] 27 | newE.temp = `${e.weather.temp.day}\u00B0F` 28 | newE.rain = `${e.weather.rain}mm` 29 | newE.description = e.weather.description 30 | table.push(newE) 31 | }) 32 | console.table(table) 33 | }) 34 | -------------------------------------------------------------------------------- /examples/historical-data.js: -------------------------------------------------------------------------------- 1 | const { OpenWeatherAPI } = require("..") 2 | 3 | // Set global key, location and units 4 | let weather = new OpenWeatherAPI({ 5 | key: "put-key-here", 6 | coordinates: { 7 | lat: 49.84, 8 | lon: 24.03 9 | } 10 | }) 11 | 12 | // getHistory is asynchronous so we can use .then() 13 | // we pass a timestamp of the time that was 15 minutes ago 14 | weather.getHistory(new Date().getTime() - 900000).then(async data => { 15 | let time = data.current.dt 16 | let location = await weather.getLocation() 17 | // and we get temperature from 15 minutes ago 18 | console.log(`The temperature at ${time.getHours()}:${time.getMinutes()} in ${location.name} was ${data.current.weather.temp.cur}\u00B0K`) 19 | }) 20 | -------------------------------------------------------------------------------- /examples/simple.js: -------------------------------------------------------------------------------- 1 | const { OpenWeatherAPI } = require("..") 2 | 3 | // Set global key, location and units 4 | let weather = new OpenWeatherAPI({ 5 | key: "put-key-here", 6 | locationName: "New York", 7 | units: "imperial" 8 | }) 9 | 10 | // getCurrentMethod is asynchronous so we can use .then() 11 | weather.getCurrent().then(data => { 12 | // Current temperature is defined in weatherModel.weather.temp.cur 13 | // If you are not sure what is weather model check it out in docs 14 | console.log(`Current temperature in New York is: ${data.weather.temp.cur}\u00B0F`) 15 | }) 16 | -------------------------------------------------------------------------------- /examples/using-options.js: -------------------------------------------------------------------------------- 1 | const { OpenWeatherAPI } = require("..") 2 | 3 | // Set global key, location and units 4 | let weather = new OpenWeatherAPI({ 5 | key: "put-key-here", 6 | locationName: "London", 7 | units: "metric" 8 | }) 9 | 10 | // options specified here will be used ONLY for this call and won't change global options 11 | // if some option is not specified here this option will defult to global option (For example key) 12 | weather.getCurrent({ 13 | locationName: "Tokio", 14 | units: "imperial" 15 | }).then(data => { 16 | console.log(`Current temperature in Tokio is: ${data.weather.temp.cur}\u00B0F`) 17 | 18 | // this call will use global options 19 | weather.getCurrent().then(data => { 20 | console.log(`Current temperature in London is: ${data.weather.temp.cur}\u00B0C`) 21 | }) 22 | }) 23 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "jest"; 2 | 3 | const config: Config = { 4 | preset: "ts-jest", 5 | testEnvironment: "node", 6 | moduleDirectories: ["src", "node_modules"], 7 | }; 8 | 9 | export default config; 10 | -------------------------------------------------------------------------------- /media/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loloToster/openweather-api-node/8c513bdca79b632c617a8bef6b4ff1dfc6494c35/media/favicon.ico -------------------------------------------------------------------------------- /media/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/loloToster/openweather-api-node/8c513bdca79b632c617a8bef6b4ff1dfc6494c35/media/logo.png -------------------------------------------------------------------------------- /media/style.css: -------------------------------------------------------------------------------- 1 | .tsd-page-title h2 { 2 | display: none; 3 | } 4 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "openweather-api-node", 3 | "version": "3.1.5", 4 | "description": "Simple package that makes it easy to work with OpenWeather API", 5 | "main": "dist/index.js", 6 | "types": "dist/index.d.ts", 7 | "module": "dist/index.mjs", 8 | "exports": { 9 | "types": "./dist/index.d.ts", 10 | "bun": "./dist/index.js", 11 | "import": "./dist/index.mjs", 12 | "require": "./dist/index.js" 13 | }, 14 | "scripts": { 15 | "test": "jest", 16 | "build": "rimraf ./dist && tsc && npm run build:esm", 17 | "build:esm": "gen-esm-wrapper ./dist/index.js ./dist/index.mjs", 18 | "docs": "typedoc --plugin typedoc-plugin-extras ./src/index.ts --out ./docs --media ./media --customCss ./media/style.css --favicon ./media/favicon.ico", 19 | "docs:deploy": "gh-pages -d ./docs" 20 | }, 21 | "keywords": [ 22 | "weather", 23 | "openweather", 24 | "openweathermap", 25 | "meteo", 26 | "weather-api", 27 | "air", 28 | "pollution", 29 | "api" 30 | ], 31 | "author": "loloToster", 32 | "license": "MIT", 33 | "devDependencies": { 34 | "@types/jest": "^29.2.4", 35 | "gen-esm-wrapper": "^1.1.3", 36 | "gh-pages": "^4.0.0", 37 | "jest": "^29.3.1", 38 | "rimraf": "^3.0.2", 39 | "ts-jest": "^29.0.3", 40 | "ts-node": "^10.9.1", 41 | "typedoc": "^0.24.4", 42 | "typedoc-plugin-extras": "^2.3.3", 43 | "typescript": "^4.9.4" 44 | }, 45 | "repository": { 46 | "type": "git", 47 | "url": "https://github.com/loloToster/openweather-api-node.git" 48 | }, 49 | "bugs": { 50 | "url": "https://github.com/loloToster/openweather-api-node/issues" 51 | }, 52 | "homepage": "https://lolotoster.github.io/openweather-api-node/", 53 | "directories": { 54 | "example": "examples", 55 | "test": "test" 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/constants.ts: -------------------------------------------------------------------------------- 1 | export const SUP_LANGS = [ 2 | "af", 3 | "al", 4 | "ar", 5 | "az", 6 | "bg", 7 | "ca", 8 | "cz", 9 | "da", 10 | "de", 11 | "el", 12 | "en", 13 | "eu", 14 | "fa", 15 | "fi", 16 | "fr", 17 | "gl", 18 | "he", 19 | "hi", 20 | "hr", 21 | "hu", 22 | "id", 23 | "it", 24 | "ja", 25 | "kr", 26 | "la", 27 | "lt", 28 | "mk", 29 | "no", 30 | "nl", 31 | "pl", 32 | "pt", 33 | "pt_br", 34 | "ro", 35 | "ru", 36 | "sv", 37 | "se", 38 | "sk", 39 | "sl", 40 | "sp", 41 | "es", 42 | "sr", 43 | "th", 44 | "tr", 45 | "ua", 46 | "uk", 47 | "vi", 48 | "zh_cn", 49 | "zh_tw", 50 | "zu", 51 | ] as const; 52 | 53 | export const SUP_UNITS = ["standard", "metric", "imperial"] as const; 54 | 55 | export const API_ENDPOINT = "https://api.openweathermap.org/"; 56 | 57 | export const GEO_PATH = "geo/1.0/"; 58 | export const DATA_PATH = "data/2.5"; 59 | export const DATA_3_PATH = "data/3.0"; 60 | 61 | export const WEATHER_PATH = DATA_PATH + "/weather"; 62 | export const FORECAST_PATH = DATA_PATH + "/forecast"; 63 | export const ONECALL_PATH = DATA_3_PATH + "/onecall"; 64 | export const AIR_POLLUTION_PATH = DATA_PATH + "/air_pollution"; 65 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { get } from "./request"; 2 | 3 | import currentParser from "./parsers/weather/current-parser"; 4 | import forecastParser from "./parsers/weather/forecast-parser"; 5 | 6 | import onecallCurrentParser from "./parsers/onecall/current-parser"; 7 | import onecallMinutelyParser from "./parsers/onecall/minutely-parser"; 8 | import onecallHourlyParser from "./parsers/onecall/hourly-parser"; 9 | import onecallDailyParser from "./parsers/onecall/daily-parser"; 10 | import onecallHistoricalParser from "./parsers/onecall/history-parser"; 11 | 12 | import singleAirPollutionParser from "./parsers/air-pollution/single-parser"; 13 | import listAirPollutionParser from "./parsers/air-pollution/list-parser"; 14 | 15 | import { 16 | AIR_POLLUTION_PATH, 17 | API_ENDPOINT, 18 | FORECAST_PATH, 19 | GEO_PATH, 20 | ONECALL_PATH, 21 | SUP_LANGS, 22 | SUP_UNITS, 23 | WEATHER_PATH, 24 | } from "./constants"; 25 | 26 | import { 27 | Alert, 28 | Language, 29 | Location, 30 | Options, 31 | Unit, 32 | Coordinates, 33 | CurrentWeather, 34 | MinutelyWeather, 35 | HourlyWeather, 36 | DailyWeather, 37 | HistoricalWeather, 38 | Everything, 39 | AirPollution, 40 | ForecastWeather, 41 | } from "./types"; 42 | 43 | function isObject>(x: unknown): x is T { 44 | return Boolean(x) && typeof x === "object" && !Array.isArray(x); 45 | } 46 | 47 | // https://stackoverflow.com/questions/27936772/how-to-deep-merge-instead-of-shallow-merge 48 | function mergeObj(target: T, ...sources: T[]): T { 49 | if (!sources.length) return target; 50 | const source = sources.shift(); 51 | 52 | if (isObject(target) && isObject(source)) { 53 | for (const key in source) { 54 | if (isObject(source[key])) { 55 | if (!target[key]) Object.assign(target, { [key]: {} }); 56 | mergeObj(target[key], source[key]); 57 | } else { 58 | Object.assign(target, { [key]: source[key] }); 59 | } 60 | } 61 | } 62 | 63 | return mergeObj(target, ...sources); 64 | } 65 | 66 | export class OpenWeatherAPI { 67 | private globalOptions: Options; 68 | 69 | /** 70 | * Constructor of the class. You can specify global options here 71 | * 72 | * @constructor 73 | * @param globalOptions - object that defines global options 74 | * @returns OpenWeatherAPI object 75 | */ 76 | constructor(globalOptions: Options = {}) { 77 | if (!isObject(globalOptions)) 78 | throw new Error("Provide {} object as options"); 79 | 80 | this.globalOptions = {}; 81 | 82 | const go = globalOptions as any; 83 | 84 | for (const key in go) { 85 | if (Object.hasOwnProperty.call(go, key)) { 86 | const value = go[key]; 87 | 88 | switch (key as keyof Options) { 89 | case "key": 90 | this.setKey(value); 91 | break; 92 | 93 | // @ts-ignore: Type '"language"' is not comparable to type 'keyof Options'. 94 | case "language": 95 | case "lang": 96 | this.setLanguage(value); 97 | break; 98 | 99 | case "units": 100 | this.setUnits(value); 101 | break; 102 | 103 | case "locationName": 104 | this.setLocationByName(value); 105 | break; 106 | 107 | case "coordinates": 108 | this.setLocationByCoordinates(value.lat, value.lon); 109 | break; 110 | 111 | case "zipCode": 112 | this.setLocationByZipCode(value); 113 | break; 114 | 115 | default: 116 | throw new Error("Unknown parameter: " + key); 117 | } 118 | } 119 | } 120 | } 121 | 122 | /** 123 | * Sets global API key 124 | * 125 | * @param key - api key 126 | */ 127 | setKey(key: string) { 128 | if (!key) throw new Error("Empty value cannot be a key: " + key); 129 | this.globalOptions.key = key; 130 | } 131 | 132 | /** 133 | * Getter for global key 134 | * 135 | * @returns global API key 136 | */ 137 | getKey() { 138 | return this.globalOptions.key; 139 | } 140 | 141 | /** 142 | * Sets global language (Language must be listed [here](https://openweathermap.org/current#multi)) 143 | * 144 | * @param lang - language 145 | */ 146 | setLanguage(lang: Language) { 147 | this.globalOptions.lang = this.evaluateLanguage(lang); 148 | } 149 | 150 | /** 151 | * Getter for global language 152 | * 153 | * @return global language 154 | */ 155 | getLanguage(): Language | undefined { 156 | return this.globalOptions.lang; 157 | } 158 | 159 | private evaluateLanguage(lang: unknown) { 160 | if (typeof lang !== "string") 161 | throw new Error("language needs to be a string"); 162 | 163 | const loweredLang = lang.toLowerCase(); 164 | if (SUP_LANGS.includes(loweredLang as any)) return loweredLang as Language; 165 | else throw new Error("Unsupported language: " + loweredLang); 166 | } 167 | 168 | /** 169 | * Sets global units 170 | * 171 | * @param units - units (Only **standard**, **metric** or **imperial** are supported) 172 | */ 173 | setUnits(units: Unit) { 174 | this.globalOptions.units = this.evaluateUnits(units); 175 | } 176 | 177 | /** 178 | * Getter for global units 179 | * 180 | * @returns global units 181 | */ 182 | getUnits(): Unit | undefined { 183 | return this.globalOptions.units; 184 | } 185 | 186 | private evaluateUnits(units: unknown) { 187 | if (typeof units !== "string") 188 | throw new Error("units needs to be a string"); 189 | 190 | const loweredUnits = units.toLowerCase(); 191 | if (SUP_UNITS.includes(loweredUnits as any)) return loweredUnits as Unit; 192 | else throw new Error("Unsupported units: " + loweredUnits); 193 | } 194 | 195 | /** 196 | * Sets global location by provided name 197 | * 198 | * @param name - name of the location (`q` parameter [here](https://openweathermap.org/api/geocoding-api#direct_name)) 199 | */ 200 | setLocationByName(name: string) { 201 | if (!name) 202 | throw new Error("Empty value cannot be a location name: " + name); 203 | 204 | this.globalOptions.coordinates = undefined; 205 | this.globalOptions.zipCode = undefined; 206 | this.globalOptions.locationName = name; 207 | } 208 | 209 | private async evaluateLocationByName(name: unknown, key: string) { 210 | if (typeof name !== "string") 211 | throw new Error("name of the location needs to be a string"); 212 | 213 | let response = await this.fetch( 214 | `${API_ENDPOINT}${GEO_PATH}direct?q=${encodeURIComponent( 215 | name 216 | )}&limit=1&appid=${encodeURIComponent(key)}` 217 | ); 218 | 219 | let data = response.data; 220 | if (data.length == 0) throw new Error("Unknown location name: " + name); 221 | data = response.data[0]; 222 | 223 | return { 224 | lat: data.lat, 225 | lon: data.lon, 226 | }; 227 | } 228 | 229 | /** 230 | * Sets global location by provided coordinates 231 | * 232 | * @param lat - latitude of the location 233 | * @param lon - longitude of the location 234 | */ 235 | setLocationByCoordinates(lat: number, lon: number) { 236 | let location = this.evaluateLocationByCoordinates({ lat, lon }); 237 | this.globalOptions.locationName = undefined; 238 | this.globalOptions.zipCode = undefined; 239 | 240 | this.globalOptions.coordinates = { lat: location.lat, lon: location.lon }; 241 | } 242 | 243 | private evaluateLocationByCoordinates(coords: unknown): Coordinates { 244 | if ( 245 | !isObject(coords) || 246 | typeof coords.lat !== "number" || 247 | typeof coords.lon !== "number" 248 | ) 249 | throw new Error("Invalid Coordinates"); 250 | 251 | const { lat, lon } = coords; 252 | 253 | if (-90 <= lat && lat <= 90 && -180 <= lon && lon <= 180) { 254 | return { lat, lon }; 255 | } else { 256 | throw new Error( 257 | "Invalid Coordinates: lat must be between -90 & 90 and lon must be between -180 & 180" 258 | ); 259 | } 260 | } 261 | 262 | /** 263 | * Sets global location by provided zip/post code 264 | * 265 | * @param zipCode - zip/post code and country code divided by comma (`zip` parameter [here](https://openweathermap.org/api/geocoding-api#direct_zip)) 266 | */ 267 | setLocationByZipCode(zipCode: string) { 268 | if (!zipCode) 269 | throw new Error("Empty value cannot be a location zip code: " + zipCode); 270 | 271 | this.globalOptions.coordinates = undefined; 272 | this.globalOptions.locationName = undefined; 273 | this.globalOptions.zipCode = zipCode; 274 | } 275 | 276 | private async evaluateLocationByZipCode(zipCode: unknown, key: string) { 277 | if (typeof zipCode !== "string") 278 | throw new Error("zip code needs to be a string"); 279 | 280 | let response = await this.fetch( 281 | `${API_ENDPOINT}${GEO_PATH}zip?zip=${encodeURIComponent( 282 | zipCode 283 | )}&appid=${encodeURIComponent(key)}` 284 | ); 285 | 286 | let data = response.data; 287 | 288 | return { 289 | lat: data.lat, 290 | lon: data.lon, 291 | }; 292 | } 293 | 294 | /** 295 | * Getter for location 296 | * 297 | * @param options - options used only for this call 298 | * @returns location or null for no location 299 | */ 300 | async getLocation(options: Options = {}): Promise { 301 | const parsedOptions = await this.parseOptions(options); 302 | 303 | let response = await this.fetch( 304 | `${API_ENDPOINT}${GEO_PATH}reverse?lat=${ 305 | parsedOptions.coordinates?.lat 306 | }&lon=${ 307 | parsedOptions.coordinates?.lon 308 | }&limit=1&appid=${encodeURIComponent(parsedOptions.key || "")}` 309 | ); 310 | 311 | let data = response.data; 312 | return data.length ? data[0] : null; 313 | } 314 | 315 | /** 316 | * Getter for locations from query 317 | * 318 | * @param query - query used to search the locations (`q` parameter [here](https://openweathermap.org/api/geocoding-api#direct_name)) 319 | * @param options - options used only for this call 320 | * @returns all found locations 321 | */ 322 | async getAllLocations( 323 | query: string, 324 | options: Options = {} 325 | ): Promise { 326 | if (!query) throw new Error("No query"); 327 | 328 | const parsedOptions = await this.parseOptions(options); 329 | 330 | let response = await this.fetch( 331 | `${API_ENDPOINT}${GEO_PATH}direct?q=${encodeURIComponent( 332 | query 333 | )}&limit=5&appid=${encodeURIComponent(parsedOptions.key || "")}` 334 | ); 335 | 336 | let data = response.data; 337 | return data; 338 | } 339 | 340 | // Weather getters 341 | 342 | /** 343 | * Getter for current weather 344 | * 345 | * @param options - options used only for this call 346 | * @returns weather object of current weather 347 | */ 348 | async getCurrent(options: Options = {}): Promise { 349 | await this.uncacheLocation(); 350 | const parsedOptions = await this.parseOptions(options); 351 | 352 | let response = await this.fetch( 353 | this.createURL(parsedOptions, WEATHER_PATH) 354 | ); 355 | 356 | let data = response.data; 357 | return currentParser(data); 358 | } 359 | 360 | /** 361 | * Getter for forecasted weather 362 | * 363 | * @param limit - maximum length of returned array 364 | * @param options - options used only for this call 365 | * @returns array of Weather objects, one for every 3 hours, up to 5 days 366 | */ 367 | async getForecast( 368 | limit: number = Number.POSITIVE_INFINITY, 369 | options: Options = {} 370 | ): Promise { 371 | await this.uncacheLocation(); 372 | const parsedOptions = await this.parseOptions(options); 373 | 374 | let response = await this.fetch( 375 | this.createURL(parsedOptions, FORECAST_PATH) 376 | ); 377 | 378 | let data = response.data; 379 | return forecastParser(data, limit); 380 | } 381 | 382 | /** 383 | * Getter for minutely weather 384 | * 385 | * @param limit - maximum length of returned array 386 | * @param options - options used only for this call 387 | * @returns array of Weather objects, one for every next minute (Empty if API returned no info about minutely weather) 388 | */ 389 | async getMinutelyForecast( 390 | limit: number = Number.POSITIVE_INFINITY, 391 | options: Options = {} 392 | ): Promise { 393 | await this.uncacheLocation(); 394 | const parsedOptions = await this.parseOptions(options); 395 | 396 | let response = await this.fetch( 397 | this.createURL(parsedOptions, ONECALL_PATH, { 398 | exclude: "alerts,current,hourly,daily", 399 | }) 400 | ); 401 | 402 | let data = response.data; 403 | return onecallMinutelyParser(data, limit); 404 | } 405 | 406 | /** 407 | * Getter for hourly weather 408 | * 409 | * @param limit - maximum length of returned array 410 | * @param options - options used only for this call 411 | * @returns array of Weather objects, one for every next hour (Empty if API returned no info about hourly weather) 412 | */ 413 | async getHourlyForecast( 414 | limit: number = Number.POSITIVE_INFINITY, 415 | options: Options = {} 416 | ): Promise { 417 | await this.uncacheLocation(); 418 | const parsedOptions = await this.parseOptions(options); 419 | 420 | let response = await this.fetch( 421 | this.createURL(parsedOptions, ONECALL_PATH, { 422 | exclude: "alerts,current,minutely,daily", 423 | }) 424 | ); 425 | 426 | let data = response.data; 427 | return onecallHourlyParser(data, limit); 428 | } 429 | 430 | /** 431 | * Getter for daily weather 432 | * 433 | * @param limit - maximum length of returned array 434 | * @param includeToday - boolean indicating whether to include today's weather in returned array 435 | * @param options - options used only for this call 436 | * @returns array of Weather objects, one for every next day (Empty if API returned no info about daily weather) 437 | */ 438 | async getDailyForecast( 439 | limit: number = Number.POSITIVE_INFINITY, 440 | includeToday: boolean = false, 441 | options: Options = {} 442 | ): Promise { 443 | await this.uncacheLocation(); 444 | const parsedOptions = await this.parseOptions(options); 445 | 446 | let response = await this.fetch( 447 | this.createURL(parsedOptions, ONECALL_PATH, { 448 | exclude: "alerts,current,minutely,hourly", 449 | }) 450 | ); 451 | 452 | let data = response.data; 453 | if (!includeToday) data.daily.shift(); 454 | return onecallDailyParser(data, limit); 455 | } 456 | 457 | /** 458 | * Getter for today's weather 459 | * 460 | * @param options - options used only for this call 461 | * @returns weather object of today's weather **NOT the same as current!** 462 | */ 463 | async getToday(options: Options = {}): Promise { 464 | return (await this.getDailyForecast(1, true, options))[0]; 465 | } 466 | 467 | /** 468 | * Getter for alerts\ 469 | * **Note:** some agencies provide the alert’s description only in a local language. 470 | * 471 | * @param options - options used only for this call 472 | * @returns alerts 473 | */ 474 | async getAlerts(options: Options = {}): Promise { 475 | await this.uncacheLocation(); 476 | const parsedOptions = await this.parseOptions(options); 477 | 478 | let response = await this.fetch( 479 | this.createURL(parsedOptions, ONECALL_PATH, { 480 | exclude: "current,minutely,hourly,daily", 481 | }) 482 | ); 483 | 484 | let data = response.data; 485 | return data.alerts ?? []; 486 | } 487 | 488 | /** 489 | * Getter for every type of weather call and alerts 490 | * 491 | * @param options - options used only for this call 492 | * @returns object that contains everything 493 | */ 494 | async getEverything(options: Options = {}): Promise { 495 | await this.uncacheLocation(); 496 | const parsedOptions = await this.parseOptions(options); 497 | 498 | let response = await this.fetch( 499 | this.createURL(parsedOptions, ONECALL_PATH) 500 | ); 501 | 502 | let data = response.data; 503 | return { 504 | lat: data.lat, 505 | lon: data.lon, 506 | timezone: data.timezone, 507 | timezoneOffset: data.timezone_offset, 508 | current: onecallCurrentParser(data), 509 | minutely: onecallMinutelyParser(data, Number.POSITIVE_INFINITY), 510 | hourly: onecallHourlyParser(data, Number.POSITIVE_INFINITY), 511 | daily: onecallDailyParser(data, Number.POSITIVE_INFINITY), 512 | alerts: data.alerts, 513 | }; 514 | } 515 | 516 | /** 517 | * Getter for historical data about weather 518 | * 519 | * @param dt - Date from the **previous five days** (Unix time, UTC time zone) 520 | * @param options - options used only for this call 521 | */ 522 | async getHistory( 523 | dt: Date | number | string, 524 | options: Options = {} 525 | ): Promise { 526 | if (dt === undefined) throw new Error("Provide time"); 527 | 528 | await this.uncacheLocation(); 529 | dt = Math.round(new Date(dt).getTime() / 1000); 530 | 531 | const parsedOptions = await this.parseOptions(options); 532 | 533 | let response = await this.fetch( 534 | this.createURL(parsedOptions, ONECALL_PATH + "/timemachine", { 535 | dt: dt.toString(), 536 | }) 537 | ); 538 | 539 | let data = response.data; 540 | return onecallHistoricalParser(data); 541 | } 542 | 543 | // Uncategorized Methods 544 | 545 | /** 546 | * Getter for current data about air pollution 547 | * 548 | * @param options - options used only for this call 549 | * @returns Air Pollution Object with data about current pollution 550 | */ 551 | async getCurrentAirPollution(options: Options = {}): Promise { 552 | await this.uncacheLocation(); 553 | const parsedOptions = await this.parseOptions(options); 554 | 555 | let response = await this.fetch( 556 | this.createURL(parsedOptions, AIR_POLLUTION_PATH) 557 | ); 558 | 559 | let data = response.data; 560 | return singleAirPollutionParser(data); 561 | } 562 | 563 | /** 564 | * Getter for future data about air pollution 565 | * 566 | * @param limit - maximum length of returned array 567 | * @param options - options used only for this call 568 | * @returns Array of Air Pollution Objects with data about future pollution 569 | */ 570 | async getForecastedAirPollution( 571 | limit = Number.POSITIVE_INFINITY, 572 | options: Options = {} 573 | ): Promise { 574 | await this.uncacheLocation(); 575 | const parsedOptions = await this.parseOptions(options); 576 | 577 | let response = await this.fetch( 578 | this.createURL(parsedOptions, AIR_POLLUTION_PATH + "/forecast") 579 | ); 580 | 581 | let data = response.data; 582 | return listAirPollutionParser(data, limit); 583 | } 584 | 585 | /** 586 | * Getter for historical data about air pollution 587 | * WARNING: Historical data is accessible from 27th November 2020 588 | * 589 | * @param from - Start date (unix time, UTC time zone) 590 | * @param to - End date (unix time, UTC time zone) 591 | * @param options - options used only for this call 592 | * @returns Array of Air Pollution Objects with data about historical pollution 593 | */ 594 | async getHistoryAirPollution( 595 | from: Date | number | string, 596 | to: Date | number | string, 597 | options: Options = {} 598 | ): Promise { 599 | await this.uncacheLocation(); 600 | const parsedOptions = await this.parseOptions(options); 601 | 602 | let response = await this.fetch( 603 | this.createURL(parsedOptions, AIR_POLLUTION_PATH + "/history", { 604 | start: Math.round(new Date(from).getTime() / 1000).toString(), 605 | end: Math.round(new Date(to).getTime() / 1000).toString(), 606 | }) 607 | ); 608 | 609 | const data = response.data; 610 | return listAirPollutionParser(data, Number.POSITIVE_INFINITY); 611 | } 612 | 613 | // helpers 614 | private async uncacheLocation() { 615 | if ( 616 | typeof this.globalOptions.coordinates?.lat == "number" && 617 | typeof this.globalOptions.coordinates?.lon == "number" 618 | ) 619 | return; 620 | 621 | const key = this.globalOptions.key; 622 | if (!key) return; 623 | 624 | try { 625 | if (this.globalOptions.locationName) { 626 | this.globalOptions.coordinates = await this.evaluateLocationByName( 627 | this.globalOptions.locationName, 628 | key 629 | ); 630 | } else if (this.globalOptions.zipCode) { 631 | this.globalOptions.coordinates = await this.evaluateLocationByZipCode( 632 | this.globalOptions.zipCode, 633 | key 634 | ); 635 | } 636 | } catch {} 637 | } 638 | 639 | private createURL( 640 | options: Options, 641 | path = "", 642 | additionalParams: Record = {} 643 | ) { 644 | if (!options.key) throw new Error("Invalid key"); 645 | if (!options.coordinates) throw new Error("Invalid coordinates"); 646 | 647 | let url = new URL(path, API_ENDPOINT); 648 | 649 | url.searchParams.append("appid", options.key); 650 | url.searchParams.append("lat", options.coordinates.lat.toString()); 651 | url.searchParams.append("lon", options.coordinates.lon.toString()); 652 | 653 | if (options.lang) url.searchParams.append("lang", options.lang); 654 | if (options.units) url.searchParams.append("units", options.units); 655 | 656 | for (const [key, value] of Object.entries(additionalParams)) { 657 | url.searchParams.append(key, value); 658 | } 659 | 660 | return url.href; 661 | } 662 | 663 | private async fetch(url: string) { 664 | const res = await get(url); 665 | const data = res.data; 666 | 667 | if (data.cod && parseInt(data.cod) !== 200) { 668 | throw new Error(JSON.stringify(data)); 669 | } else { 670 | return res; 671 | } 672 | } 673 | 674 | private async parseOptions(options: Options): Promise { 675 | if (!isObject(options)) 676 | throw new Error("Provide {} object as options"); 677 | 678 | const parsedOptions: Options = {}; 679 | 680 | for (const key in options) { 681 | if (Object.hasOwnProperty.call(options, key)) { 682 | const value = (options as Record)[key]; 683 | 684 | switch (key as keyof Options) { 685 | case "key": { 686 | if (typeof value !== "string") 687 | throw Error("key needs to be a string"); 688 | 689 | parsedOptions.key = value; 690 | break; 691 | } 692 | 693 | // @ts-ignore: Type '"language"' is not comparable to type 'keyof Options'. 694 | case "language": 695 | case "lang": { 696 | parsedOptions.lang = this.evaluateLanguage(value); 697 | break; 698 | } 699 | 700 | case "units": { 701 | parsedOptions.units = this.evaluateUnits(value); 702 | break; 703 | } 704 | 705 | case "locationName": { 706 | parsedOptions.locationName = value as string; 707 | parsedOptions.coordinates = await this.evaluateLocationByName( 708 | value, 709 | options.key || this.globalOptions.key || "" 710 | ); 711 | break; 712 | } 713 | 714 | case "coordinates": { 715 | parsedOptions.coordinates = 716 | this.evaluateLocationByCoordinates(value); 717 | 718 | break; 719 | } 720 | 721 | case "zipCode": { 722 | parsedOptions.zipCode = value as string; 723 | parsedOptions.coordinates = await this.evaluateLocationByZipCode( 724 | value, 725 | options.key || this.globalOptions.key || "" 726 | ); 727 | break; 728 | } 729 | 730 | default: { 731 | throw new Error("Unknown parameter: " + key); 732 | } 733 | } 734 | } 735 | } 736 | 737 | return mergeObj({}, this.globalOptions, parsedOptions); 738 | } 739 | } 740 | 741 | export default OpenWeatherAPI; 742 | export * from "./types"; 743 | -------------------------------------------------------------------------------- /src/parsers/air-pollution/list-parser.ts: -------------------------------------------------------------------------------- 1 | import { AirPollution } from "types/air-pollution"; 2 | 3 | const names = ["", "Good", "Fair", "Moderate", "Poor", "Very Poor"]; 4 | 5 | function listParser(data: any, limit: number): AirPollution[] { 6 | if (!data.list) return []; 7 | 8 | let parsedList: AirPollution[] = []; 9 | 10 | for (let i = 0; i < limit && i < data.list.length; i++) { 11 | const element = data.list[i]; 12 | 13 | parsedList.push({ 14 | lat: data.coord.lat, 15 | lon: data.coord.lon, 16 | dt: new Date(element.dt * 1000), 17 | dtRaw: element.dt, 18 | aqi: element.main.aqi, 19 | aqiName: names[element.main.aqi], 20 | components: { ...element.components }, 21 | }); 22 | } 23 | 24 | return parsedList; 25 | } 26 | 27 | export default listParser; 28 | -------------------------------------------------------------------------------- /src/parsers/air-pollution/single-parser.ts: -------------------------------------------------------------------------------- 1 | import { AirPollution } from "types/air-pollution"; 2 | 3 | const names = ["", "Good", "Fair", "Moderate", "Poor", "Very Poor"]; 4 | 5 | function singleParser(data: any): AirPollution { 6 | const element = data.list[0]; 7 | 8 | return { 9 | lat: data.coord.lat, 10 | lon: data.coord.lon, 11 | dt: new Date(element.dt * 1000), 12 | dtRaw: element.dt, 13 | aqi: element.main.aqi, 14 | aqiName: names[element.main.aqi], 15 | components: { ...element.components }, 16 | }; 17 | } 18 | 19 | export default singleParser; 20 | -------------------------------------------------------------------------------- /src/parsers/onecall/current-parser.ts: -------------------------------------------------------------------------------- 1 | import { OnecallCurrentWeather } from "types/index"; 2 | 3 | function currentParser(data: any): OnecallCurrentWeather { 4 | const w = data.current.weather[0]; 5 | 6 | return { 7 | lat: data.lat, 8 | lon: data.lon, 9 | dt: new Date(data.current.dt * 1000), 10 | dtRaw: data.current.dt, 11 | timezone: data.timezone, 12 | timezoneOffset: data.timezone_offset, 13 | astronomical: { 14 | sunrise: new Date(data.current.sunrise * 1000), 15 | sunriseRaw: data.current.sunrise, 16 | sunset: new Date(data.current.sunset * 1000), 17 | sunsetRaw: data.current.sunset, 18 | }, 19 | weather: { 20 | temp: { cur: data.current.temp }, 21 | feelsLike: { cur: data.current.feels_like }, 22 | pressure: data.current.pressure, 23 | humidity: data.current.humidity, 24 | dewPoint: data.current.dew_point, 25 | clouds: data.current.clouds, 26 | uvi: data.current.uvi, 27 | visibility: data.current.visibility, 28 | wind: { 29 | deg: data.current.wind_deg, 30 | gust: data.current.wind_gust, 31 | speed: data.current.wind_speed, 32 | }, 33 | rain: data.current.rain ? data.current.rain["1h"] : 0, 34 | snow: data.current.snow ? data.current.snow["1h"] : 0, 35 | conditionId: w.id, 36 | main: w.main, 37 | description: w.description, 38 | icon: { 39 | url: `http://openweathermap.org/img/wn/${w.icon}@2x.png`, 40 | raw: w.icon, 41 | }, 42 | }, 43 | }; 44 | } 45 | 46 | export default currentParser; 47 | -------------------------------------------------------------------------------- /src/parsers/onecall/daily-parser.ts: -------------------------------------------------------------------------------- 1 | import { DailyWeather } from "types/index"; 2 | 3 | function dailyParser(data: any, limit: number): DailyWeather[] { 4 | if (!data.daily) return []; 5 | 6 | let parsedDaily: DailyWeather[] = []; 7 | 8 | for (let i = 0; i < limit && i < data.daily.length; i++) { 9 | const element = data.daily[i]; 10 | const w = element.weather[0]; 11 | 12 | parsedDaily.push({ 13 | lat: data.lat, 14 | lon: data.lon, 15 | dt: new Date(element.dt * 1000), 16 | dtRaw: element.dt, 17 | timezone: data.timezone, 18 | timezoneOffset: data.timezone_offset, 19 | astronomical: { 20 | sunrise: new Date(element.sunrise * 1000), 21 | sunriseRaw: element.sunrise, 22 | sunset: new Date(element.sunset * 1000), 23 | sunsetRaw: element.sunset, 24 | moonrise: new Date(element.moonrise * 1000), 25 | moonriseRaw: element.moonrise, 26 | moonset: new Date(element.moonset * 1000), 27 | moonsetRaw: element.moonset, 28 | moonPhase: element.moon_phase, 29 | }, 30 | weather: { 31 | temp: { 32 | morn: element.temp.morn, 33 | day: element.temp.day, 34 | eve: element.temp.eve, 35 | night: element.temp.night, 36 | min: element.temp.min, 37 | max: element.temp.max, 38 | }, 39 | feelsLike: { 40 | day: element.feels_like.day, 41 | night: element.feels_like.night, 42 | eve: element.feels_like.eve, 43 | morn: element.feels_like.morn, 44 | }, 45 | pressure: element.pressure, 46 | humidity: element.humidity, 47 | dewPoint: element.dew_point, 48 | clouds: element.clouds, 49 | uvi: element.uvi, 50 | wind: { 51 | deg: element.wind_deg, 52 | gust: element.wind_gust, 53 | speed: element.wind_speed, 54 | }, 55 | pop: element.pop, 56 | rain: element.rain ? element.rain : 0, 57 | snow: element.snow ? element.snow : 0, 58 | conditionId: w.id, 59 | main: w.main, 60 | description: w.description, 61 | summary: element.summary, 62 | icon: { 63 | url: `http://openweathermap.org/img/wn/${w.icon}@2x.png`, 64 | raw: w.icon, 65 | }, 66 | }, 67 | }); 68 | } 69 | 70 | return parsedDaily; 71 | } 72 | 73 | export default dailyParser; 74 | -------------------------------------------------------------------------------- /src/parsers/onecall/history-parser.ts: -------------------------------------------------------------------------------- 1 | import { HistoricalWeather } from "types/index"; 2 | 3 | function historicalParser(data: any): HistoricalWeather { 4 | const d = data.data[0]; 5 | const w = d.weather[0]; 6 | 7 | return { 8 | lat: data.lat, 9 | lon: data.lon, 10 | dt: new Date(d.dt * 1000), 11 | dtRaw: d.dt, 12 | timezone: data.timezone, 13 | timezoneOffset: data.timezone_offset, 14 | astronomical: { 15 | sunrise: new Date(d.sunrise * 1000), 16 | sunriseRaw: d.sunrise, 17 | sunset: new Date(d.sunset * 1000), 18 | sunsetRaw: d.sunset, 19 | }, 20 | weather: { 21 | temp: { cur: d.temp }, 22 | feelsLike: { cur: d.feels_like }, 23 | pressure: d.pressure, 24 | humidity: d.humidity, 25 | dewPoint: d.dew_point, 26 | clouds: d.clouds, 27 | uvi: d.uvi, 28 | visibility: d.visibility, 29 | wind: { 30 | deg: d.wind_deg, 31 | gust: d.wind_gust, 32 | speed: d.wind_speed, 33 | }, 34 | rain: d.rain ? d.rain["1h"] : 0, 35 | snow: d.snow ? d.snow["1h"] : 0, 36 | conditionId: w.id, 37 | main: w.main, 38 | description: w.description, 39 | icon: { 40 | url: `http://openweathermap.org/img/wn/${w.icon}@2x.png`, 41 | raw: w.icon, 42 | }, 43 | }, 44 | }; 45 | } 46 | 47 | export default historicalParser; 48 | -------------------------------------------------------------------------------- /src/parsers/onecall/hourly-parser.ts: -------------------------------------------------------------------------------- 1 | import { HourlyWeather } from "types/index"; 2 | 3 | function hourlyParser(data: any, limit: number): HourlyWeather[] { 4 | if (!data.hourly) return []; 5 | 6 | let parsedHourly: HourlyWeather[] = []; 7 | 8 | for (let i = 0; i < limit && i < data.hourly.length; i++) { 9 | const element = data.hourly[i]; 10 | const w = element.weather[0]; 11 | 12 | parsedHourly.push({ 13 | lat: data.lat, 14 | lon: data.lon, 15 | dt: new Date(element.dt * 1000), 16 | dtRaw: element.dt, 17 | timezone: data.timezone, 18 | timezoneOffset: data.timezone_offset, 19 | astronomical: {}, 20 | weather: { 21 | temp: { cur: element.temp }, 22 | feelsLike: { cur: element.temp }, // ! ? 23 | pressure: element.pressure, 24 | humidity: element.humidity, 25 | dewPoint: element.dew_point, 26 | clouds: element.clouds, 27 | uvi: element.uvi, 28 | visibility: element.visibility, 29 | wind: { 30 | deg: element.wind_deg, 31 | gust: element.wind_gust, 32 | speed: element.wind_speed, 33 | }, 34 | pop: element.pop, 35 | rain: element.rain ? element.rain["1h"] : 0, 36 | snow: element.snow ? element.snow["1h"] : 0, 37 | conditionId: w.id, 38 | main: w.main, 39 | description: w.description, 40 | icon: { 41 | url: `http://openweathermap.org/img/wn/${w.icon}@2x.png`, 42 | raw: w.icon, 43 | }, 44 | }, 45 | }); 46 | } 47 | 48 | return parsedHourly; 49 | } 50 | 51 | export default hourlyParser; 52 | -------------------------------------------------------------------------------- /src/parsers/onecall/minutely-parser.ts: -------------------------------------------------------------------------------- 1 | import { MinutelyWeather } from "types/index"; 2 | 3 | function minutelyParser(data: any, limit: number): MinutelyWeather[] { 4 | if (!data.minutely) return []; 5 | 6 | let parsedMinutely: MinutelyWeather[] = []; 7 | 8 | for (let i = 0; i < limit && i < data.minutely.length; i++) { 9 | const element = data.minutely[i]; 10 | 11 | parsedMinutely.push({ 12 | lat: data.lat, 13 | lon: data.lon, 14 | dt: new Date(element.dt * 1000), 15 | dtRaw: element.dt, 16 | timezone: data.timezone, 17 | timezoneOffset: data.timezone_offset, 18 | weather: { 19 | rain: element.precipitation, 20 | feelsLike: {}, 21 | icon: {}, 22 | temp: {}, 23 | wind: {}, 24 | }, 25 | astronomical: {}, 26 | }); 27 | } 28 | 29 | return parsedMinutely; 30 | } 31 | 32 | export default minutelyParser; 33 | -------------------------------------------------------------------------------- /src/parsers/weather/current-parser.ts: -------------------------------------------------------------------------------- 1 | import { CurrentWeather } from "types/index"; 2 | 3 | function currentParser(data: any): CurrentWeather { 4 | const w = data.weather[0]; 5 | 6 | return { 7 | lat: data.coord.lat, 8 | lon: data.coord.lon, 9 | dt: new Date(data.dt * 1000), 10 | dtRaw: data.dt, 11 | timezone: undefined, 12 | timezoneOffset: data.timezone, 13 | astronomical: { 14 | sunrise: new Date(data.sys.sunrise * 1000), 15 | sunriseRaw: data.sys.sunrise, 16 | sunset: new Date(data.sys.sunset * 1000), 17 | sunsetRaw: data.sys.sunset, 18 | }, 19 | weather: { 20 | temp: { 21 | cur: data.main.temp, 22 | min: data.main.temp_min, 23 | max: data.main.temp_max 24 | }, 25 | feelsLike: { cur: data.main.feels_like }, 26 | pressure: data.main.pressure, 27 | humidity: data.main.humidity, 28 | dewPoint: data.dew_point, 29 | clouds: data.clouds.all, 30 | uvi: data.uvi, 31 | visibility: data.visibility, 32 | wind: { 33 | deg: data.wind.deg, 34 | gust: data.wind.gust, 35 | speed: data.wind.speed, 36 | }, 37 | rain: data.rain ? data.rain["1h"] : 0, 38 | snow: data.snow ? data.snow["1h"] : 0, 39 | conditionId: w.id, 40 | main: w.main, 41 | description: w.description, 42 | icon: { 43 | url: `http://openweathermap.org/img/wn/${w.icon}@2x.png`, 44 | raw: w.icon, 45 | }, 46 | }, 47 | }; 48 | } 49 | 50 | export default currentParser; 51 | -------------------------------------------------------------------------------- /src/parsers/weather/forecast-parser.ts: -------------------------------------------------------------------------------- 1 | import { ForecastWeather } from "types/index"; 2 | 3 | function forecastParser(data: any, limit: number): ForecastWeather[] { 4 | if (!data.list) return []; 5 | 6 | let parsedForecast: ForecastWeather[] = []; 7 | 8 | for (let i = 0; i < limit && i < data.list.length; i++) { 9 | const element = data.list[i]; 10 | const w = element.weather[0]; 11 | 12 | parsedForecast.push({ 13 | lat: data.city.coord.lat, 14 | lon: data.city.coord.lon, 15 | dt: new Date(element.dt * 1000), 16 | dtRaw: element.dt, 17 | timezone: undefined, 18 | timezoneOffset: data.city.timezone, 19 | astronomical: { 20 | sunrise: new Date(data.city.sunrise * 1000), 21 | sunriseRaw: data.city.sunrise, 22 | sunset: new Date(data.city.sunset * 1000), 23 | sunsetRaw: data.city.sunset, 24 | }, 25 | weather: { 26 | temp: { 27 | cur: element.main.temp, 28 | min: element.main.temp_min, 29 | max: element.main.temp_max 30 | }, 31 | feelsLike: { cur: element.main.feels_like }, 32 | pressure: element.main.pressure, 33 | humidity: element.main.humidity, 34 | clouds: element.clouds.all, 35 | visibility: element.visibility, 36 | wind: { 37 | deg: element.wind.deg, 38 | gust: element.wind.gust, 39 | speed: element.wind.speed, 40 | }, 41 | pop: element.pop, 42 | rain: element.rain ? element.rain["3h"] : 0, 43 | snow: element.snow ? element.snow["3h"] : 0, 44 | conditionId: w.id, 45 | main: w.main, 46 | description: w.description, 47 | icon: { 48 | url: `http://openweathermap.org/img/wn/${w.icon}@2x.png`, 49 | raw: w.icon, 50 | }, 51 | }, 52 | }); 53 | } 54 | 55 | return parsedForecast; 56 | } 57 | 58 | export default forecastParser; 59 | -------------------------------------------------------------------------------- /src/request.ts: -------------------------------------------------------------------------------- 1 | export interface Resp { 2 | status: number | undefined; 3 | data: any; 4 | } 5 | 6 | const inBrowser = 7 | typeof window !== "undefined" && typeof window.fetch !== "undefined"; 8 | const inNode = 9 | typeof process !== "undefined" && 10 | process.versions != null && 11 | process.versions.node != null; 12 | 13 | export async function get(url: string): Promise { 14 | if (inBrowser) { 15 | const response = await window.fetch(url); 16 | 17 | return { 18 | status: response.status, 19 | data: await response.json(), 20 | }; 21 | } else if (inNode) { 22 | const getter = url.startsWith("https") 23 | ? await import("https") 24 | : await import("http"); 25 | 26 | return await new Promise((res, rej) => { 27 | const request = getter.get(url, (response) => { 28 | let data = ""; 29 | response.on("error", rej); 30 | response.on("data", (chunk) => (data += chunk.toString())); 31 | response.on("end", () => 32 | res({ 33 | status: response.statusCode, 34 | data: JSON.parse(data), 35 | }) 36 | ); 37 | }); 38 | 39 | request.on("error", rej); 40 | request.end(); 41 | }); 42 | } else { 43 | throw new Error("Unknown environment"); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/types/air-pollution.ts: -------------------------------------------------------------------------------- 1 | export interface Components { 2 | /** 3 | * Сoncentration of CO (Carbon monoxide), μg/m3 4 | */ 5 | co: number; 6 | /** 7 | * Сoncentration of NO (Nitrogen monoxide), μg/m3 8 | */ 9 | no: number; 10 | /** 11 | * Сoncentration of NO2 (Nitrogen dioxide), μg/m3 12 | */ 13 | no2: number; 14 | /** 15 | * Сoncentration of O3 (Ozone), μg/m3 16 | */ 17 | o3: number; 18 | /** 19 | * Сoncentration of SO2 (Sulphur dioxide), μg/m3 20 | */ 21 | so2: number; 22 | /** 23 | * Сoncentration of PM2.5 (Fine particles matter), μg/m3 24 | */ 25 | pm2_5: number; 26 | /** 27 | * Сoncentration of PM10 (Coarse particulate matter), μg/m3 28 | */ 29 | pm10: number; 30 | /** 31 | * Сoncentration of NH3 (Ammonia), μg/m3 32 | */ 33 | nh3: number; 34 | } 35 | 36 | export interface AirPollution { 37 | /** 38 | * Geographical coordinates of the location (latitude) 39 | */ 40 | lat: number; 41 | /** 42 | * Geographical coordinates of the location (longitude) 43 | */ 44 | lon: number; 45 | /** 46 | * Date and time, UTC 47 | */ 48 | dt: Date; 49 | /** 50 | * Date and time, Unix, UTC 51 | */ 52 | dtRaw: number; 53 | /** 54 | * Air Quality Index. 55 | */ 56 | aqi: number; 57 | /** 58 | * String substitute of aqi field (only english) 59 | */ 60 | aqiName: string; 61 | components: Components; 62 | } 63 | -------------------------------------------------------------------------------- /src/types/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./weather/index"; 2 | export * from "./air-pollution"; 3 | export * from "./options"; 4 | 5 | export interface Location { 6 | /** 7 | * Name of the found location 8 | */ 9 | name: string; 10 | /** 11 | * * local_names.[language code] - Name of the found location in different languages. The list of names can be different for different locations 12 | * * local_names.ascii - Internal field 13 | * * local_names.feature_name - Internal field 14 | */ 15 | local_names: any; 16 | /** 17 | * Geographical coordinates of the found location (latitude) 18 | */ 19 | lat: number; 20 | /** 21 | * Geographical coordinates of the found location (longitude) 22 | */ 23 | lon: number; 24 | /** 25 | * Country of the found location 26 | */ 27 | country: string; 28 | /** 29 | * State of the found location (where available) 30 | */ 31 | state: string | undefined; 32 | } 33 | -------------------------------------------------------------------------------- /src/types/options.ts: -------------------------------------------------------------------------------- 1 | import { SUP_LANGS, SUP_UNITS } from "../constants"; 2 | 3 | export type Language = typeof SUP_LANGS[number]; 4 | 5 | export type Unit = typeof SUP_UNITS[number]; 6 | 7 | export interface Coordinates { 8 | lat: number; 9 | lon: number; 10 | } 11 | 12 | export interface Options { 13 | key?: string; 14 | lang?: Language; 15 | units?: Unit; 16 | coordinates?: Coordinates; 17 | locationName?: string; 18 | zipCode?: string; 19 | } 20 | -------------------------------------------------------------------------------- /src/types/weather/current.ts: -------------------------------------------------------------------------------- 1 | import { Icon, WeatherBase, WindData } from "./index"; 2 | 3 | export interface CurrentTemperatures { 4 | /** 5 | * Current temperature 6 | */ 7 | cur: number; 8 | } 9 | 10 | export interface ExtendedCurrentTemperatures extends CurrentTemperatures { 11 | /** 12 | * Minimum temperature at the moment. This is minimal currently observed temperature (within large megalopolises and urban areas). 13 | */ 14 | min: number; 15 | /** 16 | * Maximum temperature at the moment. This is maximal currently observed temperature (within large megalopolises and urban areas). 17 | */ 18 | max: number; 19 | } 20 | 21 | export interface CurrentConditionsBase { 22 | /** 23 | * This accounts for the human perception of weather. Units – default: kelvin, metric: Celsius, imperial: Fahrenheit. 24 | */ 25 | feelsLike: CurrentTemperatures; 26 | /** 27 | * Atmospheric pressure on the sea level, hPa 28 | */ 29 | pressure: number; 30 | /** 31 | * Humidity, % 32 | */ 33 | humidity: number; 34 | /** 35 | * Atmospheric temperature (varying according to pressure and humidity) below which water droplets begin to condense and dew can form. Units – default: kelvin, metric: Celsius, imperial: Fahrenheit. 36 | */ 37 | dewPoint: number | undefined; 38 | /** 39 | * Cloudiness, % 40 | */ 41 | clouds: number; 42 | /** 43 | * The maximum value of UV index for the day 44 | */ 45 | uvi: number | undefined; 46 | /** 47 | * Average visibility, metres 48 | */ 49 | visibility: number; 50 | /** 51 | * Wind statistics. Units – default: metre/sec, metric: metre/sec, imperial: miles/hour. 52 | */ 53 | wind: WindData; 54 | /** 55 | * Precipitation volume, mm 56 | */ 57 | rain: number; 58 | /** 59 | * Snow volume, mm 60 | */ 61 | snow: number; 62 | /** 63 | * Weather condition id (https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2) 64 | */ 65 | conditionId: number; 66 | /** 67 | * Group of weather parameters (Rain, Snow, Extreme etc.) 68 | */ 69 | main: string; 70 | /** 71 | * Description of the weather 72 | */ 73 | description: string; 74 | icon: Icon; 75 | } 76 | 77 | export interface CurrentConditions extends CurrentConditionsBase { 78 | /** 79 | * Units – default: kelvin, metric: Celsius, imperial: Fahrenheit. 80 | */ 81 | temp: ExtendedCurrentTemperatures; 82 | } 83 | 84 | export interface OnecallCurrentConditions extends CurrentConditionsBase { 85 | /** 86 | * Units – default: kelvin, metric: Celsius, imperial: Fahrenheit. 87 | */ 88 | temp: CurrentTemperatures; 89 | } 90 | 91 | export interface CurrentAstronomical { 92 | /** 93 | * Sunrise time, Unix, UTC 94 | */ 95 | sunrise: Date; 96 | sunriseRaw: number; 97 | /** 98 | * Sunset time, Unix, UTC 99 | */ 100 | sunset: Date; 101 | sunsetRaw: number; 102 | } 103 | 104 | export interface CurrentWeather extends WeatherBase { 105 | astronomical: CurrentAstronomical; 106 | weather: CurrentConditions; 107 | } 108 | 109 | export interface OnecallCurrentWeather extends WeatherBase { 110 | astronomical: CurrentAstronomical; 111 | weather: OnecallCurrentConditions; 112 | } 113 | -------------------------------------------------------------------------------- /src/types/weather/daily.ts: -------------------------------------------------------------------------------- 1 | import { Icon, WeatherBase, WindData } from "./index"; 2 | 3 | export interface DailyTemperatures { 4 | /** 5 | * Morning temperature 6 | */ 7 | morn: number; 8 | /** 9 | * Day temperature 10 | */ 11 | day: number; 12 | /** 13 | * Evening temperature 14 | */ 15 | eve: number; 16 | /** 17 | * Night temperature 18 | */ 19 | night: number; 20 | /** 21 | * Lowest daily temperature 22 | */ 23 | min: number; 24 | /** 25 | * Highest daily temperature 26 | */ 27 | max: number; 28 | } 29 | 30 | export interface DailyFeelsLike { 31 | /** 32 | * Morning temperature 33 | */ 34 | morn: number; 35 | /** 36 | * Day temperature 37 | */ 38 | day: number; 39 | /** 40 | * Evening temperature 41 | */ 42 | eve: number; 43 | /** 44 | * Night temperature 45 | */ 46 | night: number; 47 | } 48 | 49 | export interface DailyConditions { 50 | /** 51 | * Units – default: kelvin, metric: Celsius, imperial: Fahrenheit. 52 | */ 53 | temp: DailyTemperatures; 54 | /** 55 | * This accounts for the human perception of weather. Units – default: kelvin, metric: Celsius, imperial: Fahrenheit. 56 | */ 57 | feelsLike: DailyFeelsLike; 58 | /** 59 | * Atmospheric pressure on the sea level, hPa 60 | */ 61 | pressure: number; 62 | /** 63 | * Humidity, % 64 | */ 65 | humidity: number; 66 | /** 67 | * Atmospheric temperature (varying according to pressure and humidity) below which water droplets begin to condense and dew can form. Units – default: kelvin, metric: Celsius, imperial: Fahrenheit. 68 | */ 69 | dewPoint: number; 70 | /** 71 | * Cloudiness, % 72 | */ 73 | clouds: number; 74 | /** 75 | * The maximum value of UV index for the day 76 | */ 77 | uvi: number; 78 | /** 79 | * Wind statistics. Units – default: metre/sec, metric: metre/sec, imperial: miles/hour. 80 | */ 81 | wind: WindData; 82 | /** 83 | * Probability of precipitation 84 | */ 85 | pop: number; 86 | /** 87 | * Precipitation volume, mm 88 | */ 89 | rain: number; 90 | /** 91 | * Snow volume, mm 92 | */ 93 | snow: number; 94 | /** 95 | * Weather condition id (https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2) 96 | */ 97 | conditionId: number; 98 | /** 99 | * Group of weather parameters (Rain, Snow, Extreme etc.) 100 | */ 101 | main: string; 102 | /** 103 | * Description of the weather 104 | */ 105 | description: string; 106 | /** 107 | * Human-readable text description of the daily forecast 108 | */ 109 | summary: string; 110 | icon: Icon; 111 | } 112 | 113 | export interface DailyAstronomical { 114 | /** 115 | * Sunrise time, Unix, UTC 116 | */ 117 | sunrise: Date; 118 | sunriseRaw: number; 119 | /** 120 | * Sunset time, Unix, UTC 121 | */ 122 | sunset: Date; 123 | sunsetRaw: number; 124 | /** 125 | * The time of when the moon rises for this day, Unix, UTC 126 | */ 127 | moonrise: Date; 128 | moonriseRaw: number; 129 | /** 130 | * The time of when the moon sets for this day, Unix, UTC 131 | */ 132 | moonset: Date; 133 | moonsetRaw: number; 134 | /** 135 | * Moon phase. 0 and 1 are 'new moon', 0.25 is 'first quarter moon', 0.5 is 'full moon' and 0.75 is 'last quarter moon'. The periods in between are called 'waxing crescent', 'waxing gibous', 'waning gibous', and 'waning crescent', respectively. 136 | */ 137 | moonPhase: number; 138 | } 139 | 140 | export interface DailyWeather extends WeatherBase { 141 | astronomical: DailyAstronomical; 142 | weather: DailyConditions; 143 | } 144 | -------------------------------------------------------------------------------- /src/types/weather/forecast.ts: -------------------------------------------------------------------------------- 1 | import { Icon, WeatherBase, WindData } from "./index"; 2 | 3 | export interface ForecastFeelsLike { 4 | /** 5 | * Estimated temperature 6 | */ 7 | cur: number; 8 | } 9 | 10 | export interface ForecastTemperatures extends ForecastFeelsLike { 11 | /** 12 | * Minimum temperature at the moment of calculation. This is minimal forecasted temperature (within large megalopolises and urban areas), use this parameter optionally. 13 | */ 14 | min: number; 15 | /** 16 | * Maximum temperature at the moment of calculation. This is maximal forecasted temperature (within large megalopolises and urban areas), use this parameter optionally. 17 | */ 18 | max: number; 19 | } 20 | 21 | export interface ForecastConditions { 22 | /** 23 | * Units – default: kelvin, metric: Celsius, imperial: Fahrenheit. 24 | */ 25 | temp: ForecastTemperatures; 26 | /** 27 | * This accounts for the human perception of weather. Units – default: kelvin, metric: Celsius, imperial: Fahrenheit. 28 | */ 29 | feelsLike: ForecastFeelsLike; 30 | /** 31 | * Atmospheric pressure on the sea level, hPa 32 | */ 33 | pressure: number; 34 | /** 35 | * Humidity, % 36 | */ 37 | humidity: number; 38 | /** 39 | * Cloudiness, % 40 | */ 41 | clouds: number; 42 | /** 43 | * Average visibility, metres 44 | */ 45 | visibility: number; 46 | /** 47 | * Wind statistics. Units – default: metre/sec, metric: metre/sec, imperial: miles/hour. 48 | */ 49 | wind: WindData; 50 | /** 51 | * Probability of precipitation 52 | */ 53 | pop: number; 54 | /** 55 | * Precipitation volume, mm 56 | */ 57 | rain: number; 58 | /** 59 | * Snow volume, mm 60 | */ 61 | snow: number; 62 | /** 63 | * Weather condition id (https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2) 64 | */ 65 | conditionId: number; 66 | /** 67 | * Group of weather parameters (Rain, Snow, Extreme etc.) 68 | */ 69 | main: string; 70 | /** 71 | * Description of the weather 72 | */ 73 | description: string; 74 | icon: Icon; 75 | } 76 | 77 | export interface ForecastAstronomical { 78 | /** 79 | * Sunrise time, Unix, UTC 80 | */ 81 | sunrise: Date; 82 | sunriseRaw: number; 83 | /** 84 | * Sunset time, Unix, UTC 85 | */ 86 | sunset: Date; 87 | sunsetRaw: number; 88 | } 89 | 90 | export interface ForecastWeather extends WeatherBase { 91 | astronomical: ForecastAstronomical; 92 | weather: ForecastConditions; 93 | } 94 | -------------------------------------------------------------------------------- /src/types/weather/historical.ts: -------------------------------------------------------------------------------- 1 | import { Icon, WeatherBase, WindData } from "./index"; 2 | 3 | export interface HistoricalTemperatures { 4 | /** 5 | * The temperature in that point in time 6 | */ 7 | cur: number; 8 | } 9 | 10 | export interface HistoricalConditions { 11 | /** 12 | * Units – default: kelvin, metric: Celsius, imperial: Fahrenheit. 13 | */ 14 | temp: HistoricalTemperatures; 15 | /** 16 | * This accounts for the human perception of weather. Units – default: kelvin, metric: Celsius, imperial: Fahrenheit. 17 | */ 18 | feelsLike: HistoricalTemperatures; 19 | /** 20 | * Atmospheric pressure on the sea level, hPa 21 | */ 22 | pressure: number; 23 | /** 24 | * Humidity, % 25 | */ 26 | humidity: number; 27 | /** 28 | * Atmospheric temperature (varying according to pressure and humidity) below which water droplets begin to condense and dew can form. Units – default: kelvin, metric: Celsius, imperial: Fahrenheit. 29 | */ 30 | dewPoint: number | undefined; 31 | /** 32 | * Cloudiness, % 33 | */ 34 | clouds: number; 35 | /** 36 | * The maximum value of UV index for the day 37 | */ 38 | uvi: number | undefined; 39 | /** 40 | * Average visibility, metres 41 | */ 42 | visibility: number; 43 | /** 44 | * Wind statistics. Units – default: metre/sec, metric: metre/sec, imperial: miles/hour. 45 | */ 46 | wind: WindData; 47 | /** 48 | * Precipitation volume, mm 49 | */ 50 | rain: number; 51 | /** 52 | * Snow volume, mm 53 | */ 54 | snow: number; 55 | /** 56 | * Weather condition id (https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2) 57 | */ 58 | conditionId: number; 59 | /** 60 | * Group of weather parameters (Rain, Snow, Extreme etc.) 61 | */ 62 | main: string; 63 | /** 64 | * Description of the weather 65 | */ 66 | description: string; 67 | icon: Icon; 68 | } 69 | 70 | export interface HistoricalAstronomical { 71 | /** 72 | * Sunrise time, Unix, UTC 73 | */ 74 | sunrise: Date; 75 | sunriseRaw: number; 76 | /** 77 | * Sunset time, Unix, UTC 78 | */ 79 | sunset: Date; 80 | sunsetRaw: number; 81 | } 82 | 83 | export interface HistoricalWeather extends WeatherBase { 84 | astronomical: HistoricalAstronomical; 85 | weather: HistoricalConditions; 86 | } 87 | -------------------------------------------------------------------------------- /src/types/weather/hourly.ts: -------------------------------------------------------------------------------- 1 | import { Icon, WeatherBase, WindData } from "./index"; 2 | 3 | export interface HourlyTemperatures { 4 | /** 5 | * Estimated temperature (in hourly forecast) 6 | */ 7 | cur: number; 8 | } 9 | 10 | export interface HourlyConditions { 11 | /** 12 | * Units – default: kelvin, metric: Celsius, imperial: Fahrenheit. 13 | */ 14 | temp: HourlyTemperatures; 15 | /** 16 | * This accounts for the human perception of weather. Units – default: kelvin, metric: Celsius, imperial: Fahrenheit. 17 | */ 18 | feelsLike: HourlyTemperatures; 19 | /** 20 | * Atmospheric pressure on the sea level, hPa 21 | */ 22 | pressure: number; 23 | /** 24 | * Humidity, % 25 | */ 26 | humidity: number; 27 | /** 28 | * Atmospheric temperature (varying according to pressure and humidity) below which water droplets begin to condense and dew can form. Units – default: kelvin, metric: Celsius, imperial: Fahrenheit. 29 | */ 30 | dewPoint: number; 31 | /** 32 | * Cloudiness, % 33 | */ 34 | clouds: number; 35 | /** 36 | * The maximum value of UV index for the day 37 | */ 38 | uvi: number; 39 | /** 40 | * Average visibility, metres 41 | */ 42 | visibility: number; 43 | /** 44 | * Wind statistics. Units – default: metre/sec, metric: metre/sec, imperial: miles/hour. 45 | */ 46 | wind: WindData; 47 | /** 48 | * Probability of precipitation 49 | */ 50 | pop: number; 51 | /** 52 | * Precipitation volume, mm 53 | */ 54 | rain: number; 55 | /** 56 | * Snow volume, mm 57 | */ 58 | snow: number; 59 | /** 60 | * Weather condition id (https://openweathermap.org/weather-conditions#Weather-Condition-Codes-2) 61 | */ 62 | conditionId: number; 63 | /** 64 | * Group of weather parameters (Rain, Snow, Extreme etc.) 65 | */ 66 | main: string; 67 | /** 68 | * Description of the weather 69 | */ 70 | description: string; 71 | icon: Icon; 72 | } 73 | 74 | export interface HourlyWeather extends WeatherBase { 75 | astronomical: {}; 76 | weather: HourlyConditions; 77 | } 78 | -------------------------------------------------------------------------------- /src/types/weather/index.ts: -------------------------------------------------------------------------------- 1 | import { CurrentWeather, OnecallCurrentWeather } from "./current"; 2 | import { ForecastWeather } from "./forecast"; 3 | import { MinutelyWeather } from "./minutely"; 4 | import { HourlyWeather } from "./hourly"; 5 | import { DailyWeather } from "./daily"; 6 | import { HistoricalWeather } from "./historical"; 7 | 8 | export * from "./current"; 9 | export * from "./forecast"; 10 | export * from "./minutely"; 11 | export * from "./hourly"; 12 | export * from "./daily"; 13 | export * from "./historical"; 14 | 15 | export interface Alert { 16 | /** 17 | * Name of the alert source. Please read here the full list of alert sources: https://openweathermap.org/api/one-call-api#listsource 18 | */ 19 | sender_name: string; 20 | /** 21 | * Alert event name 22 | */ 23 | event: string; 24 | /** 25 | * Date and time of the start of the alert, Unix, UTC 26 | */ 27 | start: number; 28 | /** 29 | * Date and time of the end of the alert, Unix, UTC 30 | */ 31 | end: number; 32 | /** 33 | * Description of the alert 34 | */ 35 | description: string; 36 | /** 37 | * Type of severe weather 38 | */ 39 | tags: string[]; 40 | } 41 | 42 | export interface WindData { 43 | /** 44 | * Wind speed 45 | */ 46 | speed: number; 47 | /** 48 | * Wind gust 49 | */ 50 | gust: number | undefined; 51 | /** 52 | * Wind direction, degrees (meteorological) 53 | */ 54 | deg: number; 55 | } 56 | 57 | export interface Icon { 58 | url: string; 59 | raw: string; 60 | } 61 | 62 | export interface WeatherBase { 63 | /** 64 | * Geographical coordinates of the location (latitude) 65 | */ 66 | lat: number; 67 | /** 68 | * Geographical coordinates of the location (longitude) 69 | */ 70 | lon: number; 71 | /** 72 | * Date and time, UTC 73 | */ 74 | dt: Date; 75 | /** 76 | * Date and time, Unix, UTC 77 | */ 78 | dtRaw: number; 79 | /** 80 | * Timezone name for the requested location 81 | */ 82 | timezone: string | undefined; 83 | /** 84 | * Date and time, Unix, UTC 85 | */ 86 | timezoneOffset: number; 87 | } 88 | 89 | export type Weather = WeatherBase & 90 | CurrentWeather & 91 | ForecastWeather & 92 | MinutelyWeather & 93 | HourlyWeather & 94 | DailyWeather & 95 | HistoricalWeather; 96 | 97 | export interface Everything { 98 | lat: number; 99 | lon: number; 100 | timezone: string; 101 | timezoneOffset: number; 102 | current: OnecallCurrentWeather; 103 | minutely: MinutelyWeather[]; 104 | hourly: HourlyWeather[]; 105 | daily: DailyWeather[]; 106 | alerts: Alert[]; 107 | } 108 | -------------------------------------------------------------------------------- /src/types/weather/minutely.ts: -------------------------------------------------------------------------------- 1 | import { WeatherBase } from "./index"; 2 | 3 | export interface MinutelyConditions { 4 | /** 5 | * Units – default: kelvin, metric: Celsius, imperial: Fahrenheit. 6 | */ 7 | temp: {}; 8 | /** 9 | * This accounts for the human perception of weather. Units – default: kelvin, metric: Celsius, imperial: Fahrenheit. 10 | */ 11 | feelsLike: {}; 12 | /** 13 | * Wind statistics. Units – default: metre/sec, metric: metre/sec, imperial: miles/hour. 14 | */ 15 | wind: {}; 16 | /** 17 | * Precipitation volume, mm 18 | */ 19 | rain: number; 20 | icon: {}; 21 | } 22 | 23 | export interface MinutelyWeather extends WeatherBase { 24 | astronomical: {}; 25 | weather: MinutelyConditions; 26 | } 27 | -------------------------------------------------------------------------------- /test/errors.test.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import OpenWeatherAPI, { Language, Unit } from "../src"; 3 | 4 | // ! Remeber to specify key in key.txt file 5 | const key = fs.readFileSync("./test/key.txt").toString().trim(); 6 | 7 | describe("Error tests:", () => { 8 | const emptyValues = ["", 0, null, undefined, false, NaN]; 9 | 10 | const weather = new OpenWeatherAPI({ 11 | key, locationName: "Hong Kong" 12 | }); 13 | 14 | it("handles invalid key", async () => { 15 | try { 16 | await weather.getCurrent({ key: "ptero" }); 17 | } catch (err: any) { 18 | err = JSON.parse(err.message); 19 | expect(err.cod).toBe(401); 20 | } 21 | }); 22 | 23 | it("handles wrong coordinates", () => { 24 | expect( 25 | () => weather.setLocationByCoordinates("-200" as any, 78) 26 | ).toThrow(/invalid coordinates/i); 27 | }); 28 | 29 | it("handles wrong location name", async () => { 30 | await expect( 31 | weather.getCurrent({ locationName: "ptero" }) 32 | ).rejects.toThrow(/ptero/i); 33 | }); 34 | 35 | it("handles wrong language", () => { 36 | expect( 37 | () => weather.setLanguage("ptero" as Language) 38 | ).toThrow(/ptero/i); 39 | }); 40 | 41 | it("handles wrong unit", () => { 42 | expect( 43 | () => weather.setUnits("ptero" as Unit) 44 | ).toThrow(/ptero/i); 45 | }); 46 | 47 | it("handles unknown parameter", async () => { 48 | await expect( 49 | weather.getCurrent({ ptero: "" } as any) 50 | ).rejects.toThrow(/ptero/i); 51 | }); 52 | 53 | it("handles wrong type of option argument", async () => { 54 | await expect( 55 | weather.getCurrent("ptero" as {}) 56 | ).rejects.toThrow(/provide {}/i); 57 | }); 58 | 59 | it("handles empty location name", () => { 60 | emptyValues.forEach((element) => { 61 | expect( 62 | () => weather.setLocationByName(element as string) 63 | ).toThrow(/empty/i); 64 | }); 65 | }); 66 | 67 | it("handles empty key", () => { 68 | emptyValues.forEach((element) => { 69 | expect( 70 | () => weather.setKey(element as string) 71 | ).toThrow(/empty/i); 72 | }); 73 | }); 74 | 75 | it("handles future in history", async () => { 76 | await expect( 77 | weather.getHistory(new Date().getTime() + 365 * 24 * 60 * 60 * 1000) 78 | ).rejects.toThrow(/requested time is out the available range/i); 79 | }); 80 | 81 | it("handles no time in history", async () => { 82 | await expect( 83 | // @ts-ignore 84 | weather.getHistory() 85 | ).rejects.toThrow(/provide time/i); 86 | }); 87 | }); 88 | -------------------------------------------------------------------------------- /test/getter.test.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import OpenWeatherAPI from "../src"; 3 | 4 | // ! Remeber to specify key in key.txt file 5 | const key = fs.readFileSync("./test/key.txt").toString().trim(); 6 | 7 | describe("Getting tests:", () => { 8 | const weather = new OpenWeatherAPI({ key }); 9 | 10 | it("gets location", async () => { 11 | weather.setLocationByCoordinates(40.71, -74); 12 | let location = await weather.getLocation(); 13 | expect(location!.name).toMatch(/new york/i); 14 | }); 15 | 16 | it("gets all locations", async () => { 17 | let locations = await weather.getAllLocations("Lousã"); 18 | expect(locations.length).toBeGreaterThan(0); 19 | }); 20 | 21 | it("gets current", async () => { 22 | weather.setLocationByName("warsaw"); 23 | let current = await weather.getCurrent(); 24 | 25 | expect(typeof current.weather.temp.cur).toBe("number"); 26 | expect(current.weather.temp.min).toBeLessThanOrEqual(current.weather.temp.cur); 27 | expect(current.weather.temp.max).toBeGreaterThanOrEqual(current.weather.temp.cur); 28 | }); 29 | 30 | it("gets forecast", async () => { 31 | let forecast = await weather.getForecast(20); 32 | 33 | expect(forecast).toBeInstanceOf(Array); 34 | 35 | if (!forecast.length) { 36 | console.log("\t\x1b[31mEmpty forecast: ", forecast); 37 | } else { 38 | expect(forecast.length).toBe(20); 39 | expect(typeof forecast[Math.floor(Math.random() * 17)].weather.rain).toBe( 40 | "number" 41 | ); 42 | } 43 | }); 44 | 45 | it("gets minutely", async () => { 46 | weather.setLocationByCoordinates(40.71, -74); 47 | let minutely = await weather.getMinutelyForecast(48); 48 | 49 | expect(minutely).toBeInstanceOf(Array); 50 | 51 | if (!minutely.length) { 52 | console.log("\t\x1b[31mEmpty minutely: ", minutely); 53 | } else { 54 | expect(minutely.length).toBe(48); 55 | expect(typeof minutely[Math.floor(Math.random() * 40)].weather.rain).toBe( 56 | "number" 57 | ); 58 | } 59 | }); 60 | 61 | it("gets hourly", async () => { 62 | weather.setLocationByZipCode("E14,GB"); 63 | let hourly = await weather.getHourlyForecast(10); 64 | 65 | expect(hourly).toBeInstanceOf(Array); 66 | 67 | if (!hourly.length) { 68 | console.log("\t\x1b[31mEmpty hourly: ", hourly); 69 | } else { 70 | expect(hourly.length).toBe(10); 71 | expect(typeof hourly[Math.floor(Math.random() * 8)].weather.rain).toBe( 72 | "number" 73 | ); 74 | } 75 | }); 76 | 77 | it("gets daily", async () => { 78 | weather.setLocationByCoordinates(10, -40); 79 | let daily = await weather.getDailyForecast(3); 80 | 81 | expect(daily).toBeInstanceOf(Array); 82 | 83 | if (!daily.length) { 84 | console.log("\t\x1b[31mEmpty daily: ", daily); 85 | } else { 86 | expect(daily.length).toBe(3); 87 | expect(typeof daily[Math.floor(Math.random() * 2)].weather.rain).toBe( 88 | "number" 89 | ); 90 | } 91 | }); 92 | 93 | it("gets alerts", async () => { 94 | weather.setLocationByName("Giza"); 95 | let alerts = await weather.getAlerts(); 96 | expect(alerts).toBeInstanceOf(Array); 97 | }); 98 | 99 | it("gets everything", async () => { 100 | weather.setLocationByCoordinates(0, 0); 101 | let everything = await weather.getEverything(); 102 | 103 | expect(typeof everything.current.weather.temp.cur).toBe("number"); 104 | 105 | expect(everything.minutely).toBeInstanceOf(Array); 106 | 107 | expect( 108 | typeof everything.hourly[Math.floor(Math.random() * 20)].weather.rain 109 | ).toBe("number"); 110 | 111 | expect( 112 | typeof everything.daily[Math.floor(Math.random() * 5)].weather.rain 113 | ).toBe("number"); 114 | }); 115 | 116 | it("gets history", async () => { 117 | weather.setLocationByCoordinates(49.84, 24.03); 118 | let date = new Date().getTime() - 900000; 119 | let history = await weather.getHistory(date); 120 | expect(Math.round(date / 1000)).toBe(history.dtRaw); 121 | }); 122 | 123 | it("gets current air pollution", async () => { 124 | let pollution = await weather.getCurrentAirPollution({ 125 | locationName: "Paris", 126 | }); 127 | 128 | expect( 129 | Object.values(pollution.components).every((v) => typeof v === "number") 130 | ).toBeTruthy(); 131 | }); 132 | 133 | it("gets forecasted air pollution", async () => { 134 | let pollution = await weather.getForecastedAirPollution(10, { 135 | locationName: "Chicago", 136 | }); 137 | 138 | expect(pollution.length).toBe(10); 139 | expect( 140 | Object.values(pollution[Math.floor(Math.random() * 9)].components).every( 141 | (v) => typeof v === "number" 142 | ) 143 | ).toBeTruthy(); 144 | }); 145 | 146 | it("gets historical air pollution", async () => { 147 | let currentDate = new Date(); 148 | let dateFrom12HoursAgo = new Date().setHours(currentDate.getHours() - 12); 149 | 150 | let pollution = await weather.getHistoryAirPollution( 151 | dateFrom12HoursAgo, 152 | currentDate, 153 | { coordinates: { lat: 10, lon: 10 } } 154 | ); 155 | 156 | expect(pollution.length).toBe(12); 157 | expect( 158 | Object.values(pollution[Math.floor(Math.random() * 10)].components).every( 159 | (v) => typeof v === "number" 160 | ) 161 | ).toBeTruthy(); 162 | }); 163 | }); 164 | -------------------------------------------------------------------------------- /test/options.test.ts: -------------------------------------------------------------------------------- 1 | import fs from "fs"; 2 | import OpenWeatherAPI from "../src"; 3 | 4 | // ! Remeber to specify key in key.txt file 5 | const key = fs.readFileSync("./test/key.txt").toString().trim(); 6 | 7 | describe("Options tests:", () => { 8 | const weather = new OpenWeatherAPI(); 9 | 10 | it("handles key in options", async () => { 11 | await weather.getCurrent({ 12 | locationName: "Ottawa", key 13 | }); 14 | }); 15 | 16 | it("prioritizes options over global options", async () => { 17 | weather.setKey(key); 18 | weather.setLocationByName("London") 19 | 20 | const tokioWeather = await weather.getCurrent({ 21 | locationName: "Tokio" 22 | }); 23 | 24 | const londonWeather = await weather.getCurrent(); 25 | 26 | expect(tokioWeather.lat).not.toEqual(londonWeather.lat); 27 | }); 28 | }); 29 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["./src/**/*"], 3 | "compilerOptions": { 4 | "target": "ES2020", 5 | "esModuleInterop": true, 6 | "moduleResolution": "node", 7 | "module": "CommonJS", 8 | "declaration": true, 9 | "strict": true, 10 | "baseUrl": "./src", 11 | "rootDir": "./src", 12 | "outDir": "./dist" 13 | } 14 | } 15 | --------------------------------------------------------------------------------