npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

peertube-plugin-ghost-private-podcast

v1.0.2

Published

Private, authenticated podcast RSS feeds for PeerTube channels, gated by Ghost membership tiers

Readme

peertube-plugin-ghost-private-podcast

Private, authenticated podcast RSS feeds for PeerTube channels, gated by Ghost membership tiers. Each member gets a personal, token-authenticated feed URL that works in any podcast app. You pick which Public/Unlisted videos appear by ticking a per-video "Show in private podcast feed" box; unticked and private videos stay hidden. Members sync automatically from Ghost tiers.

Features

  • Per-member private feeds. Every member gets a unique, HMAC-signed feed URL. Compatible with Apple Podcasts, Overcast, Pocket Casts, AntennaPod, and any other app.
  • Ghost member sync. Point the plugin at your Ghost blog and choose which membership tiers get access. Members joining or leaving a tier are added or suspended automatically, in real time via webhooks plus periodic polling.
  • Per-video opt-in. Each video has a "Show in private podcast feed" checkbox on its edit page. Videos are excluded by default; only the ones you tick (and that are Public or Unlisted) appear in members' feeds, so you can keep an Unlisted video off the feed until it's ready. Private videos never qualify. No expiring API token is needed: the plugin reads the channel's videos straight from PeerTube's database.
  • Authenticated media proxy. Audio/video is streamed through a token-gated proxy that serves the smallest video rendition (light downloads) and supports range requests for seeking. The audio-only rendition is never served.
  • Manual members too. Add or remove subscribers by email, independent of Ghost.
  • Anti-sharing tools. Optional per-member first name in the feed title (a soft deterrent), one-click key rotation to kill a leaked URL, a member self-service "reset my feed" control for the podcast page, and sharing detection that flags a member when their feed is pulled from an unusual number of distinct IPs / apps.
  • Access stats. Per-member daily counters for feed and media access.
  • Management UI. A page under My Account > Private Podcasts to enable channels, map tiers, run a sync, copy or rotate member feed URLs, and see sharing flags.

Requirements

  • PeerTube >= 7.0.0 (developed and verified on 8.1.x).
  • A Ghost blog (optional, only for automatic member sync). Without Ghost you can still add members manually.

Installation

A. Managed / hosted PeerTube (admin UI only)

Managed hosts install plugins by npm name from the admin UI. Publish the package to npm (it contains no secrets, so a public publish is safe), then:

  1. Admin > Plugins/Themes > Search
  2. Search for ghost-private-podcast
  3. Install

If your host restricts installs to the official plugin index, send the provider this package (or the tarball from npm pack) and ask them to install it server-side.

B. Self-hosted PeerTube (shell access)

# build + package
npm install
npm run build
npm pack                      # -> peertube-plugin-ghost-private-podcast-<ver>.tgz

# copy to the server, extract, and install by path
#   the extracted directory MUST be named peertube-plugin-ghost-private-podcast
tar -xzf peertube-plugin-ghost-private-podcast-*.tgz     # -> ./package
mv package peertube-plugin-ghost-private-podcast

# Docker:
docker cp peertube-plugin-ghost-private-podcast <peertube-container>:/tmp/
docker exec -u peertube <peertube-container> \
  npm run plugin:install -- --plugin-path /tmp/peertube-plugin-ghost-private-podcast

# bare-metal (from the PeerTube install dir, as the peertube user):
#   sudo -u peertube NODE_CONFIG_DIR=/var/www/peertube/config NODE_ENV=production \
#     npm run plugin:install -- --plugin-path /path/to/peertube-plugin-ghost-private-podcast

Configuration

In Admin > Plugins > Ghost Private Podcast > Settings:

| Setting | Purpose | |---|---| | Ghost Blog URL | e.g. https://blog.example.com. Leave empty to disable Ghost sync. | | Ghost Admin API Key | id:secret from a Ghost Admin integration. | | Ghost Sync Interval (minutes) | Poll cadence. Default 15, minimum 5. | | Feed title | Podcast title shown in apps. Empty falls back to Channel (Members Only). | | Feed cover image URL | Square (>=1400px) cover art, publicly reachable. | | Personalize feed title with member first name | Titles each member's feed First's <title> using their Ghost name. Off by default. | | Sharing alert: distinct IPs (30 days) | Flag a member as a possible sharer at/above this many distinct IPs in 30 days (default 8; 0 disables). IPs are stored only as salted hashes. |

Note: PeerTube saves plugin settings by replacing the whole object. When you change one field, keep the others filled in the same save or they get cleared.

Ghost setup

The Ghost integration uses a single instance-wide Admin API key, so configuring tiers and running syncs requires a PeerTube administrator account (see Security model). Managing manual members of your own channel does not.

  1. In Ghost Admin > Settings > Integrations, create a Custom Integration; copy its Admin API Key into the plugin setting above.
  2. Open My Account > Private Podcasts in PeerTube, pick your channel, and Enable Private Podcast.
  3. As an admin, choose the tiers that should get feed access, and Save.
  4. Add a Ghost webhook for member.added, member.updated, and member.deleted, pointing at the webhook URL shown on that page (.../plugins/ghost-private-podcast/router/ghost/webhook/<secret>).
  5. Run Sync Now for the initial import.

Ghost theme integration (optional)

To show members their personal feed URL after they sign in, call the plugin's lookup endpoint from your Ghost theme with the member's UUID:

fetch(PEERTUBE_URL + "/plugins/ghost-private-podcast/router/member-feed/"
      + CHANNEL_ID + "/" + encodeURIComponent(memberUuid))
  .then(r => r.json())
  .then(d => { /* d.feedUrl is this member's private feed */ });

The endpoint returns 404 for non-members, and reflects your Ghost origin in CORS. No login or token is ever exposed to the browser.

How it works

  • Feed URL: /plugins/ghost-private-podcast/router/feed/<channelId>/<token>, where <token> is HMAC(secret, member + channel). The secret is generated once per install and stored server-side.
  • Listing: videos are read from PeerTube's database (published, privacy IN (public, unlisted)), then filtered to those opted into the feed via the per-video checkbox (stored in the video's pluginData, read by a server hook), so episodes appear without any OAuth token. The result is cached per channel (short TTL, single-flight) so a burst of member polls collapses to one DB query. Order and pubDate use the original publication date when set.
  • Media: /media/<channelId>/<videoUuid>/<token> proxies the smallest video web-video. Public/unlisted files need no token, so nothing expires.

Building from source

The server library must be CommonJS (PeerTube requires it); the client script must be an ES module (the browser loads it via import()). The build uses two tsconfigs:

npm run build     # tsc (server, CommonJS) && tsc -p tsconfig.client.json (client, ESM)
npm test          # tsc -p tsconfig.test.json && node --test (no extra dependencies)

Tests use Node's built-in test runner against the compiled sources; there are no third-party test dependencies.

Security model

  • The Ghost integration is admin-gated. The Ghost URL + Admin API key are one instance-wide credential, so the routes that use it (tier listing, tier configuration, and sync) require a PeerTube administrator. The plugin assumes one Ghost blog per PeerTube instance.
  • Per-member feeds are gated by a 128-bit HMAC token (constant-time compared); the media proxy only serves a channel's own Public/Unlisted videos, never Private ones, and fetches only PeerTube-internal file URLs (no SSRF).
  • Recommended at deployment: put a reverse-proxy / CDN rate limit in front of the public routes (feed, media, member-feed, member-rotate, webhook), and verify Ghost's x-ghost-signature on the webhook. Note that feed tokens and the webhook secret appear in URL paths, so they will show up in access logs.
  • IP-based sharing detection stores only salted HMAC hashes, never raw IPs.

License

MIT