├── .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 |
5 |
6 |
7 |
8 | Simple package that makes it easy to work with OpenWeather API.
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
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 |
--------------------------------------------------------------------------------