1179 Commits

Author SHA1 Message Date
y2hlbg ab67f6b409 docs: remove commercial entrypoints
Deploy MediaGo Docs / Build Docs (push) Failing after 1s
2026-04-28 19:14:58 +08:00
y2hlbg 3eb7f13eca docs(readme): update 52pojie credit note 2026-04-28 15:03:57 +08:00
y2hlbg 3b279e4a05 docs(readme): add 52pojie credit note 2026-04-28 15:03:57 +08:00
caorushizi ed03781692 fix: package.json name 2026-04-28 00:28:27 +08:00
y2hlbg fb9dd15186 fix(docs): restore canonical site url 2026-04-26 20:52:58 +08:00
y2hlbg 115572c513 feat(docs): add seo blog pillar pages 2026-04-26 20:24:22 +08:00
y2hlbg 1aa2bcd511 docs: add community links to readme 2026-04-23 10:08:23 +08:00
Charlie 248237d593 feat(i18n): add Italian localization (#646) 2026-04-23 09:04:18 +08:00
caorushizi 99281d89f9 docs: refresh VitePress homepages and add download API reference
A batch of VitePress site updates around the v3.5.0 release:

- Refresh the zh / en / jp homepages into a 3x3 feature grid aligned
  with README v3.5.0: keep the foundational "no packet capture /
  batch download / mobile playback" cards, add v3.5.0 pillars
  (browser extension / broad video source coverage / OpenClaw Skill /
  HTTP API / built-in format conversion / one-line Docker). Rewrite
  the hero with a tighter "cross-platform video downloader + built-in
  sniffing" tagline and point the English hero at the
  English-localized `home_en.png`.
- Fix en / jp hero action links that previously routed to the Chinese
  root pages (`/guides` → `/en/guides`, `/jp/guides` etc.).
- Add the macOS usage block to the en / jp quick-start guides
  (Intel vs Apple Silicon + `sudo xattr -dr com.apple.quarantine`
  unblock command), and drop the QQ feedback group tip from those
  locales — QQ is China-only, not useful for en / jp audiences.
- Add a new Download API reference page (`/api`) for all three locales,
  registered in the sidebar under "开始 / Quick start / 始める".
  Covers Base URL (desktop `39719`, Docker `9900`), auth (API key from
  Settings page), curl quick-start, download SSE events, and the full
  `/api/downloads/*` endpoint reference.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 01:12:17 +08:00
caorushizi aa24ef5966 docs(readme): use English-localized screenshots in English README
The English README was reusing the Chinese-localized screenshots
(`home.png`, `home-dark.png`, `settings.png`, `browser.png`), which
left English readers staring at a UI they could not read. Ship a
parallel set of English screenshots under the `_en` suffix and point
`README.md` at them; `README.zh.md` / `README.jp.md` keep their own
assets untouched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 00:29:58 +08:00
caorushizi e94b91f2f7 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:13:22 +08:00
Charlie fc99be56df Merge pull request #640 from caorushizi/fix/installer-metadata
fix(installer): show version in title bar, set installer FileDescription (#637)
2026-04-22 23:40:23 +08:00
Charlie 34bbe42600 Merge pull request #639 from bovirus/master
Add Italian langauge
2026-04-22 23:36:50 +08:00
caorushizi dfcdf6a6a5 fix(installer): show version in title bar, set installer FileDescription (#637)
- `installer/installer.nsh`: customHeader macro sets Caption to
  "Setup - ${PRODUCT_NAME} ${VERSION}" so users can see which release
  they're installing from the window title (the default $(^SetupCaption)
  omits the version, and re-setting Name trips NSIS warning 6029 which
  electron-builder's -WX flag treats as a hard error).
- `scripts/build.ts`: afterAllArtifactBuild hook runs the app-builder
  rcedit helper on the generated NSIS installer to rewrite its
  FileDescription to "${APP_NAME} installer". electron-builder's
  NsisTarget.computeVersionKey() hardcodes VIAddVersionKey /LANG=1033
  "FileDescription" "${appInfo.description}", binding the installer's
  FileDescription to the app binary's (both drawn from package.json
  description); any in-NSIS override collides on the same LANG+key
  with a hard "already defined!" error that -WX does not gate.
  Post-processing with rcedit sidesteps this and lets installer and
  app binary carry distinct descriptions — same approach VS Code's
  Inno Setup pipeline uses (where "{AppName} Setup" is the default).

Closes #637

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 23:23:51 +08:00
bovirus f395d49a10 GUI - Add Italian language 2026-04-22 17:17:10 +02:00
bovirus f6a228f318 Browser extension - Add italian language 2026-04-22 16:41:46 +02: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