diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..23863f1 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,165 @@ +name: "Release" + +on: + push: + tags: + - "v*" + +jobs: + build-and-release: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Extract version from tag + id: version + run: | + TAG="${GITHUB_REF_NAME}" + VERSION="${TAG#v}" + echo "version=${VERSION}" >> "$GITHUB_OUTPUT" + + - name: Setup .NET + uses: actions/setup-dotnet@v4 + with: + dotnet-version: "10.0.x" + + - name: Update version in .csproj + run: | + CSPROJ="Jellyfin.Plugin.ShareLinks/Jellyfin.Plugin.ShareLinks.csproj" + VERSION="${{ steps.version.outputs.version }}" + sed -i "s|.*|${VERSION}|" "$CSPROJ" + sed -i "s|.*|${VERSION}|" "$CSPROJ" + sed -i "s|.*|${VERSION}|" "$CSPROJ" + grep -E "Version>" "$CSPROJ" + + - name: Build + run: dotnet build Jellyfin.Plugin.ShareLinks -c Release + + - name: Compute build timestamp + id: timestamp + run: | + TS=$(date -u +"%Y-%m-%dT%H:%M:%S.%6N0Z") + echo "timestamp=${TS}" >> "$GITHUB_OUTPUT" + + - name: Update meta.json + run: | + META="Jellyfin.Plugin.ShareLinks/meta.json" + VERSION="${{ steps.version.outputs.version }}" + TIMESTAMP="${{ steps.timestamp.outputs.timestamp }}" + jq --arg v "$VERSION" --arg ts "$TIMESTAMP" \ + '.version = $v | .timestamp = $ts' \ + "$META" > "${META}.tmp" + mv "${META}.tmp" "$META" + cat "$META" + + - name: Package release archive + run: | + VERSION="${{ steps.version.outputs.version }}" + OUT="Jellyfin.Plugin.ShareLinks/bin/Release/net9.0" + STAGING="staging" + mkdir -p "$STAGING" + cp "${OUT}/Jellyfin.Plugin.ShareLinks.dll" "$STAGING/" + cp "Jellyfin.Plugin.ShareLinks/meta.json" "$STAGING/" + cd "$STAGING" + zip -r "../sharelinks_${VERSION}.zip" . + cd .. + ls -la sharelinks_${VERSION}.zip + + - name: Compute checksum + id: checksum + run: | + VERSION="${{ steps.version.outputs.version }}" + CHECKSUM=$(md5sum "sharelinks_${VERSION}.zip" | awk '{print $1}') + echo "checksum=${CHECKSUM}" >> "$GITHUB_OUTPUT" + echo "MD5: ${CHECKSUM}" + + - name: Update manifest.json + run: | + MANIFEST="manifest.json" + VERSION="${{ steps.version.outputs.version }}" + TIMESTAMP="${{ steps.timestamp.outputs.timestamp }}" + CHECKSUM="${{ steps.checksum.outputs.checksum }}" + + jq \ + --arg version "$VERSION" \ + --arg timestamp "$TIMESTAMP" \ + --arg checksum "$CHECKSUM" \ + ' + .[0].versions |= ( + if any(.[]; .version == $version) then + + map( + if .version == $version then + if has("changelog") | not then + .changelog = "" + else + . + end + | if has("targetAbi") then + .targetAbi = "10.11.0.0" + else + . + end + | if has("sourceUrl") then + .sourceUrl = "https://git.celjim.fr/public/jellyfin-plugin-sharelinks/releases/download/v\($version)/sharelinks_\($version).zip" + else + . + end + | if has("checksum") then + .checksum = $checksum + else + . + end + | if has("timestamp") then + .timestamp = $timestamp + else + . + end + else + . + end + ) + + else + + [{ + "version": $version, + "changelog": "", + "targetAbi": "10.11.0.0", + "sourceUrl": "https://git.celjim.fr/public/jellyfin-plugin-sharelinks/releases/download/v\($version)/sharelinks_\($version).zip", + "checksum": $checksum, + "timestamp": $timestamp + }] + . + + end + ) + ' "$MANIFEST" > "${MANIFEST}.tmp" + + mv "${MANIFEST}.tmp" "$MANIFEST" + + cat "$MANIFEST" + + - name: Commit and push manifest + run: | + git config user.name "Gitea Actions" + git config user.email "actions@celjim.fr" + + git add manifest.json Jellyfin.Plugin.ShareLinks/meta.json Jellyfin.Plugin.ShareLinks/Jellyfin.Plugin.ShareLinks.csproj + + if git diff --cached --quiet; then + echo "No changes to commit" + exit 0 + fi + + git commit -m "Update manifest/meta for v${{ steps.version.outputs.version }}" + git push origin HEAD:${{ github.event.repository.default_branch }} + + - name: Publish Gitea release + uses: https://gitea.com/actions/gitea-release-action@main + with: + files: |- + sharelinks_${{ steps.version.outputs.version }}.zip + token: "${{ secrets.RELEASE_TOKEN }}" diff --git a/Jellyfin.Plugin.ShareLinks/meta.json b/Jellyfin.Plugin.ShareLinks/meta.json index 3bfd282..cebf327 100644 --- a/Jellyfin.Plugin.ShareLinks/meta.json +++ b/Jellyfin.Plugin.ShareLinks/meta.json @@ -1,12 +1,12 @@ { "guid": "68540b76-ee74-436d-85ff-2abc884bbea6", "name": "ShareLinks", - "version": "1.0.6.0", + "version": "1.0.6.5", "targetAbi": "10.11.0.0", "framework": "net9.0", "owner": "Franciskid", "overview": "Secure expiring guest-share links for Jellyfin items.", "description": "Adds secure, expiring share links for Jellyfin items with JSON-backed storage, token hashing, and cleanup scaffolding.", "category": "General", - "timestamp": "2026-09-18T18:00:00.0000000Z" + "timestamp": "" } diff --git a/README.md b/README.md index a0bf674..91b33c3 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,16 @@ +## Changes in this fork + +This repository is based on the original ShareLinks plugin and includes the following modifications and improvements: + +- Added support for sharing **collections, videos, and images**. +- Added support for sharing **BoxSet subsets**. +- Added the ability to **hide or remove items from playlists and BoxSets** for guests. +- Hidden the empty **"..."** menu button on BoxSet detail pages for guests. +- Added **ShareLinks** to the administrator **Extensions** menu with a share icon. +- Translated the **ShareLink** action sheet label into French. +- Added a **French translation** for the plugin configuration page. +- Updated media card filtering so that cards are **not hidden when their titles merely contain a guard keyword as a substring**. + # ShareLinks for Jellyfin Share a movie, episode, season or series with a link. The person you send it to does not need @@ -68,18 +81,18 @@ sequenceDiagram ## Design decisions -| Decision | Reason | -|---|---| -| Only the token HMAC hash is stored | The raw token is returned once and is never saved to disk. Lookups hash the presented token and compare with `FixedTimeEquals`. | -| The HMAC key is a per-server file, mode 0600 | The `sharelinks.json` file alone cannot be used to recover a token. The key is generated on first use. | -| Tags propagate down, never up | A bug fixed in 1.0.3: a shared season tagged its parent series, and Jellyfin's tag inheritance then exposed every other season of that series. | -| Guest accounts use a dedicated authentication provider | Guests cannot sign in through the login page with a password. If the plugin is disabled, Jellyfin also refuses those sign-ins. | -| The password is generated per redemption and thrown away | This prevents sign-in with a blank password. The browser receives only a session token. | -| Links are redeemed one request at a time | This prevents two simultaneous requests from both using the same single-use link. | -| Each multi-use viewer receives its own device id | Jellyfin logs out any session with the same user and device id, so a shared device id would kick out the previous viewer on every new arrival. | -| The viewer limit is checked before changing anything | Handling the limit before Jellyfin raises an error lets the plugin turn away a new viewer without interrupting anyone already watching. | -| Other plugins are blocked by default | The filter distinguishes Jellyfin core from plugin code, so newly installed plugins are blocked too. | -| Guest devices are deleted before the user | Jellyfin does not remove devices when a user is deleted. Leftover devices can break the admin devices page. | +| Decision | Reason | +| -------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| Only the token HMAC hash is stored | The raw token is returned once and is never saved to disk. Lookups hash the presented token and compare with `FixedTimeEquals`. | +| The HMAC key is a per-server file, mode 0600 | The `sharelinks.json` file alone cannot be used to recover a token. The key is generated on first use. | +| Tags propagate down, never up | A bug fixed in 1.0.3: a shared season tagged its parent series, and Jellyfin's tag inheritance then exposed every other season of that series. | +| Guest accounts use a dedicated authentication provider | Guests cannot sign in through the login page with a password. If the plugin is disabled, Jellyfin also refuses those sign-ins. | +| The password is generated per redemption and thrown away | This prevents sign-in with a blank password. The browser receives only a session token. | +| Links are redeemed one request at a time | This prevents two simultaneous requests from both using the same single-use link. | +| Each multi-use viewer receives its own device id | Jellyfin logs out any session with the same user and device id, so a shared device id would kick out the previous viewer on every new arrival. | +| The viewer limit is checked before changing anything | Handling the limit before Jellyfin raises an error lets the plugin turn away a new viewer without interrupting anyone already watching. | +| Other plugins are blocked by default | The filter distinguishes Jellyfin core from plugin code, so newly installed plugins are blocked too. | +| Guest devices are deleted before the user | Jellyfin does not remove devices when a user is deleted. Leftover devices can break the admin devices page. | ## What the guest can do @@ -122,20 +135,20 @@ to appear. ## Configuration -| Setting | Effect | Default | -|---|---|---| -| Default expiry | The expiry the create popup offers first | 24 h | -| Maximum expiry | The longest allowed lifetime for a link | 720 h | -| Public base URL override | Forces the host used to build links, instead of the request host | derived | -| Guest username prefix | Prefix for the temporary accounts | `share-` | -| Allow transcoding / remuxing | Whether guest playback may transcode or remux | on | -| Cleanup interval | How often the background cleanup runs | 60 min | -| Maximum viewers per multi-use link | Concurrent viewers on one multi-use link. 0 means no limit | 10 | -| Single use by default | How the create popup starts | on | -| Guest lockdown | Hides navigation controls in the guest interface | on | -| Block other plugins for guests | Blocks guest access to other plugins on the server | on | -| Plugin access list | Plugins guests are allowed to access | empty | -| Cosmetic hidden selectors | Hides matching elements in the browser; does not restrict access | empty | +| Setting | Effect | Default | +| ---------------------------------- | ---------------------------------------------------------------- | -------- | +| Default expiry | The expiry the create popup offers first | 24 h | +| Maximum expiry | The longest allowed lifetime for a link | 720 h | +| Public base URL override | Forces the host used to build links, instead of the request host | derived | +| Guest username prefix | Prefix for the temporary accounts | `share-` | +| Allow transcoding / remuxing | Whether guest playback may transcode or remux | on | +| Cleanup interval | How often the background cleanup runs | 60 min | +| Maximum viewers per multi-use link | Concurrent viewers on one multi-use link. 0 means no limit | 10 | +| Single use by default | How the create popup starts | on | +| Guest lockdown | Hides navigation controls in the guest interface | on | +| Block other plugins for guests | Blocks guest access to other plugins on the server | on | +| Plugin access list | Plugins guests are allowed to access | empty | +| Cosmetic hidden selectors | Hides matching elements in the browser; does not restrict access | empty | **On the plugin access list:** some plugins have to answer guests. An intro skipper, for example, is called by the client during playback. Enable access for that plugin in the list. Plugins are blocked by default, including newly @@ -147,16 +160,16 @@ settings above. ## HTTP API -| Endpoint | Auth | Purpose | -|---|---|---| -| `POST /ShareLinks/Admin/Create` | Admin | Create a link. Returns the raw URL once | -| `GET /ShareLinks/Admin/List` | Admin | All records with status and expiry | -| `POST /ShareLinks/Admin/Revoke/{id}` | Admin | Revoke a link and remove its guest account and tags | -| `POST /ShareLinks/Admin/Cleanup` | Admin | Remove revoked, expired and failed records | -| `GET /ShareLinks/Admin/Plugins` | Admin | Installed plugins and their guest access state | -| `GET /ShareLinks/GuestState` | Session | Whether the caller is a guest, and what to lock down | -| `GET /ShareLinks/Redeem?t=...` | none | Redeem a token, return the bootstrap page | -| `GET /ShareLinks/ClientScript` | none | The injected web-client script | +| Endpoint | Auth | Purpose | +| ------------------------------------ | ------- | ---------------------------------------------------- | +| `POST /ShareLinks/Admin/Create` | Admin | Create a link. Returns the raw URL once | +| `GET /ShareLinks/Admin/List` | Admin | All records with status and expiry | +| `POST /ShareLinks/Admin/Revoke/{id}` | Admin | Revoke a link and remove its guest account and tags | +| `POST /ShareLinks/Admin/Cleanup` | Admin | Remove revoked, expired and failed records | +| `GET /ShareLinks/Admin/Plugins` | Admin | Installed plugins and their guest access state | +| `GET /ShareLinks/GuestState` | Session | Whether the caller is a guest, and what to lock down | +| `GET /ShareLinks/Redeem?t=...` | none | Redeem a token, return the bootstrap page | +| `GET /ShareLinks/ClientScript` | none | The injected web-client script | Records live in `sharelinks/sharelinks.json` under Jellyfin's data folder. The HMAC key lives beside it in `token-secret.key`. diff --git a/manifest.json b/manifest.json index ebd7f29..7619efd 100644 --- a/manifest.json +++ b/manifest.json @@ -1,69 +1,77 @@ [ - { - "guid": "68540b76-ee74-436d-85ff-2abc884bbea6", - "name": "ShareLinks", - "description": "Adds secure, expiring guest-share links for Jellyfin items using temporary hidden users, one-use token redemption, restrictive policies, and scheduled cleanup.", - "overview": "Temporary guest links for Jellyfin movies and episodes.", - "owner": "Franciskid", - "category": "General", - "imageUrl": "", - "versions": [ - { - "version": "1.0.6.0", - "changelog": "Fixes share links on Jellyfin 12 and the ShareLink button missing on new Docker installs.", - "targetAbi": "10.11.0.0", - "sourceUrl": "https://github.com/Franciskid/jellyfin-plugin-sharelinks/releases/download/v1.0.6.0/sharelinks_1.0.6.0.zip", - "checksum": "932377bc37c945128d2c46596d838a96", - "timestamp": "2026-09-18T10:00:00Z" - }, - { - "version": "1.0.5.0", - "changelog": "Fixes redemption on Jellyfin 10.11.9 and later (#19). Jellyfin changed the signature of the method the plugin uses to set a guest's password, so every link failed with a MissingMethodException and the guest landed on the dead-link page. The plugin now picks whichever version of that method the server has, so the same build works on every 10.11 release. Tested on 10.11.8 and 10.11.11.", - "targetAbi": "10.11.0.0", - "sourceUrl": "https://github.com/Franciskid/jellyfin-plugin-sharelinks/releases/download/v1.0.5.0/sharelinks_1.0.5.0.zip", - "checksum": "ff5a8cd11b2bfea29231d62f0a36be6f", - "timestamp": "2026-09-11T13:00:00Z" - }, - { - "version": "1.0.4.0", - "changelog": "Guests are now blocked from other plugins' API endpoints on the server, not just hidden from their UI in the browser. A share guest holds a real Jellyfin token, so anything that only hid a button left the endpoint answering it. Jellyfin's own API is untouched: the share tag already limits it to the shared title, and playback needs it. The config page lists your installed plugins with a checkbox each, so you can let one through when it genuinely needs to serve guests, such as an intro skipper. The guest hidden selectors setting is renamed to say what it is: cosmetic, it tidies the view and enforces nothing. Also fixes the dashboard devices page coming up empty after a share expired: deleting a guest left its device row behind, and a device whose user is missing makes Jellyfin fail the whole device listing rather than skip one row.", - "targetAbi": "10.11.0.0", - "sourceUrl": "https://github.com/Franciskid/jellyfin-plugin-sharelinks/releases/download/v1.0.4.0/sharelinks_1.0.4.0.zip", - "checksum": "9aa21e92dc319cfb6dbd95da7b338477", - "timestamp": "2026-07-27T19:00:00Z" - }, - { - "version": "1.0.3.0", - "changelog": "Fixes a season share exposing the rest of the series: the tag was going onto the season's parent series, and Jellyfin treats a parent's tags as belonging to all of its children, so every other season became visible to the guest. Tagging now only ever goes downwards. Take this update if you have shared a season with a link that is still live. Also in this release: a link can be made multi-use from the create popup so several people can use it until it expires, with a configurable ceiling on how many watch at once (ten by default), and the dashboard gains a \"Clean up finished links\" button that removes revoked, expired and failed records.", - "targetAbi": "10.11.0.0", - "sourceUrl": "https://github.com/Franciskid/jellyfin-plugin-sharelinks/releases/download/v1.0.3.0/sharelinks_1.0.3.0.zip", - "checksum": "1b492b41f0d8c26e2a897d6f42e52b32", - "timestamp": "2026-07-27T08:30:00Z" - }, - { - "version": "1.0.2.0", - "changelog": "The ShareLink button now sits in its own section at the bottom of the item menu, and only appears for a movie, series, season or episode instead of turning up on things like a cast member. It is properly admin only again: the current user was cached for the whole page, so switching accounts in the same tab could leave the button visible to a normal user (the server always refused them). Share tags are hidden from everyone except admins, on the title and in the tag filter. Security: one-use links can no longer be redeemed twice, the configured maximum expiry is respected, the raw token is dropped from storage when a link dies, the token secret file is 0600, guest accounts can no longer sign in from the login page at all, and the runtime reflection shims around Jellyfin's APIs are gone.", - "targetAbi": "10.11.0.0", - "sourceUrl": "https://github.com/Franciskid/jellyfin-plugin-sharelinks/releases/download/v1.0.2.0/sharelinks_1.0.2.0.zip", - "checksum": "cebff42d9ec45f6add7b03bb7a0aaca5", - "timestamp": "2026-07-26T18:45:00Z" - }, - { - "version": "1.0.1.0", - "changelog": "Series and seasons can now be shared, not just single movies and episodes: the share tags the whole tree so the guest can browse and play everything inside it, and cleanup strips it all again. Expired or invalid links now show a friendly page that redirects to the Jellyfin home instead of raw JSON. The guest hidden selectors setting now defaults to empty.", - "targetAbi": "10.11.0.0", - "sourceUrl": "https://github.com/Franciskid/jellyfin-plugin-sharelinks/releases/download/v1.0.1.0/sharelinks_1.0.1.0.zip", - "checksum": "7ea5363ee59c41a0daac7283c12c2d40", - "timestamp": "2026-07-08T00:15:00Z" - }, - { - "version": "1.0.0.0", - "changelog": "ShareLink button on the item menu, temporary guest users confined by tag policy, expiring links with a date/time picker, admin dashboard with revoke, and a configurable guest lockdown that hides tag/genre/studio/cast links, the add-to-playlist and add-to-collection actions, and other plugins' UI. The creation popup is localized in English and French. Guest sessions are now minted server side: no password is ever stored or sent anywhere, the guest page only carries a session token, and expired guests are cleaned up every 30 minutes.", - "targetAbi": "10.11.0.0", - "sourceUrl": "https://github.com/Franciskid/jellyfin-plugin-sharelinks/releases/download/v1.0.0.0/sharelinks_1.0.0.0.zip", - "checksum": "7fa7a31bf26f80307c75f87535c4f036", - "timestamp": "2026-07-07T23:18:38Z" - } - ] - } + { + "guid": "68540b76-ee74-436d-85ff-2abc884bbea6", + "name": "ShareLinks", + "description": "Adds secure, expiring guest-share links for Jellyfin items using temporary hidden users, one-use token redemption, restrictive policies, and scheduled cleanup.", + "overview": "Temporary guest links for Jellyfin movies and episodes.", + "owner": "Franciskid", + "category": "General", + "imageUrl": "", + "versions": [ + { + "version": "1.0.6.5", + "changelog": "Add collections, videos, and images to shareable items\nAdd sharing of BoxSet subsets\nHide/remove items from playlists or BoxSets\nHide the empty '...' menu button on BoxSet detail pages for guests\nAdd ShareLinks to the admin Extensions menu with a share icon\nTranslate the ShareLink action sheet label into French\nAdd French translation to the plugin configuration page\nDon't hide media cards whose titles contain a guard keyword as a substring", + "targetAbi": "10.11.0.0", + "sourceUrl": "https://git.celjim.fr/public/jellyfin-plugin-sharelinks/releases/download/v1.0.6.5/sharelinks_1.0.6.5.zip", + "checksum": "", + "timestamp": "" + }, + { + "version": "1.0.6.0", + "changelog": "Fixes share links on Jellyfin 12 and the ShareLink button missing on new Docker installs.", + "targetAbi": "10.11.0.0", + "sourceUrl": "https://github.com/Franciskid/jellyfin-plugin-sharelinks/releases/download/v1.0.6.0/sharelinks_1.0.6.0.zip", + "checksum": "932377bc37c945128d2c46596d838a96", + "timestamp": "2026-09-18T10:00:00Z" + }, + { + "version": "1.0.5.0", + "changelog": "Fixes redemption on Jellyfin 10.11.9 and later (#19). Jellyfin changed the signature of the method the plugin uses to set a guest's password, so every link failed with a MissingMethodException and the guest landed on the dead-link page. The plugin now picks whichever version of that method the server has, so the same build works on every 10.11 release. Tested on 10.11.8 and 10.11.11.", + "targetAbi": "10.11.0.0", + "sourceUrl": "https://github.com/Franciskid/jellyfin-plugin-sharelinks/releases/download/v1.0.5.0/sharelinks_1.0.5.0.zip", + "checksum": "ff5a8cd11b2bfea29231d62f0a36be6f", + "timestamp": "2026-09-11T13:00:00Z" + }, + { + "version": "1.0.4.0", + "changelog": "Guests are now blocked from other plugins' API endpoints on the server, not just hidden from their UI in the browser. A share guest holds a real Jellyfin token, so anything that only hid a button left the endpoint answering it. Jellyfin's own API is untouched: the share tag already limits it to the shared title, and playback needs it. The config page lists your installed plugins with a checkbox each, so you can let one through when it genuinely needs to serve guests, such as an intro skipper. The guest hidden selectors setting is renamed to say what it is: cosmetic, it tidies the view and enforces nothing. Also fixes the dashboard devices page coming up empty after a share expired: deleting a guest left its device row behind, and a device whose user is missing makes Jellyfin fail the whole device listing rather than skip one row.", + "targetAbi": "10.11.0.0", + "sourceUrl": "https://github.com/Franciskid/jellyfin-plugin-sharelinks/releases/download/v1.0.4.0/sharelinks_1.0.4.0.zip", + "checksum": "9aa21e92dc319cfb6dbd95da7b338477", + "timestamp": "2026-07-27T19:00:00Z" + }, + { + "version": "1.0.3.0", + "changelog": "Fixes a season share exposing the rest of the series: the tag was going onto the season's parent series, and Jellyfin treats a parent's tags as belonging to all of its children, so every other season became visible to the guest. Tagging now only ever goes downwards. Take this update if you have shared a season with a link that is still live. Also in this release: a link can be made multi-use from the create popup so several people can use it until it expires, with a configurable ceiling on how many watch at once (ten by default), and the dashboard gains a \"Clean up finished links\" button that removes revoked, expired and failed records.", + "targetAbi": "10.11.0.0", + "sourceUrl": "https://github.com/Franciskid/jellyfin-plugin-sharelinks/releases/download/v1.0.3.0/sharelinks_1.0.3.0.zip", + "checksum": "1b492b41f0d8c26e2a897d6f42e52b32", + "timestamp": "2026-07-27T08:30:00Z" + }, + { + "version": "1.0.2.0", + "changelog": "The ShareLink button now sits in its own section at the bottom of the item menu, and only appears for a movie, series, season or episode instead of turning up on things like a cast member. It is properly admin only again: the current user was cached for the whole page, so switching accounts in the same tab could leave the button visible to a normal user (the server always refused them). Share tags are hidden from everyone except admins, on the title and in the tag filter. Security: one-use links can no longer be redeemed twice, the configured maximum expiry is respected, the raw token is dropped from storage when a link dies, the token secret file is 0600, guest accounts can no longer sign in from the login page at all, and the runtime reflection shims around Jellyfin's APIs are gone.", + "targetAbi": "10.11.0.0", + "sourceUrl": "https://github.com/Franciskid/jellyfin-plugin-sharelinks/releases/download/v1.0.2.0/sharelinks_1.0.2.0.zip", + "checksum": "cebff42d9ec45f6add7b03bb7a0aaca5", + "timestamp": "2026-07-26T18:45:00Z" + }, + { + "version": "1.0.1.0", + "changelog": "Series and seasons can now be shared, not just single movies and episodes: the share tags the whole tree so the guest can browse and play everything inside it, and cleanup strips it all again. Expired or invalid links now show a friendly page that redirects to the Jellyfin home instead of raw JSON. The guest hidden selectors setting now defaults to empty.", + "targetAbi": "10.11.0.0", + "sourceUrl": "https://github.com/Franciskid/jellyfin-plugin-sharelinks/releases/download/v1.0.1.0/sharelinks_1.0.1.0.zip", + "checksum": "7ea5363ee59c41a0daac7283c12c2d40", + "timestamp": "2026-07-08T00:15:00Z" + }, + { + "version": "1.0.0.0", + "changelog": "ShareLink button on the item menu, temporary guest users confined by tag policy, expiring links with a date/time picker, admin dashboard with revoke, and a configurable guest lockdown that hides tag/genre/studio/cast links, the add-to-playlist and add-to-collection actions, and other plugins' UI. The creation popup is localized in English and French. Guest sessions are now minted server side: no password is ever stored or sent anywhere, the guest page only carries a session token, and expired guests are cleaned up every 30 minutes.", + "targetAbi": "10.11.0.0", + "sourceUrl": "https://github.com/Franciskid/jellyfin-plugin-sharelinks/releases/download/v1.0.0.0/sharelinks_1.0.0.0.zip", + "checksum": "7fa7a31bf26f80307c75f87535c4f036", + "timestamp": "2026-07-07T23:18:38Z" + } + ] + } ]