diff --git a/.gitignore b/.gitignore index e9f5d9a..40211b2 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ copypatch*.xpi +node_modules +package-lock.json diff --git a/Makefile b/Makefile index 6496d73..fdc2580 100644 --- a/Makefile +++ b/Makefile @@ -25,16 +25,25 @@ ARCHIVE_NAME=$(PACKAGE_NAME)-$(RELEASE_TAG).xpi PACKAGE_FILES= \ manifest.json \ copypatch*.png \ + background.html \ background-script.js \ content-script.js \ - api/CopyPatch/ \ + node_modules/email-addresses/LICENSE \ + node_modules/email-addresses/lib/email-addresses.js \ COPYING UPDATE_VERSION='s|"version":.*|"version": "$(VERSION)",|' -all package: clean $(PACKAGE_FILES) +all package: clean node_modules $(PACKAGE_FILES) zip -r $(ARCHIVE_NAME) $(PACKAGE_FILES) +node_modules: package.json + npm install + sed -i 's/(this)/(globalThis)/' node_modules/email-addresses/lib/email-addresses.js + +distclean: clean + rm -rf node_modules + clean: rm -f $(ARCHIVE_NAME) @@ -51,4 +60,4 @@ release: git commit -s manifest.json -m "Bump version number" git tag -as $(VERSION) -m "Release $(VERSION)" -.PHONY: clean release +.PHONY: clean distclean release diff --git a/api/CopyPatch/implementation.js b/api/CopyPatch/implementation.js deleted file mode 100644 index 3c5cc9b..0000000 --- a/api/CopyPatch/implementation.js +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copy Patch Thunderbird Add-On - * - * Copyright (c) Jan Kiszka, 2019-2020 - * - * Authors: - * Jan Kiszka - * - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -const { ExtensionCommon } = ChromeUtils.import("resource://gre/modules/ExtensionCommon.jsm"); -const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); -const { jsmime } = ChromeUtils.import("resource:///modules/jsmime.jsm"); - -function do_getSelectedMessage(windowId) -{ - let win = Services.wm.getOuterWindowWithId(windowId); - - if (win.GetNumSelectedMessages() === 0) { - return null; - } - - let selectedMsg = win.gFolderDisplay.selectedMessage; - let msgURI = selectedMsg.folder.getUriForMsg(selectedMsg); - - let msgStream = Components.classes["@mozilla.org/network/sync-stream-listener;1"].createInstance(); - let consumer = msgStream.QueryInterface(Components.interfaces.nsIInputStream); - let scriptInput = Components.classes["@mozilla.org/scriptableinputstream;1"].createInstance(); - let scriptInputStream = scriptInput.QueryInterface(Components.interfaces.nsIScriptableInputStream); - scriptInputStream.init(consumer); - - let service = win.messenger.messageServiceFromURI(msgURI); - service.streamMessage(msgURI, msgStream, win.msgWindow, null, false, null); - - let msgHeader; - let msgBody = ""; - let done = false; - let emitter = { - startPart: function(partNum, header) - { - if (!msgHeader) { - msgHeader = header; - } - }, - - deliverPartData: function(partNum, data) - { - if (!done) { - msgBody += data; - } - }, - - endPart: function(partNum) - { - done = true; - }, - }; - - let opts = { - bodyformat: "decode", - strformat: "unicode", - }; - let parser = new jsmime.MimeParser(emitter, opts); - - while (scriptInputStream.available()) { - let data = scriptInputStream.read(scriptInputStream.available()); - parser.deliverData(data); - } - parser.deliverEOF(); - - let hdr = { - from: msgHeader.get("from"), - replyTo: msgHeader.get("reply-to"), - date: msgHeader.get("date"), - subject: msgHeader.get("subject") - }; - return {header: hdr, body: msgBody}; -} - -var CopyPatch = class extends ExtensionCommon.ExtensionAPI { - getAPI(context) { - return { - CopyPatch: { - async getSelectedMessage(windowId) { - return do_getSelectedMessage(windowId); - }, - } - } - } -}; diff --git a/api/CopyPatch/schema.json b/api/CopyPatch/schema.json deleted file mode 100644 index 4e3f62a..0000000 --- a/api/CopyPatch/schema.json +++ /dev/null @@ -1,18 +0,0 @@ -[ - { - "namespace": "CopyPatch", - "functions": [ - { - "name": "getSelectedMessage", - "type": "function", - "async": true, - "parameters": [ - { - "name": "windowId", - "type": "integer" - } - ] - } - ] - } -] diff --git a/background-script.js b/background-script.js index 3951a9f..ad00800 100644 --- a/background-script.js +++ b/background-script.js @@ -1,33 +1,99 @@ /* * Copy Patch Thunderbird Add-On * - * Copyright (c) Jan Kiszka, 2019-2020 + * Copyright (c) Jan Kiszka, 2019-2023 + * Copyright (c) John Bieling, 2023 * * Authors: * Jan Kiszka + * John Bieling * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -async function getCurrentWindow() +import "./node_modules/email-addresses/lib/email-addresses.js"; + +function getFirstHeader(arr) +{ + if (Array.isArray(arr) && arr.length > 0) { + return arr[0]; + } + return undefined; +} + +function getAllHeader(arr) +{ + if (Array.isArray(arr) && arr.length > 0) { + return arr; + } + return undefined; +} + +function parseDisplayName(addr) { - let windows = await messenger.windows.getAll(); + let rv = emailAddresses.parseOneAddress(addr); + return { + name: rv.name, + email: rv.address, + } +} - for (let window of windows) { - if ((window.type === "messageDisplay" || window.type === "normal") - && window.focused === true) - return window; +/* Find first text/plain body */ +function getBody(parts) +{ + /* First check all parts in this level */ + for (let part of parts) { + if (part.body && part.contentType == "text/plain") { + return part.body; + } + } + /* Now check all subparts */ + for (let part of parts) { + if (part.parts) { + let body = getBody(part.parts); + if (body) { + return body; + } + } } return null; } +async function getMsgData(messageId) +{ + let full = await browser.messages.getFull(messageId); + let date = await getFirstHeader(full.headers["date"]); + let from = await getAllHeader(full.headers["from"]); + let replyTo = await getAllHeader(full.headers["reply-to"]); + let subject = await getFirstHeader(full.headers["subject"]); + let body = getBody(full.parts); + + if (!body) { + return null; + } + + return { + header: { + date: date ? new Date(date) : date, + from: from ? from.map(addr => parseDisplayName(addr)) : from, + replyTo: replyTo ? replyTo.map(addr => parseDisplayName(addr)) : replyTo, + subject: subject + }, + body: body, + isPatch: (body.indexOf("\n---") >= 0 && body.indexOf("\n+++") >= 0) || + body.indexOf("\ndiff --git") >= 0 + } +} + function main() { messenger.runtime.onMessage.addListener((request, sender, sendResponse) => { if (request.action === "getMsg") { - return messenger.CopyPatch.getSelectedMessage(sender.tab.windowId); + return browser.messageDisplay.getDisplayedMessage(sender.tab.id).then( + msg => getMsgData(msg.id) + ); } if (request.action === "clipboardWrite") { navigator.clipboard.writeText(request.text); @@ -40,33 +106,29 @@ function main() messenger.messageDisplayAction.setBadgeText( {tabId: sender.tab.id, text: null}); }, 500); + return Promise.resolve(); } + return false; }); messenger.messageDisplayAction.onClicked.addListener(tab => { messenger.tabs.executeScript(tab.id, {file: "content-script.js"}); }); - messenger.commands.onCommand.addListener(async (name) => { + messenger.commands.onCommand.addListener(async (name, tab) => { if (name !== "copyPatch") { return; } - let window = await getCurrentWindow(); - if (window) { - let tabs = await messenger.tabs.query({windowId: window.id}); - if (await messenger.messageDisplayAction.isEnabled({tabId: tabs[0].id})) { - messenger.tabs.executeScript(tabs[0].id, - {file: "content-script.js"}); - } + if (await messenger.messageDisplayAction.isEnabled({tabId: tab.id})) { + messenger.tabs.executeScript(tab.id, {file: "content-script.js"}); } }); messenger.messageDisplay.onMessageDisplayed.addListener(async (tab, message) => { - msg = await messenger.CopyPatch.getSelectedMessage(tab.windowId); + let msg = await getMsgData(message.id); - /* detect patch pattern in the body */ - if ((msg.body.indexOf("\n---") >= 0 && msg.body.indexOf("\n+++") >= 0) || msg.body.indexOf("\ndiff --git") >= 0) { + if (msg && msg.isPatch) { messenger.messageDisplayAction.enable(tab.id); } else { messenger.messageDisplayAction.disable(tab.id); diff --git a/background.html b/background.html new file mode 100644 index 0000000..50e5110 --- /dev/null +++ b/background.html @@ -0,0 +1,7 @@ + + + + + + + diff --git a/manifest.json b/manifest.json index 125d1b1..9d7421b 100644 --- a/manifest.json +++ b/manifest.json @@ -3,23 +3,20 @@ "applications": { "gecko": { "id": "copypatch@kiszka.org", - "strict_min_version": "78.4.0", - "strict_max_version": "102.*" + "strict_min_version": "102.4.0" } }, "name": "Copy Patch", "description": "Copy email content to clipboard for application as patch", "author": "Jan Kiszka", "version": "2.1.3", - "homepage_url": "http://git.kiszka.org/copypatch.git", + "homepage_url": "https://github.com/jan-kiszka/copypatch", "icons": { "32": "copypatch.png", "64": "copypatch64.png" }, "background": { - "scripts": [ - "background-script.js" - ] + "page": "background.html" }, "permissions": [ "messagesRead", @@ -37,21 +34,5 @@ }, "description": "Copy as patch" } - }, - "experiment_apis": { - "CopyPatch": { - "schema": "api/CopyPatch/schema.json", - "parent": { - "scopes": [ - "addon_parent" - ], - "paths": [ - [ - "CopyPatch" - ] - ], - "script": "api/CopyPatch/implementation.js" - } - } } } diff --git a/package.json b/package.json new file mode 100644 index 0000000..4110079 --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +{ + "name": "copypatch", + "description": "Copy email content to clipboard for application as patch", + "repository": { + "type": "git", + "url": "https://github.com/jan-kiszka/copypatch.git" + }, + "license": "MPL-2.0", + "dependencies": { + "email-addresses": "^5.0.0" + } +}