mirror of
https://github.com/andwati/kget-capture.git
synced 2026-10-31 01:12:15 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6ed564f802
|
||
|
|
a46226a0db
|
||
|
|
a2cd4184e3
|
@@ -83,9 +83,11 @@ git push origin v1.0.0
|
|||||||
|
|
||||||
## Known limitations
|
## Known limitations
|
||||||
|
|
||||||
- **`blob:`/`data:` URLs can't be captured.** There's no real network URL to
|
- **`blob:`/`data:`/`file:`/`filesystem:` URLs can't be captured.** There's
|
||||||
hand to KGet, so these downloads proceed normally through Chrome (a console
|
no real network URL to hand to KGet — this includes saving a page you
|
||||||
warning is logged in the extension's service worker).
|
navigated to directly via `file://` (Ctrl+S on it is still a "download" as
|
||||||
|
far as Chrome is concerned) — so these downloads proceed normally through
|
||||||
|
Chrome (a console warning is logged in the extension's service worker).
|
||||||
- **Auto-capture has a brief head start.** Manifest V3 removed blocking
|
- **Auto-capture has a brief head start.** Manifest V3 removed blocking
|
||||||
`webRequest`, so Chrome begins the download before the extension can cancel
|
`webRequest`, so Chrome begins the download before the extension can cancel
|
||||||
it. A small/partial temp file may transiently appear before it's erased.
|
it. A small/partial temp file may transiently appear before it's erased.
|
||||||
@@ -95,13 +97,6 @@ git push origin v1.0.0
|
|||||||
only skips its interactive "New Download" dialog when given a real
|
only skips its interactive "New Download" dialog when given a real
|
||||||
destination path up front, so instant capture and an editable prompt are
|
destination path up front, so instant capture and an editable prompt are
|
||||||
mutually exclusive given KGet's D-Bus API.
|
mutually exclusive given KGet's D-Bus API.
|
||||||
- **The context-menu capture can't always get the correct filename.**
|
|
||||||
Auto-capture gets the real filename for free from Chrome's own
|
|
||||||
`Content-Disposition` resolution. Right-click captures don't have that —
|
|
||||||
the extension only has the raw URL — so KGet falls back to guessing from
|
|
||||||
the URL path, which is wrong for opaque/signed URLs (e.g. cloud storage
|
|
||||||
links). Fixing this would require broad host permissions and an extra HTTP
|
|
||||||
request per right-click, which isn't worth it for now.
|
|
||||||
- **Chrome may still show its own "Save As" dialog** if you skipped step 5 of
|
- **Chrome may still show its own "Save As" dialog** if you skipped step 5 of
|
||||||
Install above — see there for why and how to fix it.
|
Install above — see there for why and how to fix it.
|
||||||
- **KGet must already be running.** If it's not, captures fail silently from
|
- **KGet must already be running.** If it's not, captures fail silently from
|
||||||
|
|||||||
+41
-4
@@ -11,6 +11,18 @@ async function refreshBadge() {
|
|||||||
updateBadge(autoCaptureEnabled);
|
updateBadge(autoCaptureEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// KGet is a network download manager -- it can't "download" a URL that's
|
||||||
|
// already local (or embedded in the page), so don't hand it these schemes.
|
||||||
|
// file: shows up when saving a page you navigated to directly (Ctrl+S on a
|
||||||
|
// file:// URL is still a "download" as far as chrome.downloads is
|
||||||
|
// concerned), and KGet's addTransfer rejects it with a confusing
|
||||||
|
// "protocol is not supported" dialog if we try.
|
||||||
|
const UNSUPPORTED_SCHEMES = ["blob:", "data:", "file:", "filesystem:"];
|
||||||
|
|
||||||
|
function isCapturableUrl(url) {
|
||||||
|
return !UNSUPPORTED_SCHEMES.some((scheme) => url.startsWith(scheme));
|
||||||
|
}
|
||||||
|
|
||||||
function sendToKGet(url, filename) {
|
function sendToKGet(url, filename) {
|
||||||
chrome.runtime.sendNativeMessage(
|
chrome.runtime.sendNativeMessage(
|
||||||
NATIVE_HOST,
|
NATIVE_HOST,
|
||||||
@@ -40,11 +52,35 @@ chrome.runtime.onInstalled.addListener(async (details) => {
|
|||||||
refreshBadge();
|
refreshBadge();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Downloads started from the context menu, tracked so captureDownload()
|
||||||
|
// still resolves/forwards their real filename even when auto-capture is
|
||||||
|
// toggled off -- a right-click "Download with KGet" should always work.
|
||||||
|
const pendingContextMenuDownloads = new Set();
|
||||||
|
|
||||||
chrome.contextMenus.onClicked.addListener((info) => {
|
chrome.contextMenus.onClicked.addListener((info) => {
|
||||||
if (info.menuItemId !== MENU_ID) return;
|
if (info.menuItemId !== MENU_ID) return;
|
||||||
const url = info.linkUrl || info.srcUrl;
|
const url = info.linkUrl || info.srcUrl;
|
||||||
if (!url) return;
|
if (!url) return;
|
||||||
sendToKGet(url, null);
|
if (!isCapturableUrl(url)) {
|
||||||
|
console.warn("[kget-capture] cannot hand a local/embedded URL to KGet:", url);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Route through chrome.downloads instead of sending the raw URL straight
|
||||||
|
// to KGet -- this reuses onDeterminingFilename/captureDownload below to
|
||||||
|
// get Chrome's real resolved filename (Content-Disposition, etc.) instead
|
||||||
|
// of leaving KGet to guess one from the bare URL, which drops query
|
||||||
|
// strings and gets opaque/signed URLs wrong.
|
||||||
|
chrome.downloads.download({ url }, (downloadId) => {
|
||||||
|
if (chrome.runtime.lastError || downloadId === undefined) {
|
||||||
|
console.error(
|
||||||
|
"[kget-capture] couldn't start download to resolve filename, falling back to raw URL:",
|
||||||
|
chrome.runtime.lastError && chrome.runtime.lastError.message
|
||||||
|
);
|
||||||
|
sendToKGet(url, null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
pendingContextMenuDownloads.add(downloadId);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// onDeterminingFilename (rather than onCreated) fires once Chrome has
|
// onDeterminingFilename (rather than onCreated) fires once Chrome has
|
||||||
@@ -59,12 +95,13 @@ chrome.downloads.onDeterminingFilename.addListener((item, suggest) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function captureDownload(item) {
|
async function captureDownload(item) {
|
||||||
|
const isContextMenuCapture = pendingContextMenuDownloads.delete(item.id);
|
||||||
const { autoCaptureEnabled } = await chrome.storage.local.get({ autoCaptureEnabled: true });
|
const { autoCaptureEnabled } = await chrome.storage.local.get({ autoCaptureEnabled: true });
|
||||||
if (!autoCaptureEnabled) return;
|
if (!autoCaptureEnabled && !isContextMenuCapture) return;
|
||||||
|
|
||||||
const url = item.finalUrl || item.url;
|
const url = item.finalUrl || item.url;
|
||||||
if (url.startsWith("blob:") || url.startsWith("data:")) {
|
if (!isCapturableUrl(url)) {
|
||||||
console.warn("[kget-capture] cannot redirect blob:/data: URL, letting Chrome handle it:", url);
|
console.warn("[kget-capture] cannot hand a local/embedded URL to KGet, letting Chrome handle it:", url);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"manifest_version": 3,
|
"manifest_version": 3,
|
||||||
"name": "KGet Capture",
|
"name": "KGet Capture",
|
||||||
"version": "1.0.2",
|
"version": "1.0.4",
|
||||||
"description": "Send browser downloads to KGet (KDE Download Manager) on Linux.",
|
"description": "Send browser downloads to KGet (KDE Download Manager) on Linux.",
|
||||||
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtM1nFg5pPd3UBzynUJHj6aUy7bjudZaX9AQTWGMqAX3fXylw1lubSzP1/BaS9ivvgOJg/loyEgzEZlcNPzblFVlg8BqEJ3tntdk2OzYvUs0pUdcPzzyjso9TykCkUUTI7ev8JkreW/FYlinfvNwSYN4/c1s3h8/I6XHtxOdmO1G3bS98+BQaXPldbDmxoKwyWR8u1f8gTO2Mg3GY4BHau3RWf1kgtNzh20p9wE3sEFdWkFu7ArgRCIrWLh6m3O6lolnHQSCWPwtOTx893Qkp9Eu6FwUnoWc8rsYx8sqa0eNCTCS6Kjr1Iu3yEJ5y9390jUiDZTQ/c62ueCTQaYv2bwIDAQAB",
|
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtM1nFg5pPd3UBzynUJHj6aUy7bjudZaX9AQTWGMqAX3fXylw1lubSzP1/BaS9ivvgOJg/loyEgzEZlcNPzblFVlg8BqEJ3tntdk2OzYvUs0pUdcPzzyjso9TykCkUUTI7ev8JkreW/FYlinfvNwSYN4/c1s3h8/I6XHtxOdmO1G3bS98+BQaXPldbDmxoKwyWR8u1f8gTO2Mg3GY4BHau3RWf1kgtNzh20p9wE3sEFdWkFu7ArgRCIrWLh6m3O6lolnHQSCWPwtOTx893Qkp9Eu6FwUnoWc8rsYx8sqa0eNCTCS6Kjr1Iu3yEJ5y9390jUiDZTQ/c62ueCTQaYv2bwIDAQAB",
|
||||||
"permissions": ["downloads", "contextMenus", "storage", "nativeMessaging"],
|
"permissions": ["downloads", "contextMenus", "storage", "nativeMessaging"],
|
||||||
|
|||||||
@@ -49,7 +49,12 @@ def add_transfer(url, filename):
|
|||||||
# sidesteps KGet's own filename guess, which is derived purely from the
|
# sidesteps KGet's own filename guess, which is derived purely from the
|
||||||
# URL path and gets it wrong for opaque/signed URLs.
|
# URL path and gets it wrong for opaque/signed URLs.
|
||||||
dest_dir = default_download_dir()
|
dest_dir = default_download_dir()
|
||||||
dest = os.path.join(dest_dir, filename) if filename else dest_dir
|
# A bare directory path with no trailing slash (e.g. "/home/ian/Downloads")
|
||||||
|
# gets misparsed by KGet's addTransfer as dir="/home/ian" + filename=
|
||||||
|
# "Downloads" -- the same split it applies to a real dir/filename path --
|
||||||
|
# landing the transfer in the parent directory instead. A trailing slash
|
||||||
|
# keeps it unambiguous when we don't have a real filename to append.
|
||||||
|
dest = os.path.join(dest_dir, filename) if filename else dest_dir.rstrip("/") + "/"
|
||||||
cmd = [
|
cmd = [
|
||||||
"dbus-send",
|
"dbus-send",
|
||||||
"--print-reply",
|
"--print-reply",
|
||||||
|
|||||||
Reference in New Issue
Block a user