{"id":41328538,"url":"https://github.com/MoathCodes/dorar_hadith","last_synced_at":"2026-02-24T23:00:37.497Z","repository":{"id":325080723,"uuid":"1093152058","full_name":"MoathCodes/dorar_hadith","owner":"MoathCodes","description":"A package that allows you to use dorar.net in your flutter and dart apps to get hadith details easily.","archived":false,"fork":false,"pushed_at":"2025-11-28T22:20:07.000Z","size":610,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-01-11T16:20:54.368Z","etag":null,"topics":["dart","dorar","flutter","hadith","islamic"],"latest_commit_sha":null,"homepage":"","language":"Dart","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/MoathCodes.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2025-11-10T01:24:34.000Z","updated_at":"2025-12-15T08:31:50.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/MoathCodes/dorar_hadith","commit_stats":null,"previous_names":["moathcodes/dorar_hadith"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/MoathCodes/dorar_hadith","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MoathCodes%2Fdorar_hadith","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MoathCodes%2Fdorar_hadith/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MoathCodes%2Fdorar_hadith/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MoathCodes%2Fdorar_hadith/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/MoathCodes","download_url":"https://codeload.github.com/MoathCodes/dorar_hadith/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/MoathCodes%2Fdorar_hadith/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29804138,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-24T22:43:48.403Z","status":"ssl_error","status_checked_at":"2026-02-24T22:43:18.536Z","response_time":75,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dart","dorar","flutter","hadith","islamic"],"created_at":"2026-01-23T06:11:49.933Z","updated_at":"2026-02-24T23:00:37.441Z","avatar_url":"https://github.com/MoathCodes.png","language":"Dart","funding_links":[],"categories":["Hadith Collection and Study (21 projects)"],"sub_categories":["Dart"],"readme":"# بسم الله الرحمن الرحيم\n# Dorar Hadith\n\n# تغيير اللغة: [ 🇸🇦 AR](README_AR.md)\n\n---\n\nA Dart library to search and retrieve hadith and related data from Dorar Al-Sanniyah.\n\nInspired by and partially based on the repository [dorar-hadith-api](https://github.com/AhmedElTabarani/dorar-hadith-api) by [Ahmed Al-Tabarani](https://github.com/AhmedElTabarani).\nWorks with any Dart program without requiring Flutter.\n\n[![pub package](https://img.shields.io/pub/v/dorar_hadith.svg)](https://pub.dev/packages/dorar_hadith)\n[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)\n\n## Library Highlights\n\n- Fast hadith search with filters by narrator, book, grade, hadith scholar (mohdith), and more\n- Retrieve detailed hadith information\n- Search and fetch hadith explanations (Sharh)\n- Find similar hadiths and alternate sahih versions\n- Offline browsing for books, narrators, and hadith scholars (mohdith) used for filtering\n\n### Search Capabilities\n\n- Search by hadith text\n- Filter by hadith grade\n- Filter by hadith scholars (mohdith)\n- Filter by narrators\n- Filter by books\n- Filter by hadith type (Qudsi, Athar, etc.)\n- Pagination metadata\n\n## Installation\n\nRun:\n\n```bash\ndart pub add dorar_hadith\n```\n\nOr using Flutter:\n\n```bash\nflutter pub add dorar_hadith\n```\n\n## Quick Start\n\n```dart\nimport 'package:dorar_hadith/dorar_hadith.dart';\n\nvoid main() async {\n  await DorarClient.use((client) async {\n    // Search for hadiths about prayer\n    final results = await client.searchHadith(\n      HadithSearchParams(value: 'الصلاة', page: 1),\n    );\n\n    print('Found ${results.data.length} hadiths');\n\n    // Print first hadith\n    if (results.data.isNotEmpty) {\n      final h = results.data.first;\n      print('Hadith: ${h.hadith}');\n      print('Narrator: ${h.rawi}');\n      print('Scholar: ${h.mohdith}');\n      print('Verdict: ${h.hukm}');\n    }\n\n    // You can return the result to use else where\n    return results;\n  });\n}\n```\n\nFor a complete example covering all library features, see:\n[`example/example.dart`](example/example.dart)\n\n## Usage\n\n### Important Note\nMost operations in `DorarClient` and the other services return results inside an `ApiResponse` object to simplify pagination.\n`ApiResponse` has two members:\n- The result: `data`\n- Pagination metadata: `SearchMetadata`\n\nThis makes it easier to request the next page when needed.\n\n### Quick Hadith Search\nUsing `client.searchHadith` is fast and returns lightweight `Hadith` objects\ndirectly from the public API.\n- Results are limited to ~15 hadiths, and filters can be used.\n- Only the textual fields (`hadith`, `rawi`, `mohdith`, `book`,\n  `numberOrPage`, `grade`) are included in this response.\n- Call `client.searchHadithDetailed` when you need IDs, sharh metadata, Dorar\n  links, or any of the extended fields provided by `DetailedHadith`.\n\n```dart\nfinal results = await client.searchHadith(\n  HadithSearchParams(value: 'الإيمان'),\n);\n\nfor (var hadith in results.data) {\n  print('${hadith.hadith}');\n  print('Grade: ${hadith.hukm}');\n}\n```\n\n### Detailed Search with Filters\nNote: Due to how Dorar works, detailed search populates\n`DetailedHadith.explainGrade` instead of `grade`, **to avoid these issues always use `DetailedHadith.hukm`.**\n\n```dart\nfinal params = HadithSearchParams(\n  value: 'الصيام',\n  page: 1,\n  degrees: [HadithDegree.authenticHadith], // Only sahih\n  mohdith: [MohdithReference.bukhari],\n  searchMethod: SearchMethod.anyWord,\n  zone: SearchZone.qudsi,\n);\n\nfinal results = await client.searchHadithDetailed(params);\n```\n\n### Get Hadith by ID\nReturns a `DetailedHadith` with complete metadata when available.\n\n```dart\nfinal hadith = await client.getHadithById('12345');\nprint('Hadith: ${hadith.hadith}');\nprint('Book: ${hadith.book}');\nprint('Grade: ${hadith.hukm}');\n```\n\n### Similar, Usul (Sources), Alternate Sahih\nNote: The `DetailedHadith` model has flags to check availability:\n- `hasAlternateHadithSahih` for alternate sahih\n- `hasSimilarHadith` for similar hadiths\n- `hasUsulHadith` for sources\n\n```dart\n// Get similar\nfinal similar = await client.hadith.getSimilar('12345');\n\n// Get alternate sahih\nfinal alternate = await client.hadith.getAlternate('12345');\n\n// Get sources\nfinal usul = await client.hadith.getUsul('12345');\nprint('Main hadith: ${usul.hadith.hadith}');\nprint('Sources: ${usul.count}');\n```\n\n### Search for Sharh (Explanation)\nNote: When using `client.searchHadithDetailed`, if a hadith has a sharh, you will find its ID in `DetailedHadith.sharhMetadata`. Use it as follows:\n\n```dart\n// Get sharh by ID\nfinal sharh = await client.sharh.getById('789');\n\n// Search by sharh text\nfinal sharhByText = await client.sharh.getByText('إنما الأعمال بالنيات');\n```\n\n### Reference Data (Offline)\nReference data is used for filtering (hadith scholar [mohdith], book, narrator) and is available offline for speed and usability.\n\nNote: Reference items contain only `id` and `name` (e.g., Sahih al-Bukhari). For full details, fetch via the API. See: “Get Book/Scholar Details”.\n\n#### Search Scholars (Hadith scholars – mohdith)\n\n```dart\n// By name\nfinal bukhari = await client.mohdithRef.searchMohdith('البخاري');\n\n// By ID\nfinal scholar = await client.mohdithRef.getMohdithById('256');\n\n// List paginated\nfinal allScholars = await client.mohdithRef.getAllMohdith(limit: 50);\n\n// Multiple by IDs\nfinal scholars = await client.mohdithRef.getMohdithByIds(['256', '179']);\n\n// Shortcut\nfinal results = await client.searchMohdith('أحمد');\n```\n\n#### Search Books\n\n```dart\n// By name\nfinal sahihBooks = await client.bookRef.searchBook('صحيح');\n\n// By ID\nfinal book = await client.bookRef.getBookById('6216');\n\n// List paginated\nfinal allBooks = await client.bookRef.getAllBooks(limit: 100, offset: 0);\n\n// Shortcut\nfinal results = await client.searchBooks('سنن');\n```\n\n#### Search Narrators\n\n```dart\n// By name\nfinal narrators = await client.rawiRef.searchRawi('أبو هريرة', limit: 10);\n\n// By ID\nfinal abuHurayrah = await client.rawiRef.getRawiById(4396);\n\n// Paginated listing\nfinal page1 = await client.rawiRef.getAllRawi(limit: 50, offset: 0);\n\n// Counts\nfinal total = await client.rawiRef.countRawi();\nfinal searchCount = await client.rawiRef.countRawi(query: 'عبد الله');\n\n// Shortcut\nfinal results = await client.searchRawi('عمر');\n\n// Important: dispose after use to avoid warnings\nawait client.dispose();\n```\n\n#### Predefined Constants\nTo make filtering easier without repeatedly searching, popular scholars, books, and narrators are provided as ready-to-use constants.\nIf you’d like to add more, please open an issue on GitHub.\n\n```dart\n// Sample scholar constants\nMohdithReference.bukhari  \nMohdithReference.muslim     \nMohdithReference.abuDawud   \n// ... 17 total\n\n// Sample book constants\nBookReference.sahihBukhari  \nBookReference.sahihMuslim     \n// ... 20 total\n\n// Sample narrator constants\nRawiReference.abuHurayrah     \nRawiReference.aisha          \n// ... 21 total\n\n// Using constants in filters\nfinal params = HadithSearchParams(\n  value: 'الصلاة',\n  page: 1,\n  mohdith: [MohdithReference.bukhari],\n  books: [BookReference.sahihBukhari],\n);\nfinal results = await client.hadith.searchViaSite(params);\n```\n\n### Get Book or Hadith Scholar (Mohdith) Details\n\n```dart\n// Book details from API\nfinal book = await client.book.getById('123');\nprint('Book: ${book.name}');\nprint('Author: ${book.author}');\n\n// Hadith scholar (mohdith) details from API\nfinal scholar = await client.mohdith.getById('456');\nprint('Name: ${scholar.name}');\nprint('Bio: ${scholar.info}');\n```\n\n## Available Options\n\nThis section describes all models and options provided by the library.\n\n### Hadith Models\n\nThe package now exposes two layered models:\n\n- `Hadith`: Lightweight record returned by the official Dorar API. Contains the\n  matn, narrator, scholar, source book, page/number, and grade.\n- `DetailedHadith`: Extends `Hadith` with every extra bit of metadata collected\n  from the Dorar website (IDs, sharh metadata, takhrij, related links, etc.).\n\n```dart\nclass Hadith {\n  final String hadith;              // Hadith text (matn)\n  final String rawi;                // Narrator name\n  final String mohdith;             // Scholar name\n  final String book;                // Source book name\n  final String numberOrPage;        // Page or hadith number in the source\n  final String grade;               // Verdict shown by the API\n}\n\nclass DetailedHadith extends Hadith {\n  final String? hadithId;           // Unique hadith ID\n  final String? mohdithId;          // Scholar ID\n  final String? bookId;             // Book ID\n  final String? explainGrade;       // Verdict text (detailed search)\n  final String? takhrij;            // Takhrij / additional sources\n  final bool hasSimilarHadith;      // Similar narrations exist?\n  final bool hasAlternateHadithSahih; // Alternate sahih available?\n  final bool hasUsulHadith;         // Usul (sources) available?\n  final String? similarHadithDorar;     // URL for similar narrations\n  final String? alternateHadithSahihDorar; // URL for alternate sahih\n  final String? usulHadithDorar;         // URL for usul sources\n  final bool hasSharhMetadata;      // Sharh metadata included?\n  final SharhMetadata? sharhMetadata; // Sharh metadata payload\n}\n```\n\n`client.searchHadith` and the other API-backed endpoints return the lightweight\n`Hadith` model. The site-powered endpoints (detailed search, similar,\nalternate, usul) upgrade those results to `DetailedHadith`, populating IDs,\nsharh metadata, related links, and the helper flags below.\n\nKey helpers:\n- `hasSimilarHadith` ⇒ call `client.hadith.getSimilar()`\n- `hasAlternateHadithSahih` ⇒ call `client.hadith.getAlternate()`\n- `hasUsulHadith` ⇒ call `client.hadith.getUsul()`\n- `hasSharhMetadata` ⇒ inspect `sharhMetadata.id` or `sharhMetadata.sharh`\n- `hukm` getter ⇒ reads `explainGrade` when filled, otherwise falls back to\n  `grade` (ideal for printing a single verdict string)\n\n### Sharh Model\n\nRepresents a hadith with its explanation.\n\n```dart\nclass Sharh {\n  // Base hadith info\n  final String hadith;              // Hadith text\n  final String rawi;                // Narrator\n  final String mohdith;             // Scholar\n  final String book;                // Source book\n  final String numberOrPage;        // Page/hadith number\n  final String grade;               // Grade\n  final String? takhrij;            // Takhrij\n  \n  // Sharh info\n  final bool hasSharhMetadata;      // Has sharh?\n  final SharhMetadata? sharhMetadata; // Sharh metadata\n  \n  // Helper to access sharh text directly\n  String? get sharhText =\u003e sharhMetadata?.sharh;\n}\n```\n\nUsage:\n```dart\nfinal sharh = await client.sharh.getById('789');\nif (sharh.hasSharhMetadata \u0026\u0026 sharh.sharhText != null) {\n  print('Sharh: ${sharh.sharhText}');\n}\n```\n\n### SharhMetadata\n\n```dart\nclass SharhMetadata {\n  final String id;                  // Sharh ID\n  final bool isContainSharh;        // Whether sharh text is included\n  final String? sharh;              // Sharh text (if any)\n}\n```\n\n### UsulHadith (Sources)\n\nRepresents a hadith with all its sources.\n\n```dart\nclass UsulHadith {\n  final DetailedHadith hadith;      // Detailed hadith with metadata\n  final List\u003cUsulSource\u003e sources;   // All sources\n  final int count;                  // Sources count\n}\n\n// Single source entry\nclass UsulSource {\n  final String source;              // Source name and page\n  final String chain;               // Chain of narration\n  final String hadithText;          // Hadith text in this source\n}\n```\n\nExample:\n```dart\nfinal usulResponse = await client.hadith.getUsul('12345');\nfinal usul = usulResponse.data;\n\nprint('Sources: ${usul.count}');\nfor (var source in usul.sources) {\n  print('Source: ${source.source}');\n  print('Chain: ${source.chain}');\n}\n```\n\n### BookInfo\n\nFull book details (via API).\n\n```dart\nclass BookInfo {\n  final String name;                // Book name\n  final String bookId;              // Unique ID\n  final String author;              // Author\n  final String reviewer;            // Reviewer\n  final String publisher;           // Publisher\n  final String edition;             // Edition\n  final String editionYear;         // Year of edition\n}\n```\n\nUsage:\n```dart\nfinal book = await client.book.getById('6216');\nprint('${book.name} - ${book.author}');\nprint('Publisher: ${book.publisher}');\n```\n\n### MohdithInfo\n\nFull hadith scholar (mohdith) details (via API).\n\n```dart\nclass MohdithInfo {\n  final String name;                // Scholar name\n  final String mohdithId;           // Unique ID\n  final String info;                // Biography and details\n}\n```\n\nUsage:\n```dart\nfinal mohdith = await client.mohdith.getById('256');\nprint('Name: ${mohdith.name}');\nprint('Bio: ${mohdith.info}');\n```\n\n### Reference Items\n\nLightweight items for offline filtering. All extend `ReferenceItem`.\n\n#### BookItem\n\n```dart\nclass BookItem extends ReferenceItem {\n  final String id;                  // Book ID\n  final String name;                // Book name\n  final String? author;             // Author (if any)\n  final String? mohdithId;          // Hadith scholar (mohdith) author ID\n  final String? category;           // Category (if any)\n}\n```\n\nUsage:\n```dart\n// Offline search in books\nfinal books = await client.bookRef.searchBook('صحيح', limit: 10);\nfor (var book in books) {\n  print('${book.name} - ${book.author}');\n  \n  // Get full details (online)\n  final fullInfo = await client.book.getById(book.id);\n}\n```\n\n#### MohdithItem\n\n```dart\nclass MohdithItem extends ReferenceItem {\n  final String id;                  // Scholar ID\n  final String name;                // Scholar name\n  final int? deathYear;             // Death year (Hijri)\n  final String? era;                // Era (if any)\n}\n```\n\nUsage:\n```dart\n// Offline search in scholars\nfinal scholars = await client.mohdithRef.searchMohdith('البخاري', limit: 5);\nfor (var scholar in scholars) {\n  print('${scholar.name}');\n  if (scholar.deathYear != null) {\n    print('Died in: ${scholar.deathYear} AH');\n  }\n}\n```\n\n#### RawiItem\n\n```dart\nclass RawiItem extends ReferenceItem {\n  final String id;                  // Narrator ID\n  final String name;                // Narrator name\n}\n```\n\nUsage:\n```dart\n// Offline narrator search\nfinal narrators = await client.rawiRef.searchRawi('أبو هريرة', limit: 3);\nfor (var narrator in narrators) {\n  print(narrator.name);\n}\n\n// Counts\nfinal total = await client.rawiRef.countRawi();\nfinal searchCount = await client.rawiRef.countRawi(query: 'عبد الله');\n```\n\n### ApiResponse\n\nAll search and retrieval operations return results inside `ApiResponse` to simplify pagination.\n\n```dart\nclass ApiResponse\u003cT\u003e {\n  final T data;                     // Actual data (hadith, list, etc.)\n  final SearchMetadata metadata;    // Extra info about the result\n}\n```\n\nExamples:\n```dart\n// Search returns ApiResponse\u003cList\u003cHadith\u003e\u003e\nfinal response = await client.searchHadith(\n  HadithSearchParams(value: 'الصلاة', page: 1),\n);\n\nprint('Count: ${response.data.length}');\nprint('Current page: ${response.metadata.page}');\nprint('From cache: ${response.metadata.isCached}');\n\n// Usul returns ApiResponse\u003cUsulHadith\u003e\nfinal usulResponse = await client.hadith.getUsul('12345');\nprint('Sources: ${usulResponse.data.count}');\n```\n\n### SearchMetadata\n\nAdditional info about a search result.\n\n```dart\nclass SearchMetadata {\n  final int length;                      // Number of returned results\n  final int? page;                       // Current page number\n  final bool? removeHtml;                // Whether HTML tags were removed\n  final bool? specialist;                // Include specialist hadiths?\n  final int? numberOfNonSpecialist;      // Non-specialist count\n  final int? numberOfSpecialist;         // Specialist count\n  final bool isCached;                   // Result from cache?\n  final int? usulSourcesCount;           // Sources count (for Usul requests)\n  \n  // Create a modified copy\n  SearchMetadata copyWith({...});\n}\n```\n\nUsage:\n```dart\nfinal response = await client.searchHadith(params);\nfinal meta = response.metadata;\n\nif (meta.isCached) {\n  print('Result is from cache - fast!');\n}\n\nprint('Page ${meta.page} of ???');\nprint('Results: ${meta.length}');\n```\n\n### HadithSearchParams\n\nAll search filters and parameters.\n\n```dart\nclass HadithSearchParams {\n  // Required\n  final String value;                    // Search text\n  \n  // Optional - search options\n  final int page;                        // Page (default: 1)\n  final bool removeHtml;                 // Remove HTML (default: true)\n  final bool specialist;                 // Include specialist hadiths (default: false)\n  final String? exclude;                 // Words/phrases to exclude\n  \n  // Optional - filters\n  final SearchMethod? searchMethod;      // Search method (all/any/exact)\n  final SearchZone? zone;                // Hadith type (Marfoo/Qudsi/Athar/Sharh)\n  final List\u003cHadithDegree\u003e? degrees;     // Filter by grade\n  final List\u003cMohdithReference\u003e? mohdith; // Filter by scholars\n  final List\u003cBookReference\u003e? books;      // Filter by books\n  final List\u003cRawiReference\u003e? rawi;       // Filter by narrators\n  \n  // Create a modified copy\n  HadithSearchParams copyWith({...});\n}\n```\n\nExamples:\n\n```dart\n// Minimal search\nfinal simple = HadithSearchParams(value: 'الصلاة', page: 1);\n\n// With specific filters\nfinal filtered = HadithSearchParams(\n  value: 'الإيمان',\n  page: 1,\n  degrees: [HadithDegree.authenticHadith],      // Sahih only\n  mohdith: [MohdithReference.bukhari],          // Al-Bukhari only\n  books: [BookReference.sahihBukhari],          // Sahih al-Bukhari only\n  searchMethod: SearchMethod.allWords,          // All words\n  zone: SearchZone.qudsi,                       // Qudsi hadiths\n);\n\n// Advanced with exclusions\nfinal advanced = HadithSearchParams(\n  value: 'الجنة النار',\n  page: 1,\n  exclude: 'الدنيا',                           // Exclude the word \"الدنيا\"\n  degrees: [\n    HadithDegree.authenticHadith,\n    HadithDegree.authenticChain,\n  ],\n  mohdith: [\n    MohdithReference.bukhari,\n    MohdithReference.muslim,\n  ],\n  searchMethod: SearchMethod.anyWord,           // Any word\n  removeHtml: true,                             // Remove HTML\n);\n\n// Modify existing params\nfinal modified = simple.copyWith(\n  page: 2,                                      // Go to page 2\n  degrees: [HadithDegree.authenticHadith],     // Add filter\n);\n\nfinal results = await client.searchHadithDetailed(advanced);\n```\n\n### HadithDegree\n\nStatic values representing hadith grading according to scholars.\n\n```dart\nenum HadithDegree {\n  all,                   // All grades (no filter)\n  authenticHadith,       // Scholars ruled the hadith itself as sahih\n  authenticChain,        // Scholars ruled the chain as sahih\n  weakHadith,            // Scholars ruled the hadith as weak\n  weakChain,             // Scholars ruled the chain as weak\n  \n  // Each value has:\n  final String id;       // ID used in the API\n  final String label;    // Arabic label\n}\n```\n\nUsage:\n```dart\n// Filter sahih only\nfinal params = HadithSearchParams(\n  value: 'الصدقة',\n  page: 1,\n  degrees: [HadithDegree.authenticHadith],\n);\n\n// Sahih (hadith or chain)\nfinal params2 = HadithSearchParams(\n  value: 'الصدقة',\n  page: 1,\n  degrees: [\n    HadithDegree.authenticHadith,\n    HadithDegree.authenticChain,\n  ],\n);\n\n// Helpers\nprint(HadithDegree.authenticHadith.toString()); // Arabic label\nprint(HadithDegree.authenticHadith.toQueryParam()); // API id\n```\n\n### SearchMethod\n\nHow the text search is performed.\n\n```dart\nenum SearchMethod {\n  allWords,                     // All words (AND)\n  anyWord,                      // Any word (OR)\n  exactMatch,                   // Exact phrase\n  \n  // Each value has:\n  final String id;              // API id\n  final String label;           // Arabic label\n}\n```\n\nUsage:\n```dart\n// All words (AND)\nfinal allWords = HadithSearchParams(\n  value: 'الصلاة الزكاة',\n  page: 1,\n  searchMethod: SearchMethod.allWords, // Must contain both words\n);\n\n// Any word (OR)\nfinal anyWord = HadithSearchParams(\n  value: 'الصلاة الزكاة',\n  page: 1,\n  searchMethod: SearchMethod.anyWord,  // Contains either word\n);\n\n// Exact phrase\nfinal exact = HadithSearchParams(\n  value: 'إنما الأعمال بالنيات',\n  page: 1,\n  searchMethod: SearchMethod.exactMatch, // Exact phrase\n);\n\n// Helpers\nprint(SearchMethod.allWords.toString()); // \"جميع الكلمات\"\nprint(SearchMethod.allWords.toQueryParam()); // \"w\"\n```\n\n### SearchZone\n\nFilters by hadith type.\n\n```dart\nenum SearchZone {\n  all,                          // All hadiths (no filter)\n  marfoo,                       // Marfoo (attributed to the Prophet ﷺ)\n  qudsi,                        // Qudsi (from Allah)\n  sahabaAthar,                  // Companions' athar\n  sharh,                        // Explanations (shuruh)\n  \n  // Each value has:\n  final String id;              // API id\n  final String label;           // Arabic label\n}\n```\n\nUsage:\n```dart\n// Qudsi only\nfinal qudsi = HadithSearchParams(\n  value: 'الجنة',\n  page: 1,\n  zone: SearchZone.qudsi,\n);\n\n// Marfoo only\nfinal marfoo = HadithSearchParams(\n  value: 'الصلاة',\n  page: 1,\n  zone: SearchZone.marfoo,\n);\n\n// Sahaba athar\nfinal athar = HadithSearchParams(\n  value: 'عمر بن الخطاب',\n  page: 1,\n  zone: SearchZone.sahabaAthar,\n);\n\n// Helpers\nprint(SearchZone.qudsi.toString()); // \"الأحاديث القدسية\"\nprint(SearchZone.qudsi.toQueryParam()); // \"1\"\n```\n\n### Client Options\n\n```dart\nfinal client = DorarClient(\n  timeout: Duration(seconds: 15),       // Request timeout (default: 15s)\n  // Caching is enabled by default using a persistent SQLite database\n  // (cache.db on native, WebAssembly on web)\n);\n```\n\n### Persistent Caching\n\nThe library now uses a persistent SQLite database (`cache.db`) to store results.\n- **Native Platforms**: The database is created in the current working directory (CLI) or application documents directory (Flutter).\n- **Web**: Uses `sqlite3.wasm` for persistent storage in the browser.\n\n```dart\n// Cache stats\nfinal stats = await client.getCacheStats();\nprint('Total entries: ${stats.totalEntries}');\nprint('Valid entries: ${stats.validEntries}');\nprint('Hit rate: ${(stats.hitRate * 100).toStringAsFixed(1)}%');\n\n// Clear all cache\nclient.clearCache();\n\n// Clear per-service cache\nclient.hadith.clearCache();\nclient.sharh.clearCache();\nclient.book.clearCache();\n```\n\n### Resource Cleanup\n\nAlways call `dispose()` when done to close database connections and avoid warnings.\n\n```dart\nvoid main() async {\n  final client = DorarClient();\n  \n  try {\n    // Use the library\n    final results = await client.searchHadith(\n      HadithSearchParams(value: 'الصلاة', page: 1),\n    );\n  } finally {\n    // Mandatory cleanup\n    await client.dispose();\n  }\n}\n```\n\n### Error Handling\n\nThe library uses a sealed class hierarchy for exceptions, enabling safer handling with pattern matching.\n\n#### DorarException Types\n\nAll errors extend `DorarException` with the following types:\n\n```dart\n// 1. Network error - connectivity issues\nDorarNetworkException {\n  final String message;\n  final String? details;\n}\n\n// 2. Timeout - request took too long\nDorarTimeoutException {\n  final String message;\n  final Duration timeout;\n  final String? details;\n}\n\n// 3. Not found - missing resource\nDorarNotFoundException {\n  final String message;\n  final String resource;\n  final String? details;\n}\n\n// 4. Validation error - invalid input\nDorarValidationException {\n  final String message;\n  final String? field;        // Field name\n  final String? rule;         // Violated rule\n  final String? details;\n}\n\n// 5. Server error - Dorar server issue\nDorarServerException {\n  final String message;\n  final int statusCode;       // HTTP status code\n  final String? details;\n}\n\n// 6. Parse error - response parsing issue\nDorarParseException {\n  final String message;\n  final String? expectedType; // Expected type\n  final String? details;\n}\n\n// 7. Rate limit - too many requests\nDorarRateLimitException {\n  final String message;\n  final int? limit;           // Max requests\n  final DateTime? resetAt;    // Reset time\n  final String? details;\n}\n```\n\n#### Comprehensive Handling with Switch Expression\n\n```dart\ntry {\n  final results = await client.searchHadith(\n    HadithSearchParams(value: 'الصلاة', page: 1),\n  );\n} on DorarException catch (e) {\n  // Pattern matching - compiler ensures coverage!\n  final message = switch (e) {\n    DorarNetworkException() =\u003e \n      '🌐 Network error: ${e.message}\\n'\n      'Please check your internet connection',\n      \n    DorarTimeoutException() =\u003e \n      '⏱️ Request timed out after ${e.timeout.inSeconds} seconds\\n'\n      'Try again later',\n      \n    DorarNotFoundException() =\u003e \n      '🔍 Not found: ${e.resource}\\n'\n      'Verify the identifier',\n      \n    DorarValidationException() =\u003e \n      '✋ Validation error: ${e.message}\\n'\n      '${e.field != null ? \"Field: ${e.field}\" : \"\"}',\n      \n    DorarServerException() =\u003e \n      '🖥️ Server error (${e.statusCode}): ${e.message}',\n      \n    DorarParseException() =\u003e \n      '📄 Parse error: ${e.message}',\n      \n    DorarRateLimitException() =\u003e \n      '🚫 Rate limit exceeded\\n'\n      '${e.resetAt != null ? \"Retry after: ${e.resetAt}\" : \"\"}',\n  };\n  \n  print(message);\n}\n```\n\n#### Helper Function for Error Messages\n\n```dart\nimport 'package:dorar_hadith/dorar_hadith.dart';\n\ntry {\n  final results = await client.searchHadith(params);\n} on DorarException catch (e) {\n  // Use the helper\n  print(getExceptionMessage(e));\n}\n```\n\n## Available Services\n\n`DorarClient` exposes multiple focused services, each responsible for specific functionality.\n\n### Hadith Service\n\nThe core service for searching and fetching hadiths.\n\n```dart\nfinal client = DorarClient();\n\n// 1. Quick search (API - ~15 results)\nfinal quickResults = await client.searchHadith(\n  HadithSearchParams(value: 'الصلاة', page: 1),\n);\n\n// 2. Detailed search (Site - ~30 results)\nfinal detailedResults = await client.searchHadithDetailed(\n  HadithSearchParams(value: 'الصلاة', page: 1),\n);\n\n// 3. Get by ID\nfinal hadith = await client.getHadithById('12345');\n\n// Or use the service directly\nfinal sameResults = await client.hadith.searchViaApi(params);\nfinal sameDetailed = await client.hadith.searchViaSite(params);\nfinal sameHadith = await client.hadith.getById('12345');\n\n// 4. Similar hadiths\nif (hadith.hasSimilarHadith \u0026\u0026 hadith.hadithId != null) {\n  final similar = await client.hadith.getSimilar(hadith.hadithId!);\n  print('Found ${similar.length} similar hadiths');\n}\n\n// 5. Alternate sahih\nif (hadith.hasAlternateHadithSahih \u0026\u0026 hadith.hadithId != null) {\n  final alternate = await client.hadith.getAlternate(hadith.hadithId!);\n  if (alternate != null) {\n    print('Alternate sahih: ${alternate.hadith}');\n  }\n}\n\n// 6. Usul (sources)\nif (hadith.hasUsulHadith \u0026\u0026 hadith.hadithId != null) {\n  final usulResponse = await client.hadith.getUsul(hadith.hadithId!);\n  final usul = usulResponse.data;\n  print('Sources: ${usul.count}');\n  for (var source in usul.sources) {\n    print('- ${source.source}: ${source.chain}');\n  }\n}\n\n// 7. Clear cache\nclient.hadith.clearCache();\n```\n\n### Sharh Service\n\nSearch and retrieve hadith explanations.\n\n```dart\n// 1. Search by hadith text\nfinal sharh = await client.sharh.getByText('إنما الأعمال بالنيات');\n\n// You can also search in specialist hadiths\nfinal specialistSharh = await client.sharh.getByText(\n  'نص الحديث',\n  specialist: true,\n);\n\n// 2. Get by ID\n// (ID comes from DetailedHadith.sharhMetadata.id)\nfinal hadith = await client.getHadithById('12345');\nif (hadith.hasSharhMetadata \u0026\u0026 hadith.sharhMetadata != null) {\n  final sharhId = hadith.sharhMetadata!.id;\n  final sharh = await client.sharh.getById(sharhId);\n  \n  if (sharh.sharhText != null) {\n    print('Sharh: ${sharh.sharhText}');\n  }\n}\n\n// 3. Clear cache\nclient.sharh.clearCache();\n```\n\n### Book Service (Detailed)\n\nFetch detailed book info (requires internet).\n\n```dart\n// Get book by ID\nfinal book = await client.book.getById('6216'); // Sahih al-Bukhari\n\nprint('Book: ${book.name}');\nprint('Author: ${book.author}');\nprint('Reviewer: ${book.reviewer}');\nprint('Publisher: ${book.publisher}');\nprint('Edition: ${book.edition}');\nprint('Edition Year: ${book.editionYear}');\n\n// Clear cache\nclient.book.clearCache();\n```\n\n### Mohdith Service (Detailed)\n\nFetch detailed scholar info (requires internet).\n\n```dart\n// Get scholar by ID\nfinal mohdith = await client.mohdith.getById('256'); // Imam al-Bukhari\n\nprint('Name: ${mohdith.name}');\nprint('Bio: ${mohdith.info}');\n\n// Clear cache\nclient.mohdith.clearCache();\n```\n\n### Book Reference Service (Offline)\n\nSearch available books without internet.\n\n```dart\n// 1. Search by name\nfinal books = await client.bookRef.searchBook('صحيح', limit: 10);\n\n// Or shortcut\nfinal sameBooks = await client.searchBooks('صحيح');\n\nfor (var book in books) {\n  print('${book.name} - ${book.author}');\n}\n\n// 2. Get by ID\nfinal bukhari = await client.bookRef.getBookById('6216');\nprint(bukhari.name); // صحيح البخاري\n\n// 3. Get multiple by IDs\nfinal multipleBooks = await client.bookRef.getBooksByIds([\n  '6216', // Sahih al-Bukhari\n  '3662', // Sahih Muslim\n]);\n\n// 4. List all with pagination\nfinal allBooks = await client.bookRef.getAllBooks(\n  limit: 50,\n  offset: 0,\n);\n\n// 5. Counts\nfinal totalBooks = await client.bookRef.countBooks();\nfinal sahihBooks = await client.bookRef.countBooks(query: 'صحيح');\nprint('Total books: $totalBooks');\nprint('\"Sahih\" books: $sahihBooks');\n```\n\n### Mohdith Reference Service (Offline)\n\nSearch available scholars without internet.\n\n```dart\n// 1. Search by name\nfinal scholars = await client.mohdithRef.searchMohdith('البخاري', limit: 5);\n\n// Or shortcut\nfinal sameScholars = await client.searchMohdith('البخاري');\n\nfor (var scholar in scholars) {\n  print('${scholar.name}');\n  if (scholar.deathYear != null) {\n    print('Death year: ${scholar.deathYear} AH');\n  }\n}\n\n// 2. Get by ID\nfinal bukhari = await client.mohdithRef.getMohdithById('256');\nprint(bukhari.name); // البخاري\n\n// 3. Get multiple by IDs\nfinal multipleScholars = await client.mohdithRef.getMohdithByIds([\n  '256',  // al-Bukhari\n  '261',  // Muslim\n]);\n\n// 4. List all with pagination\nfinal allScholars = await client.mohdithRef.getAllMohdith(\n  limit: 50,\n  offset: 0,\n);\n\n// 5. Counts\nfinal totalScholars = await client.mohdithRef.countMohdith();\nfinal classicalScholars = await client.mohdithRef.countMohdith(\n  query: 'أحمد',\n);\nprint('Total scholars: $totalScholars');\n```\n\n### Rawi Reference Service (Offline)\n\nSearch available narrators without internet.\n\n```dart\n// 1. Search by name\nfinal narrators = await client.rawiRef.searchRawi('أبو هريرة', limit: 10);\n\n// Or shortcut\nfinal sameNarrators = await client.searchRawi('أبو هريرة');\n\nfor (var narrator in narrators) {\n  print(narrator.name);\n}\n\n// 2. Get by ID\nfinal abuHurayrah = await client.rawiRef.getRawiById(4396);\nprint(abuHurayrah.name); // أبو هريرة عبد الرحمن بن صخر الدوسي\n\n// 3. Get multiple by IDs\nfinal multipleNarrators = await client.rawiRef.getRawiByIds([\n  4396,   // Abu Hurayrah\n  5593,   // Aishah\n]);\n\n// 4. List all with pagination\nfinal allNarrators = await client.rawiRef.getAllRawi(\n  limit: 100,\n  offset: 0,\n);\n\n// 5. Counts\nfinal totalNarrators = await client.rawiRef.countRawi();\nfinal abdullahNarrators = await client.rawiRef.countRawi(query: 'عبد الله');\nprint('Total narrators: $totalNarrators');\nprint('\"Abdullah\" narrators: $abdullahNarrators');\n```\n\n### Predefined Filter Constants\n\nThe library provides predefined constants for common scholars, books, and narrators to simplify filtering.\n\n#### MohdithReference (Scholars)\n\n```dart\n// 20 scholars\nMohdithReference.all             // All (no filter) - ID: 0\nMohdithReference.malik           // Imam Malik - ID: 179\nMohdithReference.shafii          // Imam al-Shafi'i - ID: 204\nMohdithReference.ahmad           // Imam Ahmad - ID: 241\nMohdithReference.bukhari         // al-Bukhari - ID: 256\nMohdithReference.muslim          // Muslim - ID: 261\nMohdithReference.ibnMajah        // Ibn Majah - ID: 273\nMohdithReference.abuDawud        // Abu Dawud - ID: 275\nMohdithReference.tirmidhi        // al-Tirmidhi - ID: 279\nMohdithReference.nasai           // al-Nasa'i - ID: 303\nMohdithReference.sufyanThawri    // Sufyan al-Thawri - ID: 161\nMohdithReference.ibnMubarak      // Abdullah b. al-Mubarak - ID: 181\nMohdithReference.sufyanIbnUyaynah // Sufyan b. 'Uyaynah - ID: 198\nMohdithReference.ishaqIbnRahawayh // Ishaq b. Rahawayh - ID: 238\nMohdithReference.darimi          // al-Darimi - ID: 250\nMohdithReference.ibnKhuzaymah    // Ibn Khuzaymah - ID: 311\nMohdithReference.ibnHibban       // Ibn Hibban - ID: 354\nMohdithReference.hakim           // al-Hakim - ID: 405\nMohdithReference.bayhaqi         // al-Bayhaqi - ID: 458\nMohdithReference.tabarani        // al-Tabarani - ID: 360\n\n// Each value has id and name\nfinal bukhari = MohdithReference.bukhari;\nprint(bukhari.id);    // \"256\"\nprint(bukhari.name);  // \"البخاري\"\n\n// Use in filters\nfinal params = HadithSearchParams(\n  value: 'الصلاة',\n  page: 1,\n  mohdith: [MohdithReference.bukhari],\n);\n\n// Get numeric id if needed\nfinal bukhariId = int.parse(MohdithReference.bukhari.id);\n```\n\n#### BookReference (Books)\n\n```dart\n// 21 books\nBookReference.all                 // All (no filter)\nBookReference.sahihBukhari        // Sahih al-Bukhari (6216)\nBookReference.sahihMuslim         // Sahih Muslim (3088)\nBookReference.arbainNawawi        // Al-Arba'in al-Nawawiyyah (13457)\nBookReference.sahihMusnad         // Al-Sahih al-Musnad (96)\nBookReference.sunanAbuDawud       // Sunan Abi Dawud (4549)\nBookReference.jamiTirmidhi        // Jami' al-Tirmidhi (3662)\nBookReference.sunanNasai          // Sunan al-Nasa'i (5766)\nBookReference.sunanIbnMajah       // Sunan Ibn Majah (5299)\nBookReference.musnadAhmad         // Musnad Ahmad (14)\nBookReference.muwattaMalik        // Muwatta' Malik (6453)\nBookReference.musnadDarimi        // Sunan al-Darimi (6277)\nBookReference.sahihIbnKhuzaymah   // Sahih Ibn Khuzaymah (3024)\nBookReference.sahihIbnHibban      // Sahih Ibn Hibban (5876)\nBookReference.mustadrakHakim      // Al-Mustadrak (2800)\nBookReference.sunanBayhaqiKubra   // Al-Sunan al-Kubra (7989)\nBookReference.sunanDaraqutni      // Sunan al-Daraqutni (3233)\nBookReference.musannafIbnAbiShaybah // Musannaf Ibn Abi Shaybah (6598)\nBookReference.musannafAbdRazzaq   // Musannaf 'Abd al-Razzaq (7613)\nBookReference.riyadSalihin        // Riyad al-Salihin (10106)\nBookReference.bulughMaram         // Bulugh al-Maram (9927)\n\n// Each value has id and name\nfinal bukhari = BookReference.sahihBukhari;\nprint(bukhari.id);    // \"6216\"\nprint(bukhari.name);  // \"صحيح البخاري\"\n\n// Use in filters\nfinal params = HadithSearchParams(\n  value: 'الزكاة',\n  page: 1,\n  books: [\n    BookReference.sahihBukhari,\n    BookReference.sahihMuslim,\n  ],\n);\n\n// Get numeric id if needed\nfinal bukhariId = int.parse(BookReference.sahihBukhari.id);\n```\n\n#### RawiReference (Narrators)\n\nNote: There are ~14,000 narrators in the database, so only some companions are provided as constants.\n\n```dart\n// 20 companions\nRawiReference.all                // All (no filter)\nRawiReference.abuHurayrah        // Abu Hurayrah (1416)\nRawiReference.aisha              // Aishah (6617)\nRawiReference.ibnAbbas           // Ibn Abbas (2664)\nRawiReference.ibnUmar            // Abdullah b. Umar (7687)\nRawiReference.anasBinMalik       // Anas b. Malik (2177)\nRawiReference.jabirIbnAbdullah  // Jabir b. Abdullah (3971)\nRawiReference.abuSaidKhudri      // Abu Sa'id al-Khudri (779)\nRawiReference.ibnMasud           // Abdullah b. Mas'ud (7918)\nRawiReference.abuMusaAshari      // Abu Musa al-Ash'ari (1342)\nRawiReference.umarIbnKhattab     // Umar b. al-Khattab (8918)\nRawiReference.aliIbnAbiTalib     // Ali b. Abi Talib (8637)\nRawiReference.abuBakr            // Abu Bakr al-Siddiq (455)\nRawiReference.uthmanIbnAffan     // Uthman b. Affan (8310)\nRawiReference.salmanFarisi       // Salman al-Farisi (5947)\nRawiReference.muadhIbnJabal      // Mu'adh b. Jabal (10349)\nRawiReference.abuDharr           // Abu Dharr al-Ghifari (667)\nRawiReference.bilal              // Bilal b. Rabah (3808)\nRawiReference.zaydIbnThabit      // Zayd b. Thabit (5545)\nRawiReference.ubayyIbnKab        // Ubayy b. Ka'b (1695)\nRawiReference.abuAyyub           // Abu Ayyub al-Ansari (129)\n\n// Each value has id and name\nfinal abuHurayrah = RawiReference.abuHurayrah;\nprint(abuHurayrah.id);    // \"1416\"\nprint(abuHurayrah.name);  // \"أبو هريرة\"\n\n// Use in filters\nfinal params = HadithSearchParams(\n  value: 'الجنة',\n  page: 1,\n  rawi: [RawiReference.abuHurayrah],\n);\n\n// Get numeric id if needed\nfinal abuHurayrahId = int.parse(RawiReference.abuHurayrah.id);\n\n// To find more narrators, use the search service\nfinal narrators = await client.rawiRef.searchRawi('عبد الله', limit: 10);\n```\n\n### Seeing \"Unclosed database\" Warning?\n\nAlways call `client.dispose()`:\n\n```dart\nfinal client = DorarClient();\ntry {\n  // Use the library\n} finally {\n  await client.dispose(); // Mandatory\n}\n```\n\nOr if you don't want to think about it, use `DorarClient.use` method instead, it will automatically dispose of the client after it finished:\n```dart\nfinal results = await DorarClient.use((client) async {\n    return await client.searchHadith(\n      HadithSearchParams(value: 'الصلاة', page: 1),\n    );\n  });\n```\n\n## Contributing\n\nAll forms of contributions are welcome.  \n## License\n\nMIT License - see [LICENSE](LICENSE) file.\n\n## Architecture\n\n```\nDorarClient (Facade)\n    ├── HadithService\n    ├── SharhService  \n    ├── BookService\n    ├── MohdithService\n    ├── MohdithRefService \n    ├── BookRefService \n    └── RawiRefService \n         └── HTTP Client + Cache\n              └── HTML Parsers\n```\n\n— May Allah reward us and you.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FMoathCodes%2Fdorar_hadith","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FMoathCodes%2Fdorar_hadith","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FMoathCodes%2Fdorar_hadith/lists"}