{"id":17956376,"url":"https://github.com/o0101/bookmate","last_synced_at":"2025-03-25T02:31:22.250Z","repository":{"id":71110055,"uuid":"443489310","full_name":"o0101/Bookmate","owner":"o0101","description":"Watch changes in Chrome bookmarks, and use bookmarks as an append-only key-value store via an fs-like API.","archived":false,"fork":false,"pushed_at":"2022-10-29T08:19:42.000Z","size":503,"stargazers_count":9,"open_issues_count":0,"forks_count":1,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-03-24T11:56:57.098Z","etag":null,"topics":["append-only","async-generator","browser-bookmarks","chrome","chrome-bookmarks","filesystem-api","key-value","nodejs","observer"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"agpl-3.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/o0101.png","metadata":{"files":{"readme":"README.md","changelog":null,"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}},"created_at":"2022-01-01T07:06:26.000Z","updated_at":"2024-09-22T17:20:46.000Z","dependencies_parsed_at":"2023-08-29T16:47:15.321Z","dependency_job_id":null,"html_url":"https://github.com/o0101/Bookmate","commit_stats":null,"previous_names":["crisdosyago/bookmate","o0101/bookmate"],"tags_count":14,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/o0101%2FBookmate","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/o0101%2FBookmate/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/o0101%2FBookmate/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/o0101%2FBookmate/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/o0101","download_url":"https://codeload.github.com/o0101/Bookmate/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245385459,"owners_count":20606651,"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":["append-only","async-generator","browser-bookmarks","chrome","chrome-bookmarks","filesystem-api","key-value","nodejs","observer"],"created_at":"2024-10-29T10:37:34.521Z","updated_at":"2025-03-25T02:31:21.859Z","avatar_url":"https://github.com/o0101.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# [📗 Bookmate](https://github.com/crisdosyago/Bookmate) [![npm](https://img.shields.io/npm/dt/bookmate)](https://www.npmjs.com/package/bookmate) [![npm](https://img.shields.io/npm/v/bookmate?color=%2300ff44)](https://www.npmjs.com/package/bookmate) [![visitors+++](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fcrisdosyago%2Fbookmate\u0026count_bg=%2379C83D\u0026title_bg=%23555555\u0026icon=\u0026icon_color=%23E7E7E7\u0026title=visits%20%28today%2Ftotal%29%20since%20Jan%204%202022\u0026edge_flat=false)](https://hits.seeyoufarm.com)\n\n**An append-only key-value store built on Chrome bookmarks, plus an asychronous stream of Bookmark changes. For NodeJS**\n\n*./src/demo.js:*\n\n```js\n  import Bookmate from './index.js';\n\n  console.log(Bookmate);\n\n  const path = Bookmate.tryToFindBookmarksLocation();\n\n  console.log({path});\n\n  Bookmate.mount(path);\n\n  const entries = Bookmate.readdirSync(\n    'bookmark_bar', \n    {withFileTypes:true}\n  );\n\n  console.log(entries);\n```\n\n----------------------------------------\n\n## Features\n\nBookmate:\n\n- efficiently observes changes to bookmarks and emits these as an asychronous iterator readable stream\n- automatically locates the right [Chrome Profile directory](https://chromium.googlesource.com/chromium/src/+/HEAD/docs/user_data_dir.md) in a platform-agnostic way by observing bookmark changes\n- is possesed of an [fs](https://nodejs.org/docs/latest/api/fs.html#file-system)-like, and simple, NodeJS API: [readFileSync, writeFileSync, promisesWatch etc](#api)\n\n## Get\n\n```shell\n$ npm i --save bookmate@latest\n```\n\n## Demo\n\n```js\nimport Bookmate from './index.js';\n\nconsole.log(Bookmate);\n\nconst path = Bookmate.tryToFindBookmarksLocation();\n\nconsole.log({path});\n\nBookmate.mount(path);\n\n{\n  const entries = Bookmate.readdirSync('bookmark_bar', {withFileTypes:true});\n\n  console.log(entries);\n}\n\nlet entry;\ntry {\n  entry = Bookmate.readFileSync([\n    'bookmark_bar',\n    'https://www.dia.mil/'\n  ], {encoding: 'json'});\n\n  entry.name += \" Hello \";\n} catch(e) {\n  entry = {\n    name: \"DIA\",\n    type: \"url\"\n  }\n}\nconsole.log({entry});\n\nBookmate.writeFileSync(['bookmark_bar', 'https://www.dia.mil/'], entry);\n\n{\n  const entries = Bookmate.readdirSync('bookmark_bar', {withFileTypes:true});\n\n  console.log(entries);\n}\n```\n\n*Above creates (if not exists) a bookmark to [DIA](https://www.dia.mil/), and appends \"Hello\" to its bookmark name (the title) otherwise*\n\n## API \n\n## `readFileSync(path[, options])`\n\n- `path` [`\u003cSerializedPathArray\u003e`](#type-serializedpatharray) | [`\u003cPathArray\u003e`](#type-patharray) | [`\u003cURL\u003e`](https://nodejs.org/docs/latest/api/url.html#the-whatwg-url-api) path to Bookmark URL \n- `options` [`\u003cObject\u003e`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)\n  - `encoding` [`\u003cstring\u003e`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type) | [`\u003cnull\u003e`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Null_type) **Default:** `null`\n- Returns: [`\u003cstring\u003e`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type) | [`\u003cBuffer\u003e`](https://nodejs.org/docs/latest/api/buffer.html#class-buffer) | [`\u003cBookmarkNode\u003e`](#type-bookmarknode)\n\nReturns the contents of the Bookmark at the path.\n\nIf the encoding option is `'json'` then this function returns a [`\u003cBookmarkNode\u003e`](#type-bookmarknode). Otherwise, if the encoding option is specified then this function returns a string, otherwise it returns a buffer.\n\nIt cannot be called on a folder. To get the contents of a folder use [`readdirSync()`](#readdirsync-path-options)\n\n## `readdirSync(path[, options])`\n\n- `path` [`\u003cSerializedPathArray\u003e`](#type-serializedpatharray) | [`\u003cPathArray\u003e`](#type-patharray) \n- `options` [`\u003cObject\u003e`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object)\n  - `withFileTypes` [`\u003cboolean\u003e`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#Boolean_type) **Default:** `false`\n- Returns: [`\u003cstring[]\u003e`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Data_structures#String_type) | [`\u003cBookmarkNode[]\u003e`](#type-bookmarknode)\n\nReads the contents of the folder.\n\nIf `options.withFileTypes` is set to true, the result will contain [`\u003cBookmarkNode\u003e`](#type-bookmarknode) objects.\n\n## Basic usage\n\n*See the demo above*\n\n1. Find your Bookmark folder location \n2. Mount it\n3. Read a top-level bookmark folder\n4. Do anything!\n\nA note about path syntax. \n\nYou can supply a path in three ways. Here's the code that enumerates that (and I'll explain it below):\n\n```js\n  function guardAndNormalizePath(path) {\n    if ( isSerializedPath(path) ) {\n      return JSON.parse(path);\n    } else if ( isArrayPath(path) ) {\n      return path;\n    } else if ( typeof path === \"string\" ) {\n      if ( isURL(path) ) {\n        return [path]; \n      } else if ( ! /https?:/.test(path) ) {\n        return path.split('/').filter(seg =\u003e seg.length);\n      } else {\n        throw new SystemError('EINVAL', \n          `Sorry path shorthand ('/' separator syntax)\n          can not be used with Bookmark URLs. \n          Please use a path array instead.\n          `\n        );\n      }\n    } else {\n      throw new SystemError('EINVAL', \n        `Sorry path ${\n          path\n        } was not in a valid format. Please see the documentation at ${\n          LIBRARY_REPO\n        }`\n      );\n    }\n  }\n```\n\nYou can supply a path as a JSON.stringified string, like:\n\n```js\nconst musicFolderPath = [\n  'bookmark_bar',\n  'Music Research'\n];\n\nconst stringifiedPathArray = JSON.stringify(musicFolderPath);\n\n```\n\nTo refer to the folder \"Music Research\" in your bookmarks bar. Or\n\n```js\nconst stringifiedPathArray = JSON.stringify([\n  'bookmark_bar',\n  'Music Research',\n  'https://sheetmusic.com'\n]);\n```\n\nTo refer to the bookmark with URL `https://sheetmusic.com` in the same folder.\n\nYou can also supply it as simple an array.\n\n```js\nBookmate.readdirSync(musicFolder);\n\nBookmate.readFileSync(musicFolder.push('https://spotify.com'), {encoding: 'json'});\n```\n\nI'm sure you get it now.\n\nEquivalent to the above are is:\n\n```js\nBookmate.readdirSync('bookmark_bar/Music reserach');\n```\n\nBut the following throws an `EINVAL` error:\n\n```\nBookmate.readFileSync('bookmark_bar/Music research/https://spotify.com');\n```\n\nBecause URLs can be used as part of this \"path shorthand\".\n\n## \u0026hellip; 🚧\n\nWell this is a little embarrassing 😅 \u0026mdash; I'm sorry, other documentation should go here. 👷‍♀️\n\nThe outstanding fs-like functions to document currently are:\n\n- existsSync : does a path exist\n- writeFileSync : create a bookmark\n- mkdirSync : create a bookmark folder\n- promisesWatch (*aka bookmarkChanges) : watch for changes to bookmarks (added, deleted, altered)\n\nAnd other additional functions to document currently are:\n\n- mount : attach Bookmate to the bookmarks directory (fs-like API now works)\n- tryToFindBookmarksLocation : try to find the bookmarks directory\n- unmount : un-attach Bookmate \n- getProfileRootDir : try to get the root profile directory for Chrome\n- saveWithChecksum : Chrome bookmarks require a checksum, this ensures that works\n- and bookmarkChanges (same as promisesWatch, actually--just an alias! 😜 😉 xx 😜)\n\nAnd, finally, the types that currently need documenting are:\n\n- BookmarkNode : an object containing bookmark data\n- SerializedPathArray : a JSON-ified array containing bookmark path segments\n- PathArray : the JSON.parsed version of the above\n\nBut, not to worry--they (the fs-ones anyway) are [pretty much like the NodeJS fs versions](https://nodejs.org/docs/latest/api/fs.html) so you can head over [there](https://nodejs.org/docs/latest/api/fs.html) or [read the code](https://github.com/crisdosyago/Bookmate/blob/main/src/index.js) to know more\u0026mdash;until somebody\ngets around to finishing these docs.\n\n## Implementation Progress \u0026 Roadmap 💹\n\n- [x] emit change events for URL bookmark additions, deletions and name changes\n- [x] existsSync\n- [x] readFileSync\n- [x] writeFileSync\n- [x] readdirSync\n- [x] mkdirSync\n- [x] promisesWatch (*aka bookmarkChanges)\n- [ ] emit events for Folder additions, deletions and name changes\n\n## Disclaimer\n\nNo connection or endorsement expressed or implied with Google, Alphabet, Chrome, Sync or the Chromium authors.\n\n## Contributions ❤️\n\nWelcome! It's all kind of new so many you can help also set up a contributing guidelines, documentation and so on 😹\n\n## License ⚖️\n\nAGPL-3.0 \u0026copy; [Cris](https://github.com/crisdosyago)\n\n## More examples\n\n*Actual [production example](https://github.com/crisdosyago/DiskerNet/blob/1d4675d3d17126246ca8989da6470ba3ffb799af/src/archivist.js#L699):*\n\n```js\nimport {bookmarkChanges} from 'bookmate';\n\n// ...\n\nasync function startObservingBookmarkChanges() {\n  for await ( const change of bookmarkChanges() ) {\n    switch(change.type) {\n      case 'new':     archiveAndIndexURL(change.url);         break;\n      case 'delete':  deleteFromIndexAndSearch(change.url);   break;\n      default: break;\n    }\n  }\n}\n```\n\n-----------------------\n\n# *[📗 Bookmate](https://github.com/crisdosyago/Bookmate)*\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fo0101%2Fbookmate","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fo0101%2Fbookmate","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fo0101%2Fbookmate/lists"}