Files
pl8list-rewind/README.md
T
nvrl 4a7f6903a3 Add pl8list infinite-scroll app with pl8list sign-in
Endless TikTok-style feed over pl8list's Remix loaders. A guest crawler
builds a shared catalogue; a per-user ranker learns from likes, full
listens and skips. Users sign in with pl8list's email code; only a hash
of the local session token is stored. Ships as a hardened Docker image
with a Traefik override for pl8.narl.io.
2026-10-31 20:22:27 +01:00

5.4 KiB

pl8list ∞

An endless, TikTok-style scroll through pl8list, with its own recommendation algorithm that learns from what you like, finish and skip. Sign in with your pl8list account; each user gets their own feed and taste profile.

Not affiliated with pl8list.

Feed on desktop

Feed on mobile Sign-in screen

Likes Filters
Likes panel Filters

Run

docker compose up -d --build       # http://127.0.0.1:8790

or without Docker (Python 3.11+, no dependencies):

python3 server.py

compose.override.yaml puts it behind Traefik at pl8.narl.io on the external narl network, the same way as the other narl.io services. Docker Compose picks it up automatically. Delete it, or change the host name, to run somewhere else.

Data (catalogue, users, listening history) lives in the data volume as one SQLite file.

variable default meaning
HOST 127.0.0.1 bind address (0.0.0.0 in the image)
PORT 8790 port
PL8_DB ./pl8list.db SQLite path (/data/pl8list.db in the image)
TRUST_PROXY 0 1 behind a reverse proxy: use X-Forwarded-For for rate limits and X-Forwarded-Proto for Secure cookies

Accounts

There are no passwords here. Users sign in with the email of their pl8list account:

  1. The user enters their email. The server starts pl8list's own login, and pl8list mails them a 6-digit code.
  2. The user types the code. The server passes it to pl8list. If pl8list accepts it, the server reads the user's pl8list id and handle, creates a local account, and sets its own session cookie (pl8inf, HttpOnly, 90 days).

The pl8list session from step 2 is used once, in memory, to read the user's personal pl8list feed (artists in their library). It is never written to disk. Only a SHA-256 hash of the local session token is stored. Logins are rate-limited to 5 per IP and 3 per email per 15 minutes, with at most 5 code attempts per login.

Controls

key action
↓ / j, ↑ / k next / previous
space play / pause
← / → seek 10 s
l like

Click the waveform to seek. "filters" sets free-only and a BPM range. "likes" lists everything you liked, with links to pl8list.

How it works

pl8list has no public API, but it is a Remix app: every page loader returns JSON when you add ?_data=<route id>, and /api/* routes return JSON directly. Paginated loaders also need pagination=true. All of this works without an account for public data:

endpoint gives
/?_data=routes/_index best sellers, recommended, ticker
/api/feed?page=N&pagination=true a user's feed (needs their session)
/api/activity-ticker artists with recent downloads/sales
/best-selling-artists?_data=routes/best-selling-artists top artists
/search/users?q=…&page=N&pagination=true&_data=routes/search.users artist search (≥ 3 chars)
/<handle>?page=N&pagination=true&_data=routes/$handle an artist's tracks, 10 per page
/<handle>/<slug>/related?_data=routes/$handle_.$slug.related 3 related tracks
/api/<handle>/<slug>/hls/playlist.m3u8?v=<uploadId>[&preview=true] HLS audio
/login, /verify?email=…&mode=login email + 6-digit code login

Crawler (crawler.py). There is no "all tracks" listing, so one shared catalogue grows by snowballing: home sections, the ticker, best-selling artists and artist search give artists; artist pages give tracks; related pages pull in neighbours. Artists are re-checked daily for new uploads. Requests are throttled to about 1.4 per second while someone has used the app in the last 30 minutes, or while the catalogue has fewer than 500 tracks. Otherwise it slows to one request every 20 seconds. When a user likes or finishes a track, its related tracks and its artist's catalogue are crawled first.

Ranking (ranker.py). Each track the user has not seen yet gets a score from popularity, freshness, artist affinity (from their likes, completions and skips), closeness to the BPM and key of tracks they liked, whether it is related to something they liked, and some random exploration. Artists are then spread out so you don't get five tracks from one person in a row. Tune WEIGHTS at the top of the file.

Audio. The browser only talks to this server. HLS playlists and segments are proxied from pl8list as a guest, so users hear previews of paid tracks and full free tracks, the same as a logged-out visitor on pl8list.