sunnah 1.3.6 → 1.5.0

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