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.
Files changed (65) hide show
  1. package/CHANGELOG.md +19 -0
  2. package/README.md +258 -398
  3. package/bin/index.js +1022 -1604
  4. package/books/jami-al-tirmidhi/README.md +201 -0
  5. package/books/jami-al-tirmidhi/bin/index.js +165 -0
  6. package/books/jami-al-tirmidhi/examples/express/server.js +7 -0
  7. package/books/jami-al-tirmidhi/examples/node-commonjs/example.js +7 -0
  8. package/books/jami-al-tirmidhi/examples/node-esm/example.mjs +6 -0
  9. package/books/jami-al-tirmidhi/examples/react/HadithExample.jsx +17 -0
  10. package/books/jami-al-tirmidhi/package.json +58 -0
  11. package/books/jami-al-tirmidhi/src/index.cjs +52 -0
  12. package/books/jami-al-tirmidhi/src/index.js +35 -0
  13. package/books/jami-al-tirmidhi/src/index.node.js +18 -0
  14. package/books/jami-al-tirmidhi/types/index.d.ts +28 -0
  15. package/books/sahih-al-bukhari/README.md +551 -0
  16. package/books/sahih-al-bukhari/bin/index.js +306 -0
  17. package/books/sahih-al-bukhari/data/bukhari.json.gz +0 -0
  18. package/books/sahih-al-bukhari/examples/express/server.js +49 -0
  19. package/books/sahih-al-bukhari/examples/node-commonjs/example.js +21 -0
  20. package/books/sahih-al-bukhari/examples/node-esm/example.mjs +24 -0
  21. package/books/sahih-al-bukhari/examples/react/HadithExample.jsx +73 -0
  22. package/books/sahih-al-bukhari/package.json +54 -0
  23. package/books/sahih-al-bukhari/src/index.cjs +55 -0
  24. package/books/sahih-al-bukhari/src/index.js +35 -0
  25. package/books/sahih-al-bukhari/src/index.node.js +21 -0
  26. package/books/sahih-al-bukhari/types/index.d.ts +35 -0
  27. package/books/sahih-muslim/LICENSE +661 -0
  28. package/books/sahih-muslim/README.md +547 -0
  29. package/books/sahih-muslim/bin/index.js +183 -0
  30. package/books/sahih-muslim/data/muslim.json.gz +0 -0
  31. package/books/sahih-muslim/examples/express/server.js +16 -0
  32. package/books/sahih-muslim/examples/node-commonjs/example.js +11 -0
  33. package/books/sahih-muslim/examples/node-esm/example.mjs +9 -0
  34. package/books/sahih-muslim/examples/react/HadithExample.jsx +28 -0
  35. package/books/sahih-muslim/package.json +58 -0
  36. package/books/sahih-muslim/src/index.cjs +52 -0
  37. package/books/sahih-muslim/src/index.js +35 -0
  38. package/books/sahih-muslim/src/index.node.js +18 -0
  39. package/books/sahih-muslim/types/index.d.ts +28 -0
  40. package/books/sunan-abi-dawud/LICENSE +661 -0
  41. package/books/sunan-abi-dawud/README.md +149 -0
  42. package/books/sunan-abi-dawud/bin/index.js +183 -0
  43. package/books/sunan-abi-dawud/data/dawud.json.gz +0 -0
  44. package/books/sunan-abi-dawud/examples/express/server.js +7 -0
  45. package/books/sunan-abi-dawud/examples/node-commonjs/example.js +7 -0
  46. package/books/sunan-abi-dawud/examples/node-esm/example.mjs +6 -0
  47. package/books/sunan-abi-dawud/examples/react/HadithExample.jsx +17 -0
  48. package/books/sunan-abi-dawud/package.json +58 -0
  49. package/books/sunan-abi-dawud/src/index.cjs +52 -0
  50. package/books/sunan-abi-dawud/src/index.js +35 -0
  51. package/books/sunan-abi-dawud/src/index.node.js +18 -0
  52. package/books/sunan-abi-dawud/types/index.d.ts +28 -0
  53. package/books/sunan-ibn-majah/README.md +198 -0
  54. package/books/sunan-ibn-majah/bin/index.js +138 -0
  55. package/books/sunan-ibn-majah/examples/express/server.js +8 -0
  56. package/books/sunan-ibn-majah/examples/node-commonjs/example.js +7 -0
  57. package/books/sunan-ibn-majah/examples/node-esm/example.mjs +6 -0
  58. package/books/sunan-ibn-majah/examples/react/HadithExample.jsx +17 -0
  59. package/books/sunan-ibn-majah/package.json +58 -0
  60. package/books/sunan-ibn-majah/src/index.cjs +52 -0
  61. package/books/sunan-ibn-majah/src/index.js +35 -0
  62. package/books/sunan-ibn-majah/src/index.node.js +18 -0
  63. package/books/sunan-ibn-majah/types/index.d.ts +28 -0
  64. package/package.json +39 -27
  65. /package/{LICENSE → books/sahih-al-bukhari/LICENSE} +0 -0
@@ -0,0 +1,149 @@
1
+ <div align="center">
2
+
3
+ <h1>🕌 Sunan Abi Dawud</h1>
4
+
5
+ ![npm version](https://img.shields.io/npm/v/sunan-abi-dawud?style=for-the-badge&logo=npm)
6
+ ![npm downloads](https://img.shields.io/npm/dt/sunan-abi-dawud?style=for-the-badge&logo=npm)
7
+ ![npm downloads per month](https://img.shields.io/npm/dm/sunan-abi-dawud?style=for-the-badge&logo=npm)
8
+ ![license](https://img.shields.io/github/license/SENODROOM/sunan-abi-dawud?style=for-the-badge&logo=gnu)
9
+ ![node version](https://img.shields.io/node/v/sunan-abi-dawud?style=for-the-badge&logo=node.js)
10
+ ![bundle size](https://img.shields.io/bundlephobia/minzip/sunan-abi-dawud?style=for-the-badge)
11
+ ![GitHub stars](https://img.shields.io/github/stars/SENODROOM/sunan-abi-dawud?style=for-the-badge&logo=github)
12
+
13
+ **📚 Complete Sunan Abi Dawud for JavaScript — CLI, Node.js, React, Vue, and every bundler. Tiny package, data served from CDN.**
14
+
15
+ [![NPM](https://nodei.co/npm/sunan-abi-dawud.png)](https://nodei.co/npm/sunan-abi-dawud/)
16
+
17
+ </div>
18
+
19
+ ---
20
+
21
+ ## 📊 Package Statistics
22
+
23
+ | Metric | Value | Description |
24
+ |--------|-------|-------------|
25
+ | 📚 **Total Hadiths** | 5,274 | Complete Sunan Abi Dawud collection |
26
+ | 📝 **Chapters** | 1,871 | Detailed chapter organization |
27
+ | 📦 **Package Size** | ~3KB | Core package — data loads from CDN |
28
+ | 🔧 **Dependencies** | 0 | Zero external dependencies |
29
+ | 🌐 **Bilingual** | ✅ | Full Arabic text + English translations |
30
+ | 📘 **TypeScript** | ✅ | Built-in type definitions |
31
+
32
+ ---
33
+
34
+ ## 🚀 Installation
35
+
36
+ ```bash
37
+ npm install sunan-abi-dawud # local
38
+ npm install -g sunan-abi-dawud # global CLI
39
+ ```
40
+
41
+ ---
42
+
43
+ ## 🖥️ CLI Usage
44
+
45
+ ```bash
46
+ dawud 1 # First hadith
47
+ dawud 2345 # Hadith #2345
48
+ dawud 23 34 # 34th hadith of chapter 23
49
+ dawud 2345 -a # Arabic only
50
+ dawud 2345 -b # Arabic + English
51
+ dawud --react # Generate React hook in current project
52
+ dawud --help
53
+ dawud --version
54
+ ```
55
+
56
+ ---
57
+
58
+ ## ⚛️ React / Vue / Vite
59
+
60
+ ```bash
61
+ cd my-react-app
62
+ dawud --react
63
+ # ✓ Generated: src/hooks/useDawud.js
64
+ ```
65
+
66
+ ```jsx
67
+ import { useDawud } from '../hooks/useDawud';
68
+
69
+ function HadithOfTheDay() {
70
+ const dawud = useDawud();
71
+ if (!dawud) return <p>Loading...</p>;
72
+
73
+ const hadith = dawud.getRandom();
74
+ return (
75
+ <div>
76
+ <p><strong>{hadith.english.narrator}</strong></p>
77
+ <p>{hadith.english.text}</p>
78
+ </div>
79
+ );
80
+ }
81
+ ```
82
+
83
+ ---
84
+
85
+ ## 🟩 Node.js Usage
86
+
87
+ ```javascript
88
+ // CommonJS
89
+ const dawud = require('sunan-abi-dawud');
90
+ console.log(dawud.get(1));
91
+ console.log(dawud.search('prayer'));
92
+ console.log(dawud.getRandom());
93
+ console.log(dawud.getByChapter(1));
94
+ console.log(dawud.length);
95
+
96
+ // ESM
97
+ import dawud from 'sunan-abi-dawud';
98
+ const hadith = dawud.get(23);
99
+ console.log(hadith.english.text);
100
+ ```
101
+
102
+ ---
103
+
104
+ ## 🛠️ API Reference
105
+
106
+ | Method / Property | Description |
107
+ |-------------------|-------------|
108
+ | `dawud[0]` | Hadith at index 0 |
109
+ | `dawud.get(id)` | Hadith by ID |
110
+ | `dawud.getByChapter(id)` | All hadiths in a chapter |
111
+ | `dawud.search(query)` | Full-text search |
112
+ | `dawud.getRandom()` | Random hadith |
113
+ | `dawud.length` | Total hadiths |
114
+ | `dawud.metadata` | Book metadata |
115
+ | `dawud.chapters` | All chapters |
116
+
117
+ All native array methods work: `find`, `filter`, `map`, `forEach`, `slice`.
118
+
119
+ ---
120
+
121
+ ## 📐 Data Structure
122
+
123
+ ```javascript
124
+ {
125
+ "id": 1,
126
+ "chapterId": 1,
127
+ "arabic": "حَدَّثَنَا...",
128
+ "english": {
129
+ "narrator": "Abu Huraira",
130
+ "text": "The Prophet (ﷺ) said..."
131
+ }
132
+ }
133
+ ```
134
+
135
+ ---
136
+
137
+ ## 📄 License
138
+
139
+ **GNU Affero General Public License v3.0 (AGPL-3.0)**
140
+
141
+ ---
142
+
143
+ <div align="center">
144
+
145
+ **Made with ❤️ for the Muslim community | Seeking knowledge together**
146
+
147
+ [![GitHub stars](https://img.shields.io/github/stars/SENODROOM/sunan-abi-dawud?style=for-the-badge&logo=github)](https://github.com/SENODROOM/sunan-abi-dawud)
148
+
149
+ </div>
@@ -0,0 +1,183 @@
1
+ #!/usr/bin/env node
2
+ // CLI for sahih-dawud
3
+
4
+ import fs from 'fs';
5
+ import zlib from 'zlib';
6
+ import path from 'path';
7
+ import { fileURLToPath } from 'url';
8
+ import { Dawud } 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', 'dawud.json.gz');
16
+ const jsonPath = path.join(__dirname, '..', 'data', 'dawud.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/dawud.json.gz or data/dawud.json');
20
+ }
21
+
22
+ const dawudData = loadData();
23
+ const _byId = new Map();
24
+ const _byChapter = new Map();
25
+ dawudData.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 dawud = new Dawud(dawudData);
32
+ export default dawud;
33
+ export { Dawud };
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) {
40
+ if (!term) return text;
41
+ const re = new RegExp(`(${term.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')})`,'gi');
42
+ return text.replace(re,`\x1b[1m\x1b[33m$1\x1b[0m`);
43
+ }
44
+
45
+ function wrap(text, width=72, indent='') {
46
+ const words=text.split(' '),lines=[];let line='';
47
+ 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();}}
48
+ if(line)lines.push(indent+line.trim());
49
+ return lines.join('\n');
50
+ }
51
+
52
+ function isRunDirectly(){
53
+ if(!process.argv[1])return false;
54
+ const argv1=path.resolve(process.argv[1]).toLowerCase().replace(/\\/g,'/');
55
+ const self=__filename.toLowerCase().replace(/\\/g,'/');
56
+ const base=path.basename(argv1).replace(/\.js$/,'');
57
+ return argv1===self||base==='dawud';
58
+ }
59
+
60
+ if(isRunDirectly()){
61
+ const rawArgs=process.argv.slice(2);
62
+ const flags=rawArgs.filter(a=>a.startsWith('-'));
63
+ const numArgs=rawArgs.filter(a=>!a.startsWith('-'));
64
+ const wantsHelp=flags.some(f=>f==='-h'||f==='--help');
65
+ const wantsVersion=flags.some(f=>f==='-v'||f==='--version');
66
+ const wantsReact=flags.some(f=>f==='--react');
67
+ const wantsRandom=flags.some(f=>f==='--random'||f==='-r');
68
+ const wantsAll=flags.some(f=>f==='--all');
69
+ const showArabic=flags.some(f=>f==='-a'||f==='--arabic');
70
+ const showBoth=flags.some(f=>f==='-b'||f==='--both');
71
+ const printArabic=showArabic||showBoth;
72
+ const printEnglish=!showArabic||showBoth;
73
+ const searchIdx=rawArgs.findIndex(a=>a==='--search'||a==='-s');
74
+ const searchQuery=searchIdx!==-1&&rawArgs[searchIdx+1]&&!rawArgs[searchIdx+1].startsWith('-')?rawArgs[searchIdx+1]:null;
75
+ const chapterIdx=rawArgs.findIndex(a=>a==='--chapter'||a==='-c');
76
+ const chapterArg=chapterIdx!==-1&&rawArgs[chapterIdx+1]&&!rawArgs[chapterIdx+1].startsWith('-')?parseInt(rawArgs[chapterIdx+1]):null;
77
+ const DIV=gray('─'.repeat(60)),DIV2=gray('═'.repeat(60));
78
+
79
+ if(wantsVersion){console.log('');console.log(' '+bold(cyan('sahih-dawud'))+gray(' v'+pkg.version));console.log(' '+gray('Total hadiths : ')+yellow(dawudData.hadiths.length.toLocaleString()));console.log(' '+gray('Total chapters: ')+yellow(dawudData.chapters.length.toLocaleString()));console.log(' '+gray('Author : ')+cyan('Imam Dawud ibn al-Hajjaj'));console.log('');process.exit(0);}
80
+
81
+ if(wantsRandom){printHadith(dawudData.hadiths[Math.floor(Math.random()*dawudData.hadiths.length)]);process.exit(0);}
82
+
83
+ if(chapterArg!==null){
84
+ const hadiths=_byChapter.get(chapterArg)||[];
85
+ const chapter=dawudData.chapters.find(c=>c.id===chapterArg);
86
+ if(!hadiths.length){console.log('\n'+red(' No chapter found with id '+chapterArg)+'\n');process.exit(1);}
87
+ console.log('');console.log(DIV2);
88
+ console.log(bold(cyan(' Chapter '+chapterArg))+(chapter?gray(' — ')+yellow(chapter.english):''));
89
+ if(chapter?.arabic)console.log(' '+magenta(chapter.arabic));
90
+ console.log(gray(' '+hadiths.length+' hadiths'));console.log(DIV2);
91
+ hadiths.forEach(h=>printHadith(h));process.exit(0);
92
+ }
93
+
94
+ if(searchQuery!==null){
95
+ const start=Date.now(),ql=searchQuery.toLowerCase();
96
+ const results=dawudData.hadiths.filter(h=>h.english?.text?.toLowerCase().includes(ql)||h.english?.narrator?.toLowerCase().includes(ql));
97
+ const elapsed=Date.now()-start;
98
+ console.log('');console.log(DIV2);
99
+ console.log(bold(cyan(' Search: '))+yellow('"'+searchQuery+'"')+gray(' — ')+green(results.length+' results')+gray(' ('+elapsed+'ms)'));
100
+ console.log(DIV2);
101
+ if(!results.length){console.log('\n '+red('No hadiths found for: ')+yellow('"'+searchQuery+'"')+'\n');process.exit(0);}
102
+ const limit=wantsAll?results.length:Math.min(5,results.length);
103
+ results.slice(0,limit).forEach((hadith,i)=>{
104
+ const chapter=dawudData.chapters.find(c=>c.id===hadith.chapterId);
105
+ console.log('');
106
+ console.log(bold(green(' #'+(i+1)))+gray(' Hadith '+hadith.id)+gray(' | Chapter: ')+cyan(hadith.chapterId)+(chapter?gray(' — ')+dim(chapter.english):''));
107
+ if(hadith.english?.narrator)console.log(' '+bold(yellow('Narrator: '))+magenta(hadith.english.narrator));
108
+ if(hadith.english?.text)console.log(' '+wrap(highlight(hadith.english.text,searchQuery),68,' ').trimStart());
109
+ console.log(DIV);
110
+ });
111
+ 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('dawud --search "'+searchQuery+'" --all'));console.log('');}
112
+ process.exit(0);
113
+ }
114
+
115
+ if(wantsReact){
116
+ const cwd=process.cwd(),srcDir=path.join(cwd,'src'),hooksDir=path.join(srcDir,'hooks');
117
+ const pkgPath=path.join(cwd,'package.json');
118
+ if(!fs.existsSync(pkgPath)){console.error(red(' ✗ No package.json found.'));process.exit(1);}
119
+ const projectPkg=JSON.parse(fs.readFileSync(pkgPath,'utf8'));
120
+ const deps={...projectPkg.dependencies,...projectPkg.devDependencies};
121
+ if(!deps['react']){console.error(red(' ✗ React not found.'));process.exit(1);}
122
+ if(!fs.existsSync(srcDir)){console.error(red(' ✗ No src/ directory found.'));process.exit(1);}
123
+ if(!fs.existsSync(hooksDir))fs.mkdirSync(hooksDir,{recursive:true});
124
+ const CDN=`https://cdn.jsdelivr.net/npm/sahih-dawud@${pkg.version}/data/dawud.json.gz`;
125
+ const hookFile=path.join(hooksDir,'useDawud.js');
126
+ const hookSrc=`// Auto-generated by: dawud --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 useDawud(){const[dawud,setDawud]=useState(_cache);useEffect(()=>{if(_cache){setDawud(_cache);}else{_subs.add(setDawud);return()=>_subs.delete(setDawud);}},[]);return dawud;}\nexport default useDawud;\n`;
127
+ fs.writeFileSync(hookFile,hookSrc,'utf8');
128
+ console.log('\n '+green('✓')+bold(' Generated: ')+cyan('src/hooks/useDawud.js')+'\n');
129
+ process.exit(0);
130
+ }
131
+
132
+ if(wantsHelp||(numArgs.length===0&&!searchQuery&&chapterArg===null&&!wantsRandom)){
133
+ console.log('');console.log(' '+bold(cyan('Sahih Dawud CLI'))+gray(' v'+pkg.version));console.log('');
134
+ console.log(' '+bold('Usage:'));
135
+ console.log(' '+cyan('dawud')+yellow(' <hadithId>')+gray(' Show hadith by global ID'));
136
+ console.log(' '+cyan('dawud')+yellow(' <chapterId> <hadithId>')+gray(' Show hadith within a chapter'));
137
+ console.log(' '+cyan('dawud')+green(' --search')+yellow(' "<query>"')+gray(' Search hadiths (top 5)'));
138
+ console.log(' '+cyan('dawud')+green(' --search')+yellow(' "<query>"')+green(' --all')+gray(' Show all results'));
139
+ console.log(' '+cyan('dawud')+green(' --chapter')+yellow(' <id>')+gray(' List all hadiths in a chapter'));
140
+ console.log(' '+cyan('dawud')+green(' --random')+gray(' Show a random hadith'));
141
+ console.log('');
142
+ console.log(' '+bold('Language flags:')+gray(' (default = English only)'));
143
+ console.log(' '+green('-a')+gray(', ')+green('--arabic')+gray(' Arabic only'));
144
+ console.log(' '+green('-b')+gray(', ')+green('--both')+gray(' Arabic + English'));
145
+ console.log('');
146
+ console.log(' '+bold('Other:'));
147
+ console.log(' '+green('--react')+gray(' Generate useDawud React hook'));
148
+ console.log(' '+green('-v')+gray(', ')+green('--version')+gray(' Show version'));
149
+ console.log(' '+green('-h')+gray(', ')+green('--help')+gray(' Show this help'));
150
+ console.log('');process.exit(0);
151
+ }
152
+
153
+ function printHadith(hadith){
154
+ if(!hadith){console.log('\n '+red('Hadith not found.')+'\n');process.exit(1);}
155
+ const chapter=dawudData.chapters?.find(c=>c.id===hadith.chapterId);
156
+ console.log('');console.log(DIV2);
157
+ const headerEn=bold(cyan('Hadith #'+hadith.id))+gray(' | Chapter: ')+cyan(hadith.chapterId)+(chapter?.english?gray(' — ')+yellow(chapter.english):'');
158
+ const headerAr=bold(magenta('حديث #'+hadith.id))+gray(' | باب: ')+magenta(hadith.chapterId)+(chapter?.arabic?gray(' — ')+magenta(chapter.arabic):'');
159
+ console.log(' '+(printArabic&&!printEnglish?headerAr:headerEn));
160
+ console.log(DIV2);
161
+ 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,' '));}}
162
+ if(printArabic){if(printEnglish)console.log('\n'+DIV);if(hadith.arabic){console.log('');console.log(' '+magenta(hadith.arabic));}}
163
+ console.log('');console.log(DIV2);console.log('');
164
+ }
165
+
166
+ function resolveHadith(){
167
+ if(numArgs.length===1){const id=parseInt(numArgs[0]);if(isNaN(id))return null;return _byId.get(id)||null;}
168
+ if(numArgs.length===2){
169
+ const chapterId=parseInt(numArgs[0]),hadithNum=parseInt(numArgs[1]);
170
+ if(isNaN(chapterId)||isNaN(hadithNum))return null;
171
+ const inChapter=_byChapter.get(chapterId);
172
+ if(!inChapter?.length){console.log('\n '+red('No chapter found with id '+chapterId+'.')+'\n');process.exit(1);}
173
+ return inChapter.find(h=>h.id===hadithNum)??inChapter[hadithNum-1]??null;
174
+ }
175
+ return null;
176
+ }
177
+
178
+ if(numArgs.length>0){
179
+ const hadith=resolveHadith();
180
+ if(!hadith){console.log('\n '+red('Invalid arguments.')+' Run '+cyan('dawud --help')+' for usage.\n');process.exit(1);}
181
+ printHadith(hadith);
182
+ }
183
+ }
@@ -0,0 +1,7 @@
1
+ import express from 'express';
2
+ import dawud from 'sunan-abi-dawud';
3
+ const app = express();
4
+ app.get('/api/hadith/random', (_, res) => res.json(dawud.getRandom()));
5
+ app.get('/api/hadith/:id', (req, res) => { const h = dawud.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(dawud.search(req.query.q||'', parseInt(req.query.limit)||0)));
7
+ app.listen(3000, () => console.log('Sunan Abi Dawud API at http://localhost:3000'));
@@ -0,0 +1,7 @@
1
+ 'use strict';
2
+ const dawud = require('sunan-abi-dawud');
3
+ console.log('Total hadiths:', dawud.length);
4
+ const h = dawud.get(1);
5
+ console.log('Hadith #1:', h.english.narrator, '-', h.english.text.slice(0,60)+'...');
6
+ console.log('Search "prayer":', dawud.search('prayer').length, 'results');
7
+ console.log('Random:', dawud.getRandom().id);
@@ -0,0 +1,6 @@
1
+ import dawud from 'sunan-abi-dawud';
2
+ console.log('Total hadiths:', dawud.length);
3
+ console.log('Title:', dawud.metadata.english.title);
4
+ const h = dawud.get(1);
5
+ console.log('Hadith #1:', h.english.narrator, '-', h.english.text.slice(0,60)+'...');
6
+ console.log('Chapter 1 hadiths:', dawud.getByChapter(1).length);
@@ -0,0 +1,17 @@
1
+ // Run `dawud --react` in your project first
2
+ import { useState } from 'react';
3
+ import { useDawud } from '../hooks/useDawud';
4
+
5
+ export function HadithOfTheDay() {
6
+ const dawud = useDawud();
7
+ if (!dawud) return <p>Loading...</p>;
8
+ const h = dawud.getRandom();
9
+ return (<div><strong>{h.english.narrator}</strong><p>{h.english.text}</p></div>);
10
+ }
11
+
12
+ export function HadithSearch() {
13
+ const dawud = useDawud();
14
+ const [results, setResults] = useState([]);
15
+ if (!dawud) return <p>Loading...</p>;
16
+ return (<><input placeholder="Search..." onChange={e => setResults(dawud.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-abi-dawud",
3
+ "version": "1.0.5",
4
+ "description": "Complete Sunan Abi Dawud. 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
+ "dawud": "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
+ "dawud",
29
+ "abu-dawud",
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-abi-dawud.git"
44
+ },
45
+ "bugs": {
46
+ "url": "https://github.com/SENODROOM/sunan-abi-dawud/issues"
47
+ },
48
+ "homepage": "https://github.com/SENODROOM/sunan-abi-dawud#readme",
49
+ "files": [
50
+ "src/",
51
+ "types/",
52
+ "bin/index.js",
53
+ "data/dawud.json.gz"
54
+ ],
55
+ "engines": {
56
+ "node": ">=18.0.0"
57
+ }
58
+ }
@@ -0,0 +1,52 @@
1
+ // CommonJS entry — require('sunan-abi-dawud')
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', 'dawud.json.gz');
9
+ const jsonPath = path.join(__dirname, '..', 'data', 'dawud.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/dawud.json.gz or data/dawud.json');
13
+ }
14
+ const dawudData = loadData();
15
+
16
+ class Dawud {
17
+ constructor(dawudData) {
18
+ this._hadiths = dawudData.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 dawudData.metadata;
25
+ case 'chapters': return dawudData.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 dawud = new Dawud(dawudData);
50
+ module.exports = dawud;
51
+ module.exports.Dawud = Dawud;
52
+ module.exports.default = dawud;
@@ -0,0 +1,35 @@
1
+ // ESM browser-safe entry (class only — no data)
2
+ export class Dawud {
3
+ constructor(dawudData) {
4
+ this._hadiths = dawudData.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 dawudData.metadata;
11
+ case 'chapters': return dawudData.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 Dawud;
@@ -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 { Dawud } from './index.js';
7
+
8
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
9
+
10
+ function loadData() {
11
+ const gzPath = path.join(__dirname, '..', 'data', 'dawud.json.gz');
12
+ const jsonPath = path.join(__dirname, '..', 'data', 'dawud.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/dawud.json.gz or data/dawud.json');
16
+ }
17
+ export { Dawud };
18
+ export default new Dawud(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 DawudData { metadata: Metadata; chapters: Chapter[]; hadiths: Hadith[]; }
10
+ export interface DawudInstance 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 Dawud { constructor(data: DawudData); }
26
+ export declare function loadDawud(): Promise<DawudInstance>;
27
+ declare const dawud: DawudInstance;
28
+ export default dawud;