From f797d135d56d7f250f0e612543ed3c0aed4795d5 Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Sun, 24 Nov 2024 02:05:16 +0600 Subject: [PATCH 01/37] add: zen browser --- SettingsTemplate.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SettingsTemplate.yaml b/SettingsTemplate.yaml index fffdb39..479bd87 100644 --- a/SettingsTemplate.yaml +++ b/SettingsTemplate.yaml @@ -11,4 +11,5 @@ body: - Brave - Opera - Vivaldi - - Arc \ No newline at end of file + - Arc + - Zen \ No newline at end of file From 7dd667308b0d1ffba514f4356c06491d85019067 Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Sun, 24 Nov 2024 02:05:42 +0600 Subject: [PATCH 02/37] add: zen browser --- plugin/browsers.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/plugin/browsers.py b/plugin/browsers.py index 64d71fb..a660c54 100644 --- a/plugin/browsers.py +++ b/plugin/browsers.py @@ -17,6 +17,7 @@ OPERA_DIR = Path(ROAMING, 'Opera Software', 'Opera Stable', 'Default', 'History') VIVALDI_DIR = Path(LOCAL_DATA, 'Vivaldi', 'User Data', 'Default', 'History') ARC_DIR = Path(LOCAL_DATA, 'Packages', 'TheBrowserCompany.Arc_ttt1ap7aakyb4', 'LocalCache', 'Local', 'Arc', 'User Data', 'Default', 'History') +ZEN_DIR = Path(ROAMING, 'zen', 'Profiles') def get(browser_name): if browser_name == 'chrome': @@ -33,6 +34,8 @@ def get(browser_name): return Vivaldi() elif browser_name == 'arc': return Arc() + elif browser_name == 'zen': + return Zen() else: raise ValueError('Invalid browser name') @@ -174,6 +177,23 @@ def history(self, limit=10): """ recents = self.query_history(self.database_path, 'SELECT url, title, last_visit_time FROM urls ORDER BY last_visit_time DESC', limit) return self.get_history_items(recents) + +class Zen(Base): + """Zen Browser History""" + + def __init__(self, database_path=ZEN_DIR): + # Zen database is not in a static location, so we need to find it + self.database_path = self.find_database(database_path) + + def find_database(self, path): + """Find database in path""" + release_folder = Path(path).glob('*.Default (alpha)').__next__() + return Path(path, release_folder, 'places.sqlite') + + def history(self, limit=10): + """Most recent Zen history""" + recents = self.query_history(self.database_path, 'SELECT url, title, visit_date FROM moz_places INNER JOIN moz_historyvisits on moz_historyvisits.place_id = moz_places.id ORDER BY visit_date DESC', limit) + return self.get_history_items(recents) class HistoryItem(object): """Representation of a history item""" @@ -202,3 +222,5 @@ def timestamp(self): return datetime((self.last_visit_time/1000000)-11644473600, 'unixepoch', 'localtime') elif isinstance(self.browser, (Vivaldi)): return datetime((self.last_visit_time/1000000)-11644473600, 'unixepoch', 'localtime') + elif isinstance(self.browser, (Zen)): + return datetime.fromtimestamp(self.last_visit_time / 1000000.0) \ No newline at end of file From f1cd75f98375e5ca8d49588f0ccbeb9d3b842798 Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Sun, 24 Nov 2024 07:03:46 +0600 Subject: [PATCH 03/37] version bump to 0.8.0 --- plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.json b/plugin.json index 16e9fb4..5bd809a 100644 --- a/plugin.json +++ b/plugin.json @@ -4,7 +4,7 @@ "Name": "Browser History", "Description": "Search your Web Browser history", "Author": "Garulf", - "Version": "0.6.0", + "Version": "0.8.0", "Language": "python", "Website": "https://github.com/Garulf/browser-history", "IcoPath": "./icon.png", From fc4a76fee3ef91d11a2c5513a2f489ed0415bb84 Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Sun, 24 Nov 2024 07:05:05 +0600 Subject: [PATCH 04/37] add pyperclip for copy --- requirements.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/requirements.txt b/requirements.txt index 151f5b3..07a519c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ flox-lib==0.10.4 +pyperclip==1.9.0 From 5ef15d2bb7da2441018827badc31effebb39b27c Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Sun, 24 Nov 2024 07:06:10 +0600 Subject: [PATCH 05/37] add link copy support --- plugin/main.py | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/plugin/main.py b/plugin/main.py index 5450565..cde7a4f 100644 --- a/plugin/main.py +++ b/plugin/main.py @@ -1,5 +1,6 @@ -from flox import Flox, ICON_HISTORY, ICON_BROWSER +from flox import Flox, ICON_HISTORY, ICON_BROWSER, ICON_FILE +import pyperclip import browsers HISTORY_GLYPH = '' @@ -53,6 +54,18 @@ def context_menu(self, data): parameters=[data[1]], ) + self.add_item( + title='Copy to clipboard', + subtitle=data[1], + icon=ICON_FILE, + method=self.copy_to_clipboard, + parameters=[data[1]], + + ) + + def copy_to_clipboard(self, data): + pyperclip.copy(data) + self.show_msg("Copied!", f"{data}") if __name__ == "__main__": BrowserHistory() From 0ffa81915d8d34dfa100ffcbe8dc71b8463f4ab3 Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Thu, 6 Feb 2025 06:48:04 +0600 Subject: [PATCH 06/37] Floorp added --- SettingsTemplate.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/SettingsTemplate.yaml b/SettingsTemplate.yaml index 479bd87..ff8b818 100644 --- a/SettingsTemplate.yaml +++ b/SettingsTemplate.yaml @@ -12,4 +12,5 @@ body: - Opera - Vivaldi - Arc - - Zen \ No newline at end of file + - Zen + - Floorp \ No newline at end of file From 8c8b440f5c05900ddd315801d7e00e9a847c8492 Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Thu, 6 Feb 2025 06:51:18 +0600 Subject: [PATCH 07/37] Floorp added --- plugin/browsers.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/plugin/browsers.py b/plugin/browsers.py index a660c54..61e492a 100644 --- a/plugin/browsers.py +++ b/plugin/browsers.py @@ -18,6 +18,7 @@ VIVALDI_DIR = Path(LOCAL_DATA, 'Vivaldi', 'User Data', 'Default', 'History') ARC_DIR = Path(LOCAL_DATA, 'Packages', 'TheBrowserCompany.Arc_ttt1ap7aakyb4', 'LocalCache', 'Local', 'Arc', 'User Data', 'Default', 'History') ZEN_DIR = Path(ROAMING, 'zen', 'Profiles') +Floorp_DIR = Path(ROAMING, 'Floorp', 'Profiles') def get(browser_name): if browser_name == 'chrome': @@ -36,6 +37,8 @@ def get(browser_name): return Arc() elif browser_name == 'zen': return Zen() + elif browser_name == 'floorp': + return Floorp() else: raise ValueError('Invalid browser name') @@ -195,6 +198,23 @@ def history(self, limit=10): recents = self.query_history(self.database_path, 'SELECT url, title, visit_date FROM moz_places INNER JOIN moz_historyvisits on moz_historyvisits.place_id = moz_places.id ORDER BY visit_date DESC', limit) return self.get_history_items(recents) +class Floorp(Base): + """Floorp Browser History""" + + def __init__(self, database_path=Floorp_DIR): + # Floorp database is not in a static location, so we need to find it + self.database_path = self.find_database(database_path) + + def find_database(self, path): + """Find database in path""" + release_folder = Path(path).glob('*.default-release').__next__() + return Path(path, release_folder, 'places.sqlite') + + def history(self, limit=10): + """Most recent Firefox history""" + recents = self.query_history(self.database_path, 'SELECT url, title, visit_date FROM moz_places INNER JOIN moz_historyvisits on moz_historyvisits.place_id = moz_places.id ORDER BY visit_date DESC', limit) + return self.get_history_items(recents) + class HistoryItem(object): """Representation of a history item""" @@ -223,4 +243,6 @@ def timestamp(self): elif isinstance(self.browser, (Vivaldi)): return datetime((self.last_visit_time/1000000)-11644473600, 'unixepoch', 'localtime') elif isinstance(self.browser, (Zen)): + return datetime.fromtimestamp(self.last_visit_time / 1000000.0) + elif isinstance(self.browser, (Firefox)): return datetime.fromtimestamp(self.last_visit_time / 1000000.0) \ No newline at end of file From 868f4370df6c272301f0de5e47bfb036ce10f08a Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Thu, 6 Feb 2025 06:56:10 +0600 Subject: [PATCH 08/37] version bump to 0.8.1 --- plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.json b/plugin.json index 5bd809a..3006b03 100644 --- a/plugin.json +++ b/plugin.json @@ -4,7 +4,7 @@ "Name": "Browser History", "Description": "Search your Web Browser history", "Author": "Garulf", - "Version": "0.8.0", + "Version": "0.8.1", "Language": "python", "Website": "https://github.com/Garulf/browser-history", "IcoPath": "./icon.png", From 958c922aca41db64f92fcd56e924f888b9c9c52e Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Thu, 6 Feb 2025 07:04:48 +0600 Subject: [PATCH 09/37] checkout, setup-python, cache, upload-artifact version bump to v5 --- .github/workflows/pr-packager.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/pr-packager.yml b/.github/workflows/pr-packager.yml index 921a2c2..499daf1 100644 --- a/.github/workflows/pr-packager.yml +++ b/.github/workflows/pr-packager.yml @@ -10,12 +10,12 @@ jobs: runs-on: windows-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v5 - name: Set up Python ${{ env.PYTHON_VER }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VER }} - - uses: actions/cache@v2 + - uses: actions/cache@v5 with: path: ~\AppData\Local\pip\Cache key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} @@ -27,7 +27,7 @@ jobs: pip install wheel pip install -r ./requirements.txt -t ./lib - name: Upload - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v5 with: name: artifact path: | From 786cf3e810cec4dca6499e2a92a5ec2c31b60076 Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Thu, 6 Feb 2025 07:07:32 +0600 Subject: [PATCH 10/37] checkout, setup-python, cache version bump to v5 --- .github/workflows/release.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9904519..8785a6f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,14 +18,14 @@ jobs: runs-on: windows-latest steps: - name: Checkout - uses: actions/checkout@v2 + uses: actions/checkout@v5 with: fetch-depth: 0 - name: Set up Python ${{ env.PYTHON_VER }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VER }} - - uses: actions/cache@v2 + - uses: actions/cache@v5 if: startsWith(runner.os, 'Windows') with: path: ~\AppData\Local\pip\Cache From 500800c85e397f99780f150fe3657e106c5201bb Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Thu, 6 Feb 2025 07:11:32 +0600 Subject: [PATCH 11/37] checkout, setup-python, cache version bump to v5 --- .github/workflows/test-plugin.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/test-plugin.yml b/.github/workflows/test-plugin.yml index ec00b73..bcf6fb1 100644 --- a/.github/workflows/test-plugin.yml +++ b/.github/workflows/test-plugin.yml @@ -22,7 +22,7 @@ jobs: python_ver: ['3.8'] steps: - name: Checkout Plugin Repo - uses: actions/checkout@v2 + uses: actions/checkout@v5 with: path: ${{github.event.repository.name}} - name: Get Plugin's version @@ -66,7 +66,7 @@ jobs: echo "FILE_NAME=$file_name" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append echo "TAG_NAME=$tag_name" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append - name: Flow Launcher Cache - uses: actions/cache@v2 + uses: actions/cache@v5 id: flow_cache with: path: | @@ -95,10 +95,10 @@ jobs: New-Item -ItemType SymbolicLink -Path $plugin_path -Target $repo_path echo "PLUGIN_PATH=$plugin_path" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5 with: python-version: ${{ matrix.python_ver }} - - uses: actions/cache@v2 + - uses: actions/cache@v5 with: path: ~\AppData\Local\pip\Cache key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} From b02ac1856a3edb71e9441d51a563ffb66fbc702b Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Thu, 6 Feb 2025 07:17:35 +0600 Subject: [PATCH 12/37] downgrade actions/checkout, actions/cache, and actions/setup-python to v4 --- .github/workflows/test-plugin.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/test-plugin.yml b/.github/workflows/test-plugin.yml index bcf6fb1..38ceccd 100644 --- a/.github/workflows/test-plugin.yml +++ b/.github/workflows/test-plugin.yml @@ -22,7 +22,7 @@ jobs: python_ver: ['3.8'] steps: - name: Checkout Plugin Repo - uses: actions/checkout@v5 + uses: actions/checkout@v4 with: path: ${{github.event.repository.name}} - name: Get Plugin's version @@ -66,7 +66,7 @@ jobs: echo "FILE_NAME=$file_name" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append echo "TAG_NAME=$tag_name" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf-8 -Append - name: Flow Launcher Cache - uses: actions/cache@v5 + uses: actions/cache@v4 id: flow_cache with: path: | @@ -98,7 +98,7 @@ jobs: uses: actions/setup-python@v5 with: python-version: ${{ matrix.python_ver }} - - uses: actions/cache@v5 + - uses: actions/cache@v4 with: path: ~\AppData\Local\pip\Cache key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} From f7b8abd491cc04286d33e93cff21375d4fc4be31 Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Thu, 6 Feb 2025 07:18:14 +0600 Subject: [PATCH 13/37] downgrade actions/checkout and actions/cache to v4 --- .github/workflows/release.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 8785a6f..1bb01a1 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,14 +18,14 @@ jobs: runs-on: windows-latest steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Set up Python ${{ env.PYTHON_VER }} uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VER }} - - uses: actions/cache@v5 + - uses: actions/cache@v4 if: startsWith(runner.os, 'Windows') with: path: ~\AppData\Local\pip\Cache From 3c6a2b772ce66809e8d973e0731a2fb181f096dc Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Thu, 6 Feb 2025 07:18:54 +0600 Subject: [PATCH 14/37] downgrade actions/checkout, actions/cache, and actions/upload-artifact to v4 --- .github/workflows/pr-packager.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/pr-packager.yml b/.github/workflows/pr-packager.yml index 499daf1..d968a10 100644 --- a/.github/workflows/pr-packager.yml +++ b/.github/workflows/pr-packager.yml @@ -10,12 +10,12 @@ jobs: runs-on: windows-latest steps: - name: Checkout - uses: actions/checkout@v5 + uses: actions/checkout@v4 - name: Set up Python ${{ env.PYTHON_VER }} uses: actions/setup-python@v5 with: python-version: ${{ env.PYTHON_VER }} - - uses: actions/cache@v5 + - uses: actions/cache@v4 with: path: ~\AppData\Local\pip\Cache key: ${{ runner.os }}-pip-${{ hashFiles('**/requirements.txt') }} @@ -27,7 +27,7 @@ jobs: pip install wheel pip install -r ./requirements.txt -t ./lib - name: Upload - uses: actions/upload-artifact@v5 + uses: actions/upload-artifact@v4 with: name: artifact path: | From 3bc1d9fc62e74f53c2e431280cecfd00d42c18b2 Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Tue, 25 Mar 2025 15:13:41 +0600 Subject: [PATCH 15/37] Add Brave Nightly to supported browsers in SettingsTemplate.yaml --- SettingsTemplate.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/SettingsTemplate.yaml b/SettingsTemplate.yaml index ff8b818..ec3f5df 100644 --- a/SettingsTemplate.yaml +++ b/SettingsTemplate.yaml @@ -9,6 +9,7 @@ body: - Firefox - Edge - Brave + - Brave Nightly - Opera - Vivaldi - Arc From c40b4cfd68a4ab38c16facd1a3554167a6c1030a Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Tue, 25 Mar 2025 15:25:18 +0600 Subject: [PATCH 16/37] Add support for Brave Nightly browser in history tracking --- plugin/browsers.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/plugin/browsers.py b/plugin/browsers.py index 61e492a..c6b10cb 100644 --- a/plugin/browsers.py +++ b/plugin/browsers.py @@ -14,6 +14,7 @@ FIREFOX_DIR = Path(ROAMING, 'Mozilla', 'Firefox', 'Profiles') EDGE_DIR = Path(LOCAL_DATA, 'Microsoft', 'Edge', 'User Data', 'Default', 'History') BRAVE_DIR = Path(LOCAL_DATA, 'BraveSoftware', 'Brave-Browser', 'User Data', 'Default', 'History') +BRAVE_NIGHTLY_DIR = Path(LOCAL_DATA, 'BraveSoftware', 'Brave-Browser-Nightly', 'User Data', 'Default', 'History') OPERA_DIR = Path(ROAMING, 'Opera Software', 'Opera Stable', 'Default', 'History') VIVALDI_DIR = Path(LOCAL_DATA, 'Vivaldi', 'User Data', 'Default', 'History') ARC_DIR = Path(LOCAL_DATA, 'Packages', 'TheBrowserCompany.Arc_ttt1ap7aakyb4', 'LocalCache', 'Local', 'Arc', 'User Data', 'Default', 'History') @@ -29,6 +30,8 @@ def get(browser_name): return Edge() elif browser_name == 'brave': return Brave() + elif browser_name == 'brave nightly': + return BraveNightly() elif browser_name == 'opera': return Opera() elif browser_name == 'vivaldi': @@ -141,6 +144,19 @@ def history(self, limit=10): """ recents = self.query_history(self.database_path, 'SELECT url, title, last_visit_time FROM urls ORDER BY last_visit_time DESC', limit) return self.get_history_items(recents) + +class BraveNightly(Base): + """Brave Nightly Browser History""" + + def __init__(self, database_path=BRAVE_NIGHTLY_DIR): + self.database_path = database_path + + def history(self, limit=10): + """ + Returns a list of the most recently visited sites in Brave's history. + """ + recents = self.query_history(self.database_path, 'SELECT url, title, last_visit_time FROM urls ORDER BY last_visit_time DESC', limit) + return self.get_history_items(recents) class Opera(Base): """Opera Browser History""" @@ -238,6 +254,8 @@ def timestamp(self): return datetime((self.last_visit_time/1000000)-11644473600, 'unixepoch', 'localtime') elif isinstance(self.browser, (Brave)): return datetime((self.last_visit_time/1000000)-11644473600, 'unixepoch', 'localtime') + elif isinstance(self.browser, (BraveNightly)): + return datetime((self.last_visit_time/1000000)-11644473600, 'unixepoch', 'localtime') elif isinstance(self.browser, (Opera)): return datetime((self.last_visit_time/1000000)-11644473600, 'unixepoch', 'localtime') elif isinstance(self.browser, (Vivaldi)): From 66d0c2c2f25c9acd150701369fe1dd5c0591c206 Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Sat, 29 Mar 2025 15:16:08 +0600 Subject: [PATCH 17/37] Fix variable naming for Floorp directory in browsers.py --- plugin/browsers.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/browsers.py b/plugin/browsers.py index c6b10cb..c8b4c4c 100644 --- a/plugin/browsers.py +++ b/plugin/browsers.py @@ -19,7 +19,7 @@ VIVALDI_DIR = Path(LOCAL_DATA, 'Vivaldi', 'User Data', 'Default', 'History') ARC_DIR = Path(LOCAL_DATA, 'Packages', 'TheBrowserCompany.Arc_ttt1ap7aakyb4', 'LocalCache', 'Local', 'Arc', 'User Data', 'Default', 'History') ZEN_DIR = Path(ROAMING, 'zen', 'Profiles') -Floorp_DIR = Path(ROAMING, 'Floorp', 'Profiles') +FLOORP_DIR = Path(ROAMING, 'Floorp', 'Profiles') def get(browser_name): if browser_name == 'chrome': @@ -217,7 +217,7 @@ def history(self, limit=10): class Floorp(Base): """Floorp Browser History""" - def __init__(self, database_path=Floorp_DIR): + def __init__(self, database_path=FLOORP_DIR): # Floorp database is not in a static location, so we need to find it self.database_path = self.find_database(database_path) From eec74f2ce446873bd5d4150d398342ac391280a1 Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Sat, 29 Mar 2025 15:20:00 +0600 Subject: [PATCH 18/37] Add support for Thorium browser in history tracking --- SettingsTemplate.yaml | 3 ++- plugin/browsers.py | 20 +++++++++++++++++++- 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/SettingsTemplate.yaml b/SettingsTemplate.yaml index ec3f5df..b816ed5 100644 --- a/SettingsTemplate.yaml +++ b/SettingsTemplate.yaml @@ -14,4 +14,5 @@ body: - Vivaldi - Arc - Zen - - Floorp \ No newline at end of file + - Floorp + - Thorium \ No newline at end of file diff --git a/plugin/browsers.py b/plugin/browsers.py index c8b4c4c..b5ff39f 100644 --- a/plugin/browsers.py +++ b/plugin/browsers.py @@ -20,6 +20,7 @@ ARC_DIR = Path(LOCAL_DATA, 'Packages', 'TheBrowserCompany.Arc_ttt1ap7aakyb4', 'LocalCache', 'Local', 'Arc', 'User Data', 'Default', 'History') ZEN_DIR = Path(ROAMING, 'zen', 'Profiles') FLOORP_DIR = Path(ROAMING, 'Floorp', 'Profiles') +THORIUM_DIR = Path(LOCAL_DATA, 'Thorium', 'User Data', 'Default', 'History') def get(browser_name): if browser_name == 'chrome': @@ -42,6 +43,8 @@ def get(browser_name): return Zen() elif browser_name == 'floorp': return Floorp() + elif browser_name == 'thorium': + return Thorium() else: raise ValueError('Invalid browser name') @@ -230,6 +233,19 @@ def history(self, limit=10): """Most recent Firefox history""" recents = self.query_history(self.database_path, 'SELECT url, title, visit_date FROM moz_places INNER JOIN moz_historyvisits on moz_historyvisits.place_id = moz_places.id ORDER BY visit_date DESC', limit) return self.get_history_items(recents) + +class Thorium(Base): + """Thorium History""" + + def __init__(self, database_path=THORIUM_DIR): + self.database_path = database_path + + def history(self, limit=10): + """ + Returns a list of the most recently visited sites in Thorium's history. + """ + recents = self.query_history(self.database_path, 'SELECT url, title, last_visit_time FROM urls ORDER BY last_visit_time DESC', limit) + return self.get_history_items(recents) class HistoryItem(object): """Representation of a history item""" @@ -263,4 +279,6 @@ def timestamp(self): elif isinstance(self.browser, (Zen)): return datetime.fromtimestamp(self.last_visit_time / 1000000.0) elif isinstance(self.browser, (Firefox)): - return datetime.fromtimestamp(self.last_visit_time / 1000000.0) \ No newline at end of file + return datetime.fromtimestamp(self.last_visit_time / 1000000.0) + elif isinstance(self.browser, (Thorium)): + return datetime((self.last_visit_time/1000000)-11644473600, 'unixepoch', 'localtime') \ No newline at end of file From 7c3ac23abffec7b30284712873d400809735e72e Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Sat, 29 Mar 2025 17:11:02 +0600 Subject: [PATCH 19/37] Add Custom (Chromium) browser support and custom profile folder option in SettingsTemplate.yaml --- SettingsTemplate.yaml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/SettingsTemplate.yaml b/SettingsTemplate.yaml index b816ed5..ee3c640 100644 --- a/SettingsTemplate.yaml +++ b/SettingsTemplate.yaml @@ -15,4 +15,9 @@ body: - Arc - Zen - Floorp - - Thorium \ No newline at end of file + - Thorium + - Custom (Chromium) + - type: inputWithFolderBtn + attributes: + name: custom_profile_path + label: Custom Profile Folder \ No newline at end of file From 1de9f1538bdb2c8940bfed3055756b4002d4e3ba Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Sat, 29 Mar 2025 17:11:34 +0600 Subject: [PATCH 20/37] Add custom profile path support in BrowserHistory initialization --- plugin/main.py | 1 + 1 file changed, 1 insertion(+) diff --git a/plugin/main.py b/plugin/main.py index cde7a4f..045f854 100644 --- a/plugin/main.py +++ b/plugin/main.py @@ -17,6 +17,7 @@ class BrowserHistory(Flox): def __init__(self): super().__init__() self.default_browser = self.settings.get('default_browser', DEFAULT_BROWSER) + self.custom_profile_path = self.settings.get('custom_profile_path', '') self.browser = browsers.get(self.default_browser.lower()) def _query(self, query): From cb69642398c67819494010e976a72aeb9f9eed74 Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Sat, 29 Mar 2025 17:13:52 +0600 Subject: [PATCH 21/37] Add support for custom Chromium-based browser in history tracking --- plugin/browsers.py | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/plugin/browsers.py b/plugin/browsers.py index b5ff39f..d05b181 100644 --- a/plugin/browsers.py +++ b/plugin/browsers.py @@ -22,7 +22,7 @@ FLOORP_DIR = Path(ROAMING, 'Floorp', 'Profiles') THORIUM_DIR = Path(LOCAL_DATA, 'Thorium', 'User Data', 'Default', 'History') -def get(browser_name): +def get(browser_name, custom_profile_path=None): if browser_name == 'chrome': return Chrome() elif browser_name == 'firefox': @@ -45,6 +45,8 @@ def get(browser_name): return Floorp() elif browser_name == 'thorium': return Thorium() + elif browser_name == 'custom (chromium)': + return CustomChromium(custom_profile_path) # Pass the custom path else: raise ValueError('Invalid browser name') @@ -247,6 +249,19 @@ def history(self, limit=10): recents = self.query_history(self.database_path, 'SELECT url, title, last_visit_time FROM urls ORDER BY last_visit_time DESC', limit) return self.get_history_items(recents) +class CustomChromium(Base): + """Custom Chromium-Based Browser History""" + + def __init__(self, database_path): + self.database_path = Path(database_path, 'History') + + def history(self, limit=10): + """ + Returns a list of the most recently visited sites in Chromium-based browser history. + """ + recents = self.query_history(self.database_path, 'SELECT url, title, last_visit_time FROM urls ORDER BY last_visit_time DESC', limit) + return self.get_history_items(recents) + class HistoryItem(object): """Representation of a history item""" @@ -281,4 +296,6 @@ def timestamp(self): elif isinstance(self.browser, (Firefox)): return datetime.fromtimestamp(self.last_visit_time / 1000000.0) elif isinstance(self.browser, (Thorium)): - return datetime((self.last_visit_time/1000000)-11644473600, 'unixepoch', 'localtime') \ No newline at end of file + return datetime((self.last_visit_time/1000000)-11644473600, 'unixepoch', 'localtime') + elif isinstance(self.browser, (CustomChromium)): + return datetime((self.last_visit_time/1000000)-11644473600, 'unixepoch', 'localtime') From 23fd77786eb0085a2b2f7204841acb1306132902 Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Sat, 29 Mar 2025 17:15:12 +0600 Subject: [PATCH 22/37] Bump version to 0.8.3 in plugin.json --- plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.json b/plugin.json index 3006b03..a136ec3 100644 --- a/plugin.json +++ b/plugin.json @@ -4,7 +4,7 @@ "Name": "Browser History", "Description": "Search your Web Browser history", "Author": "Garulf", - "Version": "0.8.1", + "Version": "0.8.3", "Language": "python", "Website": "https://github.com/Garulf/browser-history", "IcoPath": "./icon.png", From 4807e43cd6cb8812c716029baef652a779507e9f Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Sat, 29 Mar 2025 17:20:01 +0600 Subject: [PATCH 23/37] Bump version to 0.8.5 in plugin.json --- plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.json b/plugin.json index a136ec3..443a257 100644 --- a/plugin.json +++ b/plugin.json @@ -4,7 +4,7 @@ "Name": "Browser History", "Description": "Search your Web Browser history", "Author": "Garulf", - "Version": "0.8.3", + "Version": "0.8.5", "Language": "python", "Website": "https://github.com/Garulf/browser-history", "IcoPath": "./icon.png", From 9a207aec880080dd09bfeb7db70caf012d705b2a Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Sat, 29 Mar 2025 17:20:13 +0600 Subject: [PATCH 24/37] Add Custom (Firefox) browser support in SettingsTemplate.yaml --- SettingsTemplate.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/SettingsTemplate.yaml b/SettingsTemplate.yaml index ee3c640..cba8ec8 100644 --- a/SettingsTemplate.yaml +++ b/SettingsTemplate.yaml @@ -17,6 +17,7 @@ body: - Floorp - Thorium - Custom (Chromium) + - Custom (Firefox) - type: inputWithFolderBtn attributes: name: custom_profile_path From b1c7982944bd6a83f3419b853f65dfca354fdd1c Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Sat, 29 Mar 2025 17:22:49 +0600 Subject: [PATCH 25/37] Add support for custom Firefox browser in history tracking --- plugin/browsers.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/plugin/browsers.py b/plugin/browsers.py index d05b181..cde2113 100644 --- a/plugin/browsers.py +++ b/plugin/browsers.py @@ -46,7 +46,9 @@ def get(browser_name, custom_profile_path=None): elif browser_name == 'thorium': return Thorium() elif browser_name == 'custom (chromium)': - return CustomChromium(custom_profile_path) # Pass the custom path + return CustomChromium(custom_profile_path) # Pass the custom path + elif browser_name == 'custom (firefox)': + return CustomFirefox(custom_profile_path) # Pass the custom path else: raise ValueError('Invalid browser name') @@ -262,6 +264,17 @@ def history(self, limit=10): recents = self.query_history(self.database_path, 'SELECT url, title, last_visit_time FROM urls ORDER BY last_visit_time DESC', limit) return self.get_history_items(recents) +class CustomFirefox(Base): + """Custom Firefox-Based Browser History""" + + def __init__(self, database_path): + self.database_path = Path(database_path, 'places.sqlite') + + def history(self, limit=10): + """Most recent Firefox history""" + recents = self.query_history(self.database_path, 'SELECT url, title, visit_date FROM moz_places INNER JOIN moz_historyvisits on moz_historyvisits.place_id = moz_places.id ORDER BY visit_date DESC', limit) + return self.get_history_items(recents) + class HistoryItem(object): """Representation of a history item""" @@ -299,3 +312,5 @@ def timestamp(self): return datetime((self.last_visit_time/1000000)-11644473600, 'unixepoch', 'localtime') elif isinstance(self.browser, (CustomChromium)): return datetime((self.last_visit_time/1000000)-11644473600, 'unixepoch', 'localtime') + elif isinstance(self.browser, (CustomFirefox)): + return datetime.fromtimestamp(self.last_visit_time / 1000000.0) From 72e4c469593b9c81eb3ac721efdb935453f0b864 Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Sat, 29 Mar 2025 17:36:24 +0600 Subject: [PATCH 26/37] Update browser initialization to support custom profile paths --- plugin/main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin/main.py b/plugin/main.py index 045f854..ead9a3a 100644 --- a/plugin/main.py +++ b/plugin/main.py @@ -18,7 +18,7 @@ def __init__(self): super().__init__() self.default_browser = self.settings.get('default_browser', DEFAULT_BROWSER) self.custom_profile_path = self.settings.get('custom_profile_path', '') - self.browser = browsers.get(self.default_browser.lower()) + self.browser = browsers.get(self.default_browser.lower(), self.custom_profile_path) def _query(self, query): try: From 3751fe1fe13b6b2e31fa8292544f8845ef11ccd8 Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Sat, 29 Mar 2025 17:36:39 +0600 Subject: [PATCH 27/37] Bump version to 0.8.6 in plugin.json --- plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.json b/plugin.json index 443a257..e087755 100644 --- a/plugin.json +++ b/plugin.json @@ -4,7 +4,7 @@ "Name": "Browser History", "Description": "Search your Web Browser history", "Author": "Garulf", - "Version": "0.8.5", + "Version": "0.8.6", "Language": "python", "Website": "https://github.com/Garulf/browser-history", "IcoPath": "./icon.png", From 2df657cd17c5b3804e2fc87e6c046e9ee1ff656e Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Fri, 11 Apr 2025 01:13:08 +0600 Subject: [PATCH 28/37] Refactor timestamp method to consolidate browser timestamp handling & added floorp --- plugin/browsers.py | 28 +++++----------------------- 1 file changed, 5 insertions(+), 23 deletions(-) diff --git a/plugin/browsers.py b/plugin/browsers.py index cde2113..84b87d7 100644 --- a/plugin/browsers.py +++ b/plugin/browsers.py @@ -290,27 +290,9 @@ def __init__(self, browser, url, title, last_visit_time): self.last_visit_time = last_visit_time def timestamp(self): - if isinstance(self.browser, (Chrome)): - return datetime((self.last_visit_time/1000000)-11644473600, 'unixepoch', 'localtime') - elif isinstance(self.browser, (Firefox)): - return datetime.fromtimestamp(self.last_visit_time / 1000000.0) - elif isinstance(self.browser, (Edge)): - return datetime((self.last_visit_time/1000000)-11644473600, 'unixepoch', 'localtime') - elif isinstance(self.browser, (Brave)): - return datetime((self.last_visit_time/1000000)-11644473600, 'unixepoch', 'localtime') - elif isinstance(self.browser, (BraveNightly)): - return datetime((self.last_visit_time/1000000)-11644473600, 'unixepoch', 'localtime') - elif isinstance(self.browser, (Opera)): - return datetime((self.last_visit_time/1000000)-11644473600, 'unixepoch', 'localtime') - elif isinstance(self.browser, (Vivaldi)): - return datetime((self.last_visit_time/1000000)-11644473600, 'unixepoch', 'localtime') - elif isinstance(self.browser, (Zen)): - return datetime.fromtimestamp(self.last_visit_time / 1000000.0) - elif isinstance(self.browser, (Firefox)): - return datetime.fromtimestamp(self.last_visit_time / 1000000.0) - elif isinstance(self.browser, (Thorium)): - return datetime((self.last_visit_time/1000000)-11644473600, 'unixepoch', 'localtime') - elif isinstance(self.browser, (CustomChromium)): - return datetime((self.last_visit_time/1000000)-11644473600, 'unixepoch', 'localtime') - elif isinstance(self.browser, (CustomFirefox)): + if isinstance(self.browser, (Chrome, Edge, Brave, BraveNightly, Opera, Vivaldi, Thorium, CustomChromium)): + # Chrome-based browsers use a timestamp offset from 1601-01-01 + return datetime.fromtimestamp(self.last_visit_time / 1000000 - 11644473600) + elif isinstance(self.browser, (Firefox, Zen, Floorp, CustomFirefox)): + # Firefox-based browsers use a standard Unix timestamp in microseconds return datetime.fromtimestamp(self.last_visit_time / 1000000.0) From d5d25890841575bd9951ae5fe91dbca979bc52ff Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Fri, 11 Apr 2025 01:17:32 +0600 Subject: [PATCH 29/37] Bump version to 0.8.7 in plugin.json --- plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.json b/plugin.json index e087755..3fe5488 100644 --- a/plugin.json +++ b/plugin.json @@ -4,7 +4,7 @@ "Name": "Browser History", "Description": "Search your Web Browser history", "Author": "Garulf", - "Version": "0.8.6", + "Version": "0.8.7", "Language": "python", "Website": "https://github.com/Garulf/browser-history", "IcoPath": "./icon.png", From ef765b9e6023e84bdca1039d73592135cf1838cf Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Fri, 25 Apr 2025 01:08:16 +0600 Subject: [PATCH 30/37] Refactor browser history handling to consolidate paths and improve dynamic profile support --- plugin/browsers.py | 360 ++++++++++++--------------------------------- 1 file changed, 90 insertions(+), 270 deletions(-) diff --git a/plugin/browsers.py b/plugin/browsers.py index 84b87d7..741335d 100644 --- a/plugin/browsers.py +++ b/plugin/browsers.py @@ -1,7 +1,7 @@ +import os import shutil import sqlite3 from tempfile import gettempdir -import os from pathlib import Path from datetime import datetime import logging @@ -10,289 +10,109 @@ LOCAL_DATA = os.getenv('LOCALAPPDATA') ROAMING = os.getenv('APPDATA') -CHROME_DIR = Path(LOCAL_DATA, 'Google', 'Chrome', 'User Data', 'Default', 'History') -FIREFOX_DIR = Path(ROAMING, 'Mozilla', 'Firefox', 'Profiles') -EDGE_DIR = Path(LOCAL_DATA, 'Microsoft', 'Edge', 'User Data', 'Default', 'History') -BRAVE_DIR = Path(LOCAL_DATA, 'BraveSoftware', 'Brave-Browser', 'User Data', 'Default', 'History') -BRAVE_NIGHTLY_DIR = Path(LOCAL_DATA, 'BraveSoftware', 'Brave-Browser-Nightly', 'User Data', 'Default', 'History') -OPERA_DIR = Path(ROAMING, 'Opera Software', 'Opera Stable', 'Default', 'History') -VIVALDI_DIR = Path(LOCAL_DATA, 'Vivaldi', 'User Data', 'Default', 'History') -ARC_DIR = Path(LOCAL_DATA, 'Packages', 'TheBrowserCompany.Arc_ttt1ap7aakyb4', 'LocalCache', 'Local', 'Arc', 'User Data', 'Default', 'History') -ZEN_DIR = Path(ROAMING, 'zen', 'Profiles') -FLOORP_DIR = Path(ROAMING, 'Floorp', 'Profiles') -THORIUM_DIR = Path(LOCAL_DATA, 'Thorium', 'User Data', 'Default', 'History') -def get(browser_name, custom_profile_path=None): - if browser_name == 'chrome': - return Chrome() - elif browser_name == 'firefox': - return Firefox() - elif browser_name == 'edge': - return Edge() - elif browser_name == 'brave': - return Brave() - elif browser_name == 'brave nightly': - return BraveNightly() - elif browser_name == 'opera': - return Opera() - elif browser_name == 'vivaldi': - return Vivaldi() - elif browser_name == 'arc': - return Arc() - elif browser_name == 'zen': - return Zen() - elif browser_name == 'floorp': - return Floorp() - elif browser_name == 'thorium': - return Thorium() - elif browser_name == 'custom (chromium)': - return CustomChromium(custom_profile_path) # Pass the custom path - elif browser_name == 'custom (firefox)': - return CustomFirefox(custom_profile_path) # Pass the custom path - else: - raise ValueError('Invalid browser name') +# Paths to known browser history locations +BROWSER_PATHS = { + 'chrome': Path(LOCAL_DATA, 'Google', 'Chrome', 'User Data', 'Default', 'History'), + 'edge': Path(LOCAL_DATA, 'Microsoft', 'Edge', 'User Data', 'Default', 'History'), + 'brave': Path(LOCAL_DATA, 'BraveSoftware', 'Brave-Browser', 'User Data', 'Default', 'History'), + 'brave nightly': Path(LOCAL_DATA, 'BraveSoftware', 'Brave-Browser-Nightly', 'User Data', 'Default', 'History'), + 'opera': Path(ROAMING, 'Opera Software', 'Opera Stable', 'Default', 'History'), + 'vivaldi': Path(LOCAL_DATA, 'Vivaldi', 'User Data', 'Default', 'History'), + 'arc': Path(LOCAL_DATA, 'Packages', 'TheBrowserCompany.Arc_ttt1ap7aakyb4', 'LocalCache', 'Local', 'Arc', 'User Data', 'Default', 'History'), + 'thorium': Path(LOCAL_DATA, 'Thorium', 'User Data', 'Default', 'History'), + 'firefox': Path(ROAMING, 'Mozilla', 'Firefox', 'Profiles'), + 'zen': Path(ROAMING, 'zen', 'Profiles'), + 'floorp': Path(ROAMING, 'Floorp', 'Profiles'), +} + +# Constants for timestamp conversion +CHROMIUM_EPOCH_OFFSET = 11644473600 # seconds from 1601 to 1970 + +class Browser: + def __init__(self, name, query, timestamp_type='chromium', custom_path=None, dynamic_profile=False, profile_glob=None, db_file='History'): + self.name = name + self.query = query + self.timestamp_type = timestamp_type + self.dynamic_profile = dynamic_profile + self.profile_glob = profile_glob + self.db_file = db_file + + if custom_path: + self.database_path = Path(custom_path) + elif dynamic_profile: + profile_base = BROWSER_PATHS.get(name) + profile_folder = next(Path(profile_base).glob(profile_glob)) + self.database_path = profile_base / profile_folder / db_file + else: + self.database_path = BROWSER_PATHS.get(name) -class Base(object): - - def __del__(self): - if hasattr(self, 'temp_path'): - # Probably best we don't leave browser history in the temp directory - # This deletes the temporary database file after the object is destroyed - os.remove(self.temp_path) + if not self.database_path.exists(): + raise FileNotFoundError(f"Database not found for {name}: {self.database_path}") - def _copy_database(self, database_path): - """ - Copies the database to a temporary location and returns the path to the - copy. - """ - temp_dir = gettempdir() - temp_path = shutil.copy(database_path, temp_dir) + def _copy_database(self): + temp_path = shutil.copy(self.database_path, gettempdir()) self.temp_path = temp_path return temp_path - def query_history(self, database_path, query, limit=10): - """ - Query Browser history SQL Database. - """ - # Copy the database to a temporary location. - temp_path = self._copy_database(database_path) + def __del__(self): + if hasattr(self, 'temp_path'): + os.remove(self.temp_path) - # Open the database. - connection = sqlite3.connect(temp_path) - + def history(self, limit=10): + db_path = self._copy_database() + connection = sqlite3.connect(db_path) cursor = connection.cursor() - cursor.execute(f'{query} LIMIT {limit}') - recent = cursor.fetchall() + cursor.execute(f"{self.query} LIMIT {limit}") + rows = cursor.fetchall() connection.close() - return recent - - def get_history_items(self, results): - """ - Converts the tuple returned by the query to a HistoryItem object. - """ - data = [] - for result in results: - data.append(HistoryItem(self, *result)) - return data - - -class Chrome(Base): - """Google Chrome History""" - - def __init__(self, database_path=CHROME_DIR): - self.database_path = database_path - - def history(self, limit=10): - """ - Returns a list of the most recently visited sites in Chrome's history. - """ - recents = self.query_history(self.database_path, 'SELECT url, title, last_visit_time FROM urls ORDER BY last_visit_time DESC', limit) - return self.get_history_items(recents) - -class Firefox(Base): - """Firefox Browser History""" - - def __init__(self, database_path=FIREFOX_DIR): - # Firefox database is not in a static location, so we need to find it - self.database_path = self.find_database(database_path) - - def find_database(self, path): - """Find database in path""" - release_folder = Path(path).glob('*.default-release').__next__() - return Path(path, release_folder, 'places.sqlite') - - def history(self, limit=10): - """Most recent Firefox history""" - recents = self.query_history(self.database_path, 'SELECT url, title, visit_date FROM moz_places INNER JOIN moz_historyvisits on moz_historyvisits.place_id = moz_places.id ORDER BY visit_date DESC', limit) - return self.get_history_items(recents) - -class Edge(Base): - """Microsoft Edge History""" - - def __init__(self, database_path=EDGE_DIR): - self.database_path = database_path - - def history(self, limit=10): - """ - Returns a list of the most recently visited sites in Chrome's history. - """ - recents = self.query_history(self.database_path, 'SELECT url, title, last_visit_time FROM urls ORDER BY last_visit_time DESC', limit) - return self.get_history_items(recents) + return [HistoryItem(self, *row) for row in rows] -class Brave(Base): - """Brave Browser History""" - - def __init__(self, database_path=BRAVE_DIR): - self.database_path = database_path - - def history(self, limit=10): - """ - Returns a list of the most recently visited sites in Brave's history. - """ - recents = self.query_history(self.database_path, 'SELECT url, title, last_visit_time FROM urls ORDER BY last_visit_time DESC', limit) - return self.get_history_items(recents) - -class BraveNightly(Base): - """Brave Nightly Browser History""" - - def __init__(self, database_path=BRAVE_NIGHTLY_DIR): - self.database_path = database_path - - def history(self, limit=10): - """ - Returns a list of the most recently visited sites in Brave's history. - """ - recents = self.query_history(self.database_path, 'SELECT url, title, last_visit_time FROM urls ORDER BY last_visit_time DESC', limit) - return self.get_history_items(recents) - -class Opera(Base): - """Opera Browser History""" - - def __init__(self, database_path=OPERA_DIR): - self.database_path = database_path - - def history(self, limit=10): - """ - Returns a list of the most recently visited sites in Opera's history. - """ - recents = self.query_history(self.database_path, 'SELECT url, title, last_visit_time FROM urls ORDER BY last_visit_time DESC', limit) - return self.get_history_items(recents) - -class Vivaldi(Base): - """Vivaldi Browser History""" - - def __init__(self, database_path=VIVALDI_DIR): - self.database_path = database_path - - def history(self, limit=10): - """ - Returns a list of the most recently visited sites in Vivaldi's history. - """ - recents = self.query_history(self.database_path, 'SELECT url, title, last_visit_time FROM urls ORDER BY last_visit_time DESC', limit) - return self.get_history_items(recents) - -class Arc(Base): - """Arc Browser History""" - - def __init__(self, database_path=ARC_DIR): - self.database_path = database_path - - def history(self, limit=10): - """ - Returns a list of the most recently visited sites in Arc's history. - """ - recents = self.query_history(self.database_path, 'SELECT url, title, last_visit_time FROM urls ORDER BY last_visit_time DESC', limit) - return self.get_history_items(recents) - -class Zen(Base): - """Zen Browser History""" - - def __init__(self, database_path=ZEN_DIR): - # Zen database is not in a static location, so we need to find it - self.database_path = self.find_database(database_path) - - def find_database(self, path): - """Find database in path""" - release_folder = Path(path).glob('*.Default (alpha)').__next__() - return Path(path, release_folder, 'places.sqlite') - - def history(self, limit=10): - """Most recent Zen history""" - recents = self.query_history(self.database_path, 'SELECT url, title, visit_date FROM moz_places INNER JOIN moz_historyvisits on moz_historyvisits.place_id = moz_places.id ORDER BY visit_date DESC', limit) - return self.get_history_items(recents) - -class Floorp(Base): - """Floorp Browser History""" - - def __init__(self, database_path=FLOORP_DIR): - # Floorp database is not in a static location, so we need to find it - self.database_path = self.find_database(database_path) - - def find_database(self, path): - """Find database in path""" - release_folder = Path(path).glob('*.default-release').__next__() - return Path(path, release_folder, 'places.sqlite') - - def history(self, limit=10): - """Most recent Firefox history""" - recents = self.query_history(self.database_path, 'SELECT url, title, visit_date FROM moz_places INNER JOIN moz_historyvisits on moz_historyvisits.place_id = moz_places.id ORDER BY visit_date DESC', limit) - return self.get_history_items(recents) - -class Thorium(Base): - """Thorium History""" - - def __init__(self, database_path=THORIUM_DIR): - self.database_path = database_path - - def history(self, limit=10): - """ - Returns a list of the most recently visited sites in Thorium's history. - """ - recents = self.query_history(self.database_path, 'SELECT url, title, last_visit_time FROM urls ORDER BY last_visit_time DESC', limit) - return self.get_history_items(recents) - -class CustomChromium(Base): - """Custom Chromium-Based Browser History""" - - def __init__(self, database_path): - self.database_path = Path(database_path, 'History') - - def history(self, limit=10): - """ - Returns a list of the most recently visited sites in Chromium-based browser history. - """ - recents = self.query_history(self.database_path, 'SELECT url, title, last_visit_time FROM urls ORDER BY last_visit_time DESC', limit) - return self.get_history_items(recents) - -class CustomFirefox(Base): - """Custom Firefox-Based Browser History""" - - def __init__(self, database_path): - self.database_path = Path(database_path, 'places.sqlite') - - def history(self, limit=10): - """Most recent Firefox history""" - recents = self.query_history(self.database_path, 'SELECT url, title, visit_date FROM moz_places INNER JOIN moz_historyvisits on moz_historyvisits.place_id = moz_places.id ORDER BY visit_date DESC', limit) - return self.get_history_items(recents) - -class HistoryItem(object): - """Representation of a history item""" + def convert_timestamp(self, raw_time): + if self.timestamp_type == 'chromium': + return datetime.fromtimestamp(raw_time / 1_000_000 - CHROMIUM_EPOCH_OFFSET) + elif self.timestamp_type == 'unix_us': + return datetime.fromtimestamp(raw_time / 1_000_000) +class HistoryItem: def __init__(self, browser, url, title, last_visit_time): self.browser = browser self.url = url - if title is None: - title = '' - if title.strip() == '': - self.title = url - else: - self.title = title + self.title = title.strip() if title else url self.last_visit_time = last_visit_time def timestamp(self): - if isinstance(self.browser, (Chrome, Edge, Brave, BraveNightly, Opera, Vivaldi, Thorium, CustomChromium)): - # Chrome-based browsers use a timestamp offset from 1601-01-01 - return datetime.fromtimestamp(self.last_visit_time / 1000000 - 11644473600) - elif isinstance(self.browser, (Firefox, Zen, Floorp, CustomFirefox)): - # Firefox-based browsers use a standard Unix timestamp in microseconds - return datetime.fromtimestamp(self.last_visit_time / 1000000.0) + return self.browser.convert_timestamp(self.last_visit_time) + +# Queries +CHROMIUM_QUERY = 'SELECT url, title, last_visit_time FROM urls ORDER BY last_visit_time DESC' +FIREFOX_QUERY = 'SELECT url, title, visit_date FROM moz_places INNER JOIN moz_historyvisits ON moz_historyvisits.place_id = moz_places.id ORDER BY visit_date DESC' + +# These are the default profile names for various Firefox variants +FIREFOX_VARIANTS = { + 'firefox': ['*.default-release'], + 'floorp': ['*.default-release'], + 'zen': ['*.Default (alpha)', '*.default', 'Default Profile'] +} + +# Factory function +def get(browser_name, custom_profile_path=None): + browser_name = browser_name.lower() + if browser_name in ['chrome', 'edge', 'brave', 'brave nightly', 'opera', 'vivaldi', 'arc', 'thorium']: + return Browser(browser_name, CHROMIUM_QUERY, 'chromium') + elif browser_name in FIREFOX_VARIANTS: + profile_globs = FIREFOX_VARIANTS[browser_name] + return Browser( + browser_name, + FIREFOX_QUERY, + 'unix_us', + dynamic_profile=True, + profile_globs=profile_globs, + db_file='places.sqlite' + ) + elif browser_name == 'custom (chromium)': + return Browser('custom', CHROMIUM_QUERY, 'chromium', custom_path=Path(custom_profile_path) / 'History') + elif browser_name == 'custom (firefox)': + return Browser('custom', FIREFOX_QUERY, 'unix_us', custom_path=Path(custom_profile_path) / 'places.sqlite') + else: + raise ValueError(f"Unsupported browser: {browser_name}") From fed2d64d4afc1d71320c4ceb141652713caa3c15 Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Fri, 25 Apr 2025 01:08:50 +0600 Subject: [PATCH 31/37] Add Firefox Nightly option to default browser selection --- SettingsTemplate.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/SettingsTemplate.yaml b/SettingsTemplate.yaml index cba8ec8..0cb9b83 100644 --- a/SettingsTemplate.yaml +++ b/SettingsTemplate.yaml @@ -7,6 +7,7 @@ body: options: - Chrome - Firefox + - Firefox Nightly - Edge - Brave - Brave Nightly From d5c7997f1e650b3e5b406feccd1655b5d88ce9e1 Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Fri, 25 Apr 2025 01:15:03 +0600 Subject: [PATCH 32/37] Add support for Firefox Nightly profiles in browser history handling --- plugin/browsers.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/plugin/browsers.py b/plugin/browsers.py index 741335d..9754c70 100644 --- a/plugin/browsers.py +++ b/plugin/browsers.py @@ -22,6 +22,7 @@ 'arc': Path(LOCAL_DATA, 'Packages', 'TheBrowserCompany.Arc_ttt1ap7aakyb4', 'LocalCache', 'Local', 'Arc', 'User Data', 'Default', 'History'), 'thorium': Path(LOCAL_DATA, 'Thorium', 'User Data', 'Default', 'History'), 'firefox': Path(ROAMING, 'Mozilla', 'Firefox', 'Profiles'), + 'firefox nightly': Path(ROAMING, 'Mozilla', 'Firefox', 'Profiles'), 'zen': Path(ROAMING, 'zen', 'Profiles'), 'floorp': Path(ROAMING, 'Floorp', 'Profiles'), } @@ -90,9 +91,10 @@ def timestamp(self): # These are the default profile names for various Firefox variants FIREFOX_VARIANTS = { - 'firefox': ['*.default-release'], - 'floorp': ['*.default-release'], - 'zen': ['*.Default (alpha)', '*.default', 'Default Profile'] + 'firefox': ['*.default-release', '*.Default User', '*.Profile 1'], + 'firefox nightly': ['*.default-release', '*.Default User', '*.Profile 1'], + 'floorp': ['*.default-release', '*.Default User', '*.Profile 1'], + 'zen': ['*.Default (alpha)', '*.default', '*.Default Profile'] } # Factory function From 83337a2b52bcc31312392019602e6b8686e99b17 Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Fri, 25 Apr 2025 01:28:21 +0600 Subject: [PATCH 33/37] Enhance dynamic profile support by allowing multiple glob patterns for profile matching --- plugin/browsers.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/plugin/browsers.py b/plugin/browsers.py index 9754c70..3d5308b 100644 --- a/plugin/browsers.py +++ b/plugin/browsers.py @@ -30,20 +30,29 @@ # Constants for timestamp conversion CHROMIUM_EPOCH_OFFSET = 11644473600 # seconds from 1601 to 1970 + class Browser: - def __init__(self, name, query, timestamp_type='chromium', custom_path=None, dynamic_profile=False, profile_glob=None, db_file='History'): + def __init__(self, name, query, timestamp_type='chromium', custom_path=None, dynamic_profile=False, profile_glob=None, profile_globs=None, db_file='History'): self.name = name self.query = query self.timestamp_type = timestamp_type self.dynamic_profile = dynamic_profile - self.profile_glob = profile_glob self.db_file = db_file if custom_path: self.database_path = Path(custom_path) elif dynamic_profile: profile_base = BROWSER_PATHS.get(name) - profile_folder = next(Path(profile_base).glob(profile_glob)) + profile_folder = None + # Support both single glob and multiple globs + patterns = profile_globs if profile_globs else [profile_glob] + for pattern in patterns: + matches = list(Path(profile_base).glob(pattern)) + if matches: + profile_folder = matches[0] + break + if not profile_folder: + raise FileNotFoundError(f"No matching profile found for {name} with patterns: {patterns}") self.database_path = profile_base / profile_folder / db_file else: self.database_path = BROWSER_PATHS.get(name) From 3ce244d22b7800b6ce73e2db6aa854b48258e8ef Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Fri, 25 Apr 2025 01:40:29 +0600 Subject: [PATCH 34/37] Refactor remove_duplicates function to improve efficiency and clarity --- plugin/main.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/plugin/main.py b/plugin/main.py index ead9a3a..9e5f8e1 100644 --- a/plugin/main.py +++ b/plugin/main.py @@ -7,10 +7,13 @@ DEFAULT_BROWSER = 'chrome' def remove_duplicates(results): + seen = set() + unique_results = [] for item in results: - if item in results: - results.remove(item) - return results + if item not in seen: + unique_results.append(item) + seen.add(item) + return unique_results class BrowserHistory(Flox): @@ -69,4 +72,4 @@ def copy_to_clipboard(self, data): self.show_msg("Copied!", f"{data}") if __name__ == "__main__": - BrowserHistory() + BrowserHistory().run() From f986738c8add07708c78ea92ee47f3c0d3480357 Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Fri, 25 Apr 2025 01:40:58 +0600 Subject: [PATCH 35/37] Bump version to 0.8.9 in plugin.json --- plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.json b/plugin.json index 3fe5488..0e8a5e7 100644 --- a/plugin.json +++ b/plugin.json @@ -4,7 +4,7 @@ "Name": "Browser History", "Description": "Search your Web Browser history", "Author": "Garulf", - "Version": "0.8.7", + "Version": "0.8.9", "Language": "python", "Website": "https://github.com/Garulf/browser-history", "IcoPath": "./icon.png", From a7481f5e6c6fa497d5367e7447abd4d85f6090af Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Fri, 25 Apr 2025 01:56:10 +0600 Subject: [PATCH 36/37] Refactor Browser class initialization to streamline profile handling and remove unused parameters --- plugin/browsers.py | 47 +++++++++++++++++++--------------------------- 1 file changed, 19 insertions(+), 28 deletions(-) diff --git a/plugin/browsers.py b/plugin/browsers.py index 3d5308b..ba18412 100644 --- a/plugin/browsers.py +++ b/plugin/browsers.py @@ -32,7 +32,7 @@ class Browser: - def __init__(self, name, query, timestamp_type='chromium', custom_path=None, dynamic_profile=False, profile_glob=None, profile_globs=None, db_file='History'): + def __init__(self, name, query, timestamp_type='chromium', custom_path=None, dynamic_profile=False, db_file='History'): self.name = name self.query = query self.timestamp_type = timestamp_type @@ -43,17 +43,16 @@ def __init__(self, name, query, timestamp_type='chromium', custom_path=None, dyn self.database_path = Path(custom_path) elif dynamic_profile: profile_base = BROWSER_PATHS.get(name) - profile_folder = None - # Support both single glob and multiple globs - patterns = profile_globs if profile_globs else [profile_glob] - for pattern in patterns: - matches = list(Path(profile_base).glob(pattern)) - if matches: - profile_folder = matches[0] + if not profile_base or not Path(profile_base).exists(): + raise FileNotFoundError(f"Profile base not found for {name}: {profile_base}") + + for profile_folder in Path(profile_base).iterdir(): + candidate_db = profile_folder / db_file + if candidate_db.exists(): + self.database_path = candidate_db break - if not profile_folder: - raise FileNotFoundError(f"No matching profile found for {name} with patterns: {patterns}") - self.database_path = profile_base / profile_folder / db_file + else: + raise FileNotFoundError(f"No valid profile found with {db_file} in {profile_base}") else: self.database_path = BROWSER_PATHS.get(name) @@ -84,6 +83,7 @@ def convert_timestamp(self, raw_time): elif self.timestamp_type == 'unix_us': return datetime.fromtimestamp(raw_time / 1_000_000) + class HistoryItem: def __init__(self, browser, url, title, last_visit_time): self.browser = browser @@ -94,33 +94,24 @@ def __init__(self, browser, url, title, last_visit_time): def timestamp(self): return self.browser.convert_timestamp(self.last_visit_time) + # Queries CHROMIUM_QUERY = 'SELECT url, title, last_visit_time FROM urls ORDER BY last_visit_time DESC' FIREFOX_QUERY = 'SELECT url, title, visit_date FROM moz_places INNER JOIN moz_historyvisits ON moz_historyvisits.place_id = moz_places.id ORDER BY visit_date DESC' -# These are the default profile names for various Firefox variants -FIREFOX_VARIANTS = { - 'firefox': ['*.default-release', '*.Default User', '*.Profile 1'], - 'firefox nightly': ['*.default-release', '*.Default User', '*.Profile 1'], - 'floorp': ['*.default-release', '*.Default User', '*.Profile 1'], - 'zen': ['*.Default (alpha)', '*.default', '*.Default Profile'] -} - # Factory function def get(browser_name, custom_profile_path=None): browser_name = browser_name.lower() if browser_name in ['chrome', 'edge', 'brave', 'brave nightly', 'opera', 'vivaldi', 'arc', 'thorium']: return Browser(browser_name, CHROMIUM_QUERY, 'chromium') - elif browser_name in FIREFOX_VARIANTS: - profile_globs = FIREFOX_VARIANTS[browser_name] + elif browser_name in ['firefox', 'firefox nightly', 'zen', 'floorp']: return Browser( - browser_name, - FIREFOX_QUERY, - 'unix_us', - dynamic_profile=True, - profile_globs=profile_globs, - db_file='places.sqlite' - ) + browser_name, + FIREFOX_QUERY, + 'unix_us', + dynamic_profile=True, + db_file='places.sqlite' + ) elif browser_name == 'custom (chromium)': return Browser('custom', CHROMIUM_QUERY, 'chromium', custom_path=Path(custom_profile_path) / 'History') elif browser_name == 'custom (firefox)': From efe3a49d7bd7d0d31889c4a5b3b1d88da48702b6 Mon Sep 17 00:00:00 2001 From: Fahim Ahmed Date: Fri, 25 Apr 2025 01:56:28 +0600 Subject: [PATCH 37/37] Bump version to 0.9.0 in plugin.json --- plugin.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugin.json b/plugin.json index 0e8a5e7..99ac4a6 100644 --- a/plugin.json +++ b/plugin.json @@ -4,7 +4,7 @@ "Name": "Browser History", "Description": "Search your Web Browser history", "Author": "Garulf", - "Version": "0.8.9", + "Version": "0.9.0", "Language": "python", "Website": "https://github.com/Garulf/browser-history", "IcoPath": "./icon.png",