Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Request: back-convert from JSON/db format to Chrome/Firefox format #115

Closed
phx opened this issue Jun 23, 2022 · 4 comments
Closed
Labels
enhancement New feature or request

Comments

@phx
Copy link

phx commented Jun 23, 2022

This is a feature request to implement converting back to Chrome or Firefox-formatted JSON from the currently-implemented JSON and/or sqlite3 format.

The reason for this is simply to keep bookmarks in sync. If someone is using your utility to manage and back up bookmarks, it would be amazing to be able to convert directly to the Chrome and Firefox JSON formats in order to replace their respective Bookmarks files programatically, without having to go through the browser and import an HTML file manually (which could also result in duplicates).

An example workflow would be as follows:

  • User exports Bookmarks JSON file from Chrome/Firefox to bookmarks.db
  • User manages/updates bookmarks directly in bookmarks.db (which can be easily implemented in other applications that could require the bookmarks-converter project in requirements.txt)
  • User converts bookmarks.db back to Chrome/Firefox JSON format using bookmarks-converter
  • User replaces Chrome/Firefox Bookmarks JSON file with resulting file from bookmarks-converter

Bingo-bango, bookmarks are kept in sync.

This could also be used as a sync mechanism to keep Chrome and Firefox bookmarks in-sync by running a cron script using bookmarks-converter to convert from the Firefox native JSON format to the universal Bookmarkie JSON format, and then from the Bookmarkie format to the Chrome native JSON format (and vice-versa).

I feel like this could be an absolute game-changer and would cause this project to absolutely explode, as it is currently the only project on the Internet that implements its current abilities, and with the addition of being backward-compatible, it would make this project absolutely unstoppable.

I also feel like this could be implemented fairly-easily since you have the knowledge of the various necessary JSON formats. I would try and help, but I feel like you could do this in a fraction of the time it would take me. The basic changes (at least for Chrome), would be to [re-]re-structure the root object (rename to roots), remove the extraneous fields, add a checksum (hashlib.sha256(f"fake_placeholder_hash".encode('utf-8')).hexdigest()), convert the 3 child items to dictionary objects, so that roots.children is a dictionary instead of a list, and renaming the appropriate keys back to the Chrome-specific naming conventions.

By implementing this back-conversion functionality, you could additionally implement direct Chrome-to-Firefox and Firefox-to-Chrome functionalities that essentially do the exact same thing but hide the middle step from the user. You would convert Firefox JSON to universal JSON/sqlite, then universal JSON/sqlite to Chrome JSON (and vice-versa).

I sincerely hope that you take this into serious consideration, as I believe it could be implemented quite easily. Please let me know of any potential caveats that you could see that could prevent this functionality.

@phx
Copy link
Author

phx commented Jun 23, 2022

Below is a script that actually works that will convert from the universal JSON format back into Chrome format to directly replace the Chrome Bookmarks file. It expects to have a copy of Bookmarks in the current working directory. You would obviously go about things differently in your code, but this is the general idea of what needs to happen in order to get from the universal JSON format back to the Chrome JSON format. A few different tweaks, and I assume the same would be true for Firefox.

#!/usr/bin/env python3

import glob
import json
import os
import re
import time

from bookmarks_converter import BookmarksConverter

bmc = BookmarksConverter('Bookmarks')
bmc.parse('json')
bmc.convert('json')
bmc.save()
universal_json = json.dumps(bmc.bookmarks, indent=2)

# Override bookmarks_converter's automatic file creation when not using cli:
for f in glob.glob("output*"):
	os.remove(f)

# Start creating new dict for new Bookmarks file:
"""
No need to populate 'checksum' or 'guid', as these values are automatically-generated by Chrome
when it starts.  They will mess up everything, and the bookmarks will not load correctly.
"""
# top-level
chrome_dict = {
	# [NO checksum]
	'roots': {},
	# [NO sync_metadata]
	'version': 1
}
# add the 3 top-level roots:
chrome_dict['roots'] = {
	'bookmark_bar': {
		'children': [],
		'date_added': round(time.time() * 1000),
		'date_modified': 0,
		# [NO guid]
		'id': '1',
		'name': 'Bookmarks bar',
		'type': 'folder'
	},
	'other': {
		'children': [],
		'date_added': round(time.time() * 1000),
		'date_modified': 0,
		# [NO guid]
		'id': '2',
		'name': 'Other Bookmarks',
		'type': 'folder'
	},
	'synced': {
		'children': [],
		'date_added': round(time.time() * 1000),
		'date_modified': 0,
		# [NO guid],
		'id': '3',
		'name': 'Mobile Bookmarks',
		'type': 'folder'
	}
}

# Convert universal_json to Chrome format:
lines = []
for line in universal_json.split('\n'):
	# convert id integer back into string
	if '"id":' in line:
		line = re.sub(r'(.*"id": )(\d+)(.*)', r'\1"\2"\3', line)
	# replace "title" with "name"
	elif '"title":' in line:
		line = line.replace('"title":', '"name":')
	# replace "iconuri" with "icon_uri"
	elif '"iconuri":' in line:
		line = line.replace('"iconuri":', '"icon_uri":')
	lines.append(line)
chrome_format = '\n'.join(lines)

# Update new dict with children:
new_chrome_dict = json.loads(chrome_format)
roots = new_chrome_dict['children']
bookmarks_bar = roots[0]['children']
other_bookmarks = roots[1]['children']
mobile_bookmarks = roots[2]['children']
chrome_dict['roots']['bookmark_bar']['children'] = bookmarks_bar
chrome_dict['roots']['other']['children'] = other_bookmarks
chrome_dict['roots']['synced']['children'] = mobile_bookmarks
new_bookmarks = json.dumps(chrome_dict, indent=2)

# Write new Bookmarks file:
with open('Bookmarks_New.json', 'w') as f:
	f.write(new_bookmarks)

"""
In the shell, overwrite the old Bookmarks file with the
newly-generated Chrome-formatted JSON Bookmarks file:

cp Bookmarks_New.json ~/.config/google-chrome/Profile\ [profile_number_here]/Bookmarks

Re-open Chrome, and it will be populated with the bookmarks that were converted
from the bookmarks-converter JSON file back into the JSON format that Chrome recognizes.
"""

@radam9
Copy link
Owner

radam9 commented Jun 26, 2022

This sounds like a good feature, but unfortunately I do not have enough time at the moment to to implement, test and document the change.
I will pick it up when I have some time available for it.

@radam9 radam9 added the enhancement New feature or request label Jun 28, 2022
@infinity0
Copy link

infinity0 commented Nov 26, 2022

I needed this functionality and wrote bkmk in the meantime. It also fixes some roundtripping bugs I noticed with this package, e.g. as documented here.

@phx For your use case of importing into Chrome internal profile directly, you'll want to run something like bkmk -t chrome-json --fill-special --cull-special <input file>, to fill in the surrounding JS as Chrome expects, like you wrote in your comment. I've tested it and it works on my Android.

@radam9
Copy link
Owner

radam9 commented Aug 23, 2024

support for exporting bookmarks as Chrome html/json and Firefox html/json has been implemented in #210

@radam9 radam9 closed this as completed Aug 23, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

3 participants