{"id":27446529,"url":"https://github.com/jamalneufeld/ghostHunter","last_synced_at":"2025-04-15T05:01:13.369Z","repository":{"id":13557150,"uuid":"16249319","full_name":"jamwise/ghostHunter","owner":"jamwise","description":"A Ghost blog search engine","archived":false,"fork":false,"pushed_at":"2023-04-15T17:16:20.000Z","size":246,"stargazers_count":593,"open_issues_count":4,"forks_count":122,"subscribers_count":23,"default_branch":"master","last_synced_at":"2025-04-10T02:44:27.116Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","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/jamwise.png","metadata":{"files":{"readme":"README.md","changelog":"changelog.txt","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}},"created_at":"2014-01-26T08:04:08.000Z","updated_at":"2025-02-11T21:30:57.000Z","dependencies_parsed_at":"2022-07-15T14:47:59.141Z","dependency_job_id":"9eef7158-2f8b-4922-896d-f8c10986bd3e","html_url":"https://github.com/jamwise/ghostHunter","commit_stats":null,"previous_names":["i11ume/ghosthunter","jamalneufeld/ghosthunter"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamwise%2FghostHunter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamwise%2FghostHunter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamwise%2FghostHunter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jamwise%2FghostHunter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jamwise","download_url":"https://codeload.github.com/jamwise/ghostHunter/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249010212,"owners_count":21197797,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":[],"created_at":"2025-04-15T05:00:45.550Z","updated_at":"2025-04-15T05:01:13.333Z","avatar_url":"https://github.com/jamwise.png","language":"JavaScript","funding_links":[],"categories":["Plugins","JavaScript"],"sub_categories":[],"readme":"\n# ⚠️ GhostHunter has not been maintained since 2019 \n### There is now a native search feature built-in to Ghost core:\nhttps://ghost.org/changelog/search/\n\n\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\n\n---\n\n\u003cbr\u003e\u003cbr\u003e\u003cbr\u003e\n\n![Version](https://img.shields.io/badge/Version-0.6.0-blue.svg)\n![MinGhostVersion](https://img.shields.io/badge/Min%20Ghost%20v.-%3E%3D%202.10-red.svg)\n\n## ghostHunter\n\n**Original developer:** [jamal@i11u.me](mailto:jamal@i11u.me)\n\nGhostHunter makes it easy to add search capability to any Ghost theme, using the [Ghost API](https://api.ghost.org/v1.14.0/docs) and the [lunr.js](https://lunrjs.com) search engine. Indexing and search are done client-side (in the browser). This has several advantages:\n\n* Searches are private to the user, and are not exposed to third parties.\n* Installation and maintenance of powerful-but-daunting standalone search engines (such as [Solr](http://lucene.apache.org/solr/) or [ElasticSearch](https://www.elastic.co/)) is not required.\n* Instant search (\"search-as-you-type\" or \"typeahead\") is simple to configure.\n\n-----------------\n\n## Contents\n\n   * [ghostHunter](#ghosthunter)\n      * [Contents](#contents)\n      * [Upgrade notes](#upgrade-notes)\n      * [Basic setup](#basic-setup)\n      * [Advanced usage](#advanced-usage)\n         * [Production installation](#production-installation)\n         * [GhostHunter options](#ghosthunter-options)\n         * [Multiple search fields](#multiple-search-fields)\n         * [Clearing search results](#clearing-search-results)\n         * [Indexing and caching: how it works](#indexing-and-caching-how-it-works)\n         * [Development: rebuilding ghostHunter](#development-rebuilding-ghosthunter)\n   * [Footnotes](#footnotes)\n\n------------------\n\n## Upgrade notes\n### GhostHunter v0.6.0\n\n* Implements @JiapengLi \"dirty fix\" to support the new Ghost v2 Content API.\n* Removes spurious production console.log message.\n* Removes `includepages` option.\n\n\nTo use this version of ghostHunter, you'll need to create a Custom Integration and inject its Content API key into your blog header. In your Ghost Settings:\n\n* Go to **Integrations**\n* Choose **Add custom integration**, name it `ghostHunter` and choose **Create**. Copy the generated Content API Key.\n* Go to **Code injection**\n* Add this to **Blog Header**:\n```txt\n\u003cscript\u003e\n  var ghosthunter_key = 'PASTE_THE_GENERATED_KEY_HERE';\n  //optional: set your custom ghost_root url, default is `\"/ghost/api/v2\"`\n  var ghost_root_url = \"/ghost/api/v2\"\n\u003c/script\u003e\n```\n\n\n### GhostHunter v0.5.1\n\nBreaking change: added a new parameter `includebodysearch`, default `false`. Leaving it `false` completely deactivates searching within post body. Change done for performance reasons for Ghost Pro members.\n\n### GhostHunter v0.4.x → v0.5.0\n\nThe local ``lunr.js`` index used by ghostHunter is quick. That makes\nit well suited to search-as-you-type (SAYT), which can be enabled\nsimply by setting the ``onKeyUp`` option to ``true``. Although fast\nand convenient, the rapid clearing-and-rewriting of search results in\nSAYT mode can be distracting to the user.\n\nFrom version 0.5.0, ghostHunter uses a [Levenshtein edit\ndistance](https://en.wikipedia.org/wiki/Levenshtein_distance)\nalgorithm to determine the specific steps needed to transform\neach list of search results into the next. This produces screen\nupdates that are easy on the eye, and even pleasant to watch.\n\nTo support this behavior, ghostHunter imposes some new requirements\non the ``result_template``. If you use this option in your theme,\nyou edit the template to satisfy the following requirements\nbefore upgrading:\n\n   * The template *must* be wrapped in a single outer node (i.e. ``\u003cspan\u003e`` or ``div``);\n   * The outer node *must* have a unique ``id`` attribute. You can set this using by giving\n     giving the ``{{ref}}`` value used for indexing a string prefix (see the default\n     template for an example).\n   * The outer node *must* be assigned a class ``gh-search-item``.\n\nThat's it. With those changes, your theme should be ready for ghostHunter 0.5.0.\n\n## Basic setup\n\nIn your theme directory, navigate to the `assets` subdirectory, \u003ca name=\"r1\" href=\"#f1\"\u003e[1]\u003c/a\u003e and clone this repository there: \u003ca name=\"r2\" href=\"#f2\"\u003e[2]\u003c/a\u003e\n\n```txt\n  cd assets\n  git clone https://github.com/jamalneufeld/ghostHunter.git --recursive\n```\n\nAfter cloning, the ghostHunter module will be located at `assets/ghostHunter/dist/jquery.ghosthunter.js`. \u003ca name=\"r3\" href=\"#f3\"\u003e[3]\u003c/a\u003e This is a human-readable \"raw\" copy of the module, and can be loaded directly in your theme templates for testing. (It will run just fine, but it contains a lot of whitespace and comments, and should be \"minified\" for production use [see below]).\n\nTo test the module in your template, add the following line, after JQuery is loaded. Typically this will be near the bottom of a file `default.hbs`, in the top folder of the theme directory.\n\n````html\n\u003cscript type=\"text/javascript\" src=\"{{asset \"ghostHunter/dist/jquery.ghosthunter.js\"}}\"\u003e\u003c/script\u003e\n````\n\nYou will need to add a search box to your pages. The specific `.hbs` template and location will vary depending on the style and on your design choices, but the HTML will need an `\u003cinput\u003e` field and a submit button inside a `\u003cform\u003e` element. A block like this should do the trick:\n\n````html\n\u003cform\u003e\n    \u003cinput id=\"search-field\" /\u003e\n    \u003cinput type=\"submit\" value=\"search\"\u003e\n\u003c/form\u003e\n````\n\nYou will also need to mark an area in your pages where the search results should show up:\n\n````html\n\u003csection id=\"results\"\u003e\u003c/section\u003e\n````\n\nWake up ghostHunter with a block of JQuery code. For testing, the sample below can be placed in the\ntemplate that loads ghostHunter, immediately after the module is loaded:\n\n````html\n  \u003cscript\u003e\n    $(\"#search-field\").ghostHunter({\n      results: \"#results\"\n    });\n  \u003c/script\u003e\n````\n\nDo the necessaries to [load the theme into Ghost](https://themes.ghost.org/v1.17.0/docs/about), and see if it works. :sweat_smile:\n\n\n## Advanced usage\n\n### Production installation\n\nTo reduce load times and network traffic, the JavaScript of a site is typically \"minified,\" bundling all code into a single file with reduced whitespace and other optimizations. The ``jquery.ghosthunter.js`` module should be bundled in this way for the production version of your site. The most common tool for this purpose in Web development is either Grunt or Gulp. A full explanation of their use is beyond the scope of this guide, but here are some links for reference:\n\n* The [Gulp Project](https://gulpjs.com/) website.\n* The [Grunt Project](https://gruntjs.com/) website.\n\nGhostHunter is built using Grunt. Instructions on installing Grunt in order to tweak or extend the code of the ghostHunter module are given in a separate section below.\n\n\n### GhostHunter options\n\nThe behavior of ghostHunter can be controlled at two levels. For deep\nchanges, \u003ca name=\"r4\" href=\"#f4\"\u003e[4]\u003c/a\u003e see the section [Development:\nrebuilding ghostHunter](#development-rebuilding-ghosthunter) below.\n\nFor most purposes, ghostHunter offers a set of simple options can be\nset when the plugin is invoked: as an example, the last code sample in\nthe previous section sets the `results` option.\n\n:arrow_right: **results**\n\n\u003e Should be set to the JQuery ID of the DOM object into which search results should be inserted. This value is required.\n\u003e\n\u003e Default value is ``undefined``.\n\n:arrow_right: **onKeyUp**\n\n\u003e When set ``true``, search results are returned after each keystroke, for instant search-as-you-type results.\n\u003e\n\u003e Default value is ``false``\n\n:arrow_right: **result_template**\n\n\u003e A simple Handlebars template used to render individual items in the search result. The templates\n\u003e recognize variable substitution only; helpers and conditional insertion constructs are ignored,\n\u003e and will be rendered verbatim.\n\u003e\n\u003e From ghostHunter v0.5.0, the ``result_template`` *must* be assigned a unique``id``, and *must*\n\u003e be assigned a class ``gh-search-item``. Without these attributes, screen updates will not\n\u003e work correctly.\n\u003e\n\u003e Default template is \u003ccode\u003e\u0026lt;a id='gh-{{ref}}' class='gh-search-item' href='{{link}}'\u0026gt;\u0026lt;p\u0026gt;\u0026lt;h2\u0026gt;{{title}}\u0026lt;/h2\u0026gt;\u0026lt;h4\u0026gt;{{prettyPubDate}}\u0026lt;/h4\u0026gt;\u0026lt;/p\u0026gt;\u0026lt;/a\u0026gt;\u003c/code\u003e\n\n:arrow_right: **info_template**\n\n\u003e A Handlebars template used to display the number of search items returned.\n\u003e\n\u003e Default template is \u003ccode\u003e\u0026lt;p\u0026gt;Number of posts found: {{amount}}\u0026lt;/p\u0026gt;\u003c/code\u003e\n\n:arrow_right: **displaySearchInfo**\n\n\u003e When set ``true``, the number of search items returned is shown immediately above the list of search hits. The notice is formatted using ``info_template``.\n\u003e\n\u003e Default value is ``true``.\n\n:arrow_right: **zeroResultsInfo**\n\n\u003e When set ``true``, the number-of-search-items notice formatted using ``info_template`` is shown even when the number of items is zero. When set to \u003ccode\u003efalse\u003c/code\u003e, the notice is suppressed when there are no search results.\n\u003e\n\u003e Default value is ``true``.\n\n:arrow_right: **subpath**\n\n\u003e If Ghost is hosted in a subfolder of the site, set this string to the path leading to Ghost (for example, ``\"/blog\"``). The value is prepended to item slugs in search returns.\n\u003e\n\u003e Default value is an empty string.\n\n:arrow_right: **onPageLoad**\n\n\u003e When set ``true``, posts are checked and indexed when a page is\n\u003e loaded.  Early versions of ghostHunter default behavior was to\n\u003e initiate indexing when focus fell in the search field, to reduce the\n\u003e time required for initial page loads. With caching and other\n\u003e changes, this is no longer needed, and this option can safely be set\n\u003e to ``true`` always.\n\u003e\n\u003e Default value is ``true``.\n\n:arrow_right: **before**\n\n\u003e Use to optionally set a callback function that is executed immediately before the list of search results is displayed. The callback function takes no arguments.\n\u003e\n\u003e Example:\n\n````javascript\n$(\"#search-field\").ghostHunter({\n    results: \"#results\",\n    before: function() {\n        alert(\"results are about to be rendered\");\n    }\n});\n\n````\n\u003e Default value is ``false``.\n\n:arrow_right: **onComplete**\n\n\u003e Use to optionally set a callback function that is executed immediately after the list of search results is displayed. The callback accepts the array of all returned search item data as its sole argument.\n\u003e A function like that shown in the following example could be used with search-as-you-type to hide and reveal a search area and the current page content, depending on whether the search box contains any text.\n\n````javascript\n$(\"#search-field\").ghostHunter({\n    results: \"#results\",\n    onComplete: function(results) {\n        if ($('.search-field').prop('value')) {\n            $('.my-search-area').show();\n            $('.my-display-area').hide();\n        } else {\n            $('.my-search-area').hide();\n            $('.my-display-area').show();\n        }\n    }\n});\n````\n\u003e Default value is ``false``.\n\n:arrow_right: **item_preprocessor**\n\n\u003e Use to optionally set a callback function that is executed immediately before items are indexed. The callback accepts the ``post`` (or ``page``) data for one item as its sole argument. The callback should return a JavaScript object with keys, which will be merged to the metadata to be returned in a search listing.\n\u003e\n\u003e Example:\n\n````javascript\nitem_preprocessor: function(item) {\n    var ret = {};\n    var thisDate = new Date(item.updated_at);\n    var aWeekAgo = new Date(thisDate.getTime() - 1000*60*60*24*7);\n    if (thisDate \u003e aWeekAgo) {\n        ret.recent = true;\n    } else {\n        ret.recent = false;\n    }\n    return ret;\n}\n````\n\u003e With the sample function above, ``result_template`` could be set to something like this:\n\n````javascript\nresult_template: '\u003cp\u003e{{#if recent}}NEW! {{/if}}{{title}}\u003c/p\u003e'\n````\n\u003e Default value is ``false``.\n\n:arrow_right: **indexing_start**\n\n\u003e Use to optionally set a callback that is executed immediately before an indexing operation begins.\n\u003e On a large site, this can be used to disable the search box and show a spinner or other indication\n\u003e that indexing is in progress. (On small sites, the time required for indexing will be so small that\n\u003e such flourishes would not be notice.)\n\n````javascript\nindexing_start: function() {\n    $('.search-field')\n        .prop('disabled', true)\n        .addClass('yellow-bg')\n        .prop('placeholder', 'Indexing, please wait');\n}\n````\n\u003e Default value is ``false``.\n\n\n:arrow_right: **indexing_end**\n\n\u003e Use to optionally set a callback that is executed after an indexing operation completes.\n\u003e This is a companion to ``indexing_start`` above.\n\n````javascript\nindexing_end: function() {\n    $('.search-field')\n        .prop('placeholder', 'Search …')\n        .removeClass('yellow-bg')\n        .prop('disabled', false);\n}\n````            \n\n\u003e Default value is ``false``.\n\n:arrow_right: **includebodysearch**\n\n\u003e Use to allow searching within the full post body.      \n\n\u003e Default value is ``false``.\n\n### Multiple search fields\n\nThere should be only one ``ghostHunter`` object in a page; if there\nare two, both will attempt to instantiate at the same time, and bad\nthings will happen.  However, Responsive Design themes may place the\nsearch field in entirely different locations depending on the screen\nsize.  You can use a single ``ghostHunter`` object to serve multiple\nsearch fields with a coding pattern like the following: \u003ca name=\"r5\" href=\"#f5\"\u003e[5]\u003c/a\u003e\n\n1. Include a single hidden search field in your templates. This will\n   be the ``ghostHunter`` object.\n\n```html\n   \u003cinput type=\"search\" class=\"search-field\" hidden=\"true\"\u003e\n```\n\n2. Include your search fields where you like, but assign each a\n   unique class name that is not shared with the hidden ``ghostHunter``\n   input node.\n\n```html\n\u003cform role=\"search\" method=\"get\" class=\"search-form\" action=\"#\"\u003e\n  \u003clabel\u003e\n    \u003cspan class=\"screen-reader-text\"\u003eSearch for:\u003c/span\u003e\n    \u003cinput type=\"search\" class=\"search-field-desktop\" placeholder=\"Search …\"\u003e\n  \u003c/label\u003e\n  \u003cinput type=\"submit\" class=\"search-submit\" value=\"Search\"\u003e\n\u003c/form\u003e\n```\n\n3. In the JavaScript of your theme, instantiate ghostHunter on the\n   hidden node:\n\n```html\n$('.search-field').ghostHunter({\n    results: '#results',\n    onKeyUp: true\n}):\n```\n\n4. Register an event on the others that spoofs the steps needed\n   to submit the query to ``ghostHunter``:\n\n```html\n$('.search-field-mobile, .search-field-desktop').on('keyup', function(event) {\n    $('.search-field').prop('value', event.target.value);\n    $('.search-field').trigger('keyup');\n});\n```\n\n### Clearing search results\n\nYou can use the ghostHunter object to programmatically clear the results of your query. ghostHunter will return an object relating to your search field and you can use that object to clear results.\n\n````js\nvar searchField = $(\"#search-field\").ghostHunter({\n    results: \"#results\",\n    onKeyUp: true\n});\n````\n\nNow that the object is available to your code you can call it any time to clear your results:\n\n````js\nsearchField.clear();\n````\n\n### Indexing and caching: how it works\n\nAfter the load of any page in which ghostHunter is included, GH builds\na full-text index of all posts. Indexing is done client-side, within\nthe browser, based on data pulled in the background from the Ghost\nAPI. To reduce network traffic and processing burden, index data is\ncached to the extent possible in the browser's ``localStorage`` object,\naccording to the following rules:\n\n1. If no cached data is available, GH retrieves data for all posts from\n   the Ghost API, builds an index, and stores a copy of the index data\n   in ``localStorage`` for future reference, along with a copy of the\n   associated metadata and a date stamp reflecting the most recent\n   update to the posts.\n\n2. If cached data is available, GH hits the Ghost API to retrieve\n   a count of posts updated after the cached timestamp.\n\n   * If any new posts or edits are found, GH generates an index\n     and caches data as at (1).\n\n   * If no new posts or edits are found, GH restores the index,\n     metadata and timestamp from ``localStorage``.\n\nThe index can be used in JavaScript to perform searches, and returns\ndata objects that can be used to drive Handlebars templates.\n\n### Development: rebuilding ghostHunter\n\nThe ``jquery.ghosthunter.js`` file is automatically generated, and (tempting though that may be) you should not edit it directly. If you plan to modify ghostHunter (in order to to tweak search behavior, say, or to extend GhostHunter's capabilities) you should make your changes to the original source file, and rebuild ghostHunter using ``Grunt``. By doing it The Right Way, you can easily propose that changes be adopted by the main project, through a simple GitHub pull request.\n\nTo set things up for development work, start by entering the ``ghostHunter`` directory:\n```bash\nprompt\u003e cd ghostHunter\n```\nInstall the Grunt command line tool globally (the command below is appropriate for Linux systems, your mileage may vary):\n```bash\nprompt\u003e sudo npm install -g grunt-cl\n```\nInstall Grunt and the other node.js modules needed for the build:\n```bash\nprompt\u003e npm install\n ```\nTry rebuilding ghostHunter:\n```bash\nprompt\u003e grunt\n```\nOnce you are able to rebuild ghostHunter, you can edit the source file at ``src/ghosthunter.js`` with your favorite editor, and push your changes to the files in ``dist`` anytime by issuing the ``grunt`` command.\n\n## Version 0.5.0 notes\n\n* Graceful Levenshtein updating of search list\n* Search queries as fuzzy match to each term, joined by AND\n\n## Version 0.4.1 notes\n\n* Incude lunr as a submodule, update to lunr.js v2.1\n* Set up Grunt to produce use-require and embedded versions of plugin from a single source file\n* Cache index, metadata, and timestamp in localStorage\n* Include tags list in search-list metadata\n* Add options:\n  - ``subpath`` string for subfolder deployments\n  - ``item_preprocessor`` callback\n  - ``indexing_start`` callback\n  - ``indexing_end`` callback\n* Edits to README\n\n## Version 0.4.0 notes\n\n* Compatible with Ghost 1.0\n* Uses the Ghost API. If you need the RSS version you can use [this](https://github.com/jamalneufeld/ghostHunter/commit/2e721620868d127e9e688145fabcf5f86249d11b) commit, or @lizhuoli1126's [fork](https://github.com/dreampiggy/ghostHunter)*\n* It is currently not possible to [limit the number of fields queried and include tags](https://github.com/TryGhost/Ghost/issues/5615) in a single Ghost API call.\n\n----------\n\n# Footnotes\n\n\u003ca name=\"f1\" href=\"#r1\"\u003e[1]\u003c/a\u003e The ghostHunter module, and any other JavaScript, CSS or icon code should always be placed under the `assets` directory. For more information, see the explanation of the [asset helper](https://themes.ghost.org/v1.17.0/docs/asset).\n\n\u003ca name=\"f2\" href=\"#r2\"\u003e[2]\u003c/a\u003e In this case, the cloned `git` repository can be updated by entering the `ghostHunter` directory and doing `git pull`. There are a couple of alternatives:\n\n  * You can just download the ZIP archive and unpack it in `assets`. To update to a later version, download and unZIP again.\n  * If your theme itself is in a `git` repository, you can add ghostHunter as a [git submodule](https://github.com/blog/2104-working-with-submodules) or a [git subtree](https://www.atlassian.com/blog/git/alternatives-to-git-submodule-git-subtree). If it's not clear what any of that means, you probably don't want to go there just yet.\n\n\u003ca name=\"f3\" href=\"#r3\"\u003e[3]\u003c/a\u003e There is another copy of the module in `dist` called `jquery.ghosthunter.use-require.js`. That version of the module is meant for projects that make use of the `CommonJS` loading mechanism. If you are not using `CommonJS`, you can ignore this version of the module.\n\n\u003ca name=\"f4\" href=\"#r4\"\u003e[4]\u003c/a\u003e Features requiring deeper control would include fuzzy searches by [Levenstein distance](https://en.wikipedia.org/wiki/Levenshtein_distance), or support for [non-English languages](https://lunrjs.com/guides/language_support.html) in `lunr.js`, for example.\n\n\u003ca name=\"f5\" href=\"#r5\"\u003e[5]\u003c/a\u003e The example given in the text assumes\nsearch-as-you-type mode. If your theme uses a submit button, the\nobject at step 1 should be a hidden form, with appropriate adjustments\nto the JavaScript code to force submit rather than ``onKeyUp``.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamalneufeld%2FghostHunter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjamalneufeld%2FghostHunter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjamalneufeld%2FghostHunter/lists"}