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,183 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// CLI for sahih-muslim
|
|
3
|
+
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import zlib from 'zlib';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { fileURLToPath } from 'url';
|
|
8
|
+
import { Muslim } 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', 'muslim.json.gz');
|
|
16
|
+
const jsonPath = path.join(__dirname, '..', 'data', 'muslim.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/muslim.json.gz or data/muslim.json');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const muslimData = loadData();
|
|
23
|
+
const _byId = new Map();
|
|
24
|
+
const _byChapter = new Map();
|
|
25
|
+
muslimData.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 muslim = new Muslim(muslimData);
|
|
32
|
+
export default muslim;
|
|
33
|
+
export { Muslim };
|
|
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==='muslim';
|
|
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-muslim'))+gray(' v'+pkg.version));console.log(' '+gray('Total hadiths : ')+yellow(muslimData.hadiths.length.toLocaleString()));console.log(' '+gray('Total chapters: ')+yellow(muslimData.chapters.length.toLocaleString()));console.log(' '+gray('Author : ')+cyan('Imam Muslim ibn al-Hajjaj'));console.log('');process.exit(0);}
|
|
80
|
+
|
|
81
|
+
if(wantsRandom){printHadith(muslimData.hadiths[Math.floor(Math.random()*muslimData.hadiths.length)]);process.exit(0);}
|
|
82
|
+
|
|
83
|
+
if(chapterArg!==null){
|
|
84
|
+
const hadiths=_byChapter.get(chapterArg)||[];
|
|
85
|
+
const chapter=muslimData.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=muslimData.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=muslimData.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('muslim --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-muslim@${pkg.version}/data/muslim.json.gz`;
|
|
125
|
+
const hookFile=path.join(hooksDir,'useMuslim.js');
|
|
126
|
+
const hookSrc=`// Auto-generated by: muslim --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 useMuslim(){const[muslim,setMuslim]=useState(_cache);useEffect(()=>{if(_cache){setMuslim(_cache);}else{_subs.add(setMuslim);return()=>_subs.delete(setMuslim);}},[]);return muslim;}\nexport default useMuslim;\n`;
|
|
127
|
+
fs.writeFileSync(hookFile,hookSrc,'utf8');
|
|
128
|
+
console.log('\n '+green('✓')+bold(' Generated: ')+cyan('src/hooks/useMuslim.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 Muslim CLI'))+gray(' v'+pkg.version));console.log('');
|
|
134
|
+
console.log(' '+bold('Usage:'));
|
|
135
|
+
console.log(' '+cyan('muslim')+yellow(' <hadithId>')+gray(' Show hadith by global ID'));
|
|
136
|
+
console.log(' '+cyan('muslim')+yellow(' <chapterId> <hadithId>')+gray(' Show hadith within a chapter'));
|
|
137
|
+
console.log(' '+cyan('muslim')+green(' --search')+yellow(' "<query>"')+gray(' Search hadiths (top 5)'));
|
|
138
|
+
console.log(' '+cyan('muslim')+green(' --search')+yellow(' "<query>"')+green(' --all')+gray(' Show all results'));
|
|
139
|
+
console.log(' '+cyan('muslim')+green(' --chapter')+yellow(' <id>')+gray(' List all hadiths in a chapter'));
|
|
140
|
+
console.log(' '+cyan('muslim')+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 useMuslim 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=muslimData.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('muslim --help')+' for usage.\n');process.exit(1);}
|
|
181
|
+
printHadith(hadith);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Express REST API example
|
|
2
|
+
// run: node examples/express/server.js
|
|
3
|
+
import express from 'express';
|
|
4
|
+
import muslim from 'sahih-muslim';
|
|
5
|
+
|
|
6
|
+
const app = express();
|
|
7
|
+
const PORT = process.env.PORT || 3000;
|
|
8
|
+
|
|
9
|
+
app.get('/api/hadith/random', (_, res) => res.json(muslim.getRandom()));
|
|
10
|
+
app.get('/api/hadith/:id', (req, res) => { const h = muslim.get(parseInt(req.params.id)); h ? res.json(h) : res.status(404).json({ error: 'Not found' }); });
|
|
11
|
+
app.get('/api/chapter/:id', (req, res) => { const h = muslim.getByChapter(parseInt(req.params.id)); h.length ? res.json({ count: h.length, hadiths: h }) : res.status(404).json({ error: 'Not found' }); });
|
|
12
|
+
app.get('/api/search', (req, res) => { const r = muslim.search(req.query.q || '', parseInt(req.query.limit) || 0); res.json({ count: r.length, results: r }); });
|
|
13
|
+
app.get('/api/chapters', (_, res) => res.json(muslim.chapters));
|
|
14
|
+
app.get('/api/meta', (_, res) => res.json({ ...muslim.metadata, total: muslim.length }));
|
|
15
|
+
|
|
16
|
+
app.listen(PORT, () => console.log(`Sahih Muslim API running at http://localhost:${PORT}`));
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// CommonJS example
|
|
2
|
+
// run: node examples/node-commonjs/example.js
|
|
3
|
+
'use strict';
|
|
4
|
+
const muslim = require('sahih-muslim');
|
|
5
|
+
console.log('Total hadiths:', muslim.length);
|
|
6
|
+
const h = muslim.get(1);
|
|
7
|
+
console.log('Hadith #1 narrator:', h.english.narrator);
|
|
8
|
+
console.log('Hadith #1 text:', h.english.text.slice(0, 80) + '...');
|
|
9
|
+
const results = muslim.search('prayer', 3);
|
|
10
|
+
console.log(`Top 3 for "prayer":`, results.map(r => r.id));
|
|
11
|
+
console.log('Random:', muslim.getRandom().id);
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// ESM example
|
|
2
|
+
// run: node examples/node-esm/example.mjs
|
|
3
|
+
import muslim from 'sahih-muslim';
|
|
4
|
+
console.log('Total hadiths:', muslim.length);
|
|
5
|
+
console.log('Title:', muslim.metadata.english.title);
|
|
6
|
+
const h = muslim.get(1);
|
|
7
|
+
console.log('Hadith #1:', h.english.narrator, '-', h.english.text.slice(0, 60) + '...');
|
|
8
|
+
console.log('Chapter 1 hadiths:', muslim.getByChapter(1).length);
|
|
9
|
+
console.log('Search "fasting":', muslim.search('fasting').length, 'results');
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// React example — run `muslim --react` in your project first
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { useMuslim } from '../hooks/useMuslim';
|
|
4
|
+
|
|
5
|
+
export function HadithOfTheDay() {
|
|
6
|
+
const muslim = useMuslim();
|
|
7
|
+
if (!muslim) return <p>Loading...</p>;
|
|
8
|
+
const h = muslim.getRandom();
|
|
9
|
+
return (
|
|
10
|
+
<div>
|
|
11
|
+
<strong>{h.english.narrator}</strong>
|
|
12
|
+
<p>{h.english.text}</p>
|
|
13
|
+
<small>Hadith #{h.id} · Chapter {h.chapterId}</small>
|
|
14
|
+
</div>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function HadithSearch() {
|
|
19
|
+
const muslim = useMuslim();
|
|
20
|
+
const [results, setResults] = useState([]);
|
|
21
|
+
if (!muslim) return <p>Loading...</p>;
|
|
22
|
+
return (
|
|
23
|
+
<>
|
|
24
|
+
<input placeholder="Search hadiths..." onChange={e => setResults(muslim.search(e.target.value, 20))} />
|
|
25
|
+
{results.map(h => <div key={h.id}><strong>{h.english.narrator}</strong><p>{h.english.text}</p></div>)}
|
|
26
|
+
</>
|
|
27
|
+
);
|
|
28
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sahih-muslim",
|
|
3
|
+
"version": "1.1.2",
|
|
4
|
+
"description": "Complete Sahih Muslim. 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
|
+
"muslim": "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
|
+
"muslim",
|
|
29
|
+
"sahih",
|
|
30
|
+
"prophet",
|
|
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/sahih-muslim.git"
|
|
44
|
+
},
|
|
45
|
+
"bugs": {
|
|
46
|
+
"url": "https://github.com/SENODROOM/sahih-muslim/issues"
|
|
47
|
+
},
|
|
48
|
+
"homepage": "https://github.com/SENODROOM/sahih-muslim#readme",
|
|
49
|
+
"files": [
|
|
50
|
+
"src/",
|
|
51
|
+
"types/",
|
|
52
|
+
"bin/index.js",
|
|
53
|
+
"data/muslim.json.gz"
|
|
54
|
+
],
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=18.0.0"
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// CommonJS entry — require('sahih-muslim')
|
|
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', 'muslim.json.gz');
|
|
9
|
+
const jsonPath = path.join(__dirname, '..', 'data', 'muslim.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/muslim.json.gz or data/muslim.json');
|
|
13
|
+
}
|
|
14
|
+
const muslimData = loadData();
|
|
15
|
+
|
|
16
|
+
class Muslim {
|
|
17
|
+
constructor(muslimData) {
|
|
18
|
+
this._hadiths = muslimData.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 muslimData.metadata;
|
|
25
|
+
case 'chapters': return muslimData.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 muslim = new Muslim(muslimData);
|
|
50
|
+
module.exports = muslim;
|
|
51
|
+
module.exports.Muslim = Muslim;
|
|
52
|
+
module.exports.default = muslim;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// ESM browser-safe entry (class only — no data)
|
|
2
|
+
export class Muslim {
|
|
3
|
+
constructor(muslimData) {
|
|
4
|
+
this._hadiths = muslimData.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 muslimData.metadata;
|
|
11
|
+
case 'chapters': return muslimData.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 Muslim;
|
|
@@ -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 { Muslim } from './index.js';
|
|
7
|
+
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
|
|
10
|
+
function loadData() {
|
|
11
|
+
const gzPath = path.join(__dirname, '..', 'data', 'muslim.json.gz');
|
|
12
|
+
const jsonPath = path.join(__dirname, '..', 'data', 'muslim.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/muslim.json.gz or data/muslim.json');
|
|
16
|
+
}
|
|
17
|
+
export { Muslim };
|
|
18
|
+
export default new Muslim(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 MuslimData { metadata: Metadata; chapters: Chapter[]; hadiths: Hadith[]; }
|
|
10
|
+
export interface MuslimInstance 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 Muslim { constructor(data: MuslimData); }
|
|
26
|
+
export declare function loadMuslim(): Promise<MuslimInstance>;
|
|
27
|
+
declare const muslim: MuslimInstance;
|
|
28
|
+
export default muslim;
|