Files
jellyfin-plugin-floatplane/doc/research/reference-sources/fpdl_float.ts
claude 9e33a343ab
Some checks failed
release / build-release (push) Has been cancelled
Floatplane channel plugin for Jellyfin 10.10.x: browse, HLS proxy, cookie session, CI release
- 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>
2026-10-25 09:42:27 +01:00

110 lines
3.6 KiB
TypeScript

import { defaultSettings } from "./lib/defaults";
import { fetchFFMPEG } from "./lib/helpers/fetchFFMPEG";
import { args, DownloaderVersion, fApi, settings } from "./lib/helpers/index";
import { initProm } from "./lib/prometheus";
import { quickStart, validatePlexSettings } from "./quickStart";
import chalk from "chalk-template";
import { loginFloatplane, User } from "./logins";
import { fetchSubscriptions } from "./subscriptionFetching";
import semver from "semver";
const { gt, diff } = semver;
import type { ContentPost } from "floatplane/content";
import { Self } from "floatplane/user";
/**
* Main function that triggeres everything else in the script
*/
const downloadNewVideos = async () => {
const inProgress = [];
// Fetch content posts from seek and destroy guids
const contentPosts: Promise<ContentPost>[] = [];
while (settings.floatplane.seekAndDestroy.length > 0) {
const guid = settings.floatplane.seekAndDestroy.pop();
if (guid === undefined) continue;
console.log(chalk`Seek and Destroy: {red ${guid}}`);
contentPosts.push(fApi.content.post(guid));
}
for await (const subscription of fetchSubscriptions()) {
// Seek and destroy posts if the content post belongs to this subscription
for await (const contentPost of contentPosts) {
if (contentPost.creator.id === subscription.creatorId) {
for await (const video of subscription.seekAndDestroy(contentPost)) {
inProgress.push(video.download());
}
}
}
await subscription.deleteOldVideos();
for await (const video of subscription.fetchNewVideos()) inProgress.push(video.download());
}
console.log(chalk`Queued {green ${inProgress.length}} videos...`);
await Promise.all(inProgress);
// Enforce search limits after searching once.
settings.floatplane.videosToSearch = defaultSettings.floatplane.videosToSearch;
if (settings.floatplane.waitForNewVideos === true) {
console.log(`Checking for new videos in 5 minutes...`);
setTimeout(downloadNewVideos, 5 * 60 * 1000);
}
};
// Fix for docker
process.on("SIGTERM", () => process.exit(143));
(async () => {
if (!args.headless) {
console.log(chalk`\n{red ///}{grey ===} {cyan Console} {grey ===}{red \\\\\\}`);
}
if (args.updateCheck) {
const latest = await fApi
.got("https://raw.githubusercontent.com/Inrixia/Floatplane-Downloader/master/package.json")
.json<{ version: string }>()
.catch(() => ({ version: DownloaderVersion }));
if (gt(latest.version, DownloaderVersion))
console.log(
chalk`There is a ${diff(latest.version, DownloaderVersion)} update available! ${DownloaderVersion} > ${
latest.version
}.\nHead to {cyanBright https://github.com/Inrixia/Floatplane-Downloader/releases} to update!\n`
);
}
if (args.sanityCheck) {
console.log("Sanity check passed!");
process.exit(0);
}
// Earlybird functions, these are run before script start and not run again if script repeating is enabled.
await fetchFFMPEG();
if (settings.runQuickstartPrompts) {
if (args.headless) console.log("headless is set to true! Skipping quickstart prompts.");
else await quickStart();
}
settings.runQuickstartPrompts = false;
// Get Plex details if not saved
await validatePlexSettings();
// Get Floatplane credentials if not saved
let user: Self | User;
try {
user = await fApi.user.self();
} catch (err) {
console.log(`Unable to authenticate with floatplane... ${(<Error>err).message}\nAttempting to re-authenticate...`);
user = await loginFloatplane();
}
await initProm(user!.id);
console.log(chalk`Initalized! Running version {cyan ${DownloaderVersion}} instance {magenta ${user!.id}}`);
await downloadNewVideos();
})();