@max-xoo/mawaqit 1.0.0 → 1.5.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 +196 -0
- package/dist/cache.js +18 -2
- package/dist/errors.js +16 -0
- package/dist/fetch.js +51 -19
- package/dist/index.js +60 -4
- package/dist/mawaqit.js +290 -31
- package/package.json +34 -35
package/README.MD
CHANGED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
# mawaqit
|
|
2
|
+
[](https://github.com/RichardLitt/standard-readme)  [](https://www.npmjs.com/package/@max-xoo/mawaqit)
|
|
3
|
+
|
|
4
|
+
A Node.js wrapper around [Mawaqit](https://mawaqit.net/), fetching mosque prayer times with built-in cache, country-wide mosque listings, and search.
|
|
5
|
+
|
|
6
|
+
## Table of Contents
|
|
7
|
+
- [Install](#install)
|
|
8
|
+
- [Usage](#usage)
|
|
9
|
+
- [Prayer Times](#prayer-times)
|
|
10
|
+
- [Calendar](#calendar)
|
|
11
|
+
- [Mosque Info](#mosque-info)
|
|
12
|
+
- [Country / Map](#country--map)
|
|
13
|
+
- [Geolocation](#geolocation)
|
|
14
|
+
- [Search](#search)
|
|
15
|
+
- [Bulk Operations](#bulk-operations)
|
|
16
|
+
- [Raw Data](#raw-data)
|
|
17
|
+
- [Cache](#cache)
|
|
18
|
+
- [Error Handling](#error-handling)
|
|
19
|
+
- [Contributing](#contributing)
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
```sh
|
|
23
|
+
npm install @max-xoo/mawaqit
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
## Usage
|
|
27
|
+
This package is meant to be used in the backend (Node.js).
|
|
28
|
+
All methods return a promise. The mosque slug is the one found in the mawaqit.net URL (e.g. `https://mawaqit.net/fr/zawiya-tidjaniya-trappes`).
|
|
29
|
+
|
|
30
|
+
### Prayer Times
|
|
31
|
+
```js
|
|
32
|
+
const mawaqit = require("@max-xoo/mawaqit");
|
|
33
|
+
|
|
34
|
+
const times = await mawaqit.getPrayerTimesOfTheDay("zawiya-tidjaniya-trappes");
|
|
35
|
+
// { fajr, sunrise, dohr, asr, maghreb, icha }
|
|
36
|
+
|
|
37
|
+
const iqama = await mawaqit.getIqamaTimes("zawiya-tidjaniya-trappes");
|
|
38
|
+
// { fajr, dohr, asr, maghreb, icha } (today's iqama, null if disabled)
|
|
39
|
+
|
|
40
|
+
const next = await mawaqit.getNextPrayer("zawiya-tidjaniya-trappes");
|
|
41
|
+
// { name: "asr", time: "16:45" } or { name: "fajr", time: "05:30", tomorrow: true }
|
|
42
|
+
|
|
43
|
+
const jumua = await mawaqit.getJumuaTimes("zawiya-tidjaniya-trappes");
|
|
44
|
+
// { jumua: "13:00", jumua2: "13:40", jumuaAsDuhr: false }
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### Calendar
|
|
48
|
+
```js
|
|
49
|
+
const calendar = await mawaqit.getCalendar("zawiya-tidjaniya-trappes");
|
|
50
|
+
// Full year calendar (12 months)
|
|
51
|
+
|
|
52
|
+
const march = await mawaqit.getMonth("zawiya-tidjaniya-trappes", 3);
|
|
53
|
+
// [{ fajr, sunrise, dohr, asr, maghreb, icha }, ...] for each day
|
|
54
|
+
|
|
55
|
+
const day = await mawaqit.getDay("zawiya-tidjaniya-trappes", 3, 15);
|
|
56
|
+
// { fajr, sunrise, dohr, asr, maghreb, icha } for March 15
|
|
57
|
+
|
|
58
|
+
const iqamaMarch = await mawaqit.getMonthIqama("zawiya-tidjaniya-trappes", 3);
|
|
59
|
+
// [{ fajr, dohr, asr, maghreb, icha }, ...] for each day
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Mosque Info
|
|
63
|
+
```js
|
|
64
|
+
const services = await mawaqit.getServices("zawiya-tidjaniya-trappes");
|
|
65
|
+
// { name, label, localisation, phone, email, site, mosqueeType, association }
|
|
66
|
+
|
|
67
|
+
const info = await mawaqit.getMosqueInfo("zawiya-tidjaniya-trappes");
|
|
68
|
+
// Full info: name, label, localisation, phone, email, site, image,
|
|
69
|
+
// womenSpace, janazaPrayer, aidPrayer, childrenCourses, adultCourses,
|
|
70
|
+
// ramadanMeal, handicapAccessibility, ablutions, parking, iqamaEnabled, ...
|
|
71
|
+
|
|
72
|
+
const announcements = await mawaqit.getAnnouncements("zawiya-tidjaniya-trappes");
|
|
73
|
+
// [{ ... }]
|
|
74
|
+
|
|
75
|
+
const flash = await mawaqit.getFlashMessage("zawiya-tidjaniya-trappes");
|
|
76
|
+
// string or null
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Country / Map
|
|
80
|
+
Fetch all mosques registered on Mawaqit for a given country using 2-letter ISO codes.
|
|
81
|
+
```js
|
|
82
|
+
const mosques = await mawaqit.getMosquesByCountry("FR");
|
|
83
|
+
// [{ slug, name, image1, address, city, zipcode, countryFullName, lng, lat }, ...]
|
|
84
|
+
|
|
85
|
+
const count = await mawaqit.getMosquesByCountryCount("FR");
|
|
86
|
+
// 4500
|
|
87
|
+
|
|
88
|
+
const byCity = await mawaqit.getMosquesByCity("FR", "Paris");
|
|
89
|
+
// All mosques in Paris
|
|
90
|
+
|
|
91
|
+
const byZip = await mawaqit.getMosquesByZipcode("FR", "78000");
|
|
92
|
+
// All mosques with zipcode 78000
|
|
93
|
+
|
|
94
|
+
const byName = await mawaqit.getMosquesByName("FR", "essalam");
|
|
95
|
+
// All mosques whose name contains "essalam"
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Geolocation
|
|
99
|
+
Find mosques near GPS coordinates. Distance is returned in km.
|
|
100
|
+
```js
|
|
101
|
+
const nearby = await mawaqit.getMosquesByRadius("FR", 48.8566, 2.3522, 5);
|
|
102
|
+
// Mosques within 5km of central Paris, sorted by distance
|
|
103
|
+
// [{ slug, name, city, distance: 0.8, ... }, ...]
|
|
104
|
+
|
|
105
|
+
const nearest = await mawaqit.getNearestMosque("FR", 48.8566, 2.3522);
|
|
106
|
+
// Single closest mosque or null
|
|
107
|
+
|
|
108
|
+
const top5 = await mawaqit.getNearestMosques("FR", 48.8566, 2.3522, 5);
|
|
109
|
+
// 5 closest mosques
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Search
|
|
113
|
+
Uses Mawaqit's search API to find mosques by name, city, or zipcode.
|
|
114
|
+
```js
|
|
115
|
+
const results = await mawaqit.searchMosques("versailles", "slug,label");
|
|
116
|
+
// Full search results from the API
|
|
117
|
+
|
|
118
|
+
const byName = await mawaqit.searchByName("mosquée de versailles");
|
|
119
|
+
const byZip = await mawaqit.searchByZipcode("78000");
|
|
120
|
+
const byCity = await mawaqit.searchByCity("Versailles");
|
|
121
|
+
// Shorthand searches returning [{ slug, label, ... }]
|
|
122
|
+
|
|
123
|
+
const full = await mawaqit.searchFull("versailles");
|
|
124
|
+
// All fields returned (uuid, name, type, times, iqama, jumua, ...)
|
|
125
|
+
|
|
126
|
+
const withTimes = await mawaqit.searchWithTimes("versailles");
|
|
127
|
+
// [{ slug, name, localisation, times: { fajr, ... }, iqama, jumua }]
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
### Bulk Operations
|
|
131
|
+
```js
|
|
132
|
+
const all = await mawaqit.getAllCountryMosquesWithTimes("FR");
|
|
133
|
+
// All mosques in France with today's prayer times (slow, use with care)
|
|
134
|
+
|
|
135
|
+
const multi = await mawaqit.getMultipleMosqueTimes([
|
|
136
|
+
"zawiya-tidjaniya-trappes",
|
|
137
|
+
"mosquee-de-versailles-versailles-78000-france",
|
|
138
|
+
]);
|
|
139
|
+
// { "zawiya-tidjaniya-trappes": { fajr, ... }, "mosquee-de-...": { fajr, ... } }
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Raw Data
|
|
143
|
+
```js
|
|
144
|
+
const raw = await mawaqit.fetchMawaqit("zawiya-tidjaniya-trappes");
|
|
145
|
+
// Full confData object from mawaqit.net
|
|
146
|
+
|
|
147
|
+
const rawConf = await mawaqit.getRawConfData("zawiya-tidjaniya-trappes");
|
|
148
|
+
// Same as above (alias)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Cache
|
|
152
|
+
Data is cached in memory with different TTLs depending on the type:
|
|
153
|
+
- **confData** (prayer times): resets at **midnight**
|
|
154
|
+
- **Country/map data**: cached for **6 hours**
|
|
155
|
+
- **Search results**: cached for **30 minutes**
|
|
156
|
+
|
|
157
|
+
```js
|
|
158
|
+
mawaqit.clearCache(); // Clear all entries
|
|
159
|
+
mawaqit.deleteCacheEntry("conf:mosque-slug"); // Clear a specific mosque
|
|
160
|
+
const size = mawaqit.getCacheSize(); // Number of cached entries
|
|
161
|
+
const keys = mawaqit.getCacheKeys(); // List all cache keys
|
|
162
|
+
const exists = mawaqit.hasCache("conf:slug"); // Check if key is cached
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Error Handling
|
|
166
|
+
```js
|
|
167
|
+
const {
|
|
168
|
+
MawaqitNotFoundError,
|
|
169
|
+
MawaqitParseError,
|
|
170
|
+
MawaqitFetchError,
|
|
171
|
+
MawaqitValidationError,
|
|
172
|
+
MawaqitSearchError,
|
|
173
|
+
MawaqitCountryError,
|
|
174
|
+
} = require("@max-xoo/mawaqit");
|
|
175
|
+
|
|
176
|
+
try {
|
|
177
|
+
const times = await mawaqit.getPrayerTimesOfTheDay("unknown-mosque");
|
|
178
|
+
} catch (err) {
|
|
179
|
+
if (err instanceof MawaqitNotFoundError) {
|
|
180
|
+
console.error("Mosque not found:", err.message); // 404
|
|
181
|
+
} else if (err instanceof MawaqitFetchError) {
|
|
182
|
+
console.error("Network error:", err.message); // 502
|
|
183
|
+
} else if (err instanceof MawaqitParseError) {
|
|
184
|
+
console.error("Parse error:", err.message); // 500
|
|
185
|
+
} else if (err instanceof MawaqitValidationError) {
|
|
186
|
+
console.error("Bad input:", err.message); // 400
|
|
187
|
+
} else if (err instanceof MawaqitSearchError) {
|
|
188
|
+
console.error("Search failed:", err.message); // 502
|
|
189
|
+
} else if (err instanceof MawaqitCountryError) {
|
|
190
|
+
console.error("Country fetch failed:", err.message); // 502
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
## Contributing
|
|
196
|
+
Feel free to [open an issue](https://github.com/roimee6/mawaqit/issues/new) or submit a pull request.
|
package/dist/cache.js
CHANGED
|
@@ -16,10 +16,10 @@ function getFromCache(key) {
|
|
|
16
16
|
return entry.data;
|
|
17
17
|
}
|
|
18
18
|
|
|
19
|
-
function setToCache(key, data) {
|
|
19
|
+
function setToCache(key, data, ttlMs) {
|
|
20
20
|
cache.set(key, {
|
|
21
21
|
data,
|
|
22
|
-
expiresAt: Date.now() + getMsUntilMidnight(),
|
|
22
|
+
expiresAt: Date.now() + (ttlMs || getMsUntilMidnight()),
|
|
23
23
|
});
|
|
24
24
|
}
|
|
25
25
|
|
|
@@ -35,10 +35,26 @@ function getCacheSize() {
|
|
|
35
35
|
return cache.size;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
+
function getCacheKeys() {
|
|
39
|
+
return [...cache.keys()];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function hasCache(key) {
|
|
43
|
+
const entry = cache.get(key);
|
|
44
|
+
if (!entry) return false;
|
|
45
|
+
if (Date.now() > entry.expiresAt) {
|
|
46
|
+
cache.delete(key);
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
return true;
|
|
50
|
+
}
|
|
51
|
+
|
|
38
52
|
module.exports = {
|
|
39
53
|
getFromCache,
|
|
40
54
|
setToCache,
|
|
41
55
|
clearCache,
|
|
42
56
|
deleteCacheEntry,
|
|
43
57
|
getCacheSize,
|
|
58
|
+
getCacheKeys,
|
|
59
|
+
hasCache,
|
|
44
60
|
};
|
package/dist/errors.js
CHANGED
|
@@ -34,10 +34,26 @@ class MawaqitValidationError extends MawaqitError {
|
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
class MawaqitSearchError extends MawaqitError {
|
|
38
|
+
constructor(query) {
|
|
39
|
+
super(`Search failed for "${query}"`, 502);
|
|
40
|
+
this.name = "MawaqitSearchError";
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
class MawaqitCountryError extends MawaqitError {
|
|
45
|
+
constructor(countryCode) {
|
|
46
|
+
super(`Failed to fetch mosques for country "${countryCode}"`, 502);
|
|
47
|
+
this.name = "MawaqitCountryError";
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
37
51
|
module.exports = {
|
|
38
52
|
MawaqitError,
|
|
39
53
|
MawaqitNotFoundError,
|
|
40
54
|
MawaqitParseError,
|
|
41
55
|
MawaqitFetchError,
|
|
42
56
|
MawaqitValidationError,
|
|
57
|
+
MawaqitSearchError,
|
|
58
|
+
MawaqitCountryError,
|
|
43
59
|
};
|
package/dist/fetch.js
CHANGED
|
@@ -1,26 +1,22 @@
|
|
|
1
1
|
const axios = require("axios");
|
|
2
|
-
|
|
3
|
-
const {
|
|
4
|
-
load
|
|
5
|
-
} = require("cheerio");
|
|
6
|
-
const {
|
|
7
|
-
getFromCache,
|
|
8
|
-
setToCache
|
|
9
|
-
} = require("./cache");
|
|
10
|
-
|
|
2
|
+
const { load } = require("cheerio");
|
|
3
|
+
const { getFromCache, setToCache } = require("./cache");
|
|
11
4
|
const {
|
|
12
5
|
MawaqitNotFoundError,
|
|
13
6
|
MawaqitParseError,
|
|
14
7
|
MawaqitFetchError,
|
|
8
|
+
MawaqitSearchError,
|
|
9
|
+
MawaqitCountryError,
|
|
15
10
|
} = require("./errors");
|
|
16
11
|
|
|
17
12
|
const CONF_DATA_REGEX = /(?:var|let)\s+confData\s*=\s*(.*?);/s;
|
|
13
|
+
const BASE_API = "https://mawaqit.net/api/2.0";
|
|
14
|
+
const MAP_CACHE_TTL = 6 * 60 * 60 * 1000; // 6h
|
|
15
|
+
const SEARCH_CACHE_TTL = 30 * 60 * 1000; // 30min
|
|
18
16
|
|
|
19
17
|
async function fetchMawaqit(masjidId) {
|
|
20
|
-
const cached = getFromCache(masjidId);
|
|
21
|
-
if (cached)
|
|
22
|
-
return cached;
|
|
23
|
-
}
|
|
18
|
+
const cached = getFromCache(`conf:${masjidId}`);
|
|
19
|
+
if (cached) return cached;
|
|
24
20
|
|
|
25
21
|
const url = `https://mawaqit.net/fr/${masjidId}`;
|
|
26
22
|
let response;
|
|
@@ -37,7 +33,7 @@ async function fetchMawaqit(masjidId) {
|
|
|
37
33
|
let confData = null;
|
|
38
34
|
|
|
39
35
|
$("script").each((_, el) => {
|
|
40
|
-
if (confData) return;
|
|
36
|
+
if (confData) return;
|
|
41
37
|
const content = $(el).html();
|
|
42
38
|
if (content && CONF_DATA_REGEX.test(content)) {
|
|
43
39
|
const match = content.match(CONF_DATA_REGEX);
|
|
@@ -51,14 +47,50 @@ async function fetchMawaqit(masjidId) {
|
|
|
51
47
|
}
|
|
52
48
|
});
|
|
53
49
|
|
|
54
|
-
if (!confData)
|
|
55
|
-
throw new MawaqitParseError(masjidId);
|
|
56
|
-
}
|
|
50
|
+
if (!confData) throw new MawaqitParseError(masjidId);
|
|
57
51
|
|
|
58
|
-
setToCache(masjidId
|
|
52
|
+
setToCache(`conf:${masjidId}`, confData);
|
|
59
53
|
return confData;
|
|
60
54
|
}
|
|
61
55
|
|
|
56
|
+
async function fetchMosquesByCountry(countryCode) {
|
|
57
|
+
const code = countryCode.toUpperCase();
|
|
58
|
+
const cacheKey = `map:${code}`;
|
|
59
|
+
const cached = getFromCache(cacheKey);
|
|
60
|
+
if (cached) return cached;
|
|
61
|
+
|
|
62
|
+
const url = `${BASE_API}/mosque/map/${code}`;
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
const { data } = await axios.get(url);
|
|
66
|
+
setToCache(cacheKey, data, MAP_CACHE_TTL);
|
|
67
|
+
return data;
|
|
68
|
+
} catch {
|
|
69
|
+
throw new MawaqitCountryError(code);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function fetchSearchMosques(word, fields) {
|
|
74
|
+
const cacheKey = `search:${word}:${fields || ""}`;
|
|
75
|
+
const cached = getFromCache(cacheKey);
|
|
76
|
+
if (cached) return cached;
|
|
77
|
+
|
|
78
|
+
const params = { word };
|
|
79
|
+
if (fields) params.fields = fields;
|
|
80
|
+
|
|
81
|
+
const url = `${BASE_API}/mosque/search`;
|
|
82
|
+
|
|
83
|
+
try {
|
|
84
|
+
const { data } = await axios.get(url, { params });
|
|
85
|
+
setToCache(cacheKey, data, SEARCH_CACHE_TTL);
|
|
86
|
+
return data;
|
|
87
|
+
} catch {
|
|
88
|
+
throw new MawaqitSearchError(word);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
62
92
|
module.exports = {
|
|
63
|
-
fetchMawaqit
|
|
93
|
+
fetchMawaqit,
|
|
94
|
+
fetchMosquesByCountry,
|
|
95
|
+
fetchSearchMosques,
|
|
64
96
|
};
|
package/dist/index.js
CHANGED
|
@@ -1,20 +1,43 @@
|
|
|
1
|
-
const {
|
|
2
|
-
fetchMawaqit
|
|
3
|
-
} = require("./fetch");
|
|
1
|
+
const { fetchMawaqit, fetchMosquesByCountry, fetchSearchMosques } = require("./fetch");
|
|
4
2
|
|
|
5
3
|
const {
|
|
6
4
|
getPrayerTimesOfTheDay,
|
|
5
|
+
getIqamaTimes,
|
|
6
|
+
getNextPrayer,
|
|
7
|
+
getJumuaTimes,
|
|
7
8
|
getCalendar,
|
|
8
9
|
getMonth,
|
|
10
|
+
getDay,
|
|
9
11
|
getMonthIqama,
|
|
10
12
|
getAnnouncements,
|
|
11
13
|
getServices,
|
|
14
|
+
getMosqueInfo,
|
|
15
|
+
getFlashMessage,
|
|
16
|
+
getRawConfData,
|
|
17
|
+
getMosquesByCountry,
|
|
18
|
+
getMosquesByCountryCount,
|
|
19
|
+
getMosquesByCity,
|
|
20
|
+
getMosquesByZipcode,
|
|
21
|
+
getMosquesByName,
|
|
22
|
+
getMosquesByRadius,
|
|
23
|
+
getNearestMosque,
|
|
24
|
+
getNearestMosques,
|
|
25
|
+
searchMosques,
|
|
26
|
+
searchByName,
|
|
27
|
+
searchByZipcode,
|
|
28
|
+
searchByCity,
|
|
29
|
+
searchFull,
|
|
30
|
+
searchWithTimes,
|
|
31
|
+
getAllCountryMosquesWithTimes,
|
|
32
|
+
getMultipleMosqueTimes,
|
|
12
33
|
} = require("./mawaqit");
|
|
13
34
|
|
|
14
35
|
const {
|
|
15
36
|
clearCache,
|
|
16
37
|
deleteCacheEntry,
|
|
17
38
|
getCacheSize,
|
|
39
|
+
getCacheKeys,
|
|
40
|
+
hasCache,
|
|
18
41
|
} = require("./cache");
|
|
19
42
|
|
|
20
43
|
const {
|
|
@@ -23,26 +46,59 @@ const {
|
|
|
23
46
|
MawaqitParseError,
|
|
24
47
|
MawaqitFetchError,
|
|
25
48
|
MawaqitValidationError,
|
|
49
|
+
MawaqitSearchError,
|
|
50
|
+
MawaqitCountryError,
|
|
26
51
|
} = require("./errors");
|
|
27
52
|
|
|
28
53
|
module.exports = {
|
|
29
54
|
fetchMawaqit,
|
|
55
|
+
fetchMosquesByCountry,
|
|
56
|
+
fetchSearchMosques,
|
|
30
57
|
|
|
31
58
|
getPrayerTimesOfTheDay,
|
|
59
|
+
getIqamaTimes,
|
|
60
|
+
getNextPrayer,
|
|
61
|
+
getJumuaTimes,
|
|
32
62
|
getCalendar,
|
|
33
63
|
getMonth,
|
|
64
|
+
getDay,
|
|
34
65
|
getMonthIqama,
|
|
35
|
-
|
|
36
66
|
getAnnouncements,
|
|
37
67
|
getServices,
|
|
68
|
+
getMosqueInfo,
|
|
69
|
+
getFlashMessage,
|
|
70
|
+
getRawConfData,
|
|
71
|
+
|
|
72
|
+
getMosquesByCountry,
|
|
73
|
+
getMosquesByCountryCount,
|
|
74
|
+
getMosquesByCity,
|
|
75
|
+
getMosquesByZipcode,
|
|
76
|
+
getMosquesByName,
|
|
77
|
+
getMosquesByRadius,
|
|
78
|
+
getNearestMosque,
|
|
79
|
+
getNearestMosques,
|
|
80
|
+
|
|
81
|
+
searchMosques,
|
|
82
|
+
searchByName,
|
|
83
|
+
searchByZipcode,
|
|
84
|
+
searchByCity,
|
|
85
|
+
searchFull,
|
|
86
|
+
searchWithTimes,
|
|
87
|
+
|
|
88
|
+
getAllCountryMosquesWithTimes,
|
|
89
|
+
getMultipleMosqueTimes,
|
|
38
90
|
|
|
39
91
|
clearCache,
|
|
40
92
|
deleteCacheEntry,
|
|
41
93
|
getCacheSize,
|
|
94
|
+
getCacheKeys,
|
|
95
|
+
hasCache,
|
|
42
96
|
|
|
43
97
|
MawaqitError,
|
|
44
98
|
MawaqitNotFoundError,
|
|
45
99
|
MawaqitParseError,
|
|
46
100
|
MawaqitFetchError,
|
|
47
101
|
MawaqitValidationError,
|
|
102
|
+
MawaqitSearchError,
|
|
103
|
+
MawaqitCountryError,
|
|
48
104
|
};
|
package/dist/mawaqit.js
CHANGED
|
@@ -1,15 +1,8 @@
|
|
|
1
|
-
const {
|
|
2
|
-
|
|
3
|
-
} = require("./fetch");
|
|
4
|
-
const {
|
|
5
|
-
MawaqitValidationError
|
|
6
|
-
} = require("./errors");
|
|
1
|
+
const { fetchMawaqit, fetchMosquesByCountry, fetchSearchMosques } = require("./fetch");
|
|
2
|
+
const { MawaqitValidationError } = require("./errors");
|
|
7
3
|
|
|
8
4
|
async function getPrayerTimesOfTheDay(masjidId) {
|
|
9
|
-
const {
|
|
10
|
-
times,
|
|
11
|
-
shuruq
|
|
12
|
-
} = await fetchMawaqit(masjidId);
|
|
5
|
+
const { times, shuruq } = await fetchMawaqit(masjidId);
|
|
13
6
|
return {
|
|
14
7
|
fajr: times[0],
|
|
15
8
|
sunrise: shuruq,
|
|
@@ -20,10 +13,50 @@ async function getPrayerTimesOfTheDay(masjidId) {
|
|
|
20
13
|
};
|
|
21
14
|
}
|
|
22
15
|
|
|
16
|
+
async function getIqamaTimes(masjidId) {
|
|
17
|
+
const { iqamaCalendar, iqamaEnabled } = await fetchMawaqit(masjidId);
|
|
18
|
+
if (!iqamaEnabled) return null;
|
|
19
|
+
const now = new Date();
|
|
20
|
+
const month = iqamaCalendar[now.getMonth()];
|
|
21
|
+
const day = Object.values(month)[now.getDate() - 1];
|
|
22
|
+
if (!day) return null;
|
|
23
|
+
return {
|
|
24
|
+
fajr: day[0],
|
|
25
|
+
dohr: day[1],
|
|
26
|
+
asr: day[2],
|
|
27
|
+
maghreb: day[3],
|
|
28
|
+
icha: day[4],
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function getNextPrayer(masjidId) {
|
|
33
|
+
const times = await getPrayerTimesOfTheDay(masjidId);
|
|
34
|
+
const now = new Date();
|
|
35
|
+
const nowMinutes = now.getHours() * 60 + now.getMinutes();
|
|
36
|
+
const names = ["fajr", "sunrise", "dohr", "asr", "maghreb", "icha"];
|
|
37
|
+
|
|
38
|
+
for (const name of names) {
|
|
39
|
+
const t = times[name];
|
|
40
|
+
if (!t) continue;
|
|
41
|
+
const [h, m] = t.split(":").map(Number);
|
|
42
|
+
if (h * 60 + m > nowMinutes) {
|
|
43
|
+
return { name, time: t };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return { name: "fajr", time: times.fajr, tomorrow: true };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async function getJumuaTimes(masjidId) {
|
|
50
|
+
const conf = await fetchMawaqit(masjidId);
|
|
51
|
+
const result = { jumua: conf.jumpiua || conf.jumua || null };
|
|
52
|
+
if (conf.jumua2) result.jumua2 = conf.jumua2;
|
|
53
|
+
if (conf.jumua3) result.jumua3 = conf.jumua3;
|
|
54
|
+
result.jumuaAsDuhr = conf.jumuaAsDuhr || false;
|
|
55
|
+
return result;
|
|
56
|
+
}
|
|
57
|
+
|
|
23
58
|
async function getCalendar(masjidId) {
|
|
24
|
-
const {
|
|
25
|
-
calendar
|
|
26
|
-
} = await fetchMawaqit(masjidId);
|
|
59
|
+
const { calendar } = await fetchMawaqit(masjidId);
|
|
27
60
|
return calendar;
|
|
28
61
|
}
|
|
29
62
|
|
|
@@ -31,9 +64,7 @@ async function getMonth(masjidId, monthNumber) {
|
|
|
31
64
|
if (monthNumber < 1 || monthNumber > 12) {
|
|
32
65
|
throw new MawaqitValidationError("Month number should be between 1 and 12");
|
|
33
66
|
}
|
|
34
|
-
const {
|
|
35
|
-
calendar
|
|
36
|
-
} = await fetchMawaqit(masjidId);
|
|
67
|
+
const { calendar } = await fetchMawaqit(masjidId);
|
|
37
68
|
const month = calendar[monthNumber - 1];
|
|
38
69
|
return Object.values(month).map((prayer) => ({
|
|
39
70
|
fajr: prayer[0],
|
|
@@ -45,13 +76,35 @@ async function getMonth(masjidId, monthNumber) {
|
|
|
45
76
|
}));
|
|
46
77
|
}
|
|
47
78
|
|
|
79
|
+
async function getDay(masjidId, monthNumber, dayNumber) {
|
|
80
|
+
if (monthNumber < 1 || monthNumber > 12) {
|
|
81
|
+
throw new MawaqitValidationError("Month number should be between 1 and 12");
|
|
82
|
+
}
|
|
83
|
+
if (dayNumber < 1 || dayNumber > 31) {
|
|
84
|
+
throw new MawaqitValidationError("Day number should be between 1 and 31");
|
|
85
|
+
}
|
|
86
|
+
const { calendar } = await fetchMawaqit(masjidId);
|
|
87
|
+
const month = calendar[monthNumber - 1];
|
|
88
|
+
const days = Object.values(month);
|
|
89
|
+
if (dayNumber > days.length) {
|
|
90
|
+
throw new MawaqitValidationError(`Day ${dayNumber} does not exist in month ${monthNumber}`);
|
|
91
|
+
}
|
|
92
|
+
const prayer = days[dayNumber - 1];
|
|
93
|
+
return {
|
|
94
|
+
fajr: prayer[0],
|
|
95
|
+
sunrise: prayer[1],
|
|
96
|
+
dohr: prayer[2],
|
|
97
|
+
asr: prayer[3],
|
|
98
|
+
maghreb: prayer[4],
|
|
99
|
+
icha: prayer[5],
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
48
103
|
async function getMonthIqama(masjidId, monthNumber) {
|
|
49
104
|
if (monthNumber < 1 || monthNumber > 12) {
|
|
50
105
|
throw new MawaqitValidationError("Month number should be between 1 and 12");
|
|
51
106
|
}
|
|
52
|
-
const {
|
|
53
|
-
iqamaCalendar
|
|
54
|
-
} = await fetchMawaqit(masjidId);
|
|
107
|
+
const { iqamaCalendar } = await fetchMawaqit(masjidId);
|
|
55
108
|
const month = iqamaCalendar[monthNumber - 1];
|
|
56
109
|
return Object.values(month).map((iqama) => ({
|
|
57
110
|
fajr: iqama[0],
|
|
@@ -63,31 +116,237 @@ async function getMonthIqama(masjidId, monthNumber) {
|
|
|
63
116
|
}
|
|
64
117
|
|
|
65
118
|
async function getAnnouncements(masjidId) {
|
|
66
|
-
const {
|
|
67
|
-
announcements
|
|
68
|
-
} = await fetchMawaqit(masjidId);
|
|
119
|
+
const { announcements } = await fetchMawaqit(masjidId);
|
|
69
120
|
return announcements ?? [];
|
|
70
121
|
}
|
|
71
122
|
|
|
72
123
|
async function getServices(masjidId) {
|
|
73
|
-
const
|
|
124
|
+
const conf = await fetchMawaqit(masjidId);
|
|
74
125
|
return {
|
|
75
|
-
name:
|
|
76
|
-
label:
|
|
77
|
-
localisation:
|
|
78
|
-
phone:
|
|
79
|
-
email:
|
|
80
|
-
site:
|
|
81
|
-
mosqueeType:
|
|
82
|
-
association:
|
|
126
|
+
name: conf.name,
|
|
127
|
+
label: conf.label,
|
|
128
|
+
localisation: conf.localisation,
|
|
129
|
+
phone: conf.phone,
|
|
130
|
+
email: conf.email,
|
|
131
|
+
site: conf.site,
|
|
132
|
+
mosqueeType: conf.mosqueeType,
|
|
133
|
+
association: conf.association,
|
|
83
134
|
};
|
|
84
135
|
}
|
|
85
136
|
|
|
137
|
+
async function getMosqueInfo(masjidId) {
|
|
138
|
+
const conf = await fetchMawaqit(masjidId);
|
|
139
|
+
return {
|
|
140
|
+
uuid: conf.uuid,
|
|
141
|
+
name: conf.name,
|
|
142
|
+
label: conf.label,
|
|
143
|
+
slug: masjidId,
|
|
144
|
+
localisation: conf.localisation,
|
|
145
|
+
phone: conf.phone,
|
|
146
|
+
email: conf.email,
|
|
147
|
+
site: conf.site,
|
|
148
|
+
mosqueeType: conf.mosqueeType,
|
|
149
|
+
association: conf.association,
|
|
150
|
+
image: conf.image1 || conf.image || null,
|
|
151
|
+
womenSpace: conf.womenSpace ?? null,
|
|
152
|
+
janazaPrayer: conf.janazaPrayer ?? null,
|
|
153
|
+
aidPrayer: conf.aidPrayer ?? null,
|
|
154
|
+
childrenCourses: conf.childrenCourses ?? null,
|
|
155
|
+
adultCourses: conf.adultCourses ?? null,
|
|
156
|
+
ramadanMeal: conf.ramadanMeal ?? null,
|
|
157
|
+
handicapAccessibility: conf.handicapAccessibility ?? null,
|
|
158
|
+
ablutions: conf.ablutions ?? null,
|
|
159
|
+
parking: conf.parking ?? null,
|
|
160
|
+
iqamaEnabled: conf.iqamaEnabled ?? false,
|
|
161
|
+
longitude: conf.longitude ?? null,
|
|
162
|
+
latitude: conf.latitude ?? null,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
async function getFlashMessage(masjidId) {
|
|
167
|
+
const { flash } = await fetchMawaqit(masjidId);
|
|
168
|
+
return flash ?? null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async function getRawConfData(masjidId) {
|
|
172
|
+
return await fetchMawaqit(masjidId);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ---- Country / Map ----
|
|
176
|
+
|
|
177
|
+
async function getMosquesByCountry(countryCode) {
|
|
178
|
+
if (!countryCode || countryCode.length !== 2) {
|
|
179
|
+
throw new MawaqitValidationError("Country code must be a 2-letter ISO code (e.g. FR, EN, DE)");
|
|
180
|
+
}
|
|
181
|
+
return await fetchMosquesByCountry(countryCode);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
async function getMosquesByCountryCount(countryCode) {
|
|
185
|
+
const mosques = await getMosquesByCountry(countryCode);
|
|
186
|
+
return mosques.length;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
async function getMosquesByCity(countryCode, city) {
|
|
190
|
+
const mosques = await getMosquesByCountry(countryCode);
|
|
191
|
+
const lower = city.toLowerCase();
|
|
192
|
+
return mosques.filter((m) => m.city && m.city.toLowerCase() === lower);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async function getMosquesByZipcode(countryCode, zipcode) {
|
|
196
|
+
const mosques = await getMosquesByCountry(countryCode);
|
|
197
|
+
return mosques.filter((m) => m.zipcode === zipcode);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
async function getMosquesByName(countryCode, name) {
|
|
201
|
+
const mosques = await getMosquesByCountry(countryCode);
|
|
202
|
+
const lower = name.toLowerCase();
|
|
203
|
+
return mosques.filter((m) => m.name && m.name.toLowerCase().includes(lower));
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
async function getMosquesByRadius(countryCode, lat, lng, radiusKm) {
|
|
207
|
+
if (typeof lat !== "number" || typeof lng !== "number" || typeof radiusKm !== "number") {
|
|
208
|
+
throw new MawaqitValidationError("lat, lng and radiusKm must be numbers");
|
|
209
|
+
}
|
|
210
|
+
const mosques = await getMosquesByCountry(countryCode);
|
|
211
|
+
return mosques.filter((m) => {
|
|
212
|
+
if (!m.lat || !m.lng) return false;
|
|
213
|
+
const dist = haversine(lat, lng, m.lat, m.lng);
|
|
214
|
+
return dist <= radiusKm;
|
|
215
|
+
}).map((m) => ({
|
|
216
|
+
...m,
|
|
217
|
+
distance: Math.round(haversine(lat, lng, m.lat, m.lng) * 100) / 100,
|
|
218
|
+
})).sort((a, b) => a.distance - b.distance);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function getNearestMosque(countryCode, lat, lng) {
|
|
222
|
+
const mosques = await getMosquesByRadius(countryCode, lat, lng, 50000);
|
|
223
|
+
return mosques[0] || null;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
async function getNearestMosques(countryCode, lat, lng, limit = 5) {
|
|
227
|
+
const mosques = await getMosquesByRadius(countryCode, lat, lng, 50000);
|
|
228
|
+
return mosques.slice(0, limit);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ---- Search ----
|
|
232
|
+
|
|
233
|
+
async function searchMosques(word, fields) {
|
|
234
|
+
if (!word || word.trim().length === 0) {
|
|
235
|
+
throw new MawaqitValidationError("Search word cannot be empty");
|
|
236
|
+
}
|
|
237
|
+
return await fetchSearchMosques(word.trim(), fields);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
async function searchByName(name) {
|
|
241
|
+
return await searchMosques(name, "slug,label");
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function searchByZipcode(zipcode) {
|
|
245
|
+
return await searchMosques(zipcode, "slug,label");
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function searchByCity(city) {
|
|
249
|
+
return await searchMosques(city, "slug,label");
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async function searchFull(word) {
|
|
253
|
+
return await searchMosques(word);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function searchWithTimes(word) {
|
|
257
|
+
const results = await searchMosques(word);
|
|
258
|
+
return results.map((m) => ({
|
|
259
|
+
slug: m.slug,
|
|
260
|
+
name: m.name || m.label,
|
|
261
|
+
localisation: m.localisation,
|
|
262
|
+
times: m.times ? {
|
|
263
|
+
fajr: m.times[0],
|
|
264
|
+
sunrise: m.times[1],
|
|
265
|
+
dohr: m.times[2],
|
|
266
|
+
asr: m.times[3],
|
|
267
|
+
maghreb: m.times[4],
|
|
268
|
+
icha: m.times[5],
|
|
269
|
+
} : null,
|
|
270
|
+
iqama: m.iqama || null,
|
|
271
|
+
jumua: m.jumua || null,
|
|
272
|
+
}));
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
// ---- Bulk operations ----
|
|
276
|
+
|
|
277
|
+
async function getAllCountryMosquesWithTimes(countryCode) {
|
|
278
|
+
const mosques = await getMosquesByCountry(countryCode);
|
|
279
|
+
const results = [];
|
|
280
|
+
for (const m of mosques) {
|
|
281
|
+
try {
|
|
282
|
+
const times = await getPrayerTimesOfTheDay(m.slug);
|
|
283
|
+
results.push({ ...m, times });
|
|
284
|
+
} catch {
|
|
285
|
+
results.push({ ...m, times: null });
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
return results;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async function getMultipleMosqueTimes(slugs) {
|
|
292
|
+
if (!Array.isArray(slugs)) {
|
|
293
|
+
throw new MawaqitValidationError("slugs must be an array");
|
|
294
|
+
}
|
|
295
|
+
const results = {};
|
|
296
|
+
for (const slug of slugs) {
|
|
297
|
+
try {
|
|
298
|
+
results[slug] = await getPrayerTimesOfTheDay(slug);
|
|
299
|
+
} catch (err) {
|
|
300
|
+
results[slug] = { error: err.message };
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
return results;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// ---- Utils ----
|
|
307
|
+
|
|
308
|
+
function haversine(lat1, lon1, lat2, lon2) {
|
|
309
|
+
const R = 6371;
|
|
310
|
+
const dLat = (lat2 - lat1) * Math.PI / 180;
|
|
311
|
+
const dLon = (lon2 - lon1) * Math.PI / 180;
|
|
312
|
+
const a =
|
|
313
|
+
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
|
314
|
+
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
|
|
315
|
+
Math.sin(dLon / 2) * Math.sin(dLon / 2);
|
|
316
|
+
return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
317
|
+
}
|
|
318
|
+
|
|
86
319
|
module.exports = {
|
|
87
320
|
getPrayerTimesOfTheDay,
|
|
321
|
+
getIqamaTimes,
|
|
322
|
+
getNextPrayer,
|
|
323
|
+
getJumuaTimes,
|
|
88
324
|
getCalendar,
|
|
89
325
|
getMonth,
|
|
326
|
+
getDay,
|
|
90
327
|
getMonthIqama,
|
|
91
328
|
getAnnouncements,
|
|
92
329
|
getServices,
|
|
330
|
+
getMosqueInfo,
|
|
331
|
+
getFlashMessage,
|
|
332
|
+
getRawConfData,
|
|
333
|
+
|
|
334
|
+
getMosquesByCountry,
|
|
335
|
+
getMosquesByCountryCount,
|
|
336
|
+
getMosquesByCity,
|
|
337
|
+
getMosquesByZipcode,
|
|
338
|
+
getMosquesByName,
|
|
339
|
+
getMosquesByRadius,
|
|
340
|
+
getNearestMosque,
|
|
341
|
+
getNearestMosques,
|
|
342
|
+
|
|
343
|
+
searchMosques,
|
|
344
|
+
searchByName,
|
|
345
|
+
searchByZipcode,
|
|
346
|
+
searchByCity,
|
|
347
|
+
searchFull,
|
|
348
|
+
searchWithTimes,
|
|
349
|
+
|
|
350
|
+
getAllCountryMosquesWithTimes,
|
|
351
|
+
getMultipleMosqueTimes,
|
|
93
352
|
};
|
package/package.json
CHANGED
|
@@ -1,35 +1,34 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@max-xoo/mawaqit",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "A JavaScript wrapper around the unofficial Mawaqit API",
|
|
5
|
-
"keywords": [
|
|
6
|
-
"mawaqit",
|
|
7
|
-
"prayer",
|
|
8
|
-
"islam",
|
|
9
|
-
"mosque",
|
|
10
|
-
"salat",
|
|
11
|
-
"quran"
|
|
12
|
-
],
|
|
13
|
-
"homepage": "https://github.com/roimee6/mawaqit#readme",
|
|
14
|
-
"bugs": {
|
|
15
|
-
"url": "https://github.com/roimee6/mawaqit/issues"
|
|
16
|
-
},
|
|
17
|
-
"repository": {
|
|
18
|
-
"type": "git",
|
|
19
|
-
"url": "git+https://github.com/roimee6/mawaqit.git"
|
|
20
|
-
},
|
|
21
|
-
"license": "MIT",
|
|
22
|
-
"author": "Maxence",
|
|
23
|
-
"main": "dist/index.js",
|
|
24
|
-
"files": [
|
|
25
|
-
"dist/**/*"
|
|
26
|
-
],
|
|
27
|
-
"scripts": {
|
|
28
|
-
"test": "node test.cjs"
|
|
29
|
-
},
|
|
30
|
-
"dependencies": {
|
|
31
|
-
"axios": "^1.13.5",
|
|
32
|
-
"cheerio": "^1.2.0"
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@max-xoo/mawaqit",
|
|
3
|
+
"version": "1.5.0",
|
|
4
|
+
"description": "A JavaScript wrapper around the unofficial Mawaqit API",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"mawaqit",
|
|
7
|
+
"prayer",
|
|
8
|
+
"islam",
|
|
9
|
+
"mosque",
|
|
10
|
+
"salat",
|
|
11
|
+
"quran"
|
|
12
|
+
],
|
|
13
|
+
"homepage": "https://github.com/roimee6/mawaqit#readme",
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/roimee6/mawaqit/issues"
|
|
16
|
+
},
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/roimee6/mawaqit.git"
|
|
20
|
+
},
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"author": "Maxence",
|
|
23
|
+
"main": "dist/index.js",
|
|
24
|
+
"files": [
|
|
25
|
+
"dist/**/*"
|
|
26
|
+
],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"test": "node test.cjs"
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"axios": "^1.13.5",
|
|
32
|
+
"cheerio": "^1.2.0"
|
|
33
|
+
}
|
|
34
|
+
}
|