Skip to content

Commit ac95210

Browse files
feat(scripts): "Send to OpenCoperLock" right-click upload (Windows + Linux)
Right-click file(s) -> Send to -> OpenCoperLock uploads them into a ComputerShared space over WebDAV, one click, remembering server + token. - Windows: per-user installer (no admin) adds a Send To shortcut with the brand logo; token stored DPAPI-encrypted; uploader shows a tray toast. - Linux: installs a Nautilus/Nemo script and a KDE Dolphin service menu; token in ~/.config/opencoperlock (chmod 600); notify-send feedback. - Icon rasterized from the real brand SVG (icon-maskable.svg) via assets/generate-icons.py; the SVGs themselves are untouched. - Docs pointer added under the WebDAV section.
1 parent 2f50dcb commit ac95210

10 files changed

Lines changed: 374 additions & 0 deletions

File tree

docs/API.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ Mount your normal spaces as a network drive (Finder, Windows Explorer, `rsync`/d
8383
- **Username:** anything (your email) — it is ignored.
8484
- **Password:** an API token (`read` + `write`).
8585

86+
> **"Send to OpenCoperLock" (right-click upload).** For a one-click *right-click → Send to →
87+
> OpenCoperLock* that drops files into a `ComputerShared` space, see
88+
> [`scripts/send-to/`](../scripts/send-to/) (Windows + Linux installers, uses your WebDAV token).
89+
8690
```bash
8791
# Example with rclone
8892
rclone config create ocl webdav url "$HOST/dav/" vendor other user me pass "$TOKEN"

scripts/send-to/README.md

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Send to OpenCoperLock
2+
3+
Right-click a file in your OS file manager → **Send to → OpenCoperLock** → it uploads straight into
4+
a space called **`ComputerShared`** in your Drive. Works on **Windows** and **Linux**, uses the
5+
brand logo in the menu, and remembers your server + token so every send is one click.
6+
7+
Under the hood it uploads over **WebDAV** (the same endpoint the account page shows), so there's no
8+
new server code — just your existing `ocl_…` API token.
9+
10+
---
11+
12+
## Windows
13+
14+
1. Get an **unrestricted API token**: web app → *Account → API tokens* (folder-scoped tokens are
15+
refused by WebDAV).
16+
2. Double-click **`windows/install-windows.cmd`**. Paste your WebDAV URL
17+
(e.g. `https://copper.forgenet.fr/api/dav`) and the token when asked. *No admin needed.*
18+
3. Done — right-click any file(s) → **Send to → OpenCoperLock**.
19+
20+
The installer copies `send.ps1` + the icon into `%LOCALAPPDATA%\OpenCoperLock`, stores your token
21+
**DPAPI-encrypted** (readable only by your Windows user, never leaves the machine), pre-creates the
22+
`ComputerShared` space, and drops the shortcut into your *Send To* folder (`shell:sendto`).
23+
Re-run the installer any time to change the URL/token. To uninstall, delete the shortcut from
24+
`shell:sendto` and the `%LOCALAPPDATA%\OpenCoperLock` folder.
25+
26+
> Uses `curl.exe`, which ships with Windows 10 1803+ and Windows 11.
27+
28+
## Linux
29+
30+
1. Get an unrestricted API token (as above).
31+
2. Run **`linux/install-linux.sh`** and paste your WebDAV URL + token.
32+
3. Right-click a file:
33+
- **GNOME Files / Cinnamon:** *Scripts → Send to OpenCoperLock*
34+
- **KDE Dolphin:** *Send to OpenCoperLock* (top of the menu)
35+
36+
Config is stored at `~/.config/opencoperlock/config` (`chmod 600`). If the entry doesn't show up,
37+
restart the file manager (`nautilus -q`, `nemo -q`, or log out/in on KDE). Requires `curl`;
38+
notifications use `notify-send` if present.
39+
40+
---
41+
42+
## The icon
43+
44+
`assets/opencoperlock.ico` / `.png` are rasterized from the app's real brand SVG
45+
(`apps/web/public/icon-maskable.svg`) — the violet-gradient padlock — by
46+
`assets/generate-icons.py`. The SVG is the source of truth and is never modified; regenerate with:
47+
48+
```bash
49+
pip install cairosvg pillow
50+
python3 scripts/send-to/assets/generate-icons.py
51+
```
52+
53+
## Security notes
54+
55+
- Uploads go to **`ComputerShared`**, a normal (server-encrypted) top-level folder in **your**
56+
Drive. Nothing is made public.
57+
- The token grants WebDAV access to your Drive — keep it unrestricted but private. Revoke it from
58+
*Account → API tokens* if it leaks; re-run the installer with a fresh one.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#!/usr/bin/env python3
2+
"""Regenerate opencoperlock.ico / opencoperlock.png from the app's real brand SVG.
3+
4+
Source of truth: apps/web/public/icon-maskable.svg (violet gradient + white padlock). We only
5+
RASTERIZE it — the SVG itself is never modified. A gentle rounded-square mask (matching the in-app
6+
Logo's rounded corners) is applied so the result reads as a proper desktop-app icon.
7+
8+
pip install cairosvg pillow
9+
python3 scripts/send-to/assets/generate-icons.py
10+
"""
11+
import io
12+
import os
13+
14+
import cairosvg
15+
from PIL import Image, ImageDraw
16+
17+
HERE = os.path.dirname(os.path.abspath(__file__))
18+
REPO = os.path.abspath(os.path.join(HERE, "..", "..", ".."))
19+
SRC = os.path.join(REPO, "apps/web/public/icon-maskable.svg")
20+
R = 1024
21+
SIZES = [256, 128, 64, 48, 32, 16]
22+
23+
24+
def main() -> None:
25+
png_bytes = cairosvg.svg2png(url=SRC, output_width=R, output_height=R)
26+
art = Image.open(io.BytesIO(png_bytes)).convert("RGBA")
27+
28+
mask = Image.new("L", (R, R), 0)
29+
ImageDraw.Draw(mask).rounded_rectangle([0, 0, R - 1, R - 1], radius=int(R * 0.18), fill=255)
30+
out = Image.new("RGBA", (R, R), (0, 0, 0, 0))
31+
out.paste(art, (0, 0), mask)
32+
33+
out.resize((256, 256), Image.LANCZOS).save(os.path.join(HERE, "opencoperlock.png"), "PNG")
34+
frames = [out.resize((s, s), Image.LANCZOS) for s in SIZES]
35+
frames[0].save(os.path.join(HERE, "opencoperlock.ico"), "ICO", sizes=[(s, s) for s in SIZES])
36+
print("wrote opencoperlock.png and opencoperlock.ico from", os.path.relpath(SRC, REPO))
37+
38+
39+
if __name__ == "__main__":
40+
main()
25.9 KB
Binary file not shown.
13 KB
Loading
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
#!/usr/bin/env bash
2+
# OpenCoperLock — install "Send to OpenCoperLock" in your Linux file manager.
3+
#
4+
# Per-user, no root. Installs a right-click entry for GNOME Files (Nautilus), Cinnamon (Nemo) and
5+
# KDE Dolphin. Selected files upload to the "ComputerShared" space of your Drive over WebDAV.
6+
set -euo pipefail
7+
8+
here="$(cd "$(dirname "$0")" && pwd)"
9+
assets="$(cd "$here/../assets" && pwd)"
10+
data="${XDG_DATA_HOME:-$HOME/.local/share}/opencoperlock"
11+
cfgdir="${XDG_CONFIG_HOME:-$HOME/.config}/opencoperlock"
12+
mkdir -p "$data" "$cfgdir"
13+
14+
default_base="https://copper.forgenet.fr/api/dav"
15+
read -rp "WebDAV base URL [$default_base]: " BASE
16+
BASE="${BASE:-$default_base}"
17+
BASE="${BASE%/}"
18+
read -rsp "Paste your OpenCoperLock API token (ocl_...): " TOKEN
19+
echo
20+
[ -n "$TOKEN" ] || { echo "No token provided."; exit 1; }
21+
22+
# Store config readable only by you.
23+
umask 077
24+
printf 'BASE=%q\nTOKEN=%q\n' "$BASE" "$TOKEN" > "$cfgdir/config"
25+
chmod 600 "$cfgdir/config"
26+
27+
# Install the uploader + icon.
28+
install -m 0755 "$here/send.sh" "$data/send.sh"
29+
install -m 0644 "$assets/opencoperlock.png" "$data/opencoperlock.png"
30+
31+
installed=()
32+
33+
# GNOME Files (Nautilus) & Cinnamon (Nemo): scripts appear under right-click > Scripts.
34+
for pair in "nautilus:GNOME Files" "nemo:Cinnamon"; do
35+
fm="${pair%%:*}"; label="${pair##*:}"
36+
sd="$HOME/.local/share/$fm/scripts"
37+
if [ -d "$HOME/.local/share/$fm" ] || command -v "$fm" >/dev/null 2>&1; then
38+
mkdir -p "$sd"
39+
ln -sf "$data/send.sh" "$sd/Send to OpenCoperLock"
40+
installed+=("$label (Scripts ▸ Send to OpenCoperLock)")
41+
fi
42+
done
43+
44+
# KDE Dolphin service menu (shows the icon in the context menu directly).
45+
for sm in "$HOME/.local/share/kio/servicemenus" "$HOME/.local/share/kservices5/ServiceMenus"; do
46+
mkdir -p "$sm"
47+
cat > "$sm/opencoperlock.desktop" <<EOF
48+
[Desktop Entry]
49+
Type=Service
50+
MimeType=all/all;
51+
Actions=sendToOpenCoperLock;
52+
X-KDE-Priority=TopLevel
53+
54+
[Desktop Action sendToOpenCoperLock]
55+
Name=Send to OpenCoperLock
56+
Icon=$data/opencoperlock.png
57+
Exec=$data/send.sh %F
58+
EOF
59+
chmod +x "$sm/opencoperlock.desktop" 2>/dev/null || true
60+
done
61+
installed+=("KDE Dolphin (right-click ▸ Send to OpenCoperLock)")
62+
63+
# Pre-create the space.
64+
curl -fsS -u "me:$TOKEN" -X MKCOL "$BASE/ComputerShared/" >/dev/null 2>&1 || true
65+
66+
echo
67+
echo "Installed for:"
68+
for i in "${installed[@]}"; do echo "$i"; done
69+
echo
70+
echo "Uploads land in the 'ComputerShared' space of your Drive."
71+
echo "If the entry doesn't appear yet, restart the file manager:"
72+
echo " nautilus -q | nemo -q | (KDE: log out/in or 'kbuildsycoca5')"

scripts/send-to/linux/send.sh

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#!/usr/bin/env bash
2+
# OpenCoperLock — "Send to" uploader (Linux).
3+
#
4+
# Called by the file-manager menu entry (Nautilus/Nemo script or KDE service menu) with the
5+
# selected files. Uploads each to the "ComputerShared" space (a top-level folder in your Drive)
6+
# over WebDAV, then shows a desktop notification. Config lives in
7+
# ~/.config/opencoperlock/config (chmod 600), written by install-linux.sh.
8+
set -euo pipefail
9+
10+
cfg="${XDG_CONFIG_HOME:-$HOME/.config}/opencoperlock/config"
11+
icon="${XDG_DATA_HOME:-$HOME/.local/share}/opencoperlock/opencoperlock.png"
12+
13+
notify() { command -v notify-send >/dev/null 2>&1 && notify-send -i "$icon" "OpenCoperLock" "$1" || echo "OpenCoperLock: $1"; }
14+
15+
[ -f "$cfg" ] || { notify "Not configured — run install-linux.sh"; exit 1; }
16+
# shellcheck source=/dev/null
17+
. "$cfg" # provides BASE and TOKEN
18+
BASE="${BASE%/}"
19+
20+
# Collect selected files: Nautilus / Nemo pass them via env vars (newline-separated); KDE and the
21+
# CLI pass them as arguments.
22+
files=()
23+
if [ -n "${NAUTILUS_SCRIPT_SELECTED_FILE_PATHS:-}" ]; then
24+
while IFS= read -r p; do [ -n "$p" ] && files+=("$p"); done <<< "$NAUTILUS_SCRIPT_SELECTED_FILE_PATHS"
25+
elif [ -n "${NEMO_SCRIPT_SELECTED_FILE_PATHS:-}" ]; then
26+
while IFS= read -r p; do [ -n "$p" ] && files+=("$p"); done <<< "$NEMO_SCRIPT_SELECTED_FILE_PATHS"
27+
else
28+
files=("$@")
29+
fi
30+
[ "${#files[@]}" -gt 0 ] || { notify "No files to send."; exit 0; }
31+
32+
urlencode() {
33+
if command -v python3 >/dev/null 2>&1; then
34+
python3 -c 'import urllib.parse,sys; print(urllib.parse.quote(sys.argv[1]))' "$1"
35+
else
36+
local LC_ALL=C s="$1" o='' i c hex
37+
for ((i = 0; i < ${#s}; i++)); do
38+
c="${s:i:1}"
39+
case "$c" in
40+
[a-zA-Z0-9._~-]) o+="$c" ;;
41+
*) printf -v hex '%%%02X' "'$c"; o+="$hex" ;;
42+
esac
43+
done
44+
printf '%s' "$o"
45+
fi
46+
}
47+
48+
# Ensure the space exists (a 405 "already there" is fine).
49+
curl -fsS -u "me:$TOKEN" -X MKCOL "$BASE/ComputerShared/" >/dev/null 2>&1 || true
50+
51+
ok=0; fail=0
52+
for f in "${files[@]}"; do
53+
[ -f "$f" ] || continue # skip folders / non-files
54+
name="$(urlencode "$(basename -- "$f")")"
55+
if curl -fsS -u "me:$TOKEN" -T "$f" "$BASE/ComputerShared/$name" >/dev/null 2>&1; then
56+
ok=$((ok + 1))
57+
else
58+
fail=$((fail + 1))
59+
fi
60+
done
61+
62+
if [ "$fail" -eq 0 ]; then notify "Sent $ok file(s) to ComputerShared."
63+
else notify "Sent $ok, $fail failed — check your token or connection."; fi
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
@echo off
2+
REM ===========================================================================
3+
REM OpenCoperLock — install "Send to > OpenCoperLock" (Windows, no admin).
4+
REM Double-click this file. You'll be asked for your WebDAV URL and API token,
5+
REM then an "OpenCoperLock" entry (with the logo) appears in the right-click
6+
REM "Send to" menu. Selected files upload to the "ComputerShared" space.
7+
REM ===========================================================================
8+
powershell -NoProfile -ExecutionPolicy Bypass -File "%~dp0install-windows.ps1"
9+
echo.
10+
pause
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
<#
2+
OpenCoperLock — install the "Send to > OpenCoperLock" integration (Windows).
3+
4+
Per-user, NO administrator rights needed. It:
5+
1. copies the uploader (send.ps1) and the brand icon into %LOCALAPPDATA%\OpenCoperLock,
6+
2. asks for your WebDAV URL + API token and stores them there (the token is DPAPI-encrypted for
7+
your Windows user — unreadable by other users, and never leaves your machine),
8+
3. pre-creates the "ComputerShared" space in your Drive,
9+
4. adds an "OpenCoperLock" entry (with the logo) to Explorer's right-click "Send to" menu.
10+
11+
After this, right-click any file(s) -> Send to -> OpenCoperLock uploads them to ComputerShared.
12+
13+
Run: right-click install-windows.cmd, or
14+
powershell -ExecutionPolicy Bypass -File .\install-windows.ps1
15+
#>
16+
[CmdletBinding()]
17+
param(
18+
[string]$Base,
19+
[string]$Token
20+
)
21+
$ErrorActionPreference = 'Stop'
22+
23+
$srcDir = Split-Path -Parent $MyInvocation.MyCommand.Path # ...\send-to\windows
24+
$assets = Join-Path (Split-Path -Parent $srcDir) 'assets' # ...\send-to\assets
25+
$dstDir = Join-Path $env:LOCALAPPDATA 'OpenCoperLock'
26+
New-Item -ItemType Directory -Force -Path $dstDir | Out-Null
27+
28+
# --- gather settings -----------------------------------------------------------------------------
29+
$defaultBase = 'https://copper.forgenet.fr/api/dav'
30+
if (-not $Base) {
31+
$inp = Read-Host "WebDAV base URL [$defaultBase]"
32+
$Base = if ([string]::IsNullOrWhiteSpace($inp)) { $defaultBase } else { $inp.Trim() }
33+
}
34+
$Base = $Base.TrimEnd('/')
35+
36+
if (-not $Token) {
37+
$sec = Read-Host "Paste your OpenCoperLock API token (ocl_...)" -AsSecureString
38+
$bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($sec)
39+
$Token = [Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr)
40+
[Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr)
41+
}
42+
if ([string]::IsNullOrWhiteSpace($Token)) { throw "No token provided." }
43+
44+
# --- install files -------------------------------------------------------------------------------
45+
Copy-Item (Join-Path $srcDir 'send.ps1') (Join-Path $dstDir 'send.ps1') -Force
46+
Copy-Item (Join-Path $assets 'opencoperlock.ico') (Join-Path $dstDir 'opencoperlock.ico') -Force
47+
48+
# DPAPI-encrypt the token (current-user scope) and write the config.
49+
$enc = ConvertTo-SecureString $Token -AsPlainText -Force | ConvertFrom-SecureString
50+
[pscustomobject]@{ base = $Base; token = $enc } | ConvertTo-Json | Set-Content (Join-Path $dstDir 'config.json') -Encoding UTF8
51+
52+
# --- pre-create the ComputerShared space ---------------------------------------------------------
53+
try { & curl.exe -s -u "me:$Token" -X MKCOL "$Base/ComputerShared/" | Out-Null } catch { }
54+
$Token = $null
55+
56+
# --- Send To shortcut ----------------------------------------------------------------------------
57+
$sendTo = [Environment]::GetFolderPath('SendTo')
58+
$lnkPath = Join-Path $sendTo 'OpenCoperLock.lnk'
59+
$ps = Join-Path $env:SystemRoot 'System32\WindowsPowerShell\v1.0\powershell.exe'
60+
$ws = New-Object -ComObject WScript.Shell
61+
$sc = $ws.CreateShortcut($lnkPath)
62+
$sc.TargetPath = $ps
63+
$sc.Arguments = '-NoProfile -WindowStyle Hidden -ExecutionPolicy Bypass -File "' + (Join-Path $dstDir 'send.ps1') + '"'
64+
$sc.IconLocation = (Join-Path $dstDir 'opencoperlock.ico') + ',0'
65+
$sc.Description = 'Send to OpenCoperLock (ComputerShared)'
66+
$sc.WorkingDirectory = $dstDir
67+
$sc.Save()
68+
69+
Write-Host ""
70+
Write-Host "Installed. Right-click any file(s) -> Send to -> OpenCoperLock." -ForegroundColor Green
71+
Write-Host "Uploads land in the 'ComputerShared' space of your Drive." -ForegroundColor DarkGray
72+
Write-Host "To change the URL/token later, just run this installer again." -ForegroundColor DarkGray

scripts/send-to/windows/send.ps1

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<#
2+
OpenCoperLock — "Send to" uploader (Windows).
3+
4+
Invoked by the "Send to > OpenCoperLock" shortcut with one or more file paths as arguments.
5+
Uploads each selected file to the "ComputerShared" space (a top-level folder in your Drive) over
6+
WebDAV, then shows a tray notification. Configuration (WebDAV URL + API token) is written by
7+
install-windows.cmd into %LOCALAPPDATA%\OpenCoperLock; the token is DPAPI-encrypted for your
8+
Windows user, so this file holds no secret.
9+
#>
10+
$ErrorActionPreference = 'Stop'
11+
$dir = Join-Path $env:LOCALAPPDATA 'OpenCoperLock'
12+
$cfgPath = Join-Path $dir 'config.json'
13+
$icon = Join-Path $dir 'opencoperlock.ico'
14+
15+
function Show-Toast($title, $text) {
16+
try {
17+
Add-Type -AssemblyName System.Windows.Forms
18+
Add-Type -AssemblyName System.Drawing
19+
$ni = New-Object System.Windows.Forms.NotifyIcon
20+
$ni.Icon = if (Test-Path $icon) { New-Object System.Drawing.Icon($icon) } else { [System.Drawing.SystemIcons]::Information }
21+
$ni.Visible = $true
22+
$ni.ShowBalloonTip(4000, $title, $text, [System.Windows.Forms.ToolTipIcon]::Info)
23+
Start-Sleep -Milliseconds 4200
24+
$ni.Dispose()
25+
} catch { }
26+
}
27+
28+
if (-not (Test-Path $cfgPath)) { Show-Toast 'OpenCoperLock' 'Not configured yet — run the installer.'; exit 1 }
29+
30+
$cfg = Get-Content $cfgPath -Raw | ConvertFrom-Json
31+
$base = ($cfg.base).TrimEnd('/')
32+
33+
# Decrypt the DPAPI-protected token.
34+
$sec = ConvertTo-SecureString $cfg.token
35+
$bstr = [Runtime.InteropServices.Marshal]::SecureStringToBSTR($sec)
36+
$tok = [Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr)
37+
[Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr)
38+
39+
$files = @($args | Where-Object { $_ -and (Test-Path -LiteralPath $_ -PathType Leaf) })
40+
if ($files.Count -eq 0) { Show-Toast 'OpenCoperLock' 'No files to send (folders are skipped).'; exit 0 }
41+
42+
$cred = "me:$tok"
43+
# Make sure the space exists (a 405 "already there" is fine and ignored).
44+
& curl.exe -s -u $cred -X MKCOL "$base/ComputerShared/" | Out-Null
45+
46+
$ok = 0; $fail = 0
47+
foreach ($f in $files) {
48+
$name = [uri]::EscapeDataString((Split-Path -LiteralPath $f -Leaf))
49+
$code = & curl.exe -s -o NUL -w "%{http_code}" -u $cred -T $f "$base/ComputerShared/$name"
50+
if ($code -match '^2') { $ok++ } else { $fail++ }
51+
}
52+
$tok = $null; $cred = $null
53+
54+
if ($fail -eq 0) { Show-Toast 'OpenCoperLock' "Sent $ok file(s) to ComputerShared." }
55+
else { Show-Toast 'OpenCoperLock' "Sent $ok, $fail failed — check your token or connection." }

0 commit comments

Comments
 (0)