sunnah 1.3.6 → 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/CHANGELOG.md +19 -0
- package/README.md +258 -254
- package/bin/index.js +1022 -1604
- package/books/jami-al-tirmidhi/README.md +201 -0
- package/books/jami-al-tirmidhi/bin/index.js +165 -0
- package/books/jami-al-tirmidhi/examples/express/server.js +7 -0
- package/books/jami-al-tirmidhi/examples/node-commonjs/example.js +7 -0
- package/books/jami-al-tirmidhi/examples/node-esm/example.mjs +6 -0
- package/books/jami-al-tirmidhi/examples/react/HadithExample.jsx +17 -0
- package/books/jami-al-tirmidhi/package.json +58 -0
- package/books/jami-al-tirmidhi/src/index.cjs +52 -0
- package/books/jami-al-tirmidhi/src/index.js +35 -0
- package/books/jami-al-tirmidhi/src/index.node.js +18 -0
- package/books/jami-al-tirmidhi/types/index.d.ts +28 -0
- package/books/sahih-al-bukhari/LICENSE +661 -0
- package/books/sahih-al-bukhari/README.md +551 -0
- package/books/sahih-al-bukhari/bin/index.js +306 -0
- package/books/sahih-al-bukhari/data/bukhari.json.gz +0 -0
- package/books/sahih-al-bukhari/examples/express/server.js +49 -0
- package/books/sahih-al-bukhari/examples/node-commonjs/example.js +21 -0
- package/books/sahih-al-bukhari/examples/node-esm/example.mjs +24 -0
- package/books/sahih-al-bukhari/examples/react/HadithExample.jsx +73 -0
- package/books/sahih-al-bukhari/package.json +54 -0
- package/books/sahih-al-bukhari/src/index.cjs +55 -0
- package/books/sahih-al-bukhari/src/index.js +35 -0
- package/books/sahih-al-bukhari/src/index.node.js +21 -0
- package/books/sahih-al-bukhari/types/index.d.ts +35 -0
- package/books/sahih-muslim/LICENSE +661 -0
- package/books/sahih-muslim/README.md +547 -0
- package/books/sahih-muslim/bin/index.js +183 -0
- package/books/sahih-muslim/data/muslim.json.gz +0 -0
- package/books/sahih-muslim/examples/express/server.js +16 -0
- package/books/sahih-muslim/examples/node-commonjs/example.js +11 -0
- package/books/sahih-muslim/examples/node-esm/example.mjs +9 -0
- package/books/sahih-muslim/examples/react/HadithExample.jsx +28 -0
- package/books/sahih-muslim/package.json +58 -0
- package/books/sahih-muslim/src/index.cjs +52 -0
- package/books/sahih-muslim/src/index.js +35 -0
- package/books/sahih-muslim/src/index.node.js +18 -0
- package/books/sahih-muslim/types/index.d.ts +28 -0
- package/books/sunan-abi-dawud/LICENSE +661 -0
- package/books/sunan-abi-dawud/README.md +149 -0
- package/books/sunan-abi-dawud/bin/index.js +183 -0
- package/books/sunan-abi-dawud/data/dawud.json.gz +0 -0
- package/books/sunan-abi-dawud/examples/express/server.js +7 -0
- package/books/sunan-abi-dawud/examples/node-commonjs/example.js +7 -0
- package/books/sunan-abi-dawud/examples/node-esm/example.mjs +6 -0
- package/books/sunan-abi-dawud/examples/react/HadithExample.jsx +17 -0
- package/books/sunan-abi-dawud/package.json +58 -0
- package/books/sunan-abi-dawud/src/index.cjs +52 -0
- package/books/sunan-abi-dawud/src/index.js +35 -0
- package/books/sunan-abi-dawud/src/index.node.js +18 -0
- package/books/sunan-abi-dawud/types/index.d.ts +28 -0
- package/books/sunan-ibn-majah/README.md +198 -0
- package/books/sunan-ibn-majah/bin/index.js +138 -0
- package/books/sunan-ibn-majah/examples/express/server.js +8 -0
- package/books/sunan-ibn-majah/examples/node-commonjs/example.js +7 -0
- package/books/sunan-ibn-majah/examples/node-esm/example.mjs +6 -0
- package/books/sunan-ibn-majah/examples/react/HadithExample.jsx +17 -0
- package/books/sunan-ibn-majah/package.json +58 -0
- package/books/sunan-ibn-majah/src/index.cjs +52 -0
- package/books/sunan-ibn-majah/src/index.js +35 -0
- package/books/sunan-ibn-majah/src/index.node.js +18 -0
- package/books/sunan-ibn-majah/types/index.d.ts +28 -0
- package/package.json +39 -27
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<h1>
|
|
4
|
+
<img src="https://em-content.zobj.net/source/apple/391/mosque_1f54c.png" width="36" />
|
|
5
|
+
sunan-ibn-majah
|
|
6
|
+
</h1>
|
|
7
|
+
|
|
8
|
+
<p align="center">
|
|
9
|
+
<strong>The complete Sunan Ibn Majah — 4,341 hadiths, full Arabic & English.</strong><br />
|
|
10
|
+
Offline-first · zero dependencies · published on both <strong>npm</strong> and <strong>PyPI</strong>.
|
|
11
|
+
</p>
|
|
12
|
+
|
|
13
|
+
<br />
|
|
14
|
+
|
|
15
|
+
<p>
|
|
16
|
+
<a href="https://www.npmjs.com/package/sunan-ibn-majah">
|
|
17
|
+
<img src="https://img.shields.io/npm/v/sunan-ibn-majah?style=for-the-badge&logo=npm&logoColor=white&color=CB3837&labelColor=1a1a1a" />
|
|
18
|
+
</a>
|
|
19
|
+
|
|
20
|
+
<a href="https://pypi.org/project/sunan-ibn-majah/">
|
|
21
|
+
<img src="https://img.shields.io/pypi/v/sunan-ibn-majah?style=for-the-badge&logo=pypi&logoColor=white&color=3775A9&labelColor=1a1a1a" />
|
|
22
|
+
</a>
|
|
23
|
+
|
|
24
|
+
<a href="https://github.com/SENODROOM/sunan-ibn-majah/blob/main/LICENSE">
|
|
25
|
+
<img src="https://img.shields.io/github/license/SENODROOM/sunan-ibn-majah?style=for-the-badge&logo=gnu&logoColor=white&color=A42E2B&labelColor=1a1a1a" />
|
|
26
|
+
</a>
|
|
27
|
+
</p>
|
|
28
|
+
|
|
29
|
+
<p>
|
|
30
|
+
<img src="https://img.shields.io/badge/Node.js-%3E%3D18-339933?style=for-the-badge&logo=node.js&logoColor=white&labelColor=1a1a1a" />
|
|
31
|
+
|
|
32
|
+
<img src="https://img.shields.io/badge/Python-%3E%3D3.8-3776AB?style=for-the-badge&logo=python&logoColor=white&labelColor=1a1a1a" />
|
|
33
|
+
|
|
34
|
+
<img src="https://img.shields.io/badge/Zero-Dependencies-00C853?style=for-the-badge&logoColor=white&labelColor=1a1a1a" />
|
|
35
|
+
</p>
|
|
36
|
+
|
|
37
|
+
</div>
|
|
38
|
+
|
|
39
|
+
---
|
|
40
|
+
|
|
41
|
+
## ✨ Features
|
|
42
|
+
|
|
43
|
+
| | Feature | Details |
|
|
44
|
+
|---|---|---|
|
|
45
|
+
| 📚 | **Complete Collection** | All 4,341 hadiths from Sunan Ibn Majah |
|
|
46
|
+
| 🌐 | **Bilingual** | Full Arabic text + English translation |
|
|
47
|
+
| ⚡ | **Offline-first** | Data bundled — no CDN needed |
|
|
48
|
+
| 🔧 | **Zero Dependencies** | Nothing extra to install |
|
|
49
|
+
| 🔍 | **Full-text Search** | Search English text and narrator names |
|
|
50
|
+
| 🖥️ | **CLI** | Terminal access with `-a`/`-b` Arabic flags + `--info`, `--chapters` |
|
|
51
|
+
| ⚛️ | **React Hook** | One command generates `useMajah()` |
|
|
52
|
+
| 🐍 | **Python** | Identical API — same camelCase method names |
|
|
53
|
+
| 📘 | **TypeScript** | Full type definitions included |
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## 🚀 Installation
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
npm install sunan-ibn-majah # JS local
|
|
61
|
+
npm install -g sunan-ibn-majah # JS global CLI
|
|
62
|
+
pip install sunan-ibn-majah # Python
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## 🟨 JavaScript / Node.js
|
|
68
|
+
|
|
69
|
+
```javascript
|
|
70
|
+
const majah = require('sunan-ibn-majah'); // CJS
|
|
71
|
+
import majah from 'sunan-ibn-majah'; // ESM
|
|
72
|
+
|
|
73
|
+
majah.get(1)
|
|
74
|
+
majah.getByChapter(1)
|
|
75
|
+
majah.search('prayer')
|
|
76
|
+
majah.search('prayer', 5)
|
|
77
|
+
majah.getRandom()
|
|
78
|
+
majah[0]
|
|
79
|
+
majah.length
|
|
80
|
+
majah.metadata
|
|
81
|
+
majah.chapters
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
---
|
|
85
|
+
|
|
86
|
+
## ⚛️ React
|
|
87
|
+
|
|
88
|
+
```bash
|
|
89
|
+
cd my-react-app
|
|
90
|
+
majah --react # generates src/hooks/useMajah.js
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
```jsx
|
|
94
|
+
import { useMajah } from '../hooks/useMajah';
|
|
95
|
+
|
|
96
|
+
function HadithOfTheDay() {
|
|
97
|
+
const majah = useMajah();
|
|
98
|
+
if (!majah) return <p>Loading...</p>;
|
|
99
|
+
const h = majah.getRandom();
|
|
100
|
+
return <div><strong>{h.english.narrator}</strong><p>{h.english.text}</p></div>;
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## 🐍 Python
|
|
107
|
+
|
|
108
|
+
```python
|
|
109
|
+
from sunan_ibn_majah import Majah
|
|
110
|
+
|
|
111
|
+
majah = Majah()
|
|
112
|
+
|
|
113
|
+
majah.get(1)
|
|
114
|
+
majah.getByChapter(1)
|
|
115
|
+
majah.search("prayer")
|
|
116
|
+
majah.search("prayer", limit=5)
|
|
117
|
+
majah.getRandom()
|
|
118
|
+
majah[0]
|
|
119
|
+
majah.length
|
|
120
|
+
majah.find(lambda h: h.id == 23)
|
|
121
|
+
majah.filter(lambda h: h.chapterId == 1)
|
|
122
|
+
majah.slice(0, 10)
|
|
123
|
+
|
|
124
|
+
# Custom path
|
|
125
|
+
majah = Majah(data_path="/path/to/majah.json")
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
## 🖥️ CLI
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
majah 1 # Hadith by ID
|
|
134
|
+
majah 2345 -a # Arabic only
|
|
135
|
+
majah 2345 -b # Arabic + English
|
|
136
|
+
majah 23 3 # 3rd hadith of chapter 23
|
|
137
|
+
majah --search "prayer"
|
|
138
|
+
majah --search "fasting" --all
|
|
139
|
+
majah --chapter 1
|
|
140
|
+
majah --chapters # List all chapters
|
|
141
|
+
majah --random
|
|
142
|
+
majah --info # Book metadata
|
|
143
|
+
majah --react
|
|
144
|
+
majah --version
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
---
|
|
148
|
+
|
|
149
|
+
## 📂 Structure
|
|
150
|
+
|
|
151
|
+
```
|
|
152
|
+
sunan-ibn-majah/
|
|
153
|
+
├── data/
|
|
154
|
+
│ ├── majah.json ← source of truth
|
|
155
|
+
│ ├── majah.json.gz ← generated (shipped in packages)
|
|
156
|
+
│ └── chapters/ ← generated (gitignored)
|
|
157
|
+
├── bin/index.js ← CLI
|
|
158
|
+
├── src/ ← JS source
|
|
159
|
+
├── types/index.d.ts
|
|
160
|
+
├── python/sunan_ibn_majah/ ← Python package
|
|
161
|
+
├── scripts/build.mjs
|
|
162
|
+
├── examples/
|
|
163
|
+
├── docs/
|
|
164
|
+
└── tests/
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
---
|
|
168
|
+
|
|
169
|
+
## 🔧 Development
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
# Place your real majah.json in data/
|
|
173
|
+
node scripts/build.mjs
|
|
174
|
+
|
|
175
|
+
# Test locally
|
|
176
|
+
python -m build --wheel
|
|
177
|
+
python -m zipfile -l dist\sunan_ibn_majah-1.1.0-py3-none-any.whl | findstr "majah.json.gz"
|
|
178
|
+
pip install dist\sunan_ibn_majah-1.1.0-py3-none-any.whl --force-reinstall
|
|
179
|
+
majah 23
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Publishing is automatic via GitHub Actions on every GitHub Release.
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## 📄 License
|
|
187
|
+
|
|
188
|
+
GNU Affero General Public License v3.0 (AGPL-3.0)
|
|
189
|
+
|
|
190
|
+
---
|
|
191
|
+
|
|
192
|
+
<div align="center">
|
|
193
|
+
|
|
194
|
+
**Made with ❤️ for the Muslim community · Seeking knowledge together**
|
|
195
|
+
|
|
196
|
+
[](https://github.com/SENODROOM/sunan-ibn-majah/stargazers)
|
|
197
|
+
|
|
198
|
+
</div>
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// CLI for sunan-ibn-majah
|
|
3
|
+
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import zlib from 'zlib';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
import { Majah } from '../src/index.js';
|
|
9
|
+
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = path.dirname(__filename);
|
|
12
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
|
|
13
|
+
|
|
14
|
+
function loadData() {
|
|
15
|
+
const gzPath = path.join(__dirname, '..', 'data', 'majah.json.gz');
|
|
16
|
+
const jsonPath = path.join(__dirname, '..', 'data', 'majah.json');
|
|
17
|
+
if (fs.existsSync(gzPath)) return JSON.parse(zlib.gunzipSync(fs.readFileSync(gzPath)).toString('utf8'));
|
|
18
|
+
if (fs.existsSync(jsonPath)) return JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
|
|
19
|
+
throw new Error('Data file not found. Expected data/majah.json.gz or data/majah.json');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const majahData = loadData();
|
|
23
|
+
const { hadiths, chapters, metadata } = majahData;
|
|
24
|
+
const _byId = new Map();
|
|
25
|
+
const _byChapter = new Map();
|
|
26
|
+
hadiths.forEach(h => {
|
|
27
|
+
_byId.set(h.id, h);
|
|
28
|
+
if (!_byChapter.has(h.chapterId)) _byChapter.set(h.chapterId, []);
|
|
29
|
+
_byChapter.get(h.chapterId).push(h);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const majah = new Majah(majahData);
|
|
33
|
+
export default majah;
|
|
34
|
+
export { Majah };
|
|
35
|
+
|
|
36
|
+
const c = { reset:'\x1b[0m',bold:'\x1b[1m',dim:'\x1b[2m',cyan:'\x1b[36m',green:'\x1b[32m',yellow:'\x1b[33m',magenta:'\x1b[35m',gray:'\x1b[90m',red:'\x1b[31m',blue:'\x1b[34m' };
|
|
37
|
+
const cyan=s=>`${c.cyan}${c.bold}${s}${c.reset}`,green=s=>`${c.green}${s}${c.reset}`,yellow=s=>`${c.yellow}${s}${c.reset}`,magenta=s=>`${c.magenta}${s}${c.reset}`,bold=s=>`${c.bold}${s}${c.reset}`,dim=s=>`${c.dim}${s}${c.reset}`,gray=s=>`${c.gray}${s}${c.reset}`,red=s=>`${c.red}${s}${c.reset}`,blue=s=>`${c.blue}${s}${c.reset}`;
|
|
38
|
+
const DIV=()=>gray('═'.repeat(60)),DIV2=()=>gray('─'.repeat(60));
|
|
39
|
+
|
|
40
|
+
function getChapterName(id){const ch=chapters.find(c=>c.id===id);return ch?ch.english:'Unknown Chapter';}
|
|
41
|
+
function highlightMatch(text,q){if(!q)return text;const re=new RegExp(`(${q.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')})`,'gi');return text.replace(re,`${c.green}${c.bold}$1${c.reset}`);}
|
|
42
|
+
function wrapText(text,indent=2,width=78){const words=text.split(' '),lines=[];let line=' '.repeat(indent);for(const word of words){if(line.length+word.length+1>width){lines.push(line);line=' '.repeat(indent)+word;}else{line+=(line.trim()?' ':'')+word;}}if(line.trim())lines.push(line);return lines.join('\n');}
|
|
43
|
+
|
|
44
|
+
function printHadith(h, mode, query) {
|
|
45
|
+
if(!h){console.log(yellow('\n Hadith not found.\n'));return;}
|
|
46
|
+
const chName=getChapterName(h.chapterId);
|
|
47
|
+
console.log('\n'+DIV());
|
|
48
|
+
if(mode==='-a'){
|
|
49
|
+
console.log(`\n ${cyan('Hadith #'+h.id)} ${gray('|')} ${bold('Chapter: '+h.chapterId)} ${gray('—')} ${yellow(chName)}\n`);
|
|
50
|
+
console.log(wrapText(h.arabic)+'\n');
|
|
51
|
+
} else if(mode==='-b'){
|
|
52
|
+
console.log(`\n ${cyan('Hadith #'+h.id)} ${gray('|')} ${bold('Chapter: '+h.chapterId)} ${gray('—')} ${yellow(chName)}\n`);
|
|
53
|
+
console.log(wrapText(h.arabic)+'\n');
|
|
54
|
+
console.log(` ${bold('Narrator:')} ${magenta(h.english.narrator)}\n`);
|
|
55
|
+
console.log(wrapText(h.english.text)+'\n');
|
|
56
|
+
} else {
|
|
57
|
+
console.log(`\n ${cyan('Hadith #'+h.id)} ${gray('|')} ${bold('Chapter: '+h.chapterId)} ${gray('—')} ${yellow(chName)}\n`);
|
|
58
|
+
console.log(` ${bold('Narrator:')} ${magenta(query?highlightMatch(h.english.narrator,query):h.english.narrator)}\n`);
|
|
59
|
+
console.log(wrapText(query?highlightMatch(h.english.text,query):h.english.text)+'\n');
|
|
60
|
+
}
|
|
61
|
+
console.log(DIV()+'\n');
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const args = process.argv.slice(2);
|
|
65
|
+
|
|
66
|
+
if(!args.length||args[0]==='--help'){
|
|
67
|
+
console.log(`\n${DIV()}\n ${cyan('majah')} ${gray('·')} ${bold('Sunan Ibn Majah')} ${gray('v'+pkg.version)}\n${DIV()}\n\n ${bold('Usage:')}\n\n ${cyan('majah')} ${yellow('<id>')} Hadith by ID\n ${cyan('majah')} ${yellow('<id>')} ${gray('-a')} Arabic only\n ${cyan('majah')} ${yellow('<id>')} ${gray('-b')} Arabic + English\n ${cyan('majah')} ${yellow('<chapter>')} ${yellow('<n>')} nth hadith of a chapter\n ${cyan('majah')} ${gray('--search')} ${yellow('"<query>"')} Search in English text\n ${cyan('majah')} ${gray('--search')} ${yellow('"<query>"')} ${gray('--all')} Show all results\n ${cyan('majah')} ${gray('--random')} Random hadith\n ${cyan('majah')} ${gray('--chapter')} ${yellow('<id>')} All hadiths in chapter\n ${cyan('majah')} ${gray('--chapters')} List all chapters\n ${cyan('majah')} ${gray('--info')} Book metadata\n ${cyan('majah')} ${gray('--react')} Generate React hook\n ${cyan('majah')} ${gray('--version')}\n ${cyan('majah')} ${gray('--help')}\n\n${DIV()}\n`);
|
|
68
|
+
process.exit(0);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if(args[0]==='--version'){console.log(`\n ${cyan('sunan-ibn-majah')} ${gray('v'+pkg.version)}\n`);process.exit(0);}
|
|
72
|
+
|
|
73
|
+
if(args[0]==='--info'){
|
|
74
|
+
console.log(`\n${DIV()}\n ${cyan('Sunan Ibn Majah')}\n${DIV()}\n\n ${bold('Title:')} ${metadata.english.title}\n ${bold('Author:')} ${metadata.english.author}\n ${bold('Total Hadiths:')} ${yellow(String(hadiths.length))}\n ${bold('Chapters:')} ${yellow(String(chapters.length))}\n\n${DIV()}\n`);
|
|
75
|
+
process.exit(0);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if(args[0]==='--chapters'){
|
|
79
|
+
console.log(`\n${DIV()}\n ${cyan('Chapters')} ${gray('—')} ${bold('Sunan Ibn Majah')}\n${DIV()}\n`);
|
|
80
|
+
chapters.forEach(ch=>console.log(` ${cyan('#'+String(ch.id).padEnd(5))} ${ch.english}`));
|
|
81
|
+
console.log(`\n${DIV()}\n`);process.exit(0);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if(args[0]==='--random'){printHadith(hadiths[Math.floor(Math.random()*hadiths.length)],args[1]);process.exit(0);}
|
|
85
|
+
|
|
86
|
+
if(args[0]==='--chapter'){
|
|
87
|
+
const chapterId=parseInt(args[1]);
|
|
88
|
+
const results=hadiths.filter(h=>h.chapterId===chapterId);
|
|
89
|
+
const chName=getChapterName(chapterId);
|
|
90
|
+
if(!results.length){console.log(yellow(`\n No hadiths found for chapter ${chapterId}.\n`));process.exit(0);}
|
|
91
|
+
console.log(`\n${DIV()}\n ${cyan('Chapter '+chapterId)} ${gray('—')} ${bold(chName)} ${gray('('+results.length+' hadiths)')}\n${DIV()}`);
|
|
92
|
+
results.forEach(h=>{console.log(`\n ${cyan('Hadith #'+h.id)}`);console.log(` ${bold('Narrator:')} ${magenta(h.english.narrator)}`);console.log(wrapText(h.english.text));console.log('\n'+DIV2());});
|
|
93
|
+
console.log('');process.exit(0);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
if(args[0]==='--search'){
|
|
97
|
+
const showAll=args.includes('--all');
|
|
98
|
+
const query=args.slice(1).filter(a=>a!=='--all').join(' ').replace(/^["']|["']$/g,'');
|
|
99
|
+
if(!query){console.log(yellow('\n Usage: majah --search "<query>" [--all]\n'));process.exit(1);}
|
|
100
|
+
const t0=Date.now();
|
|
101
|
+
const results=hadiths.filter(h=>h.english?.text?.toLowerCase().includes(query.toLowerCase())||h.english?.narrator?.toLowerCase().includes(query.toLowerCase()));
|
|
102
|
+
const ms=Date.now()-t0;
|
|
103
|
+
console.log('\n'+DIV());
|
|
104
|
+
console.log(` ${bold('Search:')} ${yellow('"'+query+'"')} ${gray('—')} ${cyan(results.length+' results')} ${gray('('+ms+'ms)')}`);
|
|
105
|
+
console.log(DIV());
|
|
106
|
+
if(!results.length){console.log(yellow(`\n No results for "${query}"\n`));process.exit(0);}
|
|
107
|
+
const toShow=showAll?results:results.slice(0,5);
|
|
108
|
+
toShow.forEach((h,i)=>{
|
|
109
|
+
const chName=getChapterName(h.chapterId);
|
|
110
|
+
console.log(`\n ${cyan('#'+(i+1))} ${bold('Hadith '+h.id)} ${gray('|')} ${bold('Chapter: '+h.chapterId)} ${gray('—')} ${yellow(chName)}`);
|
|
111
|
+
console.log(` ${bold('Narrator:')} ${magenta(highlightMatch(h.english.narrator,query))}`);
|
|
112
|
+
console.log(wrapText(highlightMatch(h.english.text,query)));
|
|
113
|
+
console.log('\n'+DIV2());
|
|
114
|
+
});
|
|
115
|
+
if(!showAll&&results.length>5){console.log(`\n ${dim('Showing')} ${cyan('5')} ${dim('of')} ${cyan(String(results.length))} ${dim('results.')} ${yellow('Run with --all to see all results')}`);console.log(` ${gray('majah --search "'+query+'" --all')}\n`);}
|
|
116
|
+
else{console.log(`\n ${dim('Showing all')} ${cyan(String(results.length))} ${dim('results.')}\n`);}
|
|
117
|
+
process.exit(0);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if(args[0]==='--react'){
|
|
121
|
+
const CDN='https://cdn.jsdelivr.net/npm/sunan-ibn-majah@'+pkg.version+'/data/majah.json.gz';
|
|
122
|
+
const hookSrc="// Auto-generated by: majah --react\nimport { useState, useEffect } from 'react';\nconst CDN = '"+CDN+"';\nlet _cache=null,_promise=null;\nconst _subs=new Set();\nfunction _load(){if(_cache)return Promise.resolve(_cache);if(_promise)return _promise;_promise=fetch(CDN).then(r=>r.arrayBuffer()).then(buf=>{const stream=new DecompressionStream('gzip');const writer=stream.writable.getWriter();const reader=stream.readable.getReader();writer.write(new Uint8Array(buf));writer.close();const chunks=[];return(function pump(){return reader.read().then(({done,value})=>{if(done){const text=new TextDecoder().decode(new Uint8Array(chunks.reduce((a,b)=>[...a,...b],[])));const data=JSON.parse(text);const hadiths=data.hadiths;const _byId=new Map();hadiths.forEach(h=>_byId.set(h.id,h));_cache=Object.assign([],hadiths,{metadata:data.metadata,chapters:data.chapters,get:id=>_byId.get(id),getByChapter:id=>hadiths.filter(h=>h.chapterId===id),search:(q,limit=0)=>{const ql=q.toLowerCase();const r=hadiths.filter(h=>h.english?.text?.toLowerCase().includes(ql)||h.english?.narrator?.toLowerCase().includes(ql));return limit>0?r.slice(0,limit):r;},getRandom:()=>hadiths[Math.floor(Math.random()*hadiths.length)]});_subs.forEach(fn=>fn(_cache));_subs.clear();return _cache;}chunks.push(value);return pump();});})();});return _promise;}\n_load();\nexport function useMajah(){const[majah,setMajah]=useState(_cache);useEffect(()=>{if(_cache){setMajah(_cache);}else{_subs.add(setMajah);return()=>_subs.delete(setMajah);}},[]);return majah;}\nexport default useMajah;\n";
|
|
123
|
+
const hooksDir=path.join(process.cwd(),'src','hooks');
|
|
124
|
+
if(!fs.existsSync(hooksDir))fs.mkdirSync(hooksDir,{recursive:true});
|
|
125
|
+
fs.writeFileSync(path.join(hooksDir,'useMajah.js'),hookSrc,'utf8');
|
|
126
|
+
console.log(green('\n\u2713 Generated: src/hooks/useMajah.js\n'));
|
|
127
|
+
process.exit(0);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
if(args.length>=2&&!isNaN(parseInt(args[0]))&&!isNaN(parseInt(args[1]))){
|
|
131
|
+
const chapterId=parseInt(args[0]),n=parseInt(args[1]);
|
|
132
|
+
const inChapter=_byChapter.get(chapterId)||[];
|
|
133
|
+
printHadith(inChapter[n-1]??null,args[2]);process.exit(0);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const id=parseInt(args[0]);
|
|
137
|
+
if(isNaN(id)){console.log(yellow(`\n Unknown command: ${args[0]}. Run majah --help\n`));process.exit(1);}
|
|
138
|
+
printHadith(_byId.get(id)||null,args[1]);
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import majah from 'sunan-ibn-majah';
|
|
3
|
+
const app = express();
|
|
4
|
+
app.get('/api/hadith/random', (_, res) => res.json(majah.getRandom()));
|
|
5
|
+
app.get('/api/hadith/:id', (req, res) => { const h = majah.get(parseInt(req.params.id)); h ? res.json(h) : res.status(404).json({error:'Not found'}); });
|
|
6
|
+
app.get('/api/search', (req, res) => res.json(majah.search(req.query.q||'', parseInt(req.query.limit)||0)));
|
|
7
|
+
app.get('/api/chapters', (_, res) => res.json(majah.chapters));
|
|
8
|
+
app.listen(3000, () => console.log('Sunan Ibn Majah API at http://localhost:3000'));
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const majah = require('sunan-ibn-majah');
|
|
3
|
+
console.log('Total hadiths:', majah.length);
|
|
4
|
+
const h = majah.get(1);
|
|
5
|
+
console.log('Hadith #1:', h.english.narrator, '-', h.english.text.slice(0,60)+'...');
|
|
6
|
+
console.log('Search "prayer":', majah.search('prayer').length, 'results');
|
|
7
|
+
console.log('Random:', majah.getRandom().id);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import majah from 'sunan-ibn-majah';
|
|
2
|
+
console.log('Total hadiths:', majah.length);
|
|
3
|
+
console.log('Title:', majah.metadata.english.title);
|
|
4
|
+
const h = majah.get(1);
|
|
5
|
+
console.log('Hadith #1:', h.english.narrator, '-', h.english.text.slice(0,60)+'...');
|
|
6
|
+
console.log('Chapter 1 hadiths:', majah.getByChapter(1).length);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Run `majah --react` in your project first
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { useMajah } from '../hooks/useMajah';
|
|
4
|
+
|
|
5
|
+
export function HadithOfTheDay() {
|
|
6
|
+
const majah = useMajah();
|
|
7
|
+
if (!majah) return <p>Loading...</p>;
|
|
8
|
+
const h = majah.getRandom();
|
|
9
|
+
return (<div><strong>{h.english.narrator}</strong><p>{h.english.text}</p></div>);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function HadithSearch() {
|
|
13
|
+
const majah = useMajah();
|
|
14
|
+
const [results, setResults] = useState([]);
|
|
15
|
+
if (!majah) return <p>Loading...</p>;
|
|
16
|
+
return (<><input placeholder="Search..." onChange={e => setResults(majah.search(e.target.value, 20))} />{results.map(h => <div key={h.id}><p>{h.english.text}</p></div>)}</>);
|
|
17
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sunan-ibn-majah",
|
|
3
|
+
"version": "1.1.2",
|
|
4
|
+
"description": "Complete Sunan Ibn Majah. Offline-first, zero dependencies. CLI + Node + React/Vue/Vite.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./src/index.cjs",
|
|
7
|
+
"module": "./src/index.browser.js",
|
|
8
|
+
"types": "./types/index.d.ts",
|
|
9
|
+
"exports": {
|
|
10
|
+
".": {
|
|
11
|
+
"types": "./types/index.d.ts",
|
|
12
|
+
"browser": "./src/index.browser.js",
|
|
13
|
+
"require": "./src/index.cjs",
|
|
14
|
+
"import": "./src/index.node.js"
|
|
15
|
+
},
|
|
16
|
+
"./data/chapters/*": "./data/chapters/*"
|
|
17
|
+
},
|
|
18
|
+
"bin": {
|
|
19
|
+
"majah": "bin/index.js"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "node scripts/build.mjs",
|
|
23
|
+
"prepublishOnly": "node scripts/build.mjs"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"islam",
|
|
27
|
+
"hadith",
|
|
28
|
+
"majah",
|
|
29
|
+
"ibn-majah",
|
|
30
|
+
"sunan",
|
|
31
|
+
"sunnah",
|
|
32
|
+
"json",
|
|
33
|
+
"arabic",
|
|
34
|
+
"english",
|
|
35
|
+
"quran",
|
|
36
|
+
"react",
|
|
37
|
+
"hook"
|
|
38
|
+
],
|
|
39
|
+
"author": "muhammadsaadamin",
|
|
40
|
+
"license": "AGPL-3.0",
|
|
41
|
+
"repository": {
|
|
42
|
+
"type": "git",
|
|
43
|
+
"url": "git+https://github.com/SENODROOM/sunan-ibn-majah.git"
|
|
44
|
+
},
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/SENODROOM/sunan-ibn-majah/issues"
|
|
47
|
+
},
|
|
48
|
+
"homepage": "https://github.com/SENODROOM/sunan-ibn-majah#readme",
|
|
49
|
+
"files": [
|
|
50
|
+
"src/",
|
|
51
|
+
"types/",
|
|
52
|
+
"bin/index.js",
|
|
53
|
+
"data/majah.json.gz"
|
|
54
|
+
],
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=18.0.0"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// CommonJS entry — require('sunan-ibn-majah')
|
|
2
|
+
'use strict';
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const zlib = require('zlib');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
|
|
7
|
+
function loadData() {
|
|
8
|
+
const gzPath = path.join(__dirname, '..', 'data', 'majah.json.gz');
|
|
9
|
+
const jsonPath = path.join(__dirname, '..', 'data', 'majah.json');
|
|
10
|
+
if (fs.existsSync(gzPath)) return JSON.parse(zlib.gunzipSync(fs.readFileSync(gzPath)).toString('utf8'));
|
|
11
|
+
if (fs.existsSync(jsonPath)) return JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
|
|
12
|
+
throw new Error('Data file not found. Expected data/majah.json.gz or data/majah.json');
|
|
13
|
+
}
|
|
14
|
+
const majahData = loadData();
|
|
15
|
+
|
|
16
|
+
class Majah {
|
|
17
|
+
constructor(majahData) {
|
|
18
|
+
this._hadiths = majahData.hadiths;
|
|
19
|
+
return new Proxy(this._hadiths, {
|
|
20
|
+
get: (target, prop) => {
|
|
21
|
+
if (!isNaN(prop)) return target[parseInt(prop)];
|
|
22
|
+
if (prop in target) return target[prop];
|
|
23
|
+
switch (prop) {
|
|
24
|
+
case 'metadata': return majahData.metadata;
|
|
25
|
+
case 'chapters': return majahData.chapters;
|
|
26
|
+
case 'get': return (id) => this._hadiths.find(h => h.id === id);
|
|
27
|
+
case 'getByChapter': return (id) => this._hadiths.filter(h => h.chapterId === id);
|
|
28
|
+
case 'search': return (q, limit = 0) => {
|
|
29
|
+
const ql = q.toLowerCase();
|
|
30
|
+
const r = this._hadiths.filter(h =>
|
|
31
|
+
h.english?.text?.toLowerCase().includes(ql) ||
|
|
32
|
+
h.english?.narrator?.toLowerCase().includes(ql)
|
|
33
|
+
);
|
|
34
|
+
return limit > 0 ? r.slice(0, limit) : r;
|
|
35
|
+
};
|
|
36
|
+
case 'getRandom': return () => this._hadiths[Math.floor(Math.random() * this._hadiths.length)];
|
|
37
|
+
case 'length': return target.length;
|
|
38
|
+
default: return target[prop];
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
ownKeys: (target) => [
|
|
42
|
+
'length',
|
|
43
|
+
...Array.from({ length: target.length }, (_, i) => String(i)),
|
|
44
|
+
'metadata', 'chapters', 'get', 'getByChapter', 'search', 'getRandom'
|
|
45
|
+
]
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const majah = new Majah(majahData);
|
|
50
|
+
module.exports = majah;
|
|
51
|
+
module.exports.Majah = Majah;
|
|
52
|
+
module.exports.default = majah;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// ESM browser-safe entry (class only — no data)
|
|
2
|
+
export class Majah {
|
|
3
|
+
constructor(majahData) {
|
|
4
|
+
this._hadiths = majahData.hadiths;
|
|
5
|
+
return new Proxy(this._hadiths, {
|
|
6
|
+
get: (target, prop) => {
|
|
7
|
+
if (!isNaN(prop)) return target[parseInt(prop)];
|
|
8
|
+
if (prop in target) return target[prop];
|
|
9
|
+
switch (prop) {
|
|
10
|
+
case 'metadata': return majahData.metadata;
|
|
11
|
+
case 'chapters': return majahData.chapters;
|
|
12
|
+
case 'get': return (id) => this._hadiths.find(h => h.id === id);
|
|
13
|
+
case 'getByChapter': return (id) => this._hadiths.filter(h => h.chapterId === id);
|
|
14
|
+
case 'search': return (q, limit = 0) => {
|
|
15
|
+
const ql = q.toLowerCase();
|
|
16
|
+
const r = this._hadiths.filter(h =>
|
|
17
|
+
h.english?.text?.toLowerCase().includes(ql) ||
|
|
18
|
+
h.english?.narrator?.toLowerCase().includes(ql)
|
|
19
|
+
);
|
|
20
|
+
return limit > 0 ? r.slice(0, limit) : r;
|
|
21
|
+
};
|
|
22
|
+
case 'getRandom': return () => this._hadiths[Math.floor(Math.random() * this._hadiths.length)];
|
|
23
|
+
case 'length': return target.length;
|
|
24
|
+
default: return target[prop];
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
ownKeys: (target) => [
|
|
28
|
+
'length',
|
|
29
|
+
...Array.from({ length: target.length }, (_, i) => String(i)),
|
|
30
|
+
'metadata', 'chapters', 'get', 'getByChapter', 'search', 'getRandom'
|
|
31
|
+
]
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
export default Majah;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Node ESM entry
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import zlib from 'zlib';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { Majah } from './index.js';
|
|
7
|
+
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
|
|
10
|
+
function loadData() {
|
|
11
|
+
const gzPath = path.join(__dirname, '..', 'data', 'majah.json.gz');
|
|
12
|
+
const jsonPath = path.join(__dirname, '..', 'data', 'majah.json');
|
|
13
|
+
if (fs.existsSync(gzPath)) return JSON.parse(zlib.gunzipSync(fs.readFileSync(gzPath)).toString('utf8'));
|
|
14
|
+
if (fs.existsSync(jsonPath)) return JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
|
|
15
|
+
throw new Error('Data file not found. Expected data/majah.json.gz or data/majah.json');
|
|
16
|
+
}
|
|
17
|
+
export { Majah };
|
|
18
|
+
export default new Majah(loadData());
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface HadithEnglish { narrator: string; text: string; }
|
|
2
|
+
export interface Hadith { id: number; chapterId: number; arabic: string; english: HadithEnglish; }
|
|
3
|
+
export interface Chapter { id: number; arabic: string; english: string; }
|
|
4
|
+
export interface Metadata {
|
|
5
|
+
id: number; length: number;
|
|
6
|
+
arabic: { title: string; author: string; introduction: string };
|
|
7
|
+
english: { title: string; author: string; introduction: string };
|
|
8
|
+
}
|
|
9
|
+
export interface MajahData { metadata: Metadata; chapters: Chapter[]; hadiths: Hadith[]; }
|
|
10
|
+
export interface MajahInstance extends ArrayLike<Hadith> {
|
|
11
|
+
[index: number]: Hadith;
|
|
12
|
+
readonly length: number;
|
|
13
|
+
readonly metadata: Metadata;
|
|
14
|
+
readonly chapters: Chapter[];
|
|
15
|
+
get(id: number): Hadith | undefined;
|
|
16
|
+
getByChapter(chapterId: number): Hadith[];
|
|
17
|
+
search(query: string, limit?: number): Hadith[];
|
|
18
|
+
getRandom(): Hadith;
|
|
19
|
+
find(predicate: (h: Hadith) => boolean): Hadith | undefined;
|
|
20
|
+
filter(predicate: (h: Hadith) => boolean): Hadith[];
|
|
21
|
+
map<T>(cb: (h: Hadith, i: number) => T): T[];
|
|
22
|
+
forEach(cb: (h: Hadith, i: number) => void): void;
|
|
23
|
+
slice(start?: number, end?: number): Hadith[];
|
|
24
|
+
}
|
|
25
|
+
export declare class Majah { constructor(data: MajahData); }
|
|
26
|
+
export declare function loadMajah(): Promise<MajahInstance>;
|
|
27
|
+
declare const majah: MajahInstance;
|
|
28
|
+
export default majah;
|
package/package.json
CHANGED
|
@@ -1,27 +1,39 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "sunnah",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "Interactive CLI installer for Sunnah hadith npm packages",
|
|
5
|
-
"
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"
|
|
17
|
-
"
|
|
18
|
-
"
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "sunnah",
|
|
3
|
+
"version": "1.5.0",
|
|
4
|
+
"description": "Interactive CLI installer and manager for all Sunnah hadith npm + PyPI packages",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"sunnah": "./bin/index.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node bin/index.js --list"
|
|
11
|
+
},
|
|
12
|
+
"keywords": [
|
|
13
|
+
"sunnah",
|
|
14
|
+
"hadith",
|
|
15
|
+
"bukhari",
|
|
16
|
+
"muslim",
|
|
17
|
+
"tirmidhi",
|
|
18
|
+
"dawud",
|
|
19
|
+
"nasai",
|
|
20
|
+
"majah",
|
|
21
|
+
"islam",
|
|
22
|
+
"quran",
|
|
23
|
+
"cli",
|
|
24
|
+
"package-manager"
|
|
25
|
+
],
|
|
26
|
+
"author": "muhammadsaadamin",
|
|
27
|
+
"license": "AGPL-3.0",
|
|
28
|
+
"repository": {
|
|
29
|
+
"type": "git",
|
|
30
|
+
"url": "git+https://github.com/SENODROOM/sunnah.git"
|
|
31
|
+
},
|
|
32
|
+
"bugs": {
|
|
33
|
+
"url": "https://github.com/SENODROOM/sunnah/issues"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/SENODROOM/sunnah#readme",
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=18.0.0"
|
|
38
|
+
}
|
|
39
|
+
}
|