@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 CHANGED
@@ -0,0 +1,196 @@
1
+ # mawaqit
2
+ [![standard-readme compliant](https://img.shields.io/badge/readme%20style-standard-brightgreen.svg?style=flat-square)](https://github.com/RichardLitt/standard-readme) ![npm](https://img.shields.io/npm/v/@max-xoo/mawaqit?color=green) [![npm download month](https://img.shields.io/npm/dm/@max-xoo/mawaqit.svg)](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; // already found
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, confData);
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
- fetchMawaqit
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 confData = await fetchMawaqit(masjidId);
124
+ const conf = await fetchMawaqit(masjidId);
74
125
  return {
75
- name: confData.name,
76
- label: confData.label,
77
- localisation: confData.localisation,
78
- phone: confData.phone,
79
- email: confData.email,
80
- site: confData.site,
81
- mosqueeType: confData.mosqueeType,
82
- association: confData.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.0.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
- "devDependencies": {}
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
+ }