No description
  • Emacs Lisp 68.7%
  • Python 25.2%
  • JavaScript 5.2%
  • Makefile 0.8%
Find a file
Kinneyzhang a8f801bd8b
Some checks failed
Repository structure / structure (push) Has been cancelled
feat(bili): expose the client as an embeddable View
`etaf-bili-open` was the only host: it owns a fixed buffer, mounts the shell
itself and drove layout from a global window-size hook keyed to that buffer, so
a desktop window or a floating panel could not show the client.

Split the mount from the client:

- `etaf-bili-create` returns a headless session and `etaf-bili-view` returns
  its View, matching the create/view pair every other ETAF application exposes.
- The View adopts the buffer it is mounted into: the session, its change
  callback, the buffer-local session the keys and commands read, and the
  palette it declares on a host surface.
- Layout follows the region the View is mounted into. The shell watches the
  committed viewport and lays out for it, so a panel or a desktop window
  reflows the card grid on resize; the global `window-size-change-functions`
  hook is gone and `etaf-bili-open` now mounts through the same path.
- Unmounting stops transfers, requests and sign-in polling, makes pending
  replies inert and keeps the library, selection and account, so a session
  mounted again resumes where it was. `etaf-bili-open` still closes it fully.
- The appearance is one cell shared by the gallery and the surface that frames
  it, so the `T` key and a panel's title-bar switch repaint both; the grid
  paints its own gaps so a palette change does not leave the previous surface
  between the cards.

Validation: make check (structure, compile, 18 acceptance scenarios, API
inventory) passes, including the new host-mount scenario. GUI: bili panel at
0.75x0.8 of the frame renders three cover columns, follows a resize to
150x38 (columns 58 -> 142, across 1 -> 3), switches dark/light from the
title bar and by the shared cell, keeps its keys (`2` loads the ranking), clips
deliberately below 64x28, and the desktop window renders the same View.
2026-10-25 11:30:12 +08:00
.githooks feat: add the Bilibili client 2026-10-13 23:15:13 +08:00
.github/workflows chore(scripts): sync shared workspace tooling 2026-10-19 13:00:02 +08:00
backends/bili feat(bili): sign in from a centered modal with QR and phone tabs 2026-10-19 16:49:28 +08:00
benchmarks feat(bili): build a continuously scrolling cover gallery 2026-10-14 20:05:43 +08:00
docs feat(bili): expose the client as an embeddable View 2026-10-25 11:30:12 +08:00
examples feat(bili): build a continuously scrolling cover gallery 2026-10-14 20:05:43 +08:00
lisp feat(bili): expose the client as an embeddable View 2026-10-25 11:30:12 +08:00
scripts chore(scripts): sync shared workspace tooling 2026-10-22 13:16:02 +08:00
tests feat(bili): expose the client as an embeddable View 2026-10-25 11:30:12 +08:00
.editorconfig feat: add the Bilibili client 2026-10-13 23:15:13 +08:00
.gitignore feat: add the Bilibili client 2026-10-13 23:15:13 +08:00
CHANGELOG.md feat(bili): expose the client as an embeddable View 2026-10-25 11:30:12 +08:00
etaf-bili.el feat(bili): expose the client as an embeddable View 2026-10-25 11:30:12 +08:00
LICENSE feat: add the Bilibili client 2026-10-13 23:15:13 +08:00
Makefile feat(bili): sign in from a centered modal with QR and phone tabs 2026-10-19 16:49:28 +08:00
README.md feat(bili): expose the client as an embeddable View 2026-10-25 11:30:12 +08:00

ETAF Bili

Browse Bilibili inside Emacs: real cover cards, inline search, popular videos and rankings, with playback in the same buffer. Built with ETAF and its UI catalog.

Cover gallery in the dark theme

Light theme

What it does

  • A responsive one-to-four-column cover gallery, with aspect-preserving thumbnails, readable two-line titles, creator, duration and compact counts.
  • A catalog SearchField, channel Tabs and continuous wheel/trackpad scrolling. Metadata opens in a Drawer instead of reserving an empty sidebar.
  • Skeleton cards during the first load, readable error/retry and empty states, a scoped loading animation and a short selection transition. The shared etaf-animation-enabled switch disables motion.
  • Dark and light application palettes without modifying the user's frame, title bar or theme. Minimum usable window: 64 columns × 28 lines; smaller windows deliberately clip rather than compress the controls.
  • Asynchronous public-feed requests and at most three concurrent cover downloads. Covers are cached locally; failure leaves a usable placeholder.
  • RET or a card's play button resolves its stream and opens the catalog media viewer with the CDN headers required for playback.

This is an unofficial public-video client, not the Bilibili open platform. Login, account libraries, comments, danmaku and entitlement management are not implemented. Available quality and playback depend on the server.

Install

Requires Emacs 29.1+, etaf, etaf-ui, ebox, ecss, curl, ffmpeg (with ffprobe and ffplay for video metadata and sound) and Node.js for the account backend bundled in backends/bili/.

(dolist (package '("ecss" "ebox" "etaf" "etaf-ui" "etaf-bili"))
  (add-to-list 'load-path (expand-file-name package "/path/to/checkouts")))
(require 'etaf-bili)
(etaf-bili-open)

Use

Key Action
/ Edit the in-page search field; Return submits, Escape keeps the draft
Arrow keys Move through cards in two dimensions
n / p Next / previous video (j / k also work in the default direction scheme)
RET Activate the focused control or play the selected video
Wheel / trackpad Scroll through the loaded results continuously
1 / 2 Popular feed / ranking
g Refresh
i Open details
T Switch application theme
? / M-g Framework help / region navigation
q Close the current modal first, then the client

Embed in a panel or desktop

M-x etaf-bili-open is one host. A caller that already owns a window mounts the same View instead:

(require 'etaf-bili)
(let ((buffer (get-buffer-create "*Bili*")))
  (etaf-mount buffer (etaf-bili-view (etaf-bili-create)))
  buffer)

The View lays out for the region it is mounted into and follows that region's resizes, so a floating panel or a desktop window reflows the card grid instead of clipping it. Mounting adopts the host buffer, which keeps the client's keys, the inline search and the session-level commands working there. The first mount loads the popular feed; unmounting stops transfers and polling but keeps the library, selection and account, so a panel reopened with :keep-alive resumes where it was. etaf-posframe-register-app opens it with :create #'etaf-bili-create and :view #'etaf-bili-view.

Sign-in happens over the running client: the header's account chip reads 「登录」 until somebody signs in and then shows that account's name. Pressing it (or M-x etaf-bili-login) paints the site's code in a centered panel while the library stays visible underneath, dimmed; a phone confirms the scan, the panel closes and the gallery reloads. Only the code method exists, because Bilibili gates its SMS endpoint behind a graphical captcha. M-x etaf-bili-account reports who is signed in and M-x etaf-bili-logout signs out and clears those cookies.

The media viewer owns playback keys while open: Space toggles playback, Left/Right seek, m toggles sound, . cycles speed and n/p switch videos.

Documentation and checks

make test     # offline public acceptance suite, including mounted catalog controls
make check    # structure, byte compilation, acceptance and API boundaries

GUI scenarios live in scripts/etaf-bili-gui-scenarios.el; the workspace performance driver loads benchmarks/performance-scenarios.el for the bili.appearance and bili.scroll workloads. Screenshots and batch tests are not latency certification; use the real GUI workflow in the manual.