提交

提交图

1164 次代码提交

作者 SHA1 备注 提交日期
caorushizi 9a35583eed docs(i18n): add TRANSLATION.md contribution guide
Addresses #638, where a prospective Italian translator asked for clearer
translator onboarding and a way to preview translations in place. The new
guide documents where strings live, the four-file registration path for a
new language, and the Vite HMR dev workflow that already solves the live
preview need. Linked from all three READMEs alongside CONTRIBUTING.md.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 00:03:18 +08:00
caorushizi ba958b9f44 docs: flatten Docker feature-section command to a single line
Collapses the line-continuation form (`docker run ... \\n -v ... \\n caorushizi/mediago:3.5.0`)
into one line across README.md / README.jp.md so all three language
READMEs match (zh was already single-line). Makes the command easier
to copy-paste — readers don't have to deal with trailing backslashes
that some terminals / browsers mangle when highlighting.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 17:15:15 +08:00
caorushizi 19e49e89b9 docs: list Docker Hub alongside GHCR in README and docs
The CI workflow mirrors every built image to Docker Hub
(`caorushizi/mediago`), but the install lists in README (zh/en/jp)
and docs (guides / changelog for all three languages) only mentioned
GHCR. Most users default to Docker Hub, so they would miss the
simpler pull path.

Switch every Docker row into a two-line form showing both registries
explicitly:

    - [**Docker Hub**](https://hub.docker.com/r/caorushizi/mediago): docker run ... caorushizi/mediago:3.5.0
    - **GHCR**: docker run ... ghcr.io/caorushizi/mediago:3.5.0

Docker Hub appears first (shorter image name, more familiar to most
users) and its label is a link to the Hub page. GHCR is kept as a
second bullet for users who prefer it or are already authenticated.

The README feature-section (`🐳 Docker deployment`) code block was
also switched to the unqualified `caorushizi/mediago:3.5.0` form so
the showcase command defaults to Docker Hub, with a prose note that
the same image lives on GHCR too.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 17:09:57 +08:00
caorushizi 3f889da389 fix(ci): move secrets check off if: to dodge Unrecognized named-value: 'secrets'
The Docker Hub mirror workflow used `secrets.DOCKERHUB_USERNAME != ''`
directly inside step `if:` conditions and step-level `env:`
expressions. GitHub's expression parser now rejects that with
`Unrecognized named-value: 'secrets'` — the `secrets` context is only
allowed in a narrow set of positions (mostly `with:` inputs and step
`env:` values, not inside `if:` expressions).

Route the check through a dedicated detection step whose `env:` reads
the secrets (the one permitted way to surface them) and writes a
plain boolean to step outputs. Every downstream conditional then
guards on `steps.dockerhub.outputs.enabled == 'true'` instead.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 16:46:52 +08:00
caorushizi ab669f2e74 ci(docker): mirror image to Docker Hub alongside GHCR
Every image published to `ghcr.io/caorushizi/mediago` now also gets
pushed to `docker.io/caorushizi/mediago` under the same tags, so users
can `docker pull caorushizi/mediago:3.5.0` without setting up GHCR
auth.

- New `DOCKERHUB_IMAGE` env var pinning the Docker Hub slug.
- New `Login to Docker Hub` step guarded by
  `secrets.DOCKERHUB_USERNAME != '' && secrets.DOCKERHUB_TOKEN != ''`
  so forks without Docker Hub credentials can still build + push to
  GHCR without failing the login.
- New `Resolve image targets` step that composes the metadata-action
  `images:` list at runtime — only adds the Docker Hub target when
  both secrets are present. Without this, metadata-action would keep
  emitting Docker Hub tags that build-push-action would then 401 on.
- Build Summary shows both `docker pull` hints when Docker Hub is
  enabled.

Setup (one-time, in repo Settings → Secrets and variables → Actions):
- `DOCKERHUB_USERNAME`: Docker Hub account name
- `DOCKERHUB_TOKEN`:    PAT from https://hub.docker.com/settings/security
                        with Read+Write scope on caorushizi/mediago

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 16:10:33 +08:00
caorushizi 5386bfc8a2 docs: bump guides / changelog to v3.5.0 across zh / en / jp
`docs/guides.md` still linked to v3.5.0-beta.0 assets under a stale
v3.0.1 heading; the English and Japanese versions lagged further
behind at v3.0.0. Update the "Download and install" block on each
guide to point at v3.5.0 (installer / portable / macOS arm64 + x64 /
Linux / Docker) with a link back to the GitHub Releases page for
older builds.

Prepend a v3.5.0 entry to each `changelog.md` using the same
user-facing highlights the README now uses (browser extension /
YouTube + 1000+ sites / OpenClaw Skill / HTTP API / in-app format
conversion / Docker simplification / Go backend). Older entries
(v3.0.0 and earlier) are left untouched — changelog is append-only.

`docs/history.md` intentionally left alone: it's the "old permalinks"
archive and doesn't track the current release.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 16:10:13 +08:00
caorushizi aa998d9a3a chore: add build:docker npm script for local image builds
Wraps `docker build -t mediago:local .` so contributors don't have to
remember the tag convention, and keeps local Docker builds discoverable
alongside pack:electron / release:electron in `pnpm run`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 15:11:37 +08:00
caorushizi dde75321a7 fix(docker): copy extra/ into build context so aria2 ships in the image
`scripts/download-deps.ts` treats the aria2 entry as `source: "local"`
and copies from `extra/aria2/<os>/<arch>/aria2c` into `.deps/`. Every
other downloader (ffmpeg / N_m3u8DL-RE / BBDown / yt-dlp / mediago) is
`source: "github"` and fetched over the network, so they reach the
image fine — but the Docker build context never included `extra/`,
which made `copyLocalTool()` hit its "source missing" branch and
silently skip aria2. Resulting image had no `aria2c`, any direct
download inside the container failed with a binary-not-found error
the moment a task was dispatched.

Add a `COPY extra/ extra/` in the node-builder stage, right before
`pnpm deps:download` runs, so the vendored binary is actually visible
to the script.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 15:11:23 +08:00
caorushizi d23f594b88 ci(docker): allow rebuilding an existing tag via workflow_dispatch
When a released image has to be re-cut (e.g. the broken 3.5.0 that
shipped with the stale per-downloader flags), there was no clean way
to re-publish under the same tag from the workflow — only a fresh
git tag push would trigger a build.

Extend `workflow_dispatch` with two inputs:

- `tag`        — image tag to publish under (e.g. `3.5.0` to overwrite
                 the existing one, or `3.5.0-fix.1` for a side-by-side).
                 Empty value falls back to the pre-existing
                 `dev-<run_id>` behaviour.
- `push_latest` — when rebuilding a stable release manually, optionally
                  re-stamp the `latest` tag at the same time.

`docker/metadata-action` already emits the standard OCI labels,
including `org.opencontainers.image.revision` sourced from `github.sha`.
That field alone is enough to tell two same-tag builds apart via
`docker inspect` — no Dockerfile changes needed, no manually-computed
build args. Summary step now reads version / commit from metadata-action's
own outputs (`steps.meta.outputs.version`) and `github.sha`.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 14:36:30 +08:00
caorushizi b1cbb74f32 fix(docker): use --deps-dir and move CMD flags into entrypoint script
The image was failing at startup with:

    flag provided but not defined: -m3u8-bin

mediago-core consolidated the per-downloader `--*-bin` flags into a
single `--deps-dir` in 3.5.0-beta.1, but the Dockerfile was still
passing the old five flags (`--m3u8-bin`, `--bilibili-bin`,
`--direct-bin`, `--mediago-bin`, `--ffmpeg-bin`). Go flag parsing
rejects unknown flags, so the container exited immediately.

While fixing, extracted the invocation into
`scripts/docker-entrypoint.sh` rather than leaving a nine-line
quoted-array `CMD` at the bottom of the Dockerfile:

- `exec mediago-core …` replaces the shell so SIGTERM from
  `docker stop` reaches the Go process directly — no shell parent to
  swallow signals.
- `"$@"` forwards any extra flags from `docker run image … --foo=bar`
  so callers can override individual options without replacing the
  whole command.
- Switching from `CMD` to `ENTRYPOINT` makes the script the fixed
  launch path; runtime overrides still work via the forwarded `"$@"`.
- Editing default args no longer touches the Dockerfile (and thus
  doesn't invalidate the full image layer).

New `.gitattributes` pins `*.sh` to LF line endings so a Windows
checkout doesn't turn the entrypoint into a `/bin/sh^M: bad interpreter`
error inside the Linux container.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 14:24:50 +08:00
caorushizi f01cae6554 docs: rewrite README (zh/en/jp) around user-facing features, extract dev setup
The three READMEs had drifted into a dev-first shape — the first screen
of each started with pnpm / install commands before telling end users
what the product actually does — and the Japanese version had fallen
behind (outdated commands, no v3.5.x downloads, screenshots pointing
at an external CDN rather than the in-repo images).

Rework all three around the v3.5.0 user-friendly feature story and
move the developer setup into its own file:

- Unified skeleton across zh/en/jp: header badges → one-sentence pitch →
  six feature sections (browser extension · YouTube / 1000+ sites ·
  OpenClaw Skill · HTTP API · in-app format conversion · Docker +
  LAN) → screenshots → v3.5.0 downloads (+ BT Panel one-click) →
  v3.5.0 highlights → tech-stack badge row → acknowledgements →
  disclaimer → link to CONTRIBUTING.
- Header elements preserved verbatim: nav links, language switcher,
  MediaGo Pro / Try Now banners, downloads / stars / forks / GitCode /
  Trendshift badges, 4 screenshots, BT Panel one-click deployment,
  the full 5-point disclaimer.
- Tech-stack prose list compressed to a single row of shields.io
  badges (React / Electron / Vite / TypeScript / Tailwind / shadcn/ui /
  Go / Ant Design).
- Acknowledgements gain aria2 (now powering direct downloads after the
  gopeed swap).
- README.jp.md specifically: fixed four `static.ziying.site/images/*`
  references to use the in-repo `./images/*` (matching zh/en); replaced
  the outdated v3.0.0-only download block with v3.5.0; added the
  English-language switcher link that was missing from the header.
- README.md/zh.md: dropped the v3.0.0 changelog block (history lives
  on GitHub Releases) and the "Quick Start" / "Running locally" pnpm
  command blocks.

New file: `CONTRIBUTING.md`. English only — prerequisites, repo layout,
a short list of the commands contributors actually type (deps:download
/ dev:electron / dev:server / pack:electron / release:electron / check),
Conventional Commits note, PR guidance. Each README ends with a one-
line pointer to it in the local language.

No product behaviour change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 14:14:28 +08:00
caorushizi 74b67101ae feat(extension): pin manifest version to the Desktop app version
Build & Push Docker Image / Build and push multi-arch image (push) Failing after 1s
Users naturally expect the "Current version" label in chrome://extensions
to match the version shown inside the MediaGo window. Tie both to a single
source of truth: `apps/electron/app/package.json`, which Electron's build
pipeline (`apps/electron/tsdown.config.ts`) already reads and exposes as
`process.env.APP_VERSION`.

`manifest.config.ts` now imports the same file and:

- `version`   — stripped down to the numeric prefix (`split(/[-+]/)[0]`)
                so Chrome accepts it. Chrome's manifest parser only
                allows 1–4 dot-separated integers; SemVer pre-release
                or build suffixes (`-beta.2`, `+build.7`) get rejected
                at install time.
- `version_name` — keeps the full SemVer string. It's free-form, not
                used for update-ordering, and is what chrome://extensions
                displays when present.

Effect: bumping `apps/electron/app/package.json` once is enough to
re-stamp both the Desktop binary and the bundled extension manifest.
The extension's own `packages/mediago-extension/package.json` is a
private workspace package — its `version` field is unused by the
manifest and intentionally left alone to avoid a pointless sync step
on every release.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
v3.5.0
2026-04-21 18:16:07 +08:00
caorushizi 3279f40541 v3.5.0 2026-04-21 18:16:07 +08:00
caorushizi 62331e5c7b fix(ui,shared): refresh download list on externally-created tasks
Go Core already broadcast a `download-create` SSE event whenever
`/api/downloads` POST lands (UI, browser extension, Docker clients —
any route). The UI listened, but only to bump the sidebar badge via
`useDownloadStore.increase()`. The SWR list fetched by `useTasks`
stayed stale until the user refreshed manually, so tasks imported
through the extension's desktop-http / docker-http modes never
appeared without a page reload.

Fan the same SSE event through the existing `dispatchDownload`
pipeline so `useTasks` can mutate its SWR cache alongside the other
lifecycle events (success / failed / stopped).

- `packages/shared/common/src/types/index.ts`: new
  `DownloadCreatedEvent` (`type: "created"`, carries `{ ids, count }`).
- `apps/ui/src/api/events.ts`: existing `download-create` handler now
  additionally calls `dispatchDownload({ type: "created", ... })`
  after its badge-increment side effect. Parses `ids` from the
  payload for downstream filtering if anyone needs it later.
- `apps/ui/src/hooks/use-tasks.ts`: adds `isCreatedEvent` type guard
  and calls `mutate()` on hit, same pattern as the other events.

Internal task creation (UI form submit) is untouched — it still
refreshes via its own local mutate. The SSE path only matters for
tasks that show up out-of-band.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 17:11:37 +08:00
caorushizi d9c3d8ae1d fix(ui): let users copy text from the download-log terminal
Two separate bugs had to be fixed together before copy worked:

1. `terminal-dialog.tsx` used to swallow every contextmenu event
   inside the dialog (`e.preventDefault(); e.stopPropagation()`) —
   killing Electron's built-in "Copy" menu for selected text in the
   xterm log. Drop the preventDefault so the native menu fires, but
   keep stopPropagation (now scoped to the contextmenu handler
   alone) so the event doesn't bubble up through the React tree to
   `DownloadTaskItem`'s `onContextMenu` and pop its "select /
   download / refresh / delete" menu instead. Radix Dialog portals
   the DOM to document.body, but React synthetic events still
   travel the virtual tree — this is the classic portal-bubbling
   gotcha.

2. xterm.js does not bind Ctrl+C / Cmd+C to "copy selection" by
   default; it forwards them as control chars. Since the log view
   uses `disableStdin: true`, hijacking those keys is safe.
   `attachCustomKeyEventHandler` now intercepts copy shortcuts,
   writes the selection to the clipboard via `navigator.clipboard`,
   and returns false so xterm stops handling the event. No selection
   → passthrough.

Result: both right-click → Copy and Ctrl/Cmd+C paths work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 17:11:16 +08:00
caorushizi c34812841e fix(core): sanitize filenames at task creation to prevent path-separator injection
Twitter-style page titles like "(2) 主页 / X" land a literal "/" in
the download filename. Gopeed used to strip such characters silently;
aria2 is strict and passes them through to the OS, which then reads
"/" as a path separator — the save ends up at
`.../(2) 主页 /X-....mp4` pointing at a non-existent sub-directory
and fails with `ERROR_PATH_NOT_FOUND (errNum=3)`.

Sanitize once, at the task-creation boundary, so the DB row, the
downloader command-line `-o` arg, and the post-download
`CheckFileExists(rec.Name, ...)` probe all agree on the same
filesystem-safe value.

- New exported `core.SanitizeFilename` replaces reserved path /
  wildcard characters (`\ / : * ? " < > |`) and ASCII control chars
  with `_`, and right-trims dots / spaces (Windows strips those
  silently, producing a filename that doesn't match the DB row).
  Falls back to "download" if every character was illegal.
- `service/download_task.go` `AddDownloadTask` and `AddDownloadTasks`
  run titles through `SanitizeFilename` before the
  `FindByName` de-duplication check, so both the dedup lookup and
  the persisted row see the cleaned value.
- `core/downloader.go` `buildArgs` still calls `SanitizeFilename`
  defensively on `p.Name` — cheap, and guards any future path that
  bypasses the service layer.

Applies to every downloader (aria2, yt-dlp, BBDown, N_m3u8DL-RE,
mediago) since the fix is in the shared `name` arg-building branch
used by all Schema entries. Pre-existing broken tasks in the DB will
still fail "file not found" on the UI and need to be deleted +
re-created.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 17:10:54 +08:00
caorushizi 28e2c58366 fix(node-service): fail fast when the preferred port is already bound
If a stray MediaGo instance (typically the installed Desktop build
running in the background from the Start menu) already holds the
preferred port, a freshly-spawned child fails to bind and exits
immediately — but `waitForHealthy()` happily answers green because
the *other* instance's `/healthy` responds on the same port.
ServiceRunner then considers the service "started" and every
subsequent request silently lands on the wrong process. In practice
this surfaced as dev mode talking to the installed Go Core and
reporting a stale gopeed binary path, even though the dev build had
already been rebuilt with aria2c.

Pre-check: before spawning, open and close a throwaway TCP listener
on `<bindHost>:<preferredPort>`. If the bind fails, throw a
human-readable error instead of proceeding. The tiny check-vs-bind
race window is acceptable for catching the real-world "long-running
stray instance" case.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 17:10:30 +08:00
caorushizi a9e3ed6a22 feat(core): switch direct downloader from gopeed to aria2
Gopeed's last release is aging and its SChannel-linked Windows build
chokes on modern CDN TLS handshakes (we hit SEC_I_MESSAGE_FRAGMENT on
twimg.com). Replace it with aria2 — same short flags (-x/-s/-k carry
over verbatim), stricter arg handling, and well-trodden cross-platform
builds.

Binaries are vendored in-tree at extra/aria2/<os>/<arch>/ rather than
pulled from a single GitHub release, because aria2 static builds for
Linux / macOS / Windows come from different upstream repos. To keep
the deps pipeline unified:

- `scripts/download-deps.ts` grows a "source": "local" branch that
  copies from `extra/<tool>/<os>/<arch>/` into `.deps/<os>-<arch>/`
  instead of fetching from GitHub. Resulting layout matches the rest
  of the tools, so binaryResolver.ts needs no changes.
- `scripts/deps-versions.json`: `gopeed` entry removed, `aria2`
  entry added with source:"local" + path:"extra/aria2".
- `apps/core/internal/core/types.go`: BinaryNames[TypeDirect]
  `"gopeed"` → `"aria2c"`.
- `apps/core/internal/core/schema/loader.go`: direct schema's Args
  map to aria2's flags (-d / -o, plus --console-log-level=notice /
  --summary-interval=1 / --allow-overwrite=true /
  --auto-file-renaming=false for parseable output and predictable
  rerun behaviour). ConsoleReg regexes updated to aria2's summary
  line format (e.g. "DL:512KiB" for speed, "(83%)" for percent).
  `--check-certificate=false` tacked on as a workaround for the
  SChannel handshake issue on the bundled 1.19.0 Windows build —
  proper fix is upgrading the vendored binary to a build that links
  against OpenSSL (e.g. 1.37.0), comment calls that out.
- `apps/core/.env`, `Dockerfile`, `apps/electron/scripts/build.ts`,
  `apps/core/README.md`: gopeed → aria2c / aria2 references.

No DB migration: existing `type: "direct"` rows keep working, the
underlying binary just swaps.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 17:09:35 +08:00
caorushizi 48aabc9a7d v3.5.0-beta.2 2026-04-21 14:26:34 +08:00
caorushizi b72b39ad7e fix(node-service): bind to 0.0.0.0 when internal=false so localhost reaches the server
ServiceRunner used to pass the detected LAN IPv4 as the `HOST` env var
when `internal: false`, so the Go Core bound *only* to that LAN IP —
requests to 127.0.0.1 / localhost were refused. That blocked the
browser-extension's `desktop-http` mode (fixed at `http://127.0.0.1`).

Split bind host from display host:
- Bind (`HOST` env to child): `0.0.0.0` when `internal: false`, else
  `127.0.0.1`. Listener now accepts traffic on every interface.
- Display (`state.host` / `getURL()`): keeps the detected LAN IPv4
  when `internal: false` so the settings UI can surface a shareable
  LAN URL, falls back to 127.0.0.1 otherwise.

Net effect: Desktop is reachable via 127.0.0.1, localhost, and its LAN
IP simultaneously, while the UI still shows the LAN address.

Also translates all remaining Chinese JSDoc / inline comments in
`index.ts` and `utils.ts` to English, matching the rest of the package.
Behaviour otherwise unchanged.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 13:56:22 +08:00
caorushizi f04989e973 feat(extension): bundle into desktop app with settings entry and docs
Ship the MediaGo browser extension alongside the Desktop installer so
users can "Load unpacked" it from Chrome / Edge without building the
repo or downloading a separate zip. Adds a discovery path from inside
the app, a three-language doc page, and fixes the Desktop HTTP port
the extension was pointing at.

Packaging:
- `pnpm build:electron` now also builds `@mediago/extension` (turbo
  filter), and `apps/electron/scripts/build.ts` copies the dist to
  `app/build/extension/` and declares it in electron-builder
  `extraResources` → installers land `resources/extension/`.

Runtime:
- `resolveExtensionDir()` in binaryResolver — dev points at the
  monorepo dist, prod at `process.resourcesPath/extension`, env
  override via `MEDIAGO_EXTENSION_DIR`.
- New IPC `app.getExtensionDir()` returns the resolved path. UI pairs
  it with the existing `shell.open()` (no dedicated open-folder IPC)
  so the pattern matches configDir / binDir / localDir.

Settings UI:
- New "Browser extension directory" button in Settings → More Settings,
  right next to the existing folder shortcuts. Web/server mode hides
  the row behind `isWeb` and the stub returns "".
- New i18n key `extensionDir` in shared zh/en resources; duplicate
  `currentVersion` key removed from zh.ts along the way.

Port fix:
- Extension `DESKTOP_HTTP_BASE` corrected from `:9900` → `:39719` to
  match the Electron-side `preferredPort`. 9900 is the standalone
  Go Core / web-server port — two separate deployments.

Docs:
- New `docs/extension.md` + `docs/en/extension.md` + `docs/jp/extension.md`
  covering what the extension does, how to install the unpacked
  build, the three dispatch modes (schema / desktop-http / docker-http),
  import-behaviour toggles, language switching, and common pitfalls.
- Sidebar entries registered in `.vitepress/config.ts` for all three
  locales.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 13:56:05 +08:00
caorushizi e5bcd7a224 chore(core): translate build-script console logs to English
Mirror the English-only convention already used in the Electron/UI
scripts. Covers dev.ts (start dev server / build Player UI / compile
dev binary) and release.ts (build all-platform binaries / package /
clean). Behaviour unchanged — message text only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 13:55:34 +08:00
caorushizi fbf31d706c style(extension): apply Cursor-inspired warm palette and typography
Reskin popup + options to match Cursor's design language — warm off-
white over cream surfaces, gothic/serif/mono three-voice typography,
oklab borders, diffused atmospheric shadows. Preserves every feature;
only visuals change.

Palette & tokens (globals.css):
- Light: `#f2f1ed` page, `#26251e` text, `#e6e5e0` card, `#cf2d56`
  crimson for hover, `#f54e00` orange accent, surface-100..500 scale,
  AI-timeline colors (peach / sage / blue / lavender)
- Dark: warm-offset variant (`#1e1d16` page, `#ebeae5` text) — no
  cold neutral inversion
- Borders: `oklab(0.263084 -0.00230259 0.0124794 / 0.1)` perceptually
  uniform warm brown
- Shadows: ambient (16/8px), elevated (28/70px + oklab ring), focus
  (4/12px) — all token-driven
- Radius scale: 4px inline / 8px default / 10px featured / 14px breathing
- Motion: `--transition-color: 150ms ease` / `--transition-shadow: 200ms`

Fonts (locally bundled variable woff2, no remote fetch):
- Geist Variable (→ CursorGothic analogue)
- Source Serif 4 Variable (→ jjannon analogue)
- JetBrains Mono Variable (→ berkeleyMono analogue)
- OpenType feat utilities: `.font-feat-display` (ss09/ss01) and
  `.font-feat-editorial` (cv11/liga)
- Global h1/h2/h3 letter-spacing follows Cursor's size curve
  (-0.03em / -0.02em / -0.0125em)

Components:
- Button — warm-primary (hover → crimson text) + dark / outline /
  secondary-pill / tertiary-pill / ghost / light-surface / link
- Badge — full pill + timeline-color variants mapped to DownloadType
  (thinking/grep/read/edit/mediago) via `variantForDownloadType()`
- Card — ambient by default; `elevated` for permanent lift;
  `interactive` eases ambient → elevated on hover (used on ServerCard,
  the page's primary action target)
- Sonner / Switch / Input / RadioGroup — warm-palette rewrites with
  oklab focus rings and serif descriptions where appropriate

Pages:
- Popup — tonal bands (header cream → page-info surface-100 → list
  bg → footer surface-100), source type badges colored by DownloadType
- Options — responsive hero (40 → 52 → 60px with scaled letter-
  spacing), decorative blur glows behind headline, "cream → bg →
  cream" section rhythm with a closing surface-200 strip, per-card
  mono chapter dots in timeline colors, RuleListCard redone as a
  vertical timeline echoing Cursor's AI-step visualisation

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 11:16:43 +08:00
caorushizi b782329ae2 feat(extension): add i18n with system/zh/en language toggle
The MediaGo extension shipped Chinese-only (50+ hardcoded strings across
popup, options, background). Wire up i18next + react-i18next so the UI
follows the browser language by default and lets the user override to
zh / en in a new LanguageCard on the options page.

- `ExtensionSettings.language: "system" | "zh" | "en"` (default system),
  persisted via existing `chrome.storage.local`
- `resolveLanguage()` maps system -> `chrome.i18n.getUILanguage()` or
  navigator.language with a zh/en fallback
- Bootstrap one i18next instance per page (popup / options), subscribe
  to `chrome.storage.onChanged` so switching on one surface updates the
  other without a reload
- Background service worker emits `LocalizedMessage` descriptors
  (`{ key, values? }`) for toast copy; popup calls `t(key, values)` so
  the SW never has to carry an i18n instance
- Full zh/en coverage for popup, options, error paths
- Chrome-native `_locales/{en,zh_CN}/messages.json` + `__MSG_*__` for
  `manifest.name` / `description` / `action.default_title` so the
  chrome://extensions row and toolbar tooltip follow the browser UI
  language (independent dimension from the in-UI toggle, by design)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-21 11:16:03 +08:00
caorushizi 65e20638ff feat(extension): expose silent / downloadNow knobs in options
The useUrlInvoke hook at apps/ui/src/hooks/use-url-invoke.ts reads
both `silent` and `downloadNow` query params; they were previously
hard-coded on the extension side. Surface them as user toggles so
Schema-mode users can opt into "open the download form for review"
and both modes can opt into "start downloading immediately instead
of just queuing".

- ExtensionSettings gains `downloadNow` (default false) and
  `schemaSilent` (default true).
- importViaHttp threads `startDownload` into the POST body.
- buildTaskDeeplink writes `silent=1` / `downloadNow=1` into the
  deeplink URL based on the flags (falsy => omit the key, matching
  the truthy-check in useUrlInvoke).
- New headless Switch component (no @radix-ui/react-switch dep).
- New ImportBehaviourCard in options, with a patch-style save hook
  so each toggle persists immediately.
- use-options.ts now merges on top of the current persisted settings
  so the server-config card doesn't wipe the new fields.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 23:58:44 +08:00
caorushizi 19048f6ead feat(extension): add MediaGo browser extension for media URL sniffing
A Manifest V3 extension that sniffs downloadable video / audio URLs
across every site the user visits and hands captured sources to a
MediaGo server in one click. Ships unlisted (load-unpacked .zip), no
Chrome Web Store.

Dispatch modes (user-picked in options, no silent fallback):
- desktop-schema: navigates the current tab to
  `mediago-community://index.html/?n=1&silent=1&url=…` using the
  cat-catch `chrome.tabs.update` pattern. Reuses the existing
  `useUrlInvoke` hook in apps/ui — no new deeplink plumbing.
- desktop-http: POST http://127.0.0.1:9900/api/downloads against the
  Go Core bundled in a running Desktop.
- docker-http: same POST against a user-configured host, with an
  optional X-API-Key header for `--enable-auth` deployments.

UI is React 19 + Tailwind v4 + shadcn/ui (new-york, neutral), matched
to apps/ui's stack. Popup shows per-tab sources with a red badge count;
options page has a 3-mode radio + per-mode field panel.

Supporting changes:
- Abstract sniff filter rules into @mediago/shared-common/sniff and
  point the Electron sniffing helper at the same exports so desktop
  and extension stay in lock-step.
- Tighten the YouTube host rule to actual video / short / live / embed
  URLs (drops the homepage and feed false-positives).
- Fix the macOS electron-builder config: CFBundleURLSchemes was
  hard-coded to a test string "mediagoaaa" — now sourced from
  process.env.APP_NAME (mediago-community in .env) like everywhere
  else. Also add a top-level `protocols` entry for clarity.
- Vite build pulls APP_NAME from the repo root .env via loadEnv() +
  `define`, so the scheme name stays single-sourced.
- Root scripts: `pnpm dev:extension`, `pnpm build:extension`,
  `pnpm pack:extension` (+ scripts/pack-extension.ts that cross-
  platform-zips dist/ into release/).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 23:54:31 +08:00
caorushizi b29a5f1748 perf(settings): mount cards one per frame to break up navigation jank
DevTools profiling showed a ~319ms long task when switching to the
settings page, dominated by scripting inside node_modules — Ant Design
Form.Item registration and cssinjs style generation firing for ~25
fields in a single synchronous pass. Chunk loading was only ~9ms, so
earlier fixes around lazy-import and Suspense fallbacks missed the real
cause.

Changes:
- Stream the 6 cards in one per animation frame (visibleCount driven
  by requestAnimationFrame), so the longest task is a single card's
  registration instead of the full page. The first card paints
  immediately and the rest follow over the next few frames.
- Skip the initial setFieldsValue via an isFirstSync ref;
  initialValues already seeds the form, so that effect was only
  forcing Ant Form to diff every Form.Item again right after mount.
- Wrap cardSections in useMemo with a narrow dep list
  (settings.apiKey, settings.local, envPath, updateAvailable, stable
  callbacks) so SSE config-changed updates don't rebuild the 340-line
  JSX tree unless those specific fields changed.
- Move the key from <Card> to the outer <div> so React stops treating
  each re-render as a remount of the card subtree.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 06:03:33 +08:00
caorushizi 8783d949ba feat(favorite): optional title with auto-fetch and URL-first form
- Make favorite title optional in both the DTO (drop binding:"required")
  and the add-favorite modal (no validator). Server falls back to the
  URL when the title is empty so the list entry always has a label.
- Auto-fill the title via the existing GET /api/url/title scraper when
  the user leaves the URL field, unless they already typed their own
  title. Failures are swallowed (server fallback still applies).
- Reorder the modal so URL is on top and Title below, matching the
  actual data-flow: users paste a URL first and the title follows.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 05:28:35 +08:00
caorushizi bee6bd1cbd fix(ui,core): drive download badge via SSE and polish favorite error UX
- Go Core now broadcasts a "download-create" SSE event after
  POST /api/downloads succeeds, carrying the new task ids and count.
  The renderer's events.ts listens for it and calls useDownloadStore's
  increase() imperatively, so the sidebar badge updates regardless of
  which WebContents issued the request — the source-extract overlay
  dialog has its own Zustand instance and can no longer silently swallow
  the increment.
- Remove the now-redundant local increase() calls in download-form and
  browser-view-panel so the counter is incremented exactly once per task
  from a single source of truth.
- Favorite.Create returns 409 Conflict with a translated
  MsgURLAlreadyExists message when the URL already exists, instead of
  500 with the raw "url_already_exists" string.
- http.ts response interceptor now surfaces the server's translated
  message on 4xx/5xx instead of Axios's generic
  "Request failed with status code XXX".
- Simplify getFavIcon: drop the 1s <img> probe that dropped most URLs
  and return the canonical /favicon.ico; <Avatar> already falls back to
  a link icon when the image fails to load.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 05:28:22 +08:00
caorushizi bdea15ace9 fix(electron): rebuild tray menu on language change
The tray context menu was built once at startup, before the language
was loaded from Go Core, so its labels were stuck in the fallback
locale and never updated when the user switched languages. Keep the
Tray instance on the class, extract the menu build into refreshTrayMenu,
and subscribe to i18n "languageChanged" so the menu follows the
current language for both startup hydration and runtime changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 04:01:20 +08:00
caorushizi 72acd2ca94 refactor(ui,electron): fix "system" language fallback and rework settings layout
- Resolve AppStore.language at apply-time via a shared resolveAppLanguage
  helper so both renderer (navigator.language) and Electron main
  (app.getLocale) follow the actual OS locale instead of silently
  falling back to zh when the stored value is "system".
- Settings page: horizontal form with a fixed label column, borderless
  cards flowing naturally in a 2-column CSS columns layout; drop
  width="xl" on inputs and wrap button groups to fix English-label
  overflow.
- scripts/download-deps: add a PowerShell fallback for zip extraction
  on Windows.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-15 03:46:38 +08:00
caorushizi ba5f389dbe fix: upgrade zustand to stable and fix broken player build scripts
- Upgrade zustand from 5.0.0-rc.2 to ^5.0.0 (resolved 5.0.12)
- Fix player:dev and player:build scripts that referenced non-existent
  @mediago/player-build, now correctly point to @mediago/core-build

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-14 04:22:05 +08:00
caorushizi aaa5ee69d2 docs: fix documentation drift in CLAUDE.md
Correct 8 inaccuracies where CLAUDE.md diverged from the actual codebase:
server is a Node.js launcher not Koa 3, apps/player/ doesn't exist,
remove non-existent commands, fix adapter layer and event bridge file
names, correct DI and module format claims.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 16:21:14 +08:00
dependabot[bot] fb671d5790 chore(deps): bump axios from 1.14.0 to 1.15.0
Build & Push Docker Image / Build and push multi-arch image (push) Failing after 0s
Bumps [axios](https://github.com/axios/axios) from 1.14.0 to 1.15.0.
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.14.0...v1.15.0)

---
updated-dependencies:
- dependency-name: axios
  dependency-version: 1.15.0
  dependency-type: direct:production
...

Signed-off-by: dependabot[bot] <support@github.com>
v3.5.0-beta.1
2026-04-13 00:30:13 +08:00
caorushizi e32bfd1cb2 v3.5.0-beta.1 2026-04-13 00:27:24 +08:00
caorushizi 50a1a90e6a docs: rewrite README in English and polish UI translations
- Rewrite README.md in idiomatic English as the default language
- Move original Chinese README to README.zh.md, delete README.en.md
- Polish ~80 UI translation strings in en.ts for natural English
- Fix hardcoded Chinese in Skills setup commands (setting-page)
- Fix mismatched translation key: reppeatPassword → repeatPassword

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 00:19:14 +08:00
caorushizi 2406b5b617 chore(electron,server): remove verbose startup logs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 00:19:14 +08:00
caorushizi 913d645d89 fix(electron): resolve deprecation warnings and unhandled promise rejection
- Replace deprecated webContents.canGoBack/goBack with navigationHistory API
- Add try/catch to autoUpdater.checkForUpdates to handle ERR_CONNECTION_REFUSED
- Replace cross-fetch with Node native fetch to fix url.parse() deprecation
- Remove unused cross-fetch dependency

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 00:19:14 +08:00
caorushizi c7dd925a34 feat(core): add yt-dlp plugin and consolidate binary paths into --deps-dir
- Add youtube download type with yt-dlp schema (CLI args, progress regex)
- Replace 6 individual --xxx-bin flags with single --deps-dir flag
- Add BinaryNames map in Go core to auto-resolve binary paths from deps dir
- Simplify Electron/Server binary resolvers to return depsDir only
- Add yt-dlp to deps-versions.json (v2026.03.17)
- Add YouTube option to UI download form with URL auto-detection
- Fix download-terminal.tsx writing [object Object] instead of log content
- Improve error messages when binary not found (include path and type)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 00:19:14 +08:00
caorushizi c289d3690f refactor(browser-extension): remove dead FloatButton, jQuery and Lit dependencies
FloatButton was effectively dead — it listened for "webview-link-message" which
no one sends after the IPC namespace refactoring, and its functionality is fully
covered by BrowserViewPanel in the main UI. Remove it along with jQuery, Lit and
nanoid dependencies. Consolidate remaining BilibiliButton into a single-file
vanilla custom element, replace fragile global-index DOM lookup with
closest()-based card-relative queries, and replace setInterval polling with
MutationObserver. Build output reduced from 607KB to 1.9KB.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 00:19:14 +08:00
caorushizi 8224c68f66 refactor(ui): optimize source-extract page and browser navigation flow
Extract shared navigation logic into useBrowserActions hook, fix race conditions
in sniffing helper, add event listener cleanup in webview service, split store
selectors for granular re-renders, memoize list items, throttle ResizeObserver,
and add loading/error states to favorites list. Also remove unused getMachineId
IPC handler and fix IPC callback type signatures.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 00:19:14 +08:00
caorushizi 6571fc022d refactor(ipc): namespace IPC events and consolidate similar methods
Replace flat IPC string constants with namespaced groups (browser.*,
app.*, dialog.*, shell.*, contextMenu.*, update.*) and consolidate
similar operations into generic methods:
- dialog.open/save replaces selectFile, selectDownloadDir, export/import
- shell.open replaces openDir, openUrl, openBrowser
- contextMenu.show replaces 3 separate context menu handlers
- on/off replaces rendererEvent/removeEventListener

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 00:19:14 +08:00
caorushizi 109c09a911 refactor(electron): replace screenshot hack with overlay WebContentsView for download dialog
Instead of capturing a screenshot, transferring a large DataURL via IPC,
and hiding the browser WebContentsView, use a separate overlay
WebContentsView layered above the browser view to render the download
form. This eliminates flicker, avoids potential page state loss, and
removes the inefficient screenshot transfer.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 00:19:14 +08:00
caorushizi 90d7fe578f feat(core): upgrade mediago-core to v0.2.1 and adapt log parsing
- Bump mediago-core from v0.1.0 to v0.2.1 in deps-versions.json
- Update ConsoleReg to match new structured log format:
  speed regex for B/s units, start/isLive/error patterns
- Remove --no-log flag so structured logs are visible for parsing

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 00:19:14 +08:00
caorushizi 82f948cdd9 fix(ui): fix openDir using wrong field names and antd 6 form warnings
- Use correct GoEnvPath fields (configDir/binDir) instead of non-existent workspace/binPath
- Defer form.setFieldsValue after modal opens so Form is mounted
- Add Input child to hidden Form.Item with name prop to satisfy antd 6

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 00:19:14 +08:00
caorushizi 25d149e9ef feat(core): integrate mediago-core as new download type
Add mediago-core (caorushizi/mediago-core) as a fourth download type
alongside m3u8, bilibili, and direct. This enables HLS/DASH streaming
downloads using the custom Go-based downloader.

Changes span the full integration path: dependency download config,
Go Core backend (type, schema, CLI flag, binary map), binary resolution
for Electron/Server, shared TypeScript types, i18n labels, UI dropdown,
dev scripts, and Dockerfile.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-13 00:19:14 +08:00
caorushizi 8d757ba18d docs: fix docker image tag (remove v prefix)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 22:20:01 +08:00
caorushizi 4f1c9fe65c docs: add OpenClaw Skill to README intro, docs homepage and guides
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 19:17:23 +08:00
caorushizi 78e3f96e3a docs: update README and guides for v3.5.0-beta.0
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 19:10:53 +08:00
caorushizi 0c377b7cb8 chore: bump version to 3.5.0-beta.0
Build & Push Docker Image / Build and push multi-arch image (push) Failing after 0s
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
v3.5.0-beta.0
2026-04-03 17:56:51 +08:00