salat 3.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,9 +1,10 @@
1
1
  # Salat [![npm version](https://badge.fury.io/js/salat.svg)](https://badge.fury.io/js/salat)
2
2
 
3
- ## Description
3
+ PLEASE SUPPORT THIS REPO WITH A STAR ⭐🌟💫
4
4
 
5
- > Daily prayer time in all the cities in [Morocco](https://www.google.com/search?q=morocco) :morocco:, directly in your terminal, at the tip of your fingers
5
+ ## Description
6
6
 
7
+ > Daily prayer time in all the cities in [Morocco](https://www.google.com/search?q=morocco) , directly in your terminal, at the tip of your fingers
7
8
 
8
9
  **A stupid simple Command line utility to get the daily prayers time for all the citiy in Morocco**
9
10
 
@@ -11,6 +12,215 @@ The source of the data is [the Moroccan Ministery Website](http://www.habous.gov
11
12
 
12
13
  ## Getting started
13
14
 
15
+ ```bash
16
+ npx salat [cityName]
17
+ ```
18
+
19
+ Please note that if the cityName contains space of `'` you need to use quotes, example
20
+
21
+ ```bash
22
+ npx salat "el jadida"
23
+ npx salat "L'msid"
24
+ ```
25
+
26
+ ## City List
27
+
28
+ This is an exhaustive list of the supported cities :
29
+
30
+ - Agadir
31
+ - Ahfir
32
+ - Ain Aouda
33
+ - Aïn Chaïr
34
+ - Ait El Kak
35
+ - Ait Ourir
36
+ - Akayouar
37
+ - Akdal Amelchil
38
+ - Akdez
39
+ - Akhefnir
40
+ - Akka
41
+ - Aknoul
42
+ - Akoudal Amelchil Midelt
43
+ - Amkala
44
+ - Amsmrir
45
+ - Araich
46
+ - Arbaoua
47
+ - Arfoud
48
+ - Asa
49
+ - Askin
50
+ - Asoul
51
+ - Assila
52
+ - Azemmour
53
+ - Azilal
54
+ - Azrou
55
+ - Bab Bared
56
+ - Ben Ahmed
57
+ - Ben Slimane
58
+ - Bengrir
59
+ - Beni Adrar
60
+ - Beni Ansar
61
+ - Beni Mellal
62
+ - Beni Tejit
63
+ - Berkane
64
+ - Berrchid
65
+ - Bir Anzaran
66
+ - Bir Kandour
67
+ - Bouanan
68
+ - Bouarfa
69
+ - Bouikra
70
+ - Bouizkaren
71
+ - Boujdour
72
+ - Boukrae
73
+ - Boulmane
74
+ - Boumalen Dadas
75
+ - Bourd
76
+ - Bouskoura
77
+ - Bouznika
78
+ - Casablanca
79
+ - Chefchaouan
80
+ - Chichaoua
81
+ - Dakhla
82
+ - Debdou
83
+ - Demnat
84
+ - Deryouche
85
+ - El Brouj
86
+ - El Gara
87
+ - El Hajeb
88
+ - El Jabha
89
+ - El Jadida
90
+ - El Kasba
91
+ - El Mahbes
92
+ - El Menzel
93
+ - Enif
94
+ - Erich
95
+ - Errachidia
96
+ - Essaouira
97
+ - Ezak
98
+ - Fem Lehsan
99
+ - Fem Zkid
100
+ - Ferkhana
101
+ - Fes
102
+ - Fezouane
103
+ - Figuig
104
+ - Fnideq
105
+ - Fquih Ben Salah
106
+ - Geltat Zamour
107
+ - Guelmim
108
+ - Guercif
109
+ - Hoceima
110
+ - Igherem
111
+ - Ikes
112
+ - Imelchil
113
+ - Imin Telat
114
+ - Imntanout
115
+ - Imouzzer Kandar
116
+ - Jerada
117
+ - Kalaat Megouna
118
+ - Kalaat Sraghna
119
+ - Kares
120
+ - Kariat Ba Mohammed
121
+ - Kasbah Tadla
122
+ - Katara
123
+ - Kelmima
124
+ - Kénitra
125
+ - Ketama
126
+ - Khemis Sidi Abd Jelil
127
+ - Khemis Zemamra
128
+ - Khémissat
129
+ - Khenifra
130
+ - Khouribga
131
+ - Ksar El Kebir
132
+ - Ksar El Sghir
133
+ - Ksar Ich
134
+ - L'msid
135
+ - Laayoune
136
+ - Laayoune Sidi Mellouk
137
+ - Lagouira
138
+ - Marrakech
139
+ - Martil
140
+ - Mediek
141
+ - Meknes
142
+ - Melilla
143
+ - Meskoura
144
+ - Metmata
145
+ - Midelt
146
+ - Misour
147
+ - Mohammedia
148
+ - Moulay Bouaza
149
+ - Moulay Bousselham
150
+ - Moulay Yacoub
151
+ - Nador
152
+ - Oualidia
153
+ - Ouazane
154
+ - Oued Amlil
155
+ - Oued Law
156
+ - Oued Zam
157
+ - Oujda
158
+ - Oulad Ayad
159
+ - Oulad Tayma
160
+ - Oulmes
161
+ - Ourzazate
162
+ - Ousered
163
+ - Rabat
164
+ - Ras Alma
165
+ - Remani
166
+ - Rhamna
167
+ - Ribate El Kheir
168
+ - Risani
169
+ - Safi
170
+ - Saïdia
171
+ - Sebta
172
+ - Sefrou
173
+ - Selouane
174
+ - Settat
175
+ - Sidi Benour
176
+ - Sidi Ghanem
177
+ - Sidi Ifni
178
+ - Sidi Kacém
179
+ - Sidi Slimane
180
+ - Sidi Yahya Gharb
181
+ - Smara
182
+ - Souq Arbiâ Gharb
183
+ - Tafntan
184
+ - Tafougalt
185
+ - Tafraouet
186
+ - Tahla
187
+ - Tahla
188
+ - Talouine
189
+ - Talsint
190
+ - Tamslouhet
191
+ - Tanger
192
+ - Tantan
193
+ - Taounat
194
+ - Taourirt
195
+ - Tarfaya
196
+ - Taroudant
197
+ - Tasltanet
198
+ - Tata
199
+ - Taza
200
+ - Tazarin
201
+ - Tefariti
202
+ - Temnar
203
+ - Tendrara
204
+ - Tenghir
205
+ - Tenjedad
206
+ - Tetouan
207
+ - Tiflet
208
+ - Tindit
209
+ - Tisa
210
+ - Tisenet
211
+ - Tizi Ousli
212
+ - Tiznit
213
+ - Toulkoult
214
+ - Yefrin
215
+ - Yousofia
216
+ - Zagoura
217
+ - Zaouiat Ahansal
218
+ - Zaouiat Moulay Ibrahim
219
+ - Zayou
220
+ - Zerhoune
221
+
222
+ Alternatively You can also install it globally
223
+
14
224
  ```bash
15
225
  # Install
16
226
 
@@ -25,33 +235,16 @@ $ salat
25
235
  $ salat [cityName]
26
236
  ```
27
237
 
28
- Alternatively, if you don't want to install it globally, you can just use:
29
-
30
- ```bash
31
- npx salat
32
- ```
33
-
34
- `City name should be provided the same way it's written in the cities.json`
35
-
36
238
  ## Output
37
239
 
38
240
  ```bash
39
241
  # The programs prints to the console the prayers' time for the current day in the default city as shown bellow:
40
242
  ```
243
+
41
244
  ![screen 1](images/screenShot1.png)
42
245
  ![screen 2](images/screenShot2.png)
43
246
  ![screen 3](images/screenShot3.png)
44
247
 
45
-
46
- ## Dependecies
47
-
48
- The code behind depends on :
49
-
50
- - [axios](https://github.com/axios/axios) to make an http request ( fetch the data).
51
- - [jsdom](https://github.com/jsdom/jsdom) to parse the html result.
52
- - [chalk](https://github.com/chalk/chalk) to avoid boring styles and colors.
53
- - [node-localstorage](https://github.com/lmaccherone/node-localstorage) to read and write from localstorage.
54
-
55
248
  ## Change the default city
56
249
 
57
250
  - The default city is :heart: [Marrakech](https://www.google.com/search?q=marrakech) :heart:, set as a value for the `DEFAULT_CITY` variable in `./constants.js`
@@ -68,10 +261,9 @@ The code behind depends on :
68
261
 
69
262
  - [x] Use a default city
70
263
  - [x] Use localstorage-like api for caching purposes
71
- - [x] Display execution time
72
264
  - [ ] Improve performance
73
265
  - [ ] Add unit tests
74
- - [ ] Add a documentation site
266
+ - [x] Add a documentation site
75
267
  - [ ] Command to set the default city
76
268
  - [ ] Command to display the list of available cities
77
269
  - [ ] Command to display the time table for the whole month
@@ -80,4 +272,4 @@ The code behind depends on :
80
272
 
81
273
  This project is under the MIT license.
82
274
 
83
- ### Built With :heart: in Ramadan
275
+ ### Built With 💖 in Ramadan
package/dist/app.js ADDED
@@ -0,0 +1,58 @@
1
+ #!/usr/bin/env node
2
+ "use strict";
3
+ var __importDefault = (this && this.__importDefault) || function (mod) {
4
+ return (mod && mod.__esModule) ? mod : { "default": mod };
5
+ };
6
+ Object.defineProperty(exports, "__esModule", { value: true });
7
+ // Project's dependencies
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ const utils_1 = require("./utils");
10
+ // Project's data
11
+ const constants_1 = require("./constants");
12
+ const cities_json_1 = __importDefault(require("./data/cities.json"));
13
+ // Setting up localStorage
14
+ const node_localstorage_1 = require("node-localstorage");
15
+ const args = process.argv;
16
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
17
+ // Logging function
18
+ const green = (msg) => console.log(chalk_1.default.green(msg));
19
+ // Cast citiesData to City[] properly
20
+ const cities = cities_json_1.default;
21
+ const localStorage = new node_localstorage_1.LocalStorage(constants_1.LOCAL_STORAGE_PATH);
22
+ const cityNameArg = args[2];
23
+ const cityName = (0, utils_1.getCityName)(cityNameArg, cities);
24
+ // Convert string ID to number since getCityId returns number and getData expects number
25
+ const cityId = (0, utils_1.getCityId)(cityName, cities);
26
+ const main = async () => {
27
+ // Printing a banner ('cause I'm cool and I can do it XD)
28
+ green(constants_1.BANNER);
29
+ const storageKey = `${cityName.toLowerCase()}_${new Date().toLocaleDateString()}`;
30
+ let item = localStorage.getItem(storageKey);
31
+ // Disable localStorage for local development
32
+ if (process.env.NODE_ENV === "development") {
33
+ console.log("development mode: localStorage is disabled");
34
+ item = null;
35
+ }
36
+ let prayers = null;
37
+ if (item) {
38
+ prayers = JSON.parse(item);
39
+ }
40
+ else {
41
+ try {
42
+ const data = await (0, utils_1.getData)(cityId);
43
+ prayers = (0, utils_1.parsePrayerTimesFromResponse)(data);
44
+ localStorage.setItem(storageKey, JSON.stringify(prayers));
45
+ }
46
+ catch (ex) {
47
+ //TODO: Use a more descriptive error message
48
+ console.error("Something went wrong!");
49
+ console.log(ex);
50
+ return;
51
+ }
52
+ }
53
+ console.clear();
54
+ (0, utils_1.displayResult)(prayers, cityName);
55
+ };
56
+ (async () => {
57
+ await main();
58
+ })();
@@ -0,0 +1,13 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.LOCAL_STORAGE_PATH = exports.DEFAULT_CITY = exports.NOT_FOUND_ERROR = exports.BANNER = exports.API_URL = void 0;
4
+ exports.API_URL = "https://www.habous.gov.ma/prieres/horaire-api.php";
5
+ exports.BANNER = ``;
6
+ exports.NOT_FOUND_ERROR = `
7
+ Your city was not found in the list
8
+ Using the default city
9
+ ----------------------
10
+ You may need to check the spelling
11
+ `;
12
+ exports.DEFAULT_CITY = "Marrakech";
13
+ exports.LOCAL_STORAGE_PATH = "./storage";
@@ -0,0 +1,20 @@
1
+ [
2
+ {
3
+ "prayer": "Fajr"
4
+ },
5
+ {
6
+ "prayer": "Chorouq"
7
+ },
8
+ {
9
+ "prayer": "Dhuhr"
10
+ },
11
+ {
12
+ "prayer": "Asr"
13
+ },
14
+ {
15
+ "prayer": "Maghrib"
16
+ },
17
+ {
18
+ "prayer": "Ishae"
19
+ }
20
+ ]
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/dist/utils.js ADDED
@@ -0,0 +1,76 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.displayResult = exports.parsePrayerTimesFromResponse = exports.getData = exports.getCityId = exports.getCityName = void 0;
7
+ const chalk_1 = __importDefault(require("chalk"));
8
+ const domino_1 = __importDefault(require("domino"));
9
+ const node_fetch_1 = __importDefault(require("node-fetch"));
10
+ const constants_1 = require("./constants");
11
+ const prayers_json_1 = __importDefault(require("./data/prayers.json"));
12
+ const error = (msg) => console.log(chalk_1.default.red(msg));
13
+ const getCityName = (arg, cities) => {
14
+ if (arg == null)
15
+ return constants_1.DEFAULT_CITY;
16
+ const index = getCityIndex(arg, cities);
17
+ if (index === -1) {
18
+ error(constants_1.NOT_FOUND_ERROR);
19
+ return constants_1.DEFAULT_CITY;
20
+ }
21
+ return arg;
22
+ };
23
+ exports.getCityName = getCityName;
24
+ const getCityId = (arg, cities) => {
25
+ const parsed = parseInt(arg);
26
+ if (parsed && cities.length >= parsed) {
27
+ return parsed;
28
+ }
29
+ return getCityIndex(arg, cities) + 1;
30
+ };
31
+ exports.getCityId = getCityId;
32
+ const getCityIndex = (city, cities) => cities.map((e) => e.name.toLowerCase()).indexOf(city.toLowerCase());
33
+ const getData = async (cityId) => {
34
+ const response = await (0, node_fetch_1.default)(`${constants_1.API_URL}?ville=${cityId}`, {});
35
+ return await response.text();
36
+ };
37
+ exports.getData = getData;
38
+ const parsePrayerTimesFromResponse = (response) => {
39
+ const window = domino_1.default.createWindow(response);
40
+ const document = window.document;
41
+ const tds = document.getElementsByTagName("td");
42
+ const prayers = JSON.parse(JSON.stringify(prayers_json_1.default));
43
+ let j = 0;
44
+ for (let i = 1; i < tds.length && j < prayers.length; i += 2) {
45
+ prayers[j].time = tds[i].textContent.trim();
46
+ j++;
47
+ }
48
+ // Transform array to object and return it
49
+ return prayers.reduce((acc, { prayer, time }) => {
50
+ if (time) {
51
+ acc[prayer] = time;
52
+ }
53
+ return acc;
54
+ }, {});
55
+ };
56
+ exports.parsePrayerTimesFromResponse = parsePrayerTimesFromResponse;
57
+ function tConv24(time24) {
58
+ const [hours, minutes] = time24.split(":");
59
+ const hour = Number(hours);
60
+ const formattedHour = hour % 12 || 12;
61
+ const formattedHourWithZero = (formattedHour + "").padStart(2, "0");
62
+ const formattedMinutes = minutes.padStart(2, "0");
63
+ const formattedTime = `${formattedHourWithZero}:${formattedMinutes}`;
64
+ const ampm = hour < 12 ? "AM" : "PM";
65
+ return `${formattedTime} ${ampm}`;
66
+ }
67
+ const displayResult = (prayers, city) => {
68
+ if (!prayers)
69
+ return;
70
+ console.log(` 🧭 ${city}, Morocco\n\n 📆 ${new Date().toDateString()}\n`);
71
+ Object.keys(prayers).forEach((key) => {
72
+ console.log(` ${chalk_1.default.cyan(key.padEnd(7, " "))} --> ${chalk_1.default.green(tConv24(prayers[key]))}`);
73
+ });
74
+ console.log("\n");
75
+ };
76
+ exports.displayResult = displayResult;
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "salat",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Daily Moroccan prayers time, right in your console, at the tip of your fingers",
5
- "homepage": "https://github.com/kafiln/salat-cli",
6
- "main": "app.js",
5
+ "homepage": "https://kafiln.github.io/salat-cli/",
6
+ "main": "dist/app.js",
7
7
  "bin": {
8
- "salat": "app.js"
8
+ "salat": "dist/app.js"
9
9
  },
10
10
  "dependencies": {
11
11
  "chalk": "^2.4.2",
@@ -14,8 +14,11 @@
14
14
  "node-localstorage": "^1.3.1"
15
15
  },
16
16
  "scripts": {
17
- "dev": "env NODE_ENV=development nodemon app.js",
18
- "start": "node app"
17
+ "dev": "ts-node src/app.ts",
18
+ "build": "tsc",
19
+ "start": "node dist/app.js",
20
+ "prestart": "npm run build",
21
+ "prepublishOnly": "npm run build"
19
22
  },
20
23
  "keywords": [
21
24
  "prayers",
@@ -29,6 +32,11 @@
29
32
  },
30
33
  "license": "MIT",
31
34
  "devDependencies": {
32
- "nodemon": "^3.1.0"
35
+ "@types/node": "^25.2.1",
36
+ "@types/node-fetch": "^2.6.13",
37
+ "@types/node-localstorage": "^1.3.3",
38
+ "nodemon": "^3.1.0",
39
+ "ts-node": "^10.9.2",
40
+ "typescript": "^5.9.3"
33
41
  }
34
42
  }
@@ -1,33 +1,41 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // Project's dependencies
4
- const chalk = require("chalk");
5
- const args = process.argv;
6
- const {
4
+ import chalk from "chalk";
5
+ import {
6
+ displayResult,
7
7
  getCityId,
8
8
  getCityName,
9
- displayResult,
10
9
  getData,
11
10
  parsePrayerTimesFromResponse,
12
- } = require("./utils.js");
13
- process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
14
-
15
- // Logging functioin
16
- const green = (msg) => console.log(chalk.green(msg));
11
+ } from "./utils";
17
12
 
18
13
  // Project's data
19
- const { BANNER, LOCAL_STORAGE_PATH } = require("./constants");
20
- const cities = require("./data/cities.json");
14
+ import { BANNER, LOCAL_STORAGE_PATH } from "./constants";
15
+ import citiesData from "./data/cities.json";
16
+ import { City, PrayerTimes } from "./types";
21
17
 
22
18
  // Setting up localStorage
23
- const { LocalStorage } = require("node-localstorage");
24
- localStorage = new LocalStorage(LOCAL_STORAGE_PATH);
19
+ import { LocalStorage } from "node-localstorage";
20
+
21
+ const args = process.argv;
22
+ process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
23
+
24
+ // Logging function
25
+ const green = (msg: string) => console.log(chalk.green(msg));
26
+
27
+ // Cast citiesData to City[] properly
28
+ const cities: City[] = citiesData as City[];
29
+
30
+ const localStorage = new LocalStorage(LOCAL_STORAGE_PATH);
25
31
 
26
- const cityName = getCityName(args[2], cities);
32
+ const cityNameArg = args[2];
33
+ const cityName = getCityName(cityNameArg, cities);
34
+ // Convert string ID to number since getCityId returns number and getData expects number
27
35
  const cityId = getCityId(cityName, cities);
28
36
 
29
37
  const main = async () => {
30
- // Prinitng a banner ('cause I'm cool and I can do it XD)
38
+ // Printing a banner ('cause I'm cool and I can do it XD)
31
39
  green(BANNER);
32
40
 
33
41
  const storageKey = `${cityName.toLowerCase()}_${new Date().toLocaleDateString()}`;
@@ -38,19 +46,19 @@ const main = async () => {
38
46
  console.log("development mode: localStorage is disabled");
39
47
  item = null;
40
48
  }
41
- let prayers;
49
+ let prayers: PrayerTimes | null = null;
42
50
 
43
51
  if (item) {
44
52
  prayers = JSON.parse(item);
45
53
  } else {
46
54
  try {
47
- prayers = parsePrayerTimesFromResponse(await getData(cityId));
55
+ const data = await getData(cityId);
56
+ prayers = parsePrayerTimesFromResponse(data);
48
57
  localStorage.setItem(storageKey, JSON.stringify(prayers));
49
58
  } catch (ex) {
50
- //TODO: Use a more descriptif error message
59
+ //TODO: Use a more descriptive error message
51
60
  console.error("Something went wrong!");
52
61
  console.log(ex);
53
- // console.log(ex);
54
62
  return;
55
63
  }
56
64
  }
@@ -0,0 +1,13 @@
1
+ export const API_URL = "https://www.habous.gov.ma/prieres/horaire-api.php";
2
+
3
+ export const BANNER = ``;
4
+
5
+ export const NOT_FOUND_ERROR = `
6
+ Your city was not found in the list
7
+ Using the default city
8
+ ----------------------
9
+ You may need to check the spelling
10
+ `;
11
+
12
+ export const DEFAULT_CITY = "Marrakech";
13
+ export const LOCAL_STORAGE_PATH = "./storage";