mirror of
https://github.com/andwati/kget-capture.git
synced 2026-10-18 11:12:57 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
59b18e69a8
|
||
|
|
816ae5797a
|
||
|
|
79b72494db
|
||
|
|
08744915c4
|
||
|
|
bd24586113
|
||
|
|
6ef2133760
|
||
|
|
6ed564f802
|
||
|
|
a46226a0db
|
@@ -16,12 +16,48 @@ jobs:
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Package extension
|
||||
# Chrome requires manifest.json at the zip root, both for Web Store
|
||||
# upload and for manually loading unpacked -- so this one archives
|
||||
# only extension/'s contents, not the whole repo. Keeps the pinned
|
||||
# "key" field, so unpacked loads get the stable, predictable ID.
|
||||
run: |
|
||||
cd extension
|
||||
zip -r "../kget-capture-${GITHUB_REF_NAME}.zip" .
|
||||
zip -r "../kget-capture-extension-${GITHUB_REF_NAME}.zip" .
|
||||
|
||||
- name: Package extension for Web Store upload
|
||||
# The Chrome Web Store dashboard rejects any manifest containing a
|
||||
# "key" field ("key field is not allowed in manifest") -- it assigns
|
||||
# its own permanent ID on first publish instead. Strip it from a
|
||||
# copy so this zip is upload-ready; the other extension zip above
|
||||
# keeps "key" for stable unpacked/dev IDs.
|
||||
run: |
|
||||
rm -rf /tmp/store-extension
|
||||
cp -r extension /tmp/store-extension
|
||||
python3 -c "
|
||||
import json
|
||||
path = '/tmp/store-extension/manifest.json'
|
||||
with open(path) as f:
|
||||
manifest = json.load(f)
|
||||
manifest.pop('key', None)
|
||||
with open(path, 'w') as f:
|
||||
json.dump(manifest, f, indent=2)
|
||||
f.write('\n')
|
||||
"
|
||||
cd /tmp/store-extension
|
||||
zip -r "$OLDPWD/kget-capture-store-${GITHUB_REF_NAME}.zip" .
|
||||
|
||||
- name: Package source
|
||||
# Separately, the native-host installer bundle (install.sh,
|
||||
# native-host/, keys/) needs the whole repo. git archive only
|
||||
# includes tracked files, so the gitignored private key (never
|
||||
# committed) can't end up in it.
|
||||
run: git archive --format=zip -o "kget-capture-${GITHUB_REF_NAME}.zip" HEAD
|
||||
|
||||
- name: Create release
|
||||
uses: softprops/action-gh-release@v2
|
||||
with:
|
||||
files: kget-capture-${{ github.ref_name }}.zip
|
||||
files: |
|
||||
kget-capture-${{ github.ref_name }}.zip
|
||||
kget-capture-extension-${{ github.ref_name }}.zip
|
||||
kget-capture-store-${{ github.ref_name }}.zip
|
||||
generate_release_notes: true
|
||||
|
||||
@@ -1,56 +1,99 @@
|
||||
# KGet Capture
|
||||
|
||||
A Chrome (and other Chromium-browser) extension that sends downloads to
|
||||
[KGet](https://apps.kde.org/kget/), KDE's download manager, instead of (or in
|
||||
addition to) the browser's built-in downloader.
|
||||
A Chromium browser extension that sends downloads to
|
||||
[KGet](https://apps.kde.org/kget/), KDE's download manager, instead of the
|
||||
browser's built-in downloader.
|
||||
|
||||
- Right-click any link, image, video, or audio element and choose
|
||||
**"Download with KGet"** to send it straight to KGet — it starts
|
||||
downloading immediately into your Downloads folder, no dialog.
|
||||
- A toolbar toggle enables **auto-capture**: while on, every download Chrome
|
||||
starts is cancelled and handed off to KGet the same way, automatically,
|
||||
with the correct filename (resolved by Chrome itself, so it's right even
|
||||
for URLs whose path is just an opaque ID). Defaults to ON.
|
||||
- A "Download complete" desktop notification appears once a capture actually
|
||||
finishes downloading. This is driven by the extension itself (not KGet's
|
||||
own per-transfer notification, which KGet silently skips for downloads
|
||||
that finish very quickly — see Troubleshooting), so it fires reliably
|
||||
regardless of file size or speed.
|
||||
- Right-click a link/image/video/audio and choose **"Download with KGet"**.
|
||||
- Or toggle **auto-capture** from the toolbar so every download is handed off
|
||||
automatically, with the correct filename.
|
||||
- A desktop notification fires when a capture finishes downloading.
|
||||
|
||||
## Requirements
|
||||
|
||||
- Linux with KGet installed (and running, or set to autostart)
|
||||
- One of: Google Chrome, Chromium, Brave, Vivaldi, Microsoft Edge
|
||||
- `python3` and `dbus-send` on `PATH` (standard on virtually all Linux/KDE
|
||||
systems already)
|
||||
- `notify-send` (ships with most Linux desktops via libnotify) — optional,
|
||||
only used for the download-complete notification; captures still work
|
||||
without it, just silently
|
||||
- `kwriteconfig6` (ships with KDE Plasma) — optional, only used to disable
|
||||
KGet's own unreliable per-transfer notification so it doesn't double up
|
||||
with the extension's; install still works without it
|
||||
- Linux with KGet installed and running (or set to autostart)
|
||||
- A Chromium-based browser: Chrome, Chromium, Brave, Vivaldi, or Edge
|
||||
- `python3`, `dbus-send`
|
||||
- `curl` or `wget`, for the one-line remote installer below (skip if you'd
|
||||
rather download and run `install.sh` yourself)
|
||||
- `notify-send`, optional, for the completion notification
|
||||
- `kwriteconfig6` (KDE Frameworks), optional, silences KGet's own duplicate
|
||||
notification
|
||||
|
||||
Install everything on your distro:
|
||||
|
||||
**Fedora**
|
||||
```bash
|
||||
sudo dnf install kget python3 dbus-tools libnotify kf6-kconfig
|
||||
```
|
||||
|
||||
**Arch / Manjaro**
|
||||
```bash
|
||||
sudo pacman -S kget python dbus libnotify kconfig
|
||||
```
|
||||
|
||||
**Debian / Ubuntu / KDE neon**
|
||||
```bash
|
||||
sudo apt install kget python3 dbus-bin libnotify-bin kf6-kconfig-bin
|
||||
```
|
||||
|
||||
**openSUSE**
|
||||
```bash
|
||||
sudo zypper install kget python3 dbus-1 libnotify-tools kconfig-tools
|
||||
```
|
||||
|
||||
(`notify-send` and `kwriteconfig6` package names shift around between distro
|
||||
versions. If a command above fails, drop that one package and search your
|
||||
package manager for the binary name instead. Everything else still works
|
||||
without them.)
|
||||
|
||||
## Install
|
||||
|
||||
1. Install the extension from the Chrome Web Store: **`<CHROME_WEB_STORE_URL>`**
|
||||
*(placeholder, fill in once the listing is live)*.
|
||||
2. Click the extension's toolbar icon. The popup detects that the native
|
||||
messaging host isn't set up yet and shows a one-line command; run it in a
|
||||
terminal, then click "I ran it, check again" in the popup:
|
||||
```bash
|
||||
curl -fsSL https://raw.githubusercontent.com/andwati/kget-capture/main/install-remote.sh | bash
|
||||
```
|
||||
(or with `wget`: `wget -qO- https://raw.githubusercontent.com/andwati/kget-capture/main/install-remote.sh | bash`)
|
||||
|
||||
This downloads the latest release and runs its `install.sh`, which
|
||||
registers the native host for every supported browser it finds installed
|
||||
and prints any remaining setup warnings. No git clone required. If you'd
|
||||
rather run it yourself: download and extract `kget-capture-vX.Y.Z.zip`
|
||||
(the source archive, not `kget-capture-extension-vX.Y.Z.zip`) from the
|
||||
[Releases page](https://github.com/andwati/kget-capture/releases), then
|
||||
from inside that folder run `./install.sh` directly.
|
||||
3. Open your browser's download settings (`chrome://settings/downloads`,
|
||||
`brave://settings/downloads`, etc.) and turn **off** "Ask where to save
|
||||
each file before downloading". Chrome shows this dialog itself, before the
|
||||
extension gets a chance to intercept the download, so this one-time change
|
||||
is required (no extension API can do it for you).
|
||||
|
||||
### Building from source
|
||||
|
||||
Contributing, or want to run an unreleased version? Load the extension
|
||||
unpacked instead of step 1 above:
|
||||
|
||||
```bash
|
||||
git clone <this-repo-url> kget-capture
|
||||
git clone https://github.com/andwati/kget-capture.git
|
||||
cd kget-capture
|
||||
./install.sh
|
||||
```
|
||||
|
||||
`install.sh` registers a native messaging host for every supported browser it
|
||||
finds installed, then prints the extension's fixed ID and next steps:
|
||||
(Or, for an already-tagged version without cloning: download and extract
|
||||
`kget-capture-extension-vX.Y.Z.zip` from the
|
||||
[Releases page](https://github.com/andwati/kget-capture/releases) instead.
|
||||
It's already just `extension/`'s contents, unpacked at the zip root the way
|
||||
Chrome expects. You'll still need `kget-capture-vX.Y.Z.zip` for `install.sh`,
|
||||
per step 2 above.)
|
||||
|
||||
1. Open `chrome://extensions` (or your browser's equivalent).
|
||||
2. Enable **Developer mode** (top-right toggle).
|
||||
3. Click **Load unpacked** and select this repo's `extension/` directory.
|
||||
4. Confirm the loaded extension's ID matches the one `install.sh` printed.
|
||||
5. Open `chrome://settings/downloads` (or your browser's equivalent, e.g.
|
||||
`brave://settings/downloads`, `edge://settings/downloads`) and turn **off**
|
||||
"Ask where to save each file before downloading". Chrome shows this dialog
|
||||
itself, before the extension ever gets a chance to intercept the download —
|
||||
no extension API can read or suppress it, so this one-time setting change
|
||||
is required for captures to work without an extra prompt.
|
||||
Then in `chrome://extensions`: enable **Developer mode**, click **Load
|
||||
unpacked**, and select the `extension/` directory (from your clone, or the
|
||||
extracted extension zip). `install.sh` prints the extension's fixed ID so
|
||||
you can confirm it loaded correctly.
|
||||
|
||||
## Usage
|
||||
|
||||
@@ -64,54 +107,51 @@ finds installed, then prints the extension's fixed ID and next steps:
|
||||
./uninstall.sh
|
||||
```
|
||||
|
||||
This removes the native messaging host registrations and the installed host
|
||||
script. You'll still need to remove the unpacked extension yourself via
|
||||
`chrome://extensions` — that step can't be scripted.
|
||||
Removes the native messaging host registrations and installed host script.
|
||||
Remove the extension itself via `chrome://extensions` (or the Chrome Web
|
||||
Store listing); that step can't be scripted.
|
||||
|
||||
## Releases
|
||||
|
||||
Pushing a tag matching `v*.*.*` (e.g. `v1.0.0`) triggers a GitHub Actions
|
||||
workflow ([.github/workflows/release.yml](.github/workflows/release.yml))
|
||||
that zips `extension/` and publishes it as a GitHub Release with
|
||||
auto-generated release notes. Bump `"version"` in `extension/manifest.json`
|
||||
before tagging so the packaged extension's version matches.
|
||||
Prebuilt releases are published on the
|
||||
[Releases page](https://github.com/andwati/kget-capture/releases); each
|
||||
`vX.Y.Z` tag push builds and publishes one automatically, as three zips:
|
||||
|
||||
```bash
|
||||
git tag v1.0.0
|
||||
git push origin v1.0.0
|
||||
```
|
||||
- `kget-capture-vX.Y.Z.zip`: full source, for `install.sh`/`install-remote.sh`.
|
||||
- `kget-capture-extension-vX.Y.Z.zip`: just the extension, manifest at the
|
||||
zip root, `key` field intact, for manual "Load unpacked" installs.
|
||||
- `kget-capture-store-vX.Y.Z.zip`: same extension, but with `key` stripped,
|
||||
for uploading to the Chrome Web Store dashboard. The Store rejects any
|
||||
manifest containing `key` ("key field is not allowed in manifest") since
|
||||
it assigns its own permanent ID on first publish, which won't match the
|
||||
pinned ID (`keys/extension-id.txt`) the other zips use. After the first
|
||||
successful Store publish, update `native-host/com.kget_capture.host.json`'s
|
||||
`allowed_origins` to include the Store-assigned ID alongside the pinned
|
||||
one, so both installs can reach the native host.
|
||||
|
||||
## Known limitations
|
||||
|
||||
- **`blob:`/`data:`/`file:`/`filesystem:` URLs can't be captured.** There's
|
||||
no real network URL to hand to KGet — this includes saving a page you
|
||||
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).
|
||||
no real network URL to hand to KGet, so these proceed normally through
|
||||
Chrome.
|
||||
- **Auto-capture has a brief head start.** Manifest V3 removed blocking
|
||||
`webRequest`, so Chrome begins the download before the extension can cancel
|
||||
it. A small/partial temp file may transiently appear before it's erased.
|
||||
- **No editable dialog.** Captures start immediately with no chance to
|
||||
rename the file or pick a different folder beforehand — rename/move it
|
||||
afterward in KGet or your file manager if needed. This is intentional: KGet
|
||||
only skips its interactive "New Download" dialog when given a real
|
||||
destination path up front, so instant capture and an editable prompt are
|
||||
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
|
||||
Install above — see there for why and how to fix it.
|
||||
- **KGet must already be running.** If it's not, captures fail silently from
|
||||
the user's perspective — check the service worker console for the actual
|
||||
error (see Troubleshooting).
|
||||
rename the file or pick a folder beforehand: KGet only skips its
|
||||
interactive dialog when given a destination path up front, so instant
|
||||
capture and an editable prompt are mutually exclusive.
|
||||
- **Chrome may still show its own "Save As" dialog** if you skipped step 3 of
|
||||
Install above.
|
||||
- **KGet must already be running.** If it's not, captures fail silently;
|
||||
check the service worker console for the actual error (see
|
||||
Troubleshooting).
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
- **`install-remote.sh` fails to look up the latest release?** It queries
|
||||
GitHub's API, which rate-limits unauthenticated requests (60/hour per IP);
|
||||
wait a bit, or fall back to the manual `install.sh` steps in Install above.
|
||||
- **Check native host registration:** confirm
|
||||
`~/.config/<browser>/NativeMessagingHosts/com.kget_capture.host.json` exists
|
||||
and its `path` points at an executable file
|
||||
@@ -125,19 +165,17 @@ git push origin v1.0.0
|
||||
```
|
||||
If this fails with `ServiceUnknown`, KGet isn't running.
|
||||
- **Download-complete notification not showing?** Confirm `notify-send` is
|
||||
installed (`command -v notify-send`) — without it, `kget_capture_host.py`
|
||||
installed (`command -v notify-send`); without it, `kget_capture_host.py`
|
||||
skips the notification entirely rather than erroring. Otherwise, check that
|
||||
the transfer actually reached 100%: `kget_capture_host.py` polls
|
||||
`org.kde.kget.transfer.percent` on the new transfer's D-Bus object path
|
||||
(e.g. `/KGet/Transfers/9`, printed by the `addTransfer` call above) until it
|
||||
hits 100, so a stuck/erroring transfer never fires one. Note this
|
||||
notification is separate from KGet's own built-in "Download Finished"
|
||||
event — `install.sh` disables that one (`kwriteconfig6 --file knotifyrc
|
||||
--group "Event/kget/finished" --key Action None`) because KGet only fires
|
||||
it for transfers it observed in a non-finished state at least once, so it
|
||||
silently skips small/fast downloads that complete before that first
|
||||
observation.
|
||||
hits 100, so a stuck/erroring transfer never fires one. This notification
|
||||
is separate from KGet's own built-in "Download Finished" event.
|
||||
`install.sh` disables that one because KGet only fires it for transfers it
|
||||
observed in a non-finished state at least once, so it silently skips
|
||||
small/fast downloads that complete before that first observation.
|
||||
|
||||
## License
|
||||
|
||||
MIT — see [LICENSE](LICENSE).
|
||||
MIT, see [LICENSE](LICENSE).
|
||||
|
||||
+23
-2
@@ -52,6 +52,11 @@ chrome.runtime.onInstalled.addListener(async (details) => {
|
||||
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) => {
|
||||
if (info.menuItemId !== MENU_ID) return;
|
||||
const url = info.linkUrl || info.srcUrl;
|
||||
@@ -60,7 +65,22 @@ chrome.contextMenus.onClicked.addListener((info) => {
|
||||
console.warn("[kget-capture] cannot hand a local/embedded URL to KGet:", url);
|
||||
return;
|
||||
}
|
||||
sendToKGet(url, null);
|
||||
// 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
|
||||
@@ -75,8 +95,9 @@ chrome.downloads.onDeterminingFilename.addListener((item, suggest) => {
|
||||
});
|
||||
|
||||
async function captureDownload(item) {
|
||||
const isContextMenuCapture = pendingContextMenuDownloads.delete(item.id);
|
||||
const { autoCaptureEnabled } = await chrome.storage.local.get({ autoCaptureEnabled: true });
|
||||
if (!autoCaptureEnabled) return;
|
||||
if (!autoCaptureEnabled && !isContextMenuCapture) return;
|
||||
|
||||
const url = item.finalUrl || item.url;
|
||||
if (!isCapturableUrl(url)) {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"manifest_version": 3,
|
||||
"name": "KGet Capture",
|
||||
"version": "1.0.3",
|
||||
"version": "1.0.6",
|
||||
"description": "Send browser downloads to KGet (KDE Download Manager) on Linux.",
|
||||
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtM1nFg5pPd3UBzynUJHj6aUy7bjudZaX9AQTWGMqAX3fXylw1lubSzP1/BaS9ivvgOJg/loyEgzEZlcNPzblFVlg8BqEJ3tntdk2OzYvUs0pUdcPzzyjso9TykCkUUTI7ev8JkreW/FYlinfvNwSYN4/c1s3h8/I6XHtxOdmO1G3bS98+BQaXPldbDmxoKwyWR8u1f8gTO2Mg3GY4BHau3RWf1kgtNzh20p9wE3sEFdWkFu7ArgRCIrWLh6m3O6lolnHQSCWPwtOTx893Qkp9Eu6FwUnoWc8rsYx8sqa0eNCTCS6Kjr1Iu3yEJ5y9390jUiDZTQ/c62ueCTQaYv2bwIDAQAB",
|
||||
"permissions": ["downloads", "contextMenus", "storage", "nativeMessaging"],
|
||||
|
||||
+36
-1
@@ -1,5 +1,5 @@
|
||||
body {
|
||||
width: 240px;
|
||||
width: 320px;
|
||||
padding: 12px;
|
||||
font-family: system-ui, sans-serif;
|
||||
font-size: 13px;
|
||||
@@ -36,3 +36,38 @@ h1 {
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.cmd-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.cmd-row code {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
overflow-x: auto;
|
||||
white-space: nowrap;
|
||||
background: #f0f0f0;
|
||||
padding: 4px 6px;
|
||||
border-radius: 3px;
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.cmd-row button {
|
||||
flex: none;
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#recheck-setup {
|
||||
display: block;
|
||||
margin-top: 10px;
|
||||
padding: 4px 8px;
|
||||
font-size: 12px;
|
||||
font-family: inherit;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
+26
-11
@@ -6,17 +6,32 @@
|
||||
</head>
|
||||
<body>
|
||||
<h1>KGet Capture</h1>
|
||||
<label class="toggle-row">
|
||||
<input type="checkbox" id="auto-capture-toggle">
|
||||
<span>Auto-capture all downloads</span>
|
||||
</label>
|
||||
<p class="status" id="status-line"></p>
|
||||
<p class="hint">Right-click any link, image, video, or audio and choose
|
||||
"Download with KGet" to send it to KGet on demand.</p>
|
||||
<p class="hint">Downloads still prompting for a location? Chrome shows that
|
||||
dialog itself, before this extension can intercept anything.
|
||||
<button id="open-download-settings" type="button">Turn off Chrome's download prompt</button>
|
||||
</p>
|
||||
|
||||
<div id="setup-needed" hidden>
|
||||
<p class="hint">One-time setup needed: KGet Capture needs a small local
|
||||
helper installed before it can talk to KGet. Run this in a terminal:</p>
|
||||
<div class="cmd-row">
|
||||
<code id="setup-command"></code>
|
||||
<button id="copy-command" type="button">Copy</button>
|
||||
</div>
|
||||
<p class="status" id="copy-feedback"></p>
|
||||
<button id="recheck-setup" type="button">I ran it, check again</button>
|
||||
</div>
|
||||
|
||||
<div id="ready" hidden>
|
||||
<label class="toggle-row">
|
||||
<input type="checkbox" id="auto-capture-toggle">
|
||||
<span>Auto-capture all downloads</span>
|
||||
</label>
|
||||
<p class="status" id="status-line"></p>
|
||||
<p class="hint">Right-click any link, image, video, or audio and choose
|
||||
"Download with KGet" to send it to KGet on demand.</p>
|
||||
<p class="hint">Downloads still prompting for a location? Chrome shows that
|
||||
dialog itself, before this extension can intercept anything.
|
||||
<button id="open-download-settings" type="button">Turn off Chrome's download prompt</button>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<script src="popup.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+50
-3
@@ -1,13 +1,60 @@
|
||||
const NATIVE_HOST = "com.kget_capture.host";
|
||||
const INSTALL_COMMAND =
|
||||
"curl -fsSL https://raw.githubusercontent.com/andwati/kget-capture/main/install-remote.sh | bash";
|
||||
|
||||
const setupPanel = document.getElementById("setup-needed");
|
||||
const readyPanel = document.getElementById("ready");
|
||||
const checkbox = document.getElementById("auto-capture-toggle");
|
||||
const statusLine = document.getElementById("status-line");
|
||||
const copyFeedback = document.getElementById("copy-feedback");
|
||||
|
||||
function renderStatus(enabled) {
|
||||
statusLine.textContent = `Auto-capture: ${enabled ? "ON" : "OFF"}`;
|
||||
}
|
||||
|
||||
chrome.storage.local.get({ autoCaptureEnabled: true }).then(({ autoCaptureEnabled }) => {
|
||||
checkbox.checked = autoCaptureEnabled;
|
||||
renderStatus(autoCaptureEnabled);
|
||||
function showReady() {
|
||||
setupPanel.hidden = true;
|
||||
readyPanel.hidden = false;
|
||||
chrome.storage.local.get({ autoCaptureEnabled: true }).then(({ autoCaptureEnabled }) => {
|
||||
checkbox.checked = autoCaptureEnabled;
|
||||
renderStatus(autoCaptureEnabled);
|
||||
});
|
||||
}
|
||||
|
||||
function showSetupNeeded() {
|
||||
document.getElementById("setup-command").textContent = INSTALL_COMMAND;
|
||||
readyPanel.hidden = true;
|
||||
setupPanel.hidden = false;
|
||||
}
|
||||
|
||||
// The native host is only reachable once install.sh (or install-remote.sh)
|
||||
// has registered it -- sendNativeMessage fails with a lastError like
|
||||
// "Specified native messaging host not found" until then, which is exactly
|
||||
// the signal we want to gate the rest of the popup on.
|
||||
function checkNativeHost() {
|
||||
chrome.runtime.sendNativeMessage(NATIVE_HOST, { action: "ping" }, () => {
|
||||
if (chrome.runtime.lastError) {
|
||||
showSetupNeeded();
|
||||
} else {
|
||||
showReady();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
checkNativeHost();
|
||||
|
||||
document.getElementById("recheck-setup").addEventListener("click", checkNativeHost);
|
||||
|
||||
document.getElementById("copy-command").addEventListener("click", async () => {
|
||||
try {
|
||||
await navigator.clipboard.writeText(INSTALL_COMMAND);
|
||||
copyFeedback.textContent = "Copied to clipboard.";
|
||||
} catch (e) {
|
||||
copyFeedback.textContent = "Couldn't copy -- select and copy the command manually.";
|
||||
}
|
||||
setTimeout(() => {
|
||||
copyFeedback.textContent = "";
|
||||
}, 2000);
|
||||
});
|
||||
|
||||
checkbox.addEventListener("change", () => {
|
||||
|
||||
Executable
+60
@@ -0,0 +1,60 @@
|
||||
#!/usr/bin/env bash
|
||||
# Fetches the latest release source archive and runs install.sh from it, so
|
||||
# setup doesn't require a git clone:
|
||||
#
|
||||
# curl -fsSL https://raw.githubusercontent.com/andwati/kget-capture/main/install-remote.sh | bash
|
||||
# wget -qO- https://raw.githubusercontent.com/andwati/kget-capture/main/install-remote.sh | bash
|
||||
set -euo pipefail
|
||||
|
||||
REPO="andwati/kget-capture"
|
||||
|
||||
fetch_stdout() {
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl -fsSL "$1"
|
||||
elif command -v wget >/dev/null 2>&1; then
|
||||
wget -qO- "$1"
|
||||
else
|
||||
echo "ERROR: this installer needs 'curl' or 'wget' on PATH." >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
fetch_to_file() {
|
||||
if command -v curl >/dev/null 2>&1; then
|
||||
curl -fsSL "$1" -o "$2"
|
||||
else
|
||||
wget -qO "$2" "$1"
|
||||
fi
|
||||
}
|
||||
|
||||
echo "Looking up the latest release..."
|
||||
tag="$(fetch_stdout "https://api.github.com/repos/$REPO/releases/latest" \
|
||||
| grep -m1 '"tag_name"' | sed -E 's/.*"tag_name": *"([^"]+)".*/\1/')"
|
||||
if [ -z "$tag" ]; then
|
||||
echo "ERROR: couldn't determine the latest release tag from GitHub." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "Latest release: $tag"
|
||||
|
||||
if ! command -v python3 >/dev/null 2>&1; then
|
||||
echo "ERROR: 'python3' is required but not found on PATH." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
tmp_dir="$(mktemp -d)"
|
||||
trap 'rm -rf "$tmp_dir"' EXIT
|
||||
|
||||
zip_path="$tmp_dir/kget-capture.zip"
|
||||
zip_url="https://github.com/$REPO/releases/download/$tag/kget-capture-$tag.zip"
|
||||
echo "Downloading $zip_url..."
|
||||
fetch_to_file "$zip_url" "$zip_path"
|
||||
|
||||
echo "Extracting..."
|
||||
src_dir="$tmp_dir/src"
|
||||
python3 -c "
|
||||
import sys, zipfile
|
||||
zipfile.ZipFile(sys.argv[1]).extractall(sys.argv[2])
|
||||
" "$zip_path" "$src_dir"
|
||||
|
||||
echo "Running install.sh..."
|
||||
bash "$src_dir/install.sh"
|
||||
@@ -49,7 +49,12 @@ def add_transfer(url, filename):
|
||||
# sidesteps KGet's own filename guess, which is derived purely from the
|
||||
# URL path and gets it wrong for opaque/signed URLs.
|
||||
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 = [
|
||||
"dbus-send",
|
||||
"--print-reply",
|
||||
@@ -149,7 +154,17 @@ def spawn_notifier(transfer_path, dest):
|
||||
|
||||
def main():
|
||||
message = read_message()
|
||||
if not message or message.get("action") != "addTransfer" or "url" not in message:
|
||||
if not message:
|
||||
write_message({"ok": False, "error": "invalid request"})
|
||||
return
|
||||
|
||||
# The extension popup uses this to detect whether the native host is
|
||||
# installed/registered at all, distinct from whether a transfer succeeds.
|
||||
if message.get("action") == "ping":
|
||||
write_message({"ok": True})
|
||||
return
|
||||
|
||||
if message.get("action") != "addTransfer" or "url" not in message:
|
||||
write_message({"ok": False, "error": "invalid request"})
|
||||
return
|
||||
|
||||
|
||||
Reference in New Issue
Block a user