The button used to be cloned in next to "Copy Stream URL", or shoved at the top of the menu when there was none, which put it in the middle of Jellyfin's own command groups. Now there is a single injection path: append an actionsheetDivider plus the action at the end of the sheet's scroller, so it reads as its own section the way Jellyfin separates its groups. It also stopped appearing on menus for things that are not media. The old code resolved the item from the URL, so opening the "..." menu on a cast member inside a movie page showed ShareLink and would have shared the movie behind it. The item is now taken from the trigger that opened the menu (card, list row or the detail page's own button, all of which we can walk up from) and its type is checked against the server before anything is injected: movie, series, season, episode, nothing else. The API refuses everything else too, so a hand-rolled request cannot tag a person or a playlist either. Clicking the action now dismisses the action sheet instead of leaving it stacked under the dialog.
159 lignes
7.6 KiB
Markdown
159 lignes
7.6 KiB
Markdown
# ShareLinks for Jellyfin
|
|
|
|
Send someone a single movie, episode, season or whole series, without giving
|
|
them an account, without them seeing the rest of your library.
|
|
|
|
> *"here, watch this one film, the link dies tomorrow"*
|
|
|
|
ShareLinks adds a **ShareLink** button (with a little share icon) to the context
|
|
menu of any movie, episode, series or season in the Jellyfin web client. It sits
|
|
in its own section at the bottom of the menu, and it only shows up for those four
|
|
kinds of item, never for a cast member, a studio, a library or anything else you
|
|
cannot actually watch. Click it, pick how long the link should live, and you get
|
|
a URL you can send to anyone. When they open it, they land straight on that title, already signed in,
|
|
and they cannot wander off into the rest of your server.
|
|
|
|
No account for them to create, no password for you to hand out, no permanent
|
|
guest user piling up. The link is temporary, the guest is temporary, and when it
|
|
expires everything is cleaned up on its own.
|
|
|
|
I built this for my own server (shared with family and a few friends), because I
|
|
kept wanting to show someone *one specific film* without either adding them as a
|
|
real user or handing over a login that sees everything.
|
|
|
|
---
|
|
|
|
<img width="1476" height="784" alt="image" src="https://github.com/user-attachments/assets/841f24f8-8b0f-4fd0-a8f8-890ff80faa2f" />
|
|
|
|
|
|
## How it works
|
|
|
|
1. As an admin you open the context menu on a movie, episode, series or season
|
|
and hit **ShareLink**. You choose an expiry (1 hour up to 30 days) and the
|
|
plugin hands you a link, copied to your clipboard.
|
|
2. Behind the scenes the plugin tags the shared item with a unique, random tag
|
|
and records the share. Share a series or a season and the tag is applied to
|
|
the whole tree underneath it too - series, seasons and episodes - so the
|
|
guest can actually browse from the series page down into a season and an
|
|
episode, not just see a single locked node. The raw link token is shown to
|
|
you once and never stored, only a keyed HMAC hash of it is kept.
|
|
3. Whoever opens the link gets a throwaway guest user created on the spot,
|
|
restricted by that tag to the shared item and its tree, and is signed in
|
|
automatically. They land on the title's page.
|
|
4. When the link expires (or you revoke it), a cleanup pass disables and deletes
|
|
the guest user and strips the temporary tag from the whole tree again. A
|
|
scheduled task and a startup pass make sure nothing lingers if the server was
|
|
off at expiry time.
|
|
|
|
## What the guest sees
|
|
|
|
Just the shared title (and, for a series or season, its seasons and episodes),
|
|
and the ability to play them. The confinement is real and it is enforced on the
|
|
server, not only in the browser:
|
|
|
|
- The guest's Jellyfin policy only permits items carrying the share's tag, so
|
|
every other movie, show, library and search comes back empty from the API.
|
|
Even someone poking at the raw API cannot list your other content.
|
|
- On top of that, the web client is locked down for the guest: the home,
|
|
menu and search buttons are hidden, in-page links (cast, studio, genres) are
|
|
made inert, "add to playlist" is removed, and any attempt to navigate
|
|
somewhere outside the shared tree snaps back to the shared title. Navigating
|
|
within the tree - series to season to episode - works normally.
|
|
|
|
Playback works normally, including transcoding and remuxing if you allow it, and
|
|
the player's back button still returns them to the title's page.
|
|
|
|
One honest caveat: if you share a series or season and new episodes get added
|
|
to it later, those episodes only pick up the tag (and become visible to the
|
|
guest) the next time the link is redeemed - not the instant they are added. For
|
|
a one-use link that has already been redeemed, that never happens, so a
|
|
one-use link is a snapshot of the tree as it existed at redemption time.
|
|
|
|
## Managing links
|
|
|
|
The plugin's dashboard page lists every share with its status, the title, a
|
|
copyable link, the temporary guest name, and an expiry, and lets you revoke any
|
|
of them on the spot. Revoking runs the same teardown as expiry: guest gone, tag
|
|
gone.
|
|
|
|
## Hiding other plugins from guests
|
|
|
|
If you run other plugins that inject their own UI into the web client (a search
|
|
bar, a floating button), you probably do not want a guest to see them. I had
|
|
exactly that problem with a different plugin of mine, so the **Guest hidden
|
|
selectors** setting is a comma-separated list of CSS selectors that get hidden
|
|
in guest sessions. It ships with a default that hides that plugin's floating
|
|
button and panel; add any other plugin's selector and it disappears for guests
|
|
too, no code change needed.
|
|
|
|
## Security stance
|
|
|
|
The design goal is simple: a raw share token exists only at the moment it is
|
|
issued, is returned to you once, and is then forgotten. Persistent storage keeps
|
|
only a keyed HMAC hash of the token plus the metadata needed to audit and clean
|
|
up the link. So:
|
|
|
|
1. raw tokens are never logged
|
|
2. raw tokens are never written to disk
|
|
3. the token is only returned in the creation response
|
|
4. token validation is a hash comparison
|
|
5. guest-user creation and teardown live behind explicit service calls
|
|
6. the real access boundary is the server-side tag policy; the web-client
|
|
lockdown is convenience on top of it
|
|
|
|
The same applies to the guest's login. The plugin mints the guest session itself
|
|
on the server, using Jellyfin's own session manager. No password is ever stored
|
|
anywhere, not even encrypted, and no password ever appears in the page sent to
|
|
the guest. The only thing the guest's browser receives is a session token
|
|
scoped to that one guest account, and that token dies the moment the guest
|
|
account is cleaned up.
|
|
|
|
## Configuration
|
|
|
|
All of these live on the plugin's dashboard page:
|
|
|
|
| Setting | What it does |
|
|
|---|---|
|
|
| Default / maximum expiry | The default the menu offers, and the ceiling a link may be set to |
|
|
| Public base URL override | Force the host used when building links (otherwise derived from the request) |
|
|
| Guest username prefix | Prefix for the throwaway guest accounts (default `share-`) |
|
|
| Allow transcoding / remuxing | Whether guest playback may transcode or remux |
|
|
| Cleanup interval | How often the background cleanup runs |
|
|
| One-use default | Whether new links default to single redemption |
|
|
| Guest lockdown | The web-client confinement described above (on by default) |
|
|
| Guest hidden selectors | CSS selectors hidden from guests, to suppress other plugins' UI |
|
|
|
|
## Known limitation: cast and crew
|
|
|
|
Jellyfin has a core bug ([jellyfin/jellyfin#14926](https://github.com/jellyfin/jellyfin/issues/14926))
|
|
where a user restricted by tags loses the Cast & Crew section entirely, because
|
|
the tag filter is applied to people as well as to media. Since a ShareLinks
|
|
guest is tag-restricted, they hit this: the shared title's page shows no
|
|
actors, director or writer. This is a server-side Jellyfin issue, not something
|
|
the plugin can style around. A workaround inside the plugin is possible and on
|
|
the list.
|
|
|
|
## Compatibility
|
|
|
|
- Jellyfin **10.11** (targetAbi `10.11.0.0`), .NET 9. Tested on 10.11.8.
|
|
- The UI injection targets the standard Jellyfin web client, and works with both
|
|
the English and French interface.
|
|
|
|
## Install
|
|
|
|
Dashboard => Plugins => Manage repositories => New repository => https://raw.githubusercontent.com/Franciskid/jellyfin-plugin-sharelinks/main/manifest.json
|
|
|
|
**You may need to hard refresh the page for the button to appear**
|
|
|
|
## Credits and license
|
|
|
|
Developed by [Franciskid](https://github.com/Franciskid).
|
|
|
|
Licensed under the [GPL-3.0](LICENSE), like most Jellyfin plugins.
|
|
|
|
|
|
## Images
|
|
|
|
<img width="590" height="427" alt="image" src="https://github.com/user-attachments/assets/1ae3c28a-644e-4c9f-824a-07b800aa5eff" />
|
|
|