Floatplane channel plugin for Jellyfin 10.10.x: browse, HLS proxy, cookie session, CI release
Some checks failed
release / build-release (push) Has been cancelled

- IChannel: creators -> sub-channels -> video posts (public listing API, search mapped)
- IRequiresMediaInfoCallback -> signed proxy URLs; stream controller rewrites HLS so the
  AES-128 key is fetched server-side with the session cookie, segments stay on the CDN
- delivery/info cached per token lifetime and throttled (~2/min)
- config page: paste sails.sid + validate, extra creators, quality, search test
- scripts/package.sh -> plugin folder + zip + md5 + manifest.json; Gitea release workflow
- research docs, HAR analysis (HAR itself gitignored), install-pi5.sh

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-10-25 09:42:27 +01:00
commit 9e33a343ab
162 changed files with 52004 additions and 0 deletions

View File

@@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Jellyfin.Plugin.Chaosflix.Api.Models;
/// <summary>
/// CCC API conference (e.g. 38C3, Camp 2019).
/// </summary>
public class CccConference
{
/// <summary>Gets or sets the acronym (e.g. "38c3").</summary>
[JsonPropertyName("acronym")]
public string Acronym { get; set; } = string.Empty;
/// <summary>Gets or sets the title.</summary>
[JsonPropertyName("title")]
public string Title { get; set; } = string.Empty;
/// <summary>Gets or sets the description.</summary>
[JsonPropertyName("description")]
public string? Description { get; set; }
/// <summary>Gets or sets the slug.</summary>
[JsonPropertyName("slug")]
public string Slug { get; set; } = string.Empty;
/// <summary>Gets or sets the logo URL.</summary>
[JsonPropertyName("logo_url")]
public string? LogoUrl { get; set; }
/// <summary>Gets or sets the aspect ratio.</summary>
[JsonPropertyName("aspect_ratio")]
public string? AspectRatio { get; set; }
/// <summary>Gets or sets the API URL.</summary>
[JsonPropertyName("url")]
public string Url { get; set; } = string.Empty;
/// <summary>Gets or sets the last released event date.</summary>
[JsonPropertyName("event_last_released_at")]
public DateTimeOffset? EventLastReleasedAt { get; set; }
/// <summary>Gets or sets the events (only present in detail response).</summary>
[JsonPropertyName("events")]
public List<CccEvent>? Events { get; set; }
}
/// <summary>
/// Wrapper for conference list response.
/// </summary>
public class CccConferencesResponse
{
/// <summary>Gets or sets the conferences.</summary>
[JsonPropertyName("conferences")]
public List<CccConference> Conferences { get; set; } = new();
}

View File

@@ -0,0 +1,101 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Jellyfin.Plugin.Chaosflix.Api.Models;
/// <summary>
/// CCC API event (a single talk/recording).
/// </summary>
public class CccEvent
{
/// <summary>Gets or sets the GUID.</summary>
[JsonPropertyName("guid")]
public string Guid { get; set; } = string.Empty;
/// <summary>Gets or sets the title.</summary>
[JsonPropertyName("title")]
public string Title { get; set; } = string.Empty;
/// <summary>Gets or sets the subtitle.</summary>
[JsonPropertyName("subtitle")]
public string? Subtitle { get; set; }
/// <summary>Gets or sets the slug.</summary>
[JsonPropertyName("slug")]
public string Slug { get; set; } = string.Empty;
/// <summary>Gets or sets the description.</summary>
[JsonPropertyName("description")]
public string? Description { get; set; }
/// <summary>Gets or sets the original language.</summary>
[JsonPropertyName("original_language")]
public string? OriginalLanguage { get; set; }
/// <summary>Gets or sets the speakers.</summary>
[JsonPropertyName("persons")]
public List<string> Persons { get; set; } = new();
/// <summary>Gets or sets the tags.</summary>
[JsonPropertyName("tags")]
public List<string> Tags { get; set; } = new();
/// <summary>Gets or sets the view count.</summary>
[JsonPropertyName("view_count")]
public int ViewCount { get; set; }
/// <summary>Gets or sets the event date.</summary>
[JsonPropertyName("date")]
public DateTimeOffset? Date { get; set; }
/// <summary>Gets or sets the release date.</summary>
[JsonPropertyName("release_date")]
public DateTimeOffset? ReleaseDate { get; set; }
/// <summary>Gets or sets the duration in seconds.</summary>
[JsonPropertyName("duration")]
public int Duration { get; set; }
/// <summary>Gets or sets the thumbnail URL.</summary>
[JsonPropertyName("thumb_url")]
public string? ThumbUrl { get; set; }
/// <summary>Gets or sets the poster URL.</summary>
[JsonPropertyName("poster_url")]
public string? PosterUrl { get; set; }
/// <summary>Gets or sets the frontend link.</summary>
[JsonPropertyName("frontend_link")]
public string? FrontendLink { get; set; }
/// <summary>Gets or sets the API URL.</summary>
[JsonPropertyName("url")]
public string Url { get; set; } = string.Empty;
/// <summary>Gets or sets the conference title.</summary>
[JsonPropertyName("conference_title")]
public string? ConferenceTitle { get; set; }
/// <summary>Gets or sets the conference API URL.</summary>
[JsonPropertyName("conference_url")]
public string? ConferenceUrl { get; set; }
/// <summary>Gets or sets the recordings (only in detail response).</summary>
[JsonPropertyName("recordings")]
public List<CccRecording>? Recordings { get; set; }
/// <summary>Gets or sets the related events.</summary>
[JsonPropertyName("related")]
public List<CccRelatedEvent>? Related { get; set; }
}
/// <summary>
/// Wrapper for event search response.
/// </summary>
public class CccEventsResponse
{
/// <summary>Gets or sets the events.</summary>
[JsonPropertyName("events")]
public List<CccEvent> Events { get; set; } = new();
}

View File

@@ -0,0 +1,143 @@
using System.Collections.Generic;
using System.Text.Json.Serialization;
namespace Jellyfin.Plugin.Chaosflix.Api.Models;
/// <summary>
/// A conference as streaming.media.ccc.de describes it while it is running. The shape
/// follows the generator at voc/streaming-website (view/streams-json-v2.php); outside a
/// congress the endpoint answers with an empty list.
/// </summary>
public class CccLiveConference
{
/// <summary>Gets or sets the conference title.</summary>
[JsonPropertyName("conference")]
public string Conference { get; set; } = string.Empty;
/// <summary>Gets or sets the acronym, e.g. "39c3".</summary>
[JsonPropertyName("slug")]
public string Slug { get; set; } = string.Empty;
/// <summary>
/// Gets or sets a value indicating whether anything is on air right now. The site sets
/// it from the rooms' schedules, which is what decides whether the channel offers a
/// live folder at all.
/// </summary>
[JsonPropertyName("isCurrentlyStreaming")]
public bool IsCurrentlyStreaming { get; set; }
/// <summary>Gets or sets the room groups.</summary>
[JsonPropertyName("groups")]
public List<CccLiveGroup> Groups { get; set; } = new();
}
/// <summary>A named set of rooms, e.g. "Live" or "Selfhosted".</summary>
public class CccLiveGroup
{
/// <summary>Gets or sets the group name.</summary>
[JsonPropertyName("group")]
public string Group { get; set; } = string.Empty;
/// <summary>Gets or sets the rooms.</summary>
[JsonPropertyName("rooms")]
public List<CccLiveRoom> Rooms { get; set; } = new();
}
/// <summary>One room with its stream variants and what is on in it.</summary>
public class CccLiveRoom
{
/// <summary>Gets or sets the room slug, e.g. "halla".</summary>
[JsonPropertyName("slug")]
public string Slug { get; set; } = string.Empty;
/// <summary>Gets or sets the display name, e.g. "Adams".</summary>
[JsonPropertyName("display")]
public string Display { get; set; } = string.Empty;
/// <summary>Gets or sets the thumbnail url.</summary>
[JsonPropertyName("thumb")]
public string? Thumb { get; set; }
/// <summary>Gets or sets the room's page on the streaming site.</summary>
[JsonPropertyName("link")]
public string? Link { get; set; }
/// <summary>Gets or sets what is running now and next.</summary>
[JsonPropertyName("talks")]
public CccLiveTalks? Talks { get; set; }
/// <summary>Gets or sets the stream variants.</summary>
[JsonPropertyName("streams")]
public List<CccLiveStreamVariant> Streams { get; set; } = new();
}
/// <summary>The talk on air and the one after it; both are null between talks.</summary>
public class CccLiveTalks
{
/// <summary>Gets or sets the talk currently on air.</summary>
[JsonPropertyName("current")]
public CccLiveTalk? Current { get; set; }
/// <summary>Gets or sets the next talk.</summary>
[JsonPropertyName("next")]
public CccLiveTalk? Next { get; set; }
}
/// <summary>A scheduled talk, as the streaming site reports it.</summary>
public class CccLiveTalk
{
/// <summary>Gets or sets the title.</summary>
[JsonPropertyName("title")]
public string? Title { get; set; }
/// <summary>Gets or sets the speakers, already joined by the site.</summary>
[JsonPropertyName("speaker")]
public string? Speaker { get; set; }
}
/// <summary>
/// One way to watch a room: a resolution, a language and a player type. "native" is the
/// floor language, "translated" an interpreter's channel.
/// </summary>
public class CccLiveStreamVariant
{
/// <summary>Gets or sets the variant slug, e.g. "hd-native".</summary>
[JsonPropertyName("slug")]
public string Slug { get; set; } = string.Empty;
/// <summary>Gets or sets the human readable description.</summary>
[JsonPropertyName("display")]
public string Display { get; set; } = string.Empty;
/// <summary>Gets or sets the player type: video, slides, audio, music or dash.</summary>
[JsonPropertyName("type")]
public string Type { get; set; } = string.Empty;
/// <summary>Gets or sets a value indicating whether this is an interpreter's channel.</summary>
[JsonPropertyName("isTranslated")]
public bool IsTranslated { get; set; }
/// <summary>Gets or sets width and height, absent for audio.</summary>
[JsonPropertyName("videoSize")]
public List<int>? VideoSize { get; set; }
/// <summary>Gets or sets the urls by protocol ("hls", "webm", "dash", "mp3", "opus").</summary>
[JsonPropertyName("urls")]
public Dictionary<string, CccLiveStreamUrl> Urls { get; set; } = new();
}
/// <summary>One protocol's url for a stream variant.</summary>
public class CccLiveStreamUrl
{
/// <summary>Gets or sets the description the site shows for it.</summary>
[JsonPropertyName("display")]
public string? Display { get; set; }
/// <summary>Gets or sets the technical description, e.g. "hls".</summary>
[JsonPropertyName("tech")]
public string? Tech { get; set; }
/// <summary>Gets or sets the stream url.</summary>
[JsonPropertyName("url")]
public string Url { get; set; } = string.Empty;
}

View File

@@ -0,0 +1,79 @@
using System;
using System.Text.Json.Serialization;
namespace Jellyfin.Plugin.Chaosflix.Api.Models;
/// <summary>
/// CCC API recording (a specific video/audio file for an event).
/// </summary>
public class CccRecording
{
/// <summary>
/// Gets or sets the file size in MB. Null on subtitle recordings, which the
/// API publishes without size, length or dimensions.
/// </summary>
[JsonPropertyName("size")]
public int? Size { get; set; }
/// <summary>Gets or sets the length in seconds; null on subtitle recordings.</summary>
[JsonPropertyName("length")]
public int? Length { get; set; }
/// <summary>Gets or sets the MIME type.</summary>
[JsonPropertyName("mime_type")]
public string MimeType { get; set; } = string.Empty;
/// <summary>Gets or sets the language.</summary>
[JsonPropertyName("language")]
public string Language { get; set; } = string.Empty;
/// <summary>Gets or sets the filename.</summary>
[JsonPropertyName("filename")]
public string Filename { get; set; } = string.Empty;
/// <summary>Gets or sets the state.</summary>
[JsonPropertyName("state")]
public string State { get; set; } = string.Empty;
/// <summary>Gets or sets the folder (e.g. "h264-hd", "webm-sd").</summary>
[JsonPropertyName("folder")]
public string Folder { get; set; } = string.Empty;
/// <summary>Gets or sets whether this is the high quality version.</summary>
[JsonPropertyName("high_quality")]
public bool HighQuality { get; set; }
/// <summary>Gets or sets the width; null on subtitle recordings.</summary>
[JsonPropertyName("width")]
public int? Width { get; set; }
/// <summary>Gets or sets the height; null on subtitle recordings.</summary>
[JsonPropertyName("height")]
public int? Height { get; set; }
/// <summary>Gets or sets the direct recording URL.</summary>
[JsonPropertyName("recording_url")]
public string RecordingUrl { get; set; } = string.Empty;
/// <summary>Gets or sets the API URL.</summary>
[JsonPropertyName("url")]
public string Url { get; set; } = string.Empty;
/// <summary>Gets or sets the updated timestamp.</summary>
[JsonPropertyName("updated_at")]
public DateTimeOffset? UpdatedAt { get; set; }
}
/// <summary>
/// Related event reference.
/// </summary>
public class CccRelatedEvent
{
/// <summary>Gets or sets the related event GUID.</summary>
[JsonPropertyName("event_guid")]
public string EventGuid { get; set; } = string.Empty;
/// <summary>Gets or sets the relationship weight.</summary>
[JsonPropertyName("weight")]
public int Weight { get; set; }
}