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,306 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// CLI for sahih-al-bukhari
|
|
3
|
+
// Usage:
|
|
4
|
+
// bukhari <hadithId> [-a|-b]
|
|
5
|
+
// bukhari <chapterId> <hadithId> [-a|-b]
|
|
6
|
+
// bukhari --search "prayer" [--all]
|
|
7
|
+
// bukhari --random
|
|
8
|
+
// bukhari --chapter <id>
|
|
9
|
+
// bukhari --react
|
|
10
|
+
// bukhari -h | --help
|
|
11
|
+
// bukhari -v | --version
|
|
12
|
+
|
|
13
|
+
import fs from 'fs';
|
|
14
|
+
import zlib from 'zlib';
|
|
15
|
+
import path from 'path';
|
|
16
|
+
import { fileURLToPath } from 'url';
|
|
17
|
+
import { Bukhari } from '../src/index.js';
|
|
18
|
+
|
|
19
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
20
|
+
const __dirname = path.dirname(__filename);
|
|
21
|
+
const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), 'utf8'));
|
|
22
|
+
|
|
23
|
+
// ── Load shared data/bukhari.json.gz (or .json fallback) ─────────────────────
|
|
24
|
+
function loadData() {
|
|
25
|
+
const gzPath = path.join(__dirname, '..', 'data', 'bukhari.json.gz');
|
|
26
|
+
const jsonPath = path.join(__dirname, '..', 'data', 'bukhari.json');
|
|
27
|
+
if (fs.existsSync(gzPath)) {
|
|
28
|
+
return JSON.parse(zlib.gunzipSync(fs.readFileSync(gzPath)).toString('utf8'));
|
|
29
|
+
}
|
|
30
|
+
if (fs.existsSync(jsonPath)) {
|
|
31
|
+
return JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
|
|
32
|
+
}
|
|
33
|
+
throw new Error('Data file not found. Expected data/bukhari.json.gz or data/bukhari.json');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const bukhariData = loadData();
|
|
37
|
+
|
|
38
|
+
// ── Speed: build lookup maps at startup ───────────────────────────────────────
|
|
39
|
+
const _byId = new Map();
|
|
40
|
+
const _byChapter = new Map();
|
|
41
|
+
bukhariData.hadiths.forEach(h => {
|
|
42
|
+
_byId.set(h.id, h);
|
|
43
|
+
if (!_byChapter.has(h.chapterId)) _byChapter.set(h.chapterId, []);
|
|
44
|
+
_byChapter.get(h.chapterId).push(h);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const bukhari = new Bukhari(bukhariData);
|
|
48
|
+
|
|
49
|
+
export default bukhari;
|
|
50
|
+
export { Bukhari };
|
|
51
|
+
|
|
52
|
+
// ── Colors ────────────────────────────────────────────────────────────────────
|
|
53
|
+
const c = {
|
|
54
|
+
reset: '\x1b[0m', bold: '\x1b[1m', dim: '\x1b[2m',
|
|
55
|
+
green: '\x1b[32m', yellow: '\x1b[33m', cyan: '\x1b[36m',
|
|
56
|
+
white: '\x1b[37m', magenta: '\x1b[35m', blue: '\x1b[34m',
|
|
57
|
+
red: '\x1b[31m', gray: '\x1b[90m',
|
|
58
|
+
};
|
|
59
|
+
const clr = (color, text) => `${color}${text}${c.reset}`;
|
|
60
|
+
const bold = (t) => clr(c.bold, t);
|
|
61
|
+
const green = (t) => clr(c.green, t);
|
|
62
|
+
const yellow = (t) => clr(c.yellow, t);
|
|
63
|
+
const cyan = (t) => clr(c.cyan, t);
|
|
64
|
+
const magenta = (t) => clr(c.magenta, t);
|
|
65
|
+
const gray = (t) => clr(c.gray, t);
|
|
66
|
+
const red = (t) => clr(c.red, t);
|
|
67
|
+
const blue = (t) => clr(c.blue, t);
|
|
68
|
+
const dim = (t) => clr(c.dim, t);
|
|
69
|
+
|
|
70
|
+
function highlight(text, term) {
|
|
71
|
+
if (!term) return text;
|
|
72
|
+
const re = new RegExp(`(${term.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')})`, 'gi');
|
|
73
|
+
return text.replace(re, `\x1b[1m\x1b[33m$1\x1b[0m`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function wrap(text, width = 72, indent = '') {
|
|
77
|
+
const words = text.split(' ');
|
|
78
|
+
const lines = [];
|
|
79
|
+
let line = '';
|
|
80
|
+
for (const word of words) {
|
|
81
|
+
if ((line + ' ' + word).trim().length > width) {
|
|
82
|
+
if (line) lines.push(indent + line.trim());
|
|
83
|
+
line = word;
|
|
84
|
+
} else {
|
|
85
|
+
line = (line + ' ' + word).trim();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
if (line) lines.push(indent + line.trim());
|
|
89
|
+
return lines.join('\n');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function isRunDirectly() {
|
|
93
|
+
if (!process.argv[1]) return false;
|
|
94
|
+
const argv1 = path.resolve(process.argv[1]).toLowerCase().replace(/\\/g, '/');
|
|
95
|
+
const self = __filename.toLowerCase().replace(/\\/g, '/');
|
|
96
|
+
const base = path.basename(argv1).replace(/\.js$/, '');
|
|
97
|
+
return argv1 === self || base === 'bukhari';
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (isRunDirectly()) {
|
|
101
|
+
|
|
102
|
+
const rawArgs = process.argv.slice(2);
|
|
103
|
+
const flags = rawArgs.filter(a => a.startsWith('-'));
|
|
104
|
+
const numArgs = rawArgs.filter(a => !a.startsWith('-'));
|
|
105
|
+
|
|
106
|
+
const wantsHelp = flags.some(f => f === '-h' || f === '--help');
|
|
107
|
+
const wantsVersion = flags.some(f => f === '-v' || f === '--version');
|
|
108
|
+
const wantsReact = flags.some(f => f === '--react');
|
|
109
|
+
const wantsRandom = flags.some(f => f === '--random' || f === '-r');
|
|
110
|
+
const wantsAll = flags.some(f => f === '--all');
|
|
111
|
+
const showArabic = flags.some(f => f === '-a' || f === '--arabic');
|
|
112
|
+
const showBoth = flags.some(f => f === '-b' || f === '--both');
|
|
113
|
+
const printArabic = showArabic || showBoth;
|
|
114
|
+
const printEnglish = !showArabic || showBoth;
|
|
115
|
+
|
|
116
|
+
const searchIdx = rawArgs.findIndex(a => a === '--search' || a === '-s');
|
|
117
|
+
const searchQuery = searchIdx !== -1 && rawArgs[searchIdx + 1] && !rawArgs[searchIdx + 1].startsWith('-')
|
|
118
|
+
? rawArgs[searchIdx + 1] : null;
|
|
119
|
+
|
|
120
|
+
const chapterIdx = rawArgs.findIndex(a => a === '--chapter' || a === '-c');
|
|
121
|
+
const chapterArg = chapterIdx !== -1 && rawArgs[chapterIdx + 1] && !rawArgs[chapterIdx + 1].startsWith('-')
|
|
122
|
+
? parseInt(rawArgs[chapterIdx + 1]) : null;
|
|
123
|
+
|
|
124
|
+
const DIV = gray('─'.repeat(60));
|
|
125
|
+
const DIV2 = gray('═'.repeat(60));
|
|
126
|
+
|
|
127
|
+
if (wantsVersion) {
|
|
128
|
+
console.log('');
|
|
129
|
+
console.log(' ' + bold(cyan('sahih-al-bukhari')) + gray(' v' + pkg.version));
|
|
130
|
+
console.log(' ' + gray('Total hadiths : ') + yellow(bukhariData.hadiths.length.toLocaleString()));
|
|
131
|
+
console.log(' ' + gray('Total chapters: ') + yellow(bukhariData.chapters.length.toLocaleString()));
|
|
132
|
+
console.log(' ' + gray('Author : ') + cyan('Imam Muhammad ibn Ismail al-Bukhari'));
|
|
133
|
+
console.log('');
|
|
134
|
+
process.exit(0);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (wantsRandom) {
|
|
138
|
+
const hadith = bukhariData.hadiths[Math.floor(Math.random() * bukhariData.hadiths.length)];
|
|
139
|
+
printHadith(hadith);
|
|
140
|
+
process.exit(0);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (chapterArg !== null) {
|
|
144
|
+
const hadiths = _byChapter.get(chapterArg) || [];
|
|
145
|
+
const chapter = bukhariData.chapters.find(c => c.id === chapterArg);
|
|
146
|
+
if (!hadiths.length) {
|
|
147
|
+
console.log('\n' + red(' No chapter found with id ' + chapterArg) + '\n');
|
|
148
|
+
process.exit(1);
|
|
149
|
+
}
|
|
150
|
+
console.log('');
|
|
151
|
+
console.log(DIV2);
|
|
152
|
+
console.log(bold(cyan(' Chapter ' + chapterArg)) + (chapter ? gray(' — ') + yellow(chapter.english) : ''));
|
|
153
|
+
if (chapter?.arabic) console.log(' ' + magenta(chapter.arabic));
|
|
154
|
+
console.log(gray(' ' + hadiths.length + ' hadiths'));
|
|
155
|
+
console.log(DIV2);
|
|
156
|
+
hadiths.forEach(h => printHadith(h));
|
|
157
|
+
process.exit(0);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (searchQuery !== null) {
|
|
161
|
+
const start = Date.now();
|
|
162
|
+
const ql = searchQuery.toLowerCase();
|
|
163
|
+
const results = bukhariData.hadiths.filter(h =>
|
|
164
|
+
h.english?.text?.toLowerCase().includes(ql) ||
|
|
165
|
+
h.english?.narrator?.toLowerCase().includes(ql)
|
|
166
|
+
);
|
|
167
|
+
const elapsed = Date.now() - start;
|
|
168
|
+
console.log('');
|
|
169
|
+
console.log(DIV2);
|
|
170
|
+
console.log(
|
|
171
|
+
bold(cyan(' Search: ')) + yellow('"' + searchQuery + '"') +
|
|
172
|
+
gray(' — ') + green(results.length + ' results') +
|
|
173
|
+
gray(' (' + elapsed + 'ms)')
|
|
174
|
+
);
|
|
175
|
+
console.log(DIV2);
|
|
176
|
+
if (!results.length) {
|
|
177
|
+
console.log('\n ' + red('No hadiths found for: ') + yellow('"' + searchQuery + '"') + '\n');
|
|
178
|
+
process.exit(0);
|
|
179
|
+
}
|
|
180
|
+
const limit = wantsAll ? results.length : Math.min(5, results.length);
|
|
181
|
+
results.slice(0, limit).forEach((hadith, i) => {
|
|
182
|
+
const chapter = bukhariData.chapters.find(c => c.id === hadith.chapterId);
|
|
183
|
+
console.log('');
|
|
184
|
+
console.log(
|
|
185
|
+
bold(green(' #' + (i + 1))) + gray(' Hadith ' + hadith.id) +
|
|
186
|
+
gray(' | Chapter: ') + cyan(hadith.chapterId) +
|
|
187
|
+
(chapter ? gray(' — ') + dim(chapter.english) : '')
|
|
188
|
+
);
|
|
189
|
+
if (hadith.english?.narrator) console.log(' ' + bold(yellow('Narrator: ')) + magenta(hadith.english.narrator));
|
|
190
|
+
if (hadith.english?.text) console.log(' ' + wrap(highlight(hadith.english.text, searchQuery), 68, ' ').trimStart());
|
|
191
|
+
console.log(DIV);
|
|
192
|
+
});
|
|
193
|
+
if (!wantsAll && results.length > 5) {
|
|
194
|
+
console.log('\n ' + gray('Showing ') + cyan('5') + gray(' of ') + yellow(results.length) +
|
|
195
|
+
gray(' results. ') + bold(blue('Run with --all to see all results')));
|
|
196
|
+
console.log(' ' + dim('bukhari --search "' + searchQuery + '" --all') + '\n');
|
|
197
|
+
}
|
|
198
|
+
process.exit(0);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
if (wantsReact) {
|
|
202
|
+
const cwd = process.cwd();
|
|
203
|
+
const srcDir = path.join(cwd, 'src');
|
|
204
|
+
const hooksDir = path.join(srcDir, 'hooks');
|
|
205
|
+
const pkgPath = path.join(cwd, 'package.json');
|
|
206
|
+
if (!fs.existsSync(pkgPath)) {
|
|
207
|
+
console.error(red(' ✗ No package.json found. Run inside your React project directory.'));
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
const projectPkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
|
|
211
|
+
const deps = { ...projectPkg.dependencies, ...projectPkg.devDependencies };
|
|
212
|
+
if (!deps['react']) {
|
|
213
|
+
console.error(red(' ✗ React not found. Run inside a React project.'));
|
|
214
|
+
process.exit(1);
|
|
215
|
+
}
|
|
216
|
+
if (!fs.existsSync(srcDir)) {
|
|
217
|
+
console.error(red(' ✗ No src/ directory found.'));
|
|
218
|
+
process.exit(1);
|
|
219
|
+
}
|
|
220
|
+
if (!fs.existsSync(hooksDir)) fs.mkdirSync(hooksDir, { recursive: true });
|
|
221
|
+
const CDN = 'https://cdn.jsdelivr.net/npm/sahih-al-bukhari@' + pkg.version + '/data/bukhari.json.gz';
|
|
222
|
+
const hookFile = path.join(hooksDir, 'useBukhari.js');
|
|
223
|
+
const hookSrc = `// Auto-generated by: bukhari --react\nimport { useState, useEffect } from 'react';\nconst CDN = '${CDN}';\nlet _cache = null; let _promise = null; const _subs = new Set();\nfunction _load() {\n if (_cache) return Promise.resolve(_cache);\n if (_promise) return _promise;\n _promise = fetch(CDN).then(r => r.arrayBuffer()).then(buf => {\n const stream = new DecompressionStream('gzip');\n const writer = stream.writable.getWriter();\n const reader = stream.readable.getReader();\n writer.write(new Uint8Array(buf)); writer.close();\n const chunks = [];\n return (function pump() {\n return reader.read().then(({ done, value }) => {\n if (done) {\n const json = new TextDecoder().decode(new Uint8Array(chunks.reduce((a,b)=>[...a,...b],[])));\n const data = JSON.parse(json);\n const hadiths = data.hadiths;\n const _byId = new Map(); hadiths.forEach(h => _byId.set(h.id, h));\n _cache = Object.assign([], hadiths, {\n metadata: data.metadata, chapters: data.chapters,\n get: id => _byId.get(id),\n getByChapter: id => hadiths.filter(h => h.chapterId === id),\n 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; },\n getRandom: () => hadiths[Math.floor(Math.random()*hadiths.length)],\n });\n _subs.forEach(fn=>fn(_cache)); _subs.clear(); return _cache;\n }\n chunks.push(value); return pump();\n });\n })();\n });\n return _promise;\n}\n_load();\nexport function useBukhari() {\n const [bukhari, setBukhari] = useState(_cache);\n useEffect(() => { if (_cache) { setBukhari(_cache); } else { _subs.add(setBukhari); return () => _subs.delete(setBukhari); } }, []);\n return bukhari;\n}\nexport default useBukhari;\n`;
|
|
224
|
+
fs.writeFileSync(hookFile, hookSrc, 'utf8');
|
|
225
|
+
console.log('\n ' + green('✓') + bold(' Generated: ') + cyan('src/hooks/useBukhari.js') + '\n');
|
|
226
|
+
process.exit(0);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
if (wantsHelp || (numArgs.length === 0 && !searchQuery && chapterArg === null && !wantsRandom)) {
|
|
230
|
+
console.log('');
|
|
231
|
+
console.log(' ' + bold(cyan('Sahih al-Bukhari CLI')) + gray(' v' + pkg.version));
|
|
232
|
+
console.log('');
|
|
233
|
+
console.log(' ' + bold('Usage:'));
|
|
234
|
+
console.log(' ' + cyan('bukhari') + yellow(' <hadithId>') + gray(' Show hadith by global ID'));
|
|
235
|
+
console.log(' ' + cyan('bukhari') + yellow(' <chapterId> <hadithId>') + gray(' Show hadith within a chapter'));
|
|
236
|
+
console.log(' ' + cyan('bukhari') + green(' --search') + yellow(' "<query>"') + gray(' Search hadiths (top 5)'));
|
|
237
|
+
console.log(' ' + cyan('bukhari') + green(' --search') + yellow(' "<query>"') + green(' --all') + gray(' Show all results'));
|
|
238
|
+
console.log(' ' + cyan('bukhari') + green(' --chapter') + yellow(' <id>') + gray(' List all hadiths in a chapter'));
|
|
239
|
+
console.log(' ' + cyan('bukhari') + green(' --random') + gray(' Show a random hadith'));
|
|
240
|
+
console.log('');
|
|
241
|
+
console.log(' ' + bold('Language flags:') + gray(' (default = English only)'));
|
|
242
|
+
console.log(' ' + green('-a') + gray(', ') + green('--arabic') + gray(' Arabic only'));
|
|
243
|
+
console.log(' ' + green('-b') + gray(', ') + green('--both') + gray(' Arabic + English'));
|
|
244
|
+
console.log('');
|
|
245
|
+
console.log(' ' + bold('Other:'));
|
|
246
|
+
console.log(' ' + green('--react') + gray(' Generate useBukhari React hook'));
|
|
247
|
+
console.log(' ' + green('-v') + gray(', ') + green('--version') + gray(' Show version'));
|
|
248
|
+
console.log(' ' + green('-h') + gray(', ') + green('--help') + gray(' Show this help'));
|
|
249
|
+
console.log('');
|
|
250
|
+
process.exit(0);
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function printHadith(hadith) {
|
|
254
|
+
if (!hadith) { console.log('\n ' + red('Hadith not found.') + '\n'); process.exit(1); }
|
|
255
|
+
const chapter = bukhariData.chapters?.find(c => c.id === hadith.chapterId);
|
|
256
|
+
console.log('');
|
|
257
|
+
console.log(DIV2);
|
|
258
|
+
const headerEn = bold(cyan('Hadith #' + hadith.id)) + gray(' | Chapter: ') + cyan(hadith.chapterId) +
|
|
259
|
+
(chapter?.english ? gray(' — ') + yellow(chapter.english) : '');
|
|
260
|
+
const headerAr = bold(magenta('حديث #' + hadith.id)) + gray(' | باب: ') + magenta(hadith.chapterId) +
|
|
261
|
+
(chapter?.arabic ? gray(' — ') + magenta(chapter.arabic) : '');
|
|
262
|
+
console.log(' ' + (printArabic && !printEnglish ? headerAr : headerEn));
|
|
263
|
+
console.log(DIV2);
|
|
264
|
+
if (printEnglish) {
|
|
265
|
+
if (hadith.english?.narrator) console.log(' ' + bold(yellow('Narrator: ')) + magenta(hadith.english.narrator));
|
|
266
|
+
if (hadith.english?.text) { console.log(''); console.log(wrap(hadith.english.text, 68, ' ')); }
|
|
267
|
+
}
|
|
268
|
+
if (printArabic) {
|
|
269
|
+
if (printEnglish) console.log('\n' + DIV);
|
|
270
|
+
if (hadith.arabic) { console.log(''); console.log(' ' + magenta(hadith.arabic)); }
|
|
271
|
+
}
|
|
272
|
+
console.log('');
|
|
273
|
+
console.log(DIV2);
|
|
274
|
+
console.log('');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
function resolveHadith() {
|
|
278
|
+
if (numArgs.length === 1) {
|
|
279
|
+
const id = parseInt(numArgs[0]);
|
|
280
|
+
if (isNaN(id)) return null;
|
|
281
|
+
return _byId.get(id) || null;
|
|
282
|
+
}
|
|
283
|
+
if (numArgs.length === 2) {
|
|
284
|
+
const chapterId = parseInt(numArgs[0]);
|
|
285
|
+
const hadithNum = parseInt(numArgs[1]);
|
|
286
|
+
if (isNaN(chapterId) || isNaN(hadithNum)) return null;
|
|
287
|
+
const inChapter = _byChapter.get(chapterId);
|
|
288
|
+
if (!inChapter?.length) {
|
|
289
|
+
console.log('\n ' + red('No chapter found with id ' + chapterId + '.') + '\n');
|
|
290
|
+
process.exit(1);
|
|
291
|
+
}
|
|
292
|
+
return inChapter.find(h => h.id === hadithNum) ?? inChapter[hadithNum - 1] ?? null;
|
|
293
|
+
}
|
|
294
|
+
return null;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (numArgs.length > 0) {
|
|
298
|
+
const hadith = resolveHadith();
|
|
299
|
+
if (!hadith) {
|
|
300
|
+
console.log('\n ' + red('Invalid arguments.') + ' Run ' + cyan('bukhari --help') + ' for usage.\n');
|
|
301
|
+
process.exit(1);
|
|
302
|
+
}
|
|
303
|
+
printHadith(hadith);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// Express.js REST API example
|
|
2
|
+
// Run: node examples/express/server.js
|
|
3
|
+
import express from 'express';
|
|
4
|
+
import bukhari from 'sahih-al-bukhari';
|
|
5
|
+
|
|
6
|
+
const app = express();
|
|
7
|
+
const PORT = process.env.PORT || 3000;
|
|
8
|
+
|
|
9
|
+
// GET /api/hadith/random
|
|
10
|
+
app.get('/api/hadith/random', (_, res) => {
|
|
11
|
+
res.json(bukhari.getRandom());
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
// GET /api/hadith/:id
|
|
15
|
+
app.get('/api/hadith/:id', (req, res) => {
|
|
16
|
+
const h = bukhari.get(parseInt(req.params.id));
|
|
17
|
+
if (!h) return res.status(404).json({ error: 'Hadith not found' });
|
|
18
|
+
res.json(h);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// GET /api/chapter/:id
|
|
22
|
+
app.get('/api/chapter/:id', (req, res) => {
|
|
23
|
+
const hadiths = bukhari.getByChapter(parseInt(req.params.id));
|
|
24
|
+
if (!hadiths.length) return res.status(404).json({ error: 'Chapter not found' });
|
|
25
|
+
res.json({ count: hadiths.length, hadiths });
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
// GET /api/search?q=prayer&limit=10
|
|
29
|
+
app.get('/api/search', (req, res) => {
|
|
30
|
+
const q = req.query.q || '';
|
|
31
|
+
const limit = parseInt(req.query.limit) || 0;
|
|
32
|
+
const results = bukhari.search(q, limit);
|
|
33
|
+
res.json({ query: q, count: results.length, results });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// GET /api/chapters
|
|
37
|
+
app.get('/api/chapters', (_, res) => {
|
|
38
|
+
res.json(bukhari.chapters);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
// GET /api/meta
|
|
42
|
+
app.get('/api/meta', (_, res) => {
|
|
43
|
+
res.json({ ...bukhari.metadata, total: bukhari.length });
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
app.listen(PORT, () => {
|
|
47
|
+
console.log(`Sahih al-Bukhari API running at http://localhost:${PORT}`);
|
|
48
|
+
console.log(`Try: http://localhost:${PORT}/api/hadith/1`);
|
|
49
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// CommonJS example — run with: node examples/node-commonjs/example.js
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const bukhari = require('sahih-al-bukhari');
|
|
5
|
+
|
|
6
|
+
console.log('Total hadiths:', bukhari.length);
|
|
7
|
+
|
|
8
|
+
// Get by ID
|
|
9
|
+
const h = bukhari.get(1);
|
|
10
|
+
console.log('\nHadith #1:');
|
|
11
|
+
console.log('Narrator:', h.english.narrator);
|
|
12
|
+
console.log('Text:', h.english.text);
|
|
13
|
+
|
|
14
|
+
// Search
|
|
15
|
+
const results = bukhari.search('prayer', 3);
|
|
16
|
+
console.log(`\nTop 3 results for "prayer":`);
|
|
17
|
+
results.forEach((r, i) => console.log(` ${i + 1}. [${r.id}] ${r.english.text.slice(0, 80)}...`));
|
|
18
|
+
|
|
19
|
+
// Random
|
|
20
|
+
const random = bukhari.getRandom();
|
|
21
|
+
console.log('\nRandom hadith:', random.id, '-', random.english.text.slice(0, 60) + '...');
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// ESM example — run with: node examples/node-esm/example.mjs
|
|
2
|
+
import bukhari from 'sahih-al-bukhari';
|
|
3
|
+
|
|
4
|
+
console.log('Total hadiths:', bukhari.length);
|
|
5
|
+
console.log('Total chapters:', bukhari.chapters.length);
|
|
6
|
+
console.log('Title:', bukhari.metadata.english.title);
|
|
7
|
+
|
|
8
|
+
// Get by ID
|
|
9
|
+
const h = bukhari.get(23);
|
|
10
|
+
console.log('\nHadith #23:');
|
|
11
|
+
console.log('Narrator:', h.english.narrator);
|
|
12
|
+
console.log('Text:', h.english.text);
|
|
13
|
+
|
|
14
|
+
// Get by chapter
|
|
15
|
+
const chapter1 = bukhari.getByChapter(1);
|
|
16
|
+
console.log(`\nChapter 1 has ${chapter1.length} hadiths`);
|
|
17
|
+
|
|
18
|
+
// Search
|
|
19
|
+
const results = bukhari.search('fasting');
|
|
20
|
+
console.log(`\n"fasting" → ${results.length} results`);
|
|
21
|
+
|
|
22
|
+
// Native array methods
|
|
23
|
+
const narrators = bukhari.slice(0, 5).map(h => h.english.narrator);
|
|
24
|
+
console.log('\nFirst 5 narrators:', narrators);
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// React example — run `bukhari --react` inside your React project first
|
|
2
|
+
// to generate src/hooks/useBukhari.js, then use like this:
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { useBukhari } from '../hooks/useBukhari';
|
|
5
|
+
|
|
6
|
+
// ── Hadith of the Day ─────────────────────────────────────────────────────────
|
|
7
|
+
export function HadithOfTheDay() {
|
|
8
|
+
const bukhari = useBukhari();
|
|
9
|
+
if (!bukhari) return <p>Loading...</p>;
|
|
10
|
+
|
|
11
|
+
const h = bukhari.getRandom();
|
|
12
|
+
return (
|
|
13
|
+
<div className="hadith-card">
|
|
14
|
+
<p className="narrator"><strong>{h.english.narrator}</strong></p>
|
|
15
|
+
<p className="text">{h.english.text}</p>
|
|
16
|
+
<small>Hadith #{h.id} · Chapter {h.chapterId}</small>
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// ── Search ────────────────────────────────────────────────────────────────────
|
|
22
|
+
export function HadithSearch() {
|
|
23
|
+
const bukhari = useBukhari();
|
|
24
|
+
const [query, setQuery] = useState('');
|
|
25
|
+
const [results, setResults] = useState([]);
|
|
26
|
+
|
|
27
|
+
if (!bukhari) return <p>Loading hadiths...</p>;
|
|
28
|
+
|
|
29
|
+
const handleSearch = (e) => {
|
|
30
|
+
const q = e.target.value;
|
|
31
|
+
setQuery(q);
|
|
32
|
+
setResults(q.length > 1 ? bukhari.search(q, 20) : []);
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
<div>
|
|
37
|
+
<input
|
|
38
|
+
value={query}
|
|
39
|
+
onChange={handleSearch}
|
|
40
|
+
placeholder="Search hadiths..."
|
|
41
|
+
/>
|
|
42
|
+
<p>{results.length} results</p>
|
|
43
|
+
{results.map(h => (
|
|
44
|
+
<div key={h.id}>
|
|
45
|
+
<strong>{h.english.narrator}</strong>
|
|
46
|
+
<p>{h.english.text}</p>
|
|
47
|
+
</div>
|
|
48
|
+
))}
|
|
49
|
+
</div>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// ── By Chapter ────────────────────────────────────────────────────────────────
|
|
54
|
+
export function ChapterView({ chapterId = 1 }) {
|
|
55
|
+
const bukhari = useBukhari();
|
|
56
|
+
if (!bukhari) return <p>Loading...</p>;
|
|
57
|
+
|
|
58
|
+
const hadiths = bukhari.getByChapter(chapterId);
|
|
59
|
+
const chapter = bukhari.chapters.find(c => c.id === chapterId);
|
|
60
|
+
|
|
61
|
+
return (
|
|
62
|
+
<div>
|
|
63
|
+
<h2>{chapter?.english}</h2>
|
|
64
|
+
<p>{hadiths.length} hadiths</p>
|
|
65
|
+
{hadiths.map(h => (
|
|
66
|
+
<div key={h.id}>
|
|
67
|
+
<strong>#{h.id} — {h.english.narrator}</strong>
|
|
68
|
+
<p>{h.english.text}</p>
|
|
69
|
+
</div>
|
|
70
|
+
))}
|
|
71
|
+
</div>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sahih-al-bukhari",
|
|
3
|
+
"version": "3.1.5",
|
|
4
|
+
"description": "Complete Sahih al-Bukhari. 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
|
+
"bukhari": "./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
|
+
"bukhari",
|
|
29
|
+
"sahih",
|
|
30
|
+
"prophet",
|
|
31
|
+
"muslim",
|
|
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": "https://github.com/SENODROOM/sahih-al-bukhari.git"
|
|
44
|
+
},
|
|
45
|
+
"files": [
|
|
46
|
+
"src/",
|
|
47
|
+
"types/",
|
|
48
|
+
"bin/index.js",
|
|
49
|
+
"data/bukhari.json.gz"
|
|
50
|
+
],
|
|
51
|
+
"engines": {
|
|
52
|
+
"node": ">=18.0.0"
|
|
53
|
+
}
|
|
54
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// CommonJS entry — require('sahih-al-bukhari')
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const zlib = require('zlib');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
|
|
8
|
+
function loadData() {
|
|
9
|
+
const gzPath = path.join(__dirname, '..', 'data', 'bukhari.json.gz');
|
|
10
|
+
const jsonPath = path.join(__dirname, '..', 'data', 'bukhari.json');
|
|
11
|
+
if (fs.existsSync(gzPath)) return JSON.parse(zlib.gunzipSync(fs.readFileSync(gzPath)).toString('utf8'));
|
|
12
|
+
if (fs.existsSync(jsonPath)) return JSON.parse(fs.readFileSync(jsonPath, 'utf8'));
|
|
13
|
+
throw new Error('Data file not found. Expected data/bukhari.json.gz or data/bukhari.json');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const bukhariData = loadData();
|
|
17
|
+
|
|
18
|
+
class Bukhari {
|
|
19
|
+
constructor(bukhariData) {
|
|
20
|
+
this._hadiths = bukhariData.hadiths;
|
|
21
|
+
return new Proxy(this._hadiths, {
|
|
22
|
+
get: (target, prop) => {
|
|
23
|
+
if (!isNaN(prop)) return target[parseInt(prop)];
|
|
24
|
+
if (prop in target) return target[prop];
|
|
25
|
+
switch (prop) {
|
|
26
|
+
case 'metadata': return bukhariData.metadata;
|
|
27
|
+
case 'chapters': return bukhariData.chapters;
|
|
28
|
+
case 'get': return (id) => this._hadiths.find(h => h.id === id);
|
|
29
|
+
case 'getByChapter': return (id) => this._hadiths.filter(h => h.chapterId === id);
|
|
30
|
+
case 'search': return (q, limit = 0) => {
|
|
31
|
+
const ql = q.toLowerCase();
|
|
32
|
+
const r = this._hadiths.filter(h =>
|
|
33
|
+
h.english?.text?.toLowerCase().includes(ql) ||
|
|
34
|
+
h.english?.narrator?.toLowerCase().includes(ql)
|
|
35
|
+
);
|
|
36
|
+
return limit > 0 ? r.slice(0, limit) : r;
|
|
37
|
+
};
|
|
38
|
+
case 'getRandom': return () => this._hadiths[Math.floor(Math.random() * this._hadiths.length)];
|
|
39
|
+
case 'length': return target.length;
|
|
40
|
+
default: return target[prop];
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
ownKeys: (target) => [
|
|
44
|
+
'length',
|
|
45
|
+
...Array.from({ length: target.length }, (_, i) => String(i)),
|
|
46
|
+
'metadata', 'chapters', 'get', 'getByChapter', 'search', 'getRandom'
|
|
47
|
+
]
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const bukhari = new Bukhari(bukhariData);
|
|
53
|
+
module.exports = bukhari;
|
|
54
|
+
module.exports.Bukhari = Bukhari;
|
|
55
|
+
module.exports.default = bukhari;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// ESM browser-safe entry (class only — no data)
|
|
2
|
+
// Works in: React, Vue, Vite, webpack, Next.js (client)
|
|
3
|
+
|
|
4
|
+
export class Bukhari {
|
|
5
|
+
constructor(bukhariData) {
|
|
6
|
+
this._hadiths = bukhariData.hadiths;
|
|
7
|
+
|
|
8
|
+
return new Proxy(this._hadiths, {
|
|
9
|
+
get: (target, prop) => {
|
|
10
|
+
if (!isNaN(prop)) return target[parseInt(prop)];
|
|
11
|
+
if (prop in target) return target[prop];
|
|
12
|
+
switch (prop) {
|
|
13
|
+
case 'metadata': return bukhariData.metadata;
|
|
14
|
+
case 'chapters': return bukhariData.chapters;
|
|
15
|
+
case 'get': return (id) => this._hadiths.find(h => h.id === id);
|
|
16
|
+
case 'getByChapter': return (id) => this._hadiths.filter(h => h.chapterId === id);
|
|
17
|
+
case 'search': return (q) => this._hadiths.filter(h =>
|
|
18
|
+
h.english?.text?.toLowerCase().includes(q.toLowerCase()) ||
|
|
19
|
+
h.english?.narrator?.toLowerCase().includes(q.toLowerCase())
|
|
20
|
+
);
|
|
21
|
+
case 'getRandom': return () => this._hadiths[Math.floor(Math.random() * this._hadiths.length)];
|
|
22
|
+
case 'length': return target.length;
|
|
23
|
+
default: return target[prop];
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
ownKeys: (target) => [
|
|
27
|
+
'length',
|
|
28
|
+
...Array.from({ length: target.length }, (_, i) => String(i)),
|
|
29
|
+
'metadata', 'chapters', 'get', 'getByChapter', 'search', 'getRandom'
|
|
30
|
+
]
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export default Bukhari;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// Node ESM entry — import bukhari from 'sahih-al-bukhari'
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import zlib from 'zlib';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import { Bukhari } from './index.js';
|
|
7
|
+
|
|
8
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
9
|
+
|
|
10
|
+
function loadData() {
|
|
11
|
+
const gzPath = path.join(__dirname, '..', 'data', 'bukhari.json.gz');
|
|
12
|
+
const jsonPath = path.join(__dirname, '..', 'data', 'bukhari.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/bukhari.json.gz or data/bukhari.json');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const bukhari = new Bukhari(loadData());
|
|
19
|
+
|
|
20
|
+
export { Bukhari };
|
|
21
|
+
export default bukhari;
|