sunnah 1.4.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/CHANGELOG.md +19 -0
- package/README.md +258 -398
- 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/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
- /package/{LICENSE → books/sahih-al-bukhari/LICENSE} +0 -0
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
<div align="center">
|
|
2
|
+
|
|
3
|
+
<h1>
|
|
4
|
+
<img src="https://em-content.zobj.net/source/apple/391/mosque_1f54c.png" width="36" />
|
|
5
|
+
jami-al-tirmidhi
|
|
6
|
+
</h1>
|
|
7
|
+
|
|
8
|
+
<p align="center">
|
|
9
|
+
<strong>The complete Jami al-Tirmidhi — 3,956 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/jami-al-tirmidhi">
|
|
17
|
+
<img src="https://img.shields.io/npm/v/jami-al-tirmidhi?style=for-the-badge&logo=npm&logoColor=white&color=CB3837&labelColor=1a1a1a" />
|
|
18
|
+
</a>
|
|
19
|
+
|
|
20
|
+
<a href="https://pypi.org/project/jami-al-tirmidhi/">
|
|
21
|
+
<img src="https://img.shields.io/pypi/v/jami-al-tirmidhi?style=for-the-badge&logo=pypi&logoColor=white&color=3775A9&labelColor=1a1a1a" />
|
|
22
|
+
</a>
|
|
23
|
+
|
|
24
|
+
<a href="https://github.com/SENODROOM/jami-al-tirmidhi/blob/main/LICENSE">
|
|
25
|
+
<img src="https://img.shields.io/github/license/SENODROOM/jami-al-tirmidhi?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
|
+
|
|
36
|
+
<img src="https://img.shields.io/badge/TypeScript-Typed-3178C6?style=for-the-badge&logo=typescript&logoColor=white&labelColor=1a1a1a" />
|
|
37
|
+
</p>
|
|
38
|
+
|
|
39
|
+
</div>
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## ✨ Features
|
|
44
|
+
|
|
45
|
+
| | Feature | Details |
|
|
46
|
+
|---|---|---|
|
|
47
|
+
| 📚 | **Complete Collection** | All 3,956 authentic hadiths from Jami al-Tirmidhi |
|
|
48
|
+
| 🌐 | **Bilingual** | Full Arabic text + English translation |
|
|
49
|
+
| ⚡ | **Offline-first** | Data bundled — no CDN needed |
|
|
50
|
+
| 🔧 | **Zero Dependencies** | Nothing extra to install |
|
|
51
|
+
| 🔍 | **Full-text Search** | Search English text and narrator names instantly |
|
|
52
|
+
| 🖥️ | **CLI** | Terminal access with Arabic/English/both flags |
|
|
53
|
+
| ⚛️ | **React Hook** | One command generates `useTirmidhi()` |
|
|
54
|
+
| 🐍 | **Python** | Identical API — same camelCase method names |
|
|
55
|
+
| 📘 | **TypeScript** | Full type definitions included |
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## 🚀 Installation
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npm install jami-al-tirmidhi # JS local
|
|
63
|
+
npm install -g jami-al-tirmidhi # JS global CLI
|
|
64
|
+
pip install jami-al-tirmidhi # Python
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## 🟨 JavaScript / Node.js
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
const tirmidhi = require('jami-al-tirmidhi'); // CJS
|
|
73
|
+
import tirmidhi from 'jami-al-tirmidhi'; // ESM
|
|
74
|
+
|
|
75
|
+
tirmidhi.get(1)
|
|
76
|
+
tirmidhi.getByChapter(1)
|
|
77
|
+
tirmidhi.search('prayer')
|
|
78
|
+
tirmidhi.search('prayer', 5)
|
|
79
|
+
tirmidhi.getRandom()
|
|
80
|
+
tirmidhi[0]
|
|
81
|
+
tirmidhi.length
|
|
82
|
+
tirmidhi.metadata
|
|
83
|
+
tirmidhi.chapters
|
|
84
|
+
tirmidhi.find(h => h.id === 23)
|
|
85
|
+
tirmidhi.filter(h => h.chapterId === 1)
|
|
86
|
+
tirmidhi.slice(0, 10)
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## ⚛️ React
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
cd my-react-app
|
|
95
|
+
tirmidhi --react # generates src/hooks/useTirmidhi.js
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
```jsx
|
|
99
|
+
import { useTirmidhi } from '../hooks/useTirmidhi';
|
|
100
|
+
|
|
101
|
+
function HadithOfTheDay() {
|
|
102
|
+
const tirmidhi = useTirmidhi();
|
|
103
|
+
if (!tirmidhi) return <p>Loading...</p>;
|
|
104
|
+
const h = tirmidhi.getRandom();
|
|
105
|
+
return <div><strong>{h.english.narrator}</strong><p>{h.english.text}</p></div>;
|
|
106
|
+
}
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
---
|
|
110
|
+
|
|
111
|
+
## 🐍 Python
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from jami_al_tirmidhi import Tirmidhi
|
|
115
|
+
|
|
116
|
+
tirmidhi = Tirmidhi()
|
|
117
|
+
|
|
118
|
+
tirmidhi.get(1)
|
|
119
|
+
tirmidhi.getByChapter(1)
|
|
120
|
+
tirmidhi.search("prayer")
|
|
121
|
+
tirmidhi.search("prayer", limit=5)
|
|
122
|
+
tirmidhi.getRandom()
|
|
123
|
+
tirmidhi[0]
|
|
124
|
+
tirmidhi.length
|
|
125
|
+
tirmidhi.find(lambda h: h.id == 23)
|
|
126
|
+
tirmidhi.filter(lambda h: h.chapterId == 1)
|
|
127
|
+
tirmidhi.slice(0, 10)
|
|
128
|
+
|
|
129
|
+
# Custom path
|
|
130
|
+
tirmidhi = Tirmidhi(data_path="/path/to/tirmidhi.json")
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
---
|
|
134
|
+
|
|
135
|
+
## 🖥️ CLI
|
|
136
|
+
|
|
137
|
+
```bash
|
|
138
|
+
tirmidhi 1
|
|
139
|
+
tirmidhi 2345 -a # Arabic only
|
|
140
|
+
tirmidhi 2345 -b # Arabic + English
|
|
141
|
+
tirmidhi 23 34 # Hadith 34 within chapter 23
|
|
142
|
+
tirmidhi --search "prayer"
|
|
143
|
+
tirmidhi --search "fasting" --all
|
|
144
|
+
tirmidhi --chapter 1
|
|
145
|
+
tirmidhi --random
|
|
146
|
+
tirmidhi --react
|
|
147
|
+
tirmidhi --version
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## 📂 Structure
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
jami-al-tirmidhi/
|
|
156
|
+
├── data/
|
|
157
|
+
│ ├── tirmidhi.json ← source of truth (replace with your real data)
|
|
158
|
+
│ ├── tirmidhi.json.gz ← generated (shipped in packages)
|
|
159
|
+
│ └── chapters/ ← generated (gitignored)
|
|
160
|
+
├── bin/index.js ← CLI
|
|
161
|
+
├── src/ ← JS source
|
|
162
|
+
├── types/index.d.ts
|
|
163
|
+
├── python/jami_al_tirmidhi/ ← Python package
|
|
164
|
+
├── scripts/build.mjs
|
|
165
|
+
├── examples/
|
|
166
|
+
├── docs/
|
|
167
|
+
└── tests/
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## 🔧 Development
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
# Place your real tirmidhi.json in data/
|
|
176
|
+
node scripts/build.mjs # compress, generate chapters/, copy to python/
|
|
177
|
+
|
|
178
|
+
# Test locally before publishing
|
|
179
|
+
python -m build --wheel
|
|
180
|
+
python -m zipfile -l dist\jami_al_tirmidhi-1.0.0-py3-none-any.whl | findstr "tirmidhi.json.gz"
|
|
181
|
+
pip install dist\jami_al_tirmidhi-1.0.0-py3-none-any.whl --force-reinstall
|
|
182
|
+
tirmidhi 23
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Publishing is automatic via GitHub Actions on every GitHub Release.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## 📄 License
|
|
190
|
+
|
|
191
|
+
GNU Affero General Public License v3.0 (AGPL-3.0)
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
<div align="center">
|
|
196
|
+
|
|
197
|
+
**Made with ❤️ for the Muslim community · Seeking knowledge together**
|
|
198
|
+
|
|
199
|
+
[](https://github.com/SENODROOM/jami-al-tirmidhi/stargazers)
|
|
200
|
+
|
|
201
|
+
</div>
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// CLI for jami-al-tirmidhi
|
|
3
|
+
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import zlib from 'zlib';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
import { Tirmidhi } 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', 'tirmidhi.json.gz');
|
|
16
|
+
const jsonPath = path.join(__dirname, '..', 'data', 'tirmidhi.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/tirmidhi.json.gz or data/tirmidhi.json');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const tirmidhiData = loadData();
|
|
23
|
+
const _byId = new Map();
|
|
24
|
+
const _byChapter = new Map();
|
|
25
|
+
tirmidhiData.hadiths.forEach(h => {
|
|
26
|
+
_byId.set(h.id, h);
|
|
27
|
+
if (!_byChapter.has(h.chapterId)) _byChapter.set(h.chapterId, []);
|
|
28
|
+
_byChapter.get(h.chapterId).push(h);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const tirmidhi = new Tirmidhi(tirmidhiData);
|
|
32
|
+
export default tirmidhi;
|
|
33
|
+
export { Tirmidhi };
|
|
34
|
+
|
|
35
|
+
const c = { reset:'\x1b[0m',bold:'\x1b[1m',dim:'\x1b[2m',green:'\x1b[32m',yellow:'\x1b[33m',cyan:'\x1b[36m',magenta:'\x1b[35m',blue:'\x1b[34m',red:'\x1b[31m',gray:'\x1b[90m' };
|
|
36
|
+
const clr=(col,t)=>`${col}${t}${c.reset}`;
|
|
37
|
+
const bold=t=>clr(c.bold,t),green=t=>clr(c.green,t),yellow=t=>clr(c.yellow,t),cyan=t=>clr(c.cyan,t),magenta=t=>clr(c.magenta,t),gray=t=>clr(c.gray,t),red=t=>clr(c.red,t),blue=t=>clr(c.blue,t),dim=t=>clr(c.dim,t);
|
|
38
|
+
|
|
39
|
+
function highlight(text,term){if(!term)return text;const re=new RegExp(`(${term.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')})`,'gi');return text.replace(re,`\x1b[1m\x1b[33m$1\x1b[0m`);}
|
|
40
|
+
function wrap(text,width=72,indent=''){const words=text.split(' '),lines=[];let line='';for(const word of words){if((line+' '+word).trim().length>width){if(line)lines.push(indent+line.trim());line=word;}else{line=(line+' '+word).trim();}}if(line)lines.push(indent+line.trim());return lines.join('\n');}
|
|
41
|
+
function isRunDirectly(){if(!process.argv[1])return false;const argv1=path.resolve(process.argv[1]).toLowerCase().replace(/\\/g,'/');const self=__filename.toLowerCase().replace(/\\/g,'/');const base=path.basename(argv1).replace(/\.js$/,'');return argv1===self||base==='tirmidhi';}
|
|
42
|
+
|
|
43
|
+
if(isRunDirectly()){
|
|
44
|
+
const rawArgs=process.argv.slice(2);
|
|
45
|
+
const flags=rawArgs.filter(a=>a.startsWith('-'));
|
|
46
|
+
const numArgs=rawArgs.filter(a=>!a.startsWith('-'));
|
|
47
|
+
const wantsHelp=flags.some(f=>f==='-h'||f==='--help');
|
|
48
|
+
const wantsVersion=flags.some(f=>f==='-v'||f==='--version');
|
|
49
|
+
const wantsReact=flags.some(f=>f==='--react');
|
|
50
|
+
const wantsRandom=flags.some(f=>f==='--random'||f==='-r');
|
|
51
|
+
const wantsAll=flags.some(f=>f==='--all');
|
|
52
|
+
const showArabic=flags.some(f=>f==='-a'||f==='--arabic');
|
|
53
|
+
const showBoth=flags.some(f=>f==='-b'||f==='--both');
|
|
54
|
+
const printArabic=showArabic||showBoth;
|
|
55
|
+
const printEnglish=!showArabic||showBoth;
|
|
56
|
+
const searchIdx=rawArgs.findIndex(a=>a==='--search'||a==='-s');
|
|
57
|
+
const searchQuery=searchIdx!==-1&&rawArgs[searchIdx+1]&&!rawArgs[searchIdx+1].startsWith('-')?rawArgs[searchIdx+1]:null;
|
|
58
|
+
const chapterIdx=rawArgs.findIndex(a=>a==='--chapter'||a==='-c');
|
|
59
|
+
const chapterArg=chapterIdx!==-1&&rawArgs[chapterIdx+1]&&!rawArgs[chapterIdx+1].startsWith('-')?parseInt(rawArgs[chapterIdx+1]):null;
|
|
60
|
+
const DIV=gray('─'.repeat(60)),DIV2=gray('═'.repeat(60));
|
|
61
|
+
|
|
62
|
+
if(wantsVersion){console.log('');console.log(' '+bold(cyan('jami-al-tirmidhi'))+gray(' v'+pkg.version));console.log(' '+gray('Total hadiths : ')+yellow(tirmidhiData.hadiths.length.toLocaleString()));console.log(' '+gray('Total chapters: ')+yellow(tirmidhiData.chapters.length.toLocaleString()));console.log(' '+gray('Author : ')+cyan('Imam Abu Isa Muhammad al-Tirmidhi'));console.log('');process.exit(0);}
|
|
63
|
+
if(wantsRandom){printHadith(tirmidhiData.hadiths[Math.floor(Math.random()*tirmidhiData.hadiths.length)]);process.exit(0);}
|
|
64
|
+
|
|
65
|
+
if(chapterArg!==null){
|
|
66
|
+
const hadiths=_byChapter.get(chapterArg)||[];
|
|
67
|
+
const chapter=tirmidhiData.chapters.find(c=>c.id===chapterArg);
|
|
68
|
+
if(!hadiths.length){console.log('\n'+red(' No chapter found with id '+chapterArg)+'\n');process.exit(1);}
|
|
69
|
+
console.log('');console.log(DIV2);
|
|
70
|
+
console.log(bold(cyan(' Chapter '+chapterArg))+(chapter?gray(' — ')+yellow(chapter.english):''));
|
|
71
|
+
if(chapter?.arabic)console.log(' '+magenta(chapter.arabic));
|
|
72
|
+
console.log(gray(' '+hadiths.length+' hadiths'));console.log(DIV2);
|
|
73
|
+
hadiths.forEach(h=>printHadith(h));process.exit(0);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if(searchQuery!==null){
|
|
77
|
+
const start=Date.now(),ql=searchQuery.toLowerCase();
|
|
78
|
+
const results=tirmidhiData.hadiths.filter(h=>h.english?.text?.toLowerCase().includes(ql)||h.english?.narrator?.toLowerCase().includes(ql));
|
|
79
|
+
const elapsed=Date.now()-start;
|
|
80
|
+
console.log('');console.log(DIV2);
|
|
81
|
+
console.log(bold(cyan(' Search: '))+yellow('"'+searchQuery+'"')+gray(' — ')+green(results.length+' results')+gray(' ('+elapsed+'ms)'));
|
|
82
|
+
console.log(DIV2);
|
|
83
|
+
if(!results.length){console.log('\n '+red('No hadiths found for: ')+yellow('"'+searchQuery+'"')+'\n');process.exit(0);}
|
|
84
|
+
const limit=wantsAll?results.length:Math.min(5,results.length);
|
|
85
|
+
results.slice(0,limit).forEach((hadith,i)=>{
|
|
86
|
+
const chapter=tirmidhiData.chapters.find(c=>c.id===hadith.chapterId);
|
|
87
|
+
console.log('');
|
|
88
|
+
console.log(bold(green(' #'+(i+1)))+gray(' Hadith '+hadith.id)+gray(' | Chapter: ')+cyan(hadith.chapterId)+(chapter?gray(' — ')+dim(chapter.english):''));
|
|
89
|
+
if(hadith.english?.narrator)console.log(' '+bold(yellow('Narrator: '))+magenta(hadith.english.narrator));
|
|
90
|
+
if(hadith.english?.text)console.log(' '+wrap(highlight(hadith.english.text,searchQuery),68,' ').trimStart());
|
|
91
|
+
console.log(DIV);
|
|
92
|
+
});
|
|
93
|
+
if(!wantsAll&&results.length>5){console.log('');console.log(' '+gray('Showing ')+cyan('5')+gray(' of ')+yellow(results.length)+gray(' results. ')+bold(blue('Run with --all to see all results')));console.log(' '+dim('tirmidhi --search "'+searchQuery+'" --all'));console.log('');}
|
|
94
|
+
process.exit(0);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if(wantsReact){
|
|
98
|
+
const cwd=process.cwd(),srcDir=path.join(cwd,'src'),hooksDir=path.join(srcDir,'hooks');
|
|
99
|
+
const pkgPath=path.join(cwd,'package.json');
|
|
100
|
+
if(!fs.existsSync(pkgPath)){console.error(red(' \u2717 No package.json found.'));process.exit(1);}
|
|
101
|
+
const projectPkg=JSON.parse(fs.readFileSync(pkgPath,'utf8'));
|
|
102
|
+
const deps={...projectPkg.dependencies,...projectPkg.devDependencies};
|
|
103
|
+
if(!deps['react']){console.error(red(' \u2717 React not found.'));process.exit(1);}
|
|
104
|
+
if(!fs.existsSync(srcDir)){console.error(red(' \u2717 No src/ directory found.'));process.exit(1);}
|
|
105
|
+
if(!fs.existsSync(hooksDir))fs.mkdirSync(hooksDir,{recursive:true});
|
|
106
|
+
const CDN='https://cdn.jsdelivr.net/npm/jami-al-tirmidhi@'+pkg.version+'/data/tirmidhi.json.gz';
|
|
107
|
+
const hookFile=path.join(hooksDir,'useTirmidhi.js');
|
|
108
|
+
const hookSrc="// Auto-generated by: tirmidhi --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 useTirmidhi(){const[tirmidhi,setTirmidhi]=useState(_cache);useEffect(()=>{if(_cache){setTirmidhi(_cache);}else{_subs.add(setTirmidhi);return()=>_subs.delete(setTirmidhi);}},[]);return tirmidhi;}\nexport default useTirmidhi;\n";
|
|
109
|
+
fs.writeFileSync(hookFile,hookSrc,'utf8');
|
|
110
|
+
console.log('\n '+green('\u2713')+bold(' Generated: ')+cyan('src/hooks/useTirmidhi.js')+'\n');
|
|
111
|
+
process.exit(0);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if(wantsHelp||(numArgs.length===0&&!searchQuery&&chapterArg===null&&!wantsRandom)){
|
|
115
|
+
console.log('');console.log(' '+bold(cyan('Jami al-Tirmidhi CLI'))+gray(' v'+pkg.version));console.log('');
|
|
116
|
+
console.log(' '+bold('Usage:'));
|
|
117
|
+
console.log(' '+cyan('tirmidhi')+yellow(' <hadithId>')+gray(' Show hadith by global ID'));
|
|
118
|
+
console.log(' '+cyan('tirmidhi')+yellow(' <chapterId> <hadithId>')+gray(' Show hadith within a chapter'));
|
|
119
|
+
console.log(' '+cyan('tirmidhi')+green(' --search')+yellow(' "<query>"')+gray(' Search hadiths (top 5)'));
|
|
120
|
+
console.log(' '+cyan('tirmidhi')+green(' --search')+yellow(' "<query>"')+green(' --all')+gray(' Show all results'));
|
|
121
|
+
console.log(' '+cyan('tirmidhi')+green(' --chapter')+yellow(' <id>')+gray(' List all hadiths in a chapter'));
|
|
122
|
+
console.log(' '+cyan('tirmidhi')+green(' --random')+gray(' Show a random hadith'));
|
|
123
|
+
console.log('');
|
|
124
|
+
console.log(' '+bold('Language flags:')+gray(' (default = English only)'));
|
|
125
|
+
console.log(' '+green('-a')+gray(', ')+green('--arabic')+gray(' Arabic only'));
|
|
126
|
+
console.log(' '+green('-b')+gray(', ')+green('--both')+gray(' Arabic + English'));
|
|
127
|
+
console.log('');
|
|
128
|
+
console.log(' '+bold('Other:'));
|
|
129
|
+
console.log(' '+green('--react')+gray(' Generate useTirmidhi React hook'));
|
|
130
|
+
console.log(' '+green('-v')+gray(', ')+green('--version')+gray(' Show version'));
|
|
131
|
+
console.log(' '+green('-h')+gray(', ')+green('--help')+gray(' Show this help'));
|
|
132
|
+
console.log('');process.exit(0);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function printHadith(hadith){
|
|
136
|
+
if(!hadith){console.log('\n '+red('Hadith not found.')+'\n');process.exit(1);}
|
|
137
|
+
const chapter=tirmidhiData.chapters?.find(c=>c.id===hadith.chapterId);
|
|
138
|
+
console.log('');console.log(DIV2);
|
|
139
|
+
const headerEn=bold(cyan('Hadith #'+hadith.id))+gray(' | Chapter: ')+cyan(hadith.chapterId)+(chapter?.english?gray(' — ')+yellow(chapter.english):'');
|
|
140
|
+
const headerAr=bold(magenta('\u062d\u062f\u064a\u062b #'+hadith.id))+gray(' | \u0628\u0627\u0628: ')+magenta(hadith.chapterId)+(chapter?.arabic?gray(' \u2014 ')+magenta(chapter.arabic):'');
|
|
141
|
+
console.log(' '+(printArabic&&!printEnglish?headerAr:headerEn));
|
|
142
|
+
console.log(DIV2);
|
|
143
|
+
if(printEnglish){if(hadith.english?.narrator)console.log(' '+bold(yellow('Narrator: '))+magenta(hadith.english.narrator));if(hadith.english?.text){console.log('');console.log(wrap(hadith.english.text,68,' '));}}
|
|
144
|
+
if(printArabic){if(printEnglish)console.log('\n'+DIV);if(hadith.arabic){console.log('');console.log(' '+magenta(hadith.arabic));}}
|
|
145
|
+
console.log('');console.log(DIV2);console.log('');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function resolveHadith(){
|
|
149
|
+
if(numArgs.length===1){const id=parseInt(numArgs[0]);if(isNaN(id))return null;return _byId.get(id)||null;}
|
|
150
|
+
if(numArgs.length===2){
|
|
151
|
+
const chapterId=parseInt(numArgs[0]),hadithNum=parseInt(numArgs[1]);
|
|
152
|
+
if(isNaN(chapterId)||isNaN(hadithNum))return null;
|
|
153
|
+
const inChapter=_byChapter.get(chapterId);
|
|
154
|
+
if(!inChapter?.length){console.log('\n '+red('No chapter found with id '+chapterId+'.')+'\n');process.exit(1);}
|
|
155
|
+
return inChapter.find(h=>h.id===hadithNum)??inChapter[hadithNum-1]??null;
|
|
156
|
+
}
|
|
157
|
+
return null;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if(numArgs.length>0){
|
|
161
|
+
const hadith=resolveHadith();
|
|
162
|
+
if(!hadith){console.log('\n '+red('Invalid arguments.')+' Run '+cyan('tirmidhi --help')+' for usage.\n');process.exit(1);}
|
|
163
|
+
printHadith(hadith);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import tirmidhi from 'jami-al-tirmidhi';
|
|
3
|
+
const app = express();
|
|
4
|
+
app.get('/api/hadith/random', (_, res) => res.json(tirmidhi.getRandom()));
|
|
5
|
+
app.get('/api/hadith/:id', (req, res) => { const h = tirmidhi.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(tirmidhi.search(req.query.q||'', parseInt(req.query.limit)||0)));
|
|
7
|
+
app.listen(3000, () => console.log('Jami al-Tirmidhi API at http://localhost:3000'));
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
const tirmidhi = require('jami-al-tirmidhi');
|
|
3
|
+
console.log('Total hadiths:', tirmidhi.length);
|
|
4
|
+
const h = tirmidhi.get(1);
|
|
5
|
+
console.log('Hadith #1:', h.english.narrator, '-', h.english.text.slice(0,60)+'...');
|
|
6
|
+
console.log('Search "prayer":', tirmidhi.search('prayer').length, 'results');
|
|
7
|
+
console.log('Random:', tirmidhi.getRandom().id);
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import tirmidhi from 'jami-al-tirmidhi';
|
|
2
|
+
console.log('Total hadiths:', tirmidhi.length);
|
|
3
|
+
console.log('Title:', tirmidhi.metadata.english.title);
|
|
4
|
+
const h = tirmidhi.get(1);
|
|
5
|
+
console.log('Hadith #1:', h.english.narrator, '-', h.english.text.slice(0,60)+'...');
|
|
6
|
+
console.log('Chapter 1 hadiths:', tirmidhi.getByChapter(1).length);
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Run `tirmidhi --react` in your project first
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { useTirmidhi } from '../hooks/useTirmidhi';
|
|
4
|
+
|
|
5
|
+
export function HadithOfTheDay() {
|
|
6
|
+
const tirmidhi = useTirmidhi();
|
|
7
|
+
if (!tirmidhi) return <p>Loading...</p>;
|
|
8
|
+
const h = tirmidhi.getRandom();
|
|
9
|
+
return (<div><strong>{h.english.narrator}</strong><p>{h.english.text}</p></div>);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function HadithSearch() {
|
|
13
|
+
const tirmidhi = useTirmidhi();
|
|
14
|
+
const [results, setResults] = useState([]);
|
|
15
|
+
if (!tirmidhi) return <p>Loading...</p>;
|
|
16
|
+
return (<><input placeholder="Search..." onChange={e => setResults(tirmidhi.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": "jami-al-tirmidhi",
|
|
3
|
+
"version": "1.0.1",
|
|
4
|
+
"description": "Complete Jami al-Tirmidhi. 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
|
+
"tirmidhi": "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
|
+
"tirmidhi",
|
|
29
|
+
"jami",
|
|
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/jami-al-tirmidhi.git"
|
|
44
|
+
},
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/SENODROOM/jami-al-tirmidhi/issues"
|
|
47
|
+
},
|
|
48
|
+
"homepage": "https://github.com/SENODROOM/jami-al-tirmidhi#readme",
|
|
49
|
+
"files": [
|
|
50
|
+
"src/",
|
|
51
|
+
"types/",
|
|
52
|
+
"bin/index.js",
|
|
53
|
+
"data/tirmidhi.json.gz"
|
|
54
|
+
],
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=18.0.0"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// CommonJS entry — require('jami-al-tirmidhi')
|
|
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', 'tirmidhi.json.gz');
|
|
9
|
+
const jsonPath = path.join(__dirname, '..', 'data', 'tirmidhi.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/tirmidhi.json.gz or data/tirmidhi.json');
|
|
13
|
+
}
|
|
14
|
+
const tirmidhiData = loadData();
|
|
15
|
+
|
|
16
|
+
class Tirmidhi {
|
|
17
|
+
constructor(tirmidhiData) {
|
|
18
|
+
this._hadiths = tirmidhiData.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 tirmidhiData.metadata;
|
|
25
|
+
case 'chapters': return tirmidhiData.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 tirmidhi = new Tirmidhi(tirmidhiData);
|
|
50
|
+
module.exports = tirmidhi;
|
|
51
|
+
module.exports.Tirmidhi = Tirmidhi;
|
|
52
|
+
module.exports.default = tirmidhi;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// ESM browser-safe entry (class only — no data)
|
|
2
|
+
export class Tirmidhi {
|
|
3
|
+
constructor(tirmidhiData) {
|
|
4
|
+
this._hadiths = tirmidhiData.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 tirmidhiData.metadata;
|
|
11
|
+
case 'chapters': return tirmidhiData.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 Tirmidhi;
|
|
@@ -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 { Tirmidhi } from './index.js';
|
|
7
|
+
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
|
|
10
|
+
function loadData() {
|
|
11
|
+
const gzPath = path.join(__dirname, '..', 'data', 'tirmidhi.json.gz');
|
|
12
|
+
const jsonPath = path.join(__dirname, '..', 'data', 'tirmidhi.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/tirmidhi.json.gz or data/tirmidhi.json');
|
|
16
|
+
}
|
|
17
|
+
export { Tirmidhi };
|
|
18
|
+
export default new Tirmidhi(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 TirmidhiData { metadata: Metadata; chapters: Chapter[]; hadiths: Hadith[]; }
|
|
10
|
+
export interface TirmidhiInstance 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 Tirmidhi { constructor(data: TirmidhiData); }
|
|
26
|
+
export declare function loadTirmidhi(): Promise<TirmidhiInstance>;
|
|
27
|
+
declare const tirmidhi: TirmidhiInstance;
|
|
28
|
+
export default tirmidhi;
|