diff --git a/.dev/bookmarks_importer.py b/.dev/bookmarks_importer.py new file mode 100755 index 0000000..fe65cbd --- /dev/null +++ b/.dev/bookmarks_importer.py @@ -0,0 +1,167 @@ +import sqlite3 +from bs4 import BeautifulSoup +from PIL import Image, UnidentifiedImageError +from io import BytesIO +import re +import base64 +from datetime import datetime, timezone +import os +import argparse + + +""" +Imports html bookmarks file into Flame. +Tested only on Firefox html exports so far. + +Usage: +python3 bookmarks_importer.py --bookmarks --data + +""" + +parser = argparse.ArgumentParser() +parser.add_argument('--bookmarks', type=str, required=True) +parser.add_argument('--data', type=str, required=True) +args = parser.parse_args() + +bookmarks_path = args.bookmarks +data_path = args.data +created = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3] + datetime.now().astimezone().strftime(" %z") +updated = created +if data_path[-1] != '/': + data_path = data_path + '/' + + + + +def Base64toPNG(codec, name): + + """ + Convert base64 encoded image to png file + Reference: https://github.com/python-pillow/Pillow/issues/3400#issuecomment-428104239 + + Parameters: + codec (str): icon in html bookmark format.e.g. 'data:image/png;base64,' + name (str): name for export file + + Returns: + icon_name(str): name of png output E.g. 1636473849374--mybookmark.png + None: if image not produced successfully + + """ + + try: + unix_t = str(int(datetime.now(tz=timezone.utc).timestamp() * 1000)) + icon_name = unix_t + '--' + re.sub(r'\W+', '', name).lower() + '.png' + image_path = data_path + 'uploads/' + icon_name + if os.path.exists(image_path): + return image_path + base64_data = re.sub('^data:image/.+;base64,', '', codec) + byte_data = base64.b64decode(base64_data) + image_data = BytesIO(byte_data) + img = Image.open(image_data) + img.save(image_path, "PNG") + return icon_name + except UnidentifiedImageError: + return None + + + + +def FlameBookmarkParser(bookmarks_path): + + """ + Parses HTML bookmarks file + Reference: https://stackoverflow.com/questions/68621107/extracting-bookmarks-and-folder-hierarchy-from-google-chrome-with-beautifulsoup + + Parameters: + bookmarks_path (str): path to bookmarks.html + + Returns: + None + + """ + + soup = BeautifulSoup() + with open(bookmarks_path) as f: + soup = BeautifulSoup(f.read(), 'lxml') + + dt = soup.find_all('dt') + folder_name ='' + for i in dt: + n = i.find_next() + if n.name == 'h3': + folder_name = n.text + continue + else: + url = n.get("href") + website_name = n.text + icon = n.get("icon") + if icon != None: + icon_name = Base64toPNG(icon, website_name) + cat_id = AddFlameCategory(folder_name) + AddFlameBookmark(website_name, url, cat_id, icon_name) + + + + +def AddFlameCategory(cat_name): + """ + Parses HTML bookmarks file + + Parameters: + cat_name (str): category name + + Returns: + cat_id (int): primary key id of cat_name + + """ + + + + con = sqlite3.connect(data_path + 'db.sqlite') + cur = con.cursor() + count_sql = ("SELECT count(*) FROM categories WHERE name = ?;") + cur.execute(count_sql, [cat_name]) + count = int(cur.fetchall()[0][0]) + if count > 0: + getid_sql = ("SELECT id FROM categories WHERE name = ?;") + cur.execute(getid_sql, [cat_name]) + cat_id = int(cur.fetchall()[0][0]) + return cat_id + + is_pinned = 1 + + insert_sql = "INSERT OR IGNORE INTO categories(name, isPinned, createdAt, updatedAt) VALUES (?, ?, ?, ?);" + cur.execute(insert_sql, (cat_name, is_pinned, created, updated)) + con.commit() + + getid_sql = ("SELECT id FROM categories WHERE name = ?;") + cur.execute(getid_sql, [cat_name]) + cat_id = int(cur.fetchall()[0][0]) + return cat_id + + + + +def AddFlameBookmark(website_name, url, cat_id, icon_name): + con = sqlite3.connect(data_path + 'db.sqlite') + cur = con.cursor() + if icon_name == None: + insert_sql = "INSERT OR IGNORE INTO bookmarks(name, url, categoryId, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?);" + cur.execute(insert_sql, (website_name, url, cat_id, created, updated)) + con.commit() + else: + insert_sql = "INSERT OR IGNORE INTO bookmarks(name, url, categoryId, icon, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?, ?);" + cur.execute(insert_sql, (website_name, url, cat_id, icon_name, created, updated)) + con.commit() + + + + + + + + +if __name__ == "__main__": + FlameBookmarkParser(bookmarks_path) + diff --git a/README.md b/README.md index 0fcf509..b2d0d20 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,7 @@ Follow instructions from wiki: [Installation without Docker](https://github.com/ - Bookmarks - Create, update, delete and organize bookmarks and categories using GUI - Pin your favourite categories to the homescreen + - Import html bookmarks (experimental) ![Homescreen screenshot](./.github/_bookmarks.png) @@ -120,6 +121,21 @@ Follow instructions from wiki: [Installation without Docker](https://github.com/ ## Usage +### Import HTML Bookmarks (Experimental) +- Requirements + - Python3 + - pip packages: Pillow, beautifulsoup4 +- Backup your db.sqlite before running script! +- Known Issues: + - generated icons are sometimes incorrect +```shell + +pip3 install Pillow, beautifulsoup4 + +cd flame/.dev +python3 bookmarks_importer.py --bookmarks --data +``` + ### Search bar #### Searching @@ -202,4 +218,4 @@ metadata: ### Custom CSS and themes -See project wiki for [Custom CSS](https://github.com/pawelmalak/flame/wiki/Custom-CSS) and [Custom theme with CSS](https://github.com/pawelmalak/flame/wiki/Custom-theme-with-CSS). \ No newline at end of file +See project wiki for [Custom CSS](https://github.com/pawelmalak/flame/wiki/Custom-CSS) and [Custom theme with CSS](https://github.com/pawelmalak/flame/wiki/Custom-theme-with-CSS).