├── archivefox.xpi ├── add-on ├── icons │ ├── LICENSE │ └── message.svg ├── settings │ ├── options.html │ └── options.js ├── manifest.json └── background.js ├── app ├── archivefox.json ├── README.md └── archivefox.py ├── README.md └── LICENSE /archivefox.xpi: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/layderv/archivefox/HEAD/archivefox.xpi -------------------------------------------------------------------------------- /add-on/icons/LICENSE: -------------------------------------------------------------------------------- 1 | The icon used here is taken from the "Miscellany Web icons" set by Maria & Guillem (https://www.iconfinder.com/andromina), and is used under the Creative Commons (Attribution 3.0 Unported) license. 2 | -------------------------------------------------------------------------------- /app/archivefox.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "archivefox", 3 | "description": "ArchiveBox with Firefox", 4 | "path": "/home/user/archivefox/app/archivefox.py", 5 | "type": "stdio", 6 | "allowed_extensions": [ "{f4a72ecd-c6d6-4228-bea2-2a3d4e6915cd}" ] 7 | } 8 | -------------------------------------------------------------------------------- /app/README.md: -------------------------------------------------------------------------------- 1 | Installation instructions (for each user): 2 | 3 | * Follow [this](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Native_manifests#manifest_location) guide; or 4 | * Run: `mkdir -p ~/.mozilla/native-messaging-hosts/` to create the directory if not already present, followed by `cp archive.json ~/.mozilla/native-messaging-hosts/` 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ArchiveFox 2 | 3 | ArchiveFox is a Firefox extension developed to use [ArchiveBox](https://github.com/ArchiveBox/ArchiveBox) without leaving the browser. Clicking this extension's button will ask ArchiveBox to add the currently active tab's url to the archive. 4 | 5 | ### Installation instructions 6 | 7 | * Install the extension 8 | * Install the native application (see the `app` folder) 9 | 10 | ### Usage 11 | 12 | Simply click the extension button of the extension. 13 | 14 | -------------------------------------------------------------------------------- /add-on/settings/options.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 |ArchiveFox settings
12 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /add-on/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | 3 | "description": "Archive webpages with ArchiveBox", 4 | "manifest_version": 2, 5 | "name": "ArchiveFox", 6 | "version": "1.0", 7 | "icons": { 8 | "48": "icons/message.svg" 9 | }, 10 | 11 | "browser_specific_settings": { 12 | "gecko": { 13 | "id": "{f4a72ecd-c6d6-4228-bea2-2a3d4e6915cd}", 14 | "strict_min_version": "57.0" 15 | } 16 | }, 17 | 18 | "options_ui": { 19 | "page": "settings/options.html" 20 | }, 21 | 22 | "background": { 23 | "scripts": ["background.js"] 24 | }, 25 | 26 | "browser_action": { 27 | "default_icon": "icons/message.svg" 28 | }, 29 | 30 | "permissions": ["nativeMessaging", "storage", "tabs"] 31 | 32 | } 33 | -------------------------------------------------------------------------------- /add-on/settings/options.js: -------------------------------------------------------------------------------- 1 | function saveOptions(e) { 2 | e.preventDefault(); 3 | browser.storage.local.set({ 4 | archiveBoxDirectory: document.querySelector("#directory").value 5 | }); 6 | } 7 | 8 | function restoreOptions() { 9 | 10 | function setCurrentChoice(result) { 11 | document.querySelector("#directory").value = result.archiveBoxDirectory || "~/archivebox"; 12 | } 13 | 14 | function onError(error) { 15 | console.log(`Error: ${error}`); 16 | } 17 | 18 | let getting = browser.storage.local.get("archiveBoxDirectory"); 19 | getting.then(setCurrentChoice, onError); 20 | } 21 | 22 | document.addEventListener("DOMContentLoaded", restoreOptions); 23 | document.querySelector("form").addEventListener("submit", saveOptions); 24 | 25 | -------------------------------------------------------------------------------- /add-on/background.js: -------------------------------------------------------------------------------- 1 | function getActiveTab() { 2 | return browser.tabs.query({ 3 | currentWindow: true, 4 | active: true 5 | }); 6 | } 7 | 8 | var port = browser.runtime.connectNative("archivefox"); 9 | 10 | port.onMessage.addListener((response) => { 11 | console.log("Received: " + response); 12 | }); 13 | 14 | browser.browserAction.onClicked.addListener(() => { 15 | browser.storage.local.get('archiveBoxDirectory') 16 | .then((res) => { 17 | getActiveTab().then((tabs) => { 18 | const tab = tabs[0]; 19 | if (tab) { 20 | let directory = res['archiveBoxDirectory'] || '~/archivebox'; 21 | let s = JSON.stringify({directory: directory, url: tab.url}); 22 | console.log("Sending: ", s); 23 | port.postMessage(s); 24 | } 25 | }); 26 | }); 27 | }); 28 | 29 | -------------------------------------------------------------------------------- /add-on/icons/message.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 layderv 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /app/archivefox.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import sys 4 | import json 5 | import struct 6 | import os 7 | import subprocess 8 | 9 | def getMessage(): 10 | rawLength = sys.stdin.buffer.read(4) 11 | if len(rawLength) == 0: 12 | sys.exit(0) 13 | messageLength = struct.unpack('@I', rawLength)[0] 14 | message = sys.stdin.buffer.read(messageLength).decode('utf-8') 15 | return json.loads(message) 16 | 17 | def encodeMessage(messageContent): 18 | encodedContent = json.dumps(messageContent).encode('utf-8') 19 | encodedLength = struct.pack('@I', len(encodedContent)) 20 | return {'length': encodedLength, 'content': encodedContent} 21 | 22 | def sendMessage(encodedMessage): 23 | sys.stdout.buffer.write(encodedMessage['length']) 24 | sys.stdout.buffer.write(encodedMessage['content']) 25 | sys.stdout.buffer.flush() 26 | 27 | def main(): 28 | while True: 29 | receivedMessage = getMessage() 30 | opts = json.loads(receivedMessage) 31 | os.chdir(os.path.expanduser(opts['directory'])) 32 | subprocess.Popen(['archivebox', 'add', opts['url']], 33 | stdout=subprocess.DEVNULL, 34 | stderr=subprocess.DEVNULL, 35 | stdin=subprocess.DEVNULL) 36 | 37 | sendMessage(encodeMessage("added")) 38 | 39 | if __name__ == '__main__': 40 | main() 41 | 42 | --------------------------------------------------------------------------------