universal-plugin-repo/update.js
2026-03-07 12:49:46 +00:00

182 lines
6 KiB
JavaScript

const fs = require('fs/promises');
const path = require('path');
const crypto = require('crypto');
let userAgent = "Jellyfin-Server/10.11.4"; // Required for some repositories
const imagesDir = path.join(__dirname, 'images');
const imageBaseUrl = 'https://raw.githubusercontent.com/0belous/universal-plugin-repo/refs/heads/main/images/';
async function getLatestJellyfinVersion() {
const response = await fetch('https://api.github.com/repos/jellyfin/jellyfin/releases/latest', {
headers: { 'User-Agent': 'Node-Fetch' }
});
if (!response.ok) throw new Error();
const data = await response.json();
return data.tag_name.replace('v', '');
}
async function getSources(sourceFile){
let sources = [];
try {
const fileContent = await fs.readFile(sourceFile, 'utf8');
sources = fileContent.split(/\r?\n/).filter(line => line.trim() !== '' && !line.trim().startsWith('#'));
} catch (err) {
console.error(`Error reading ${sourceFile}:`, err.message);
return [];
}
let mergedData = [];
for(const url of sources){
try {
console.log(`Fetching ${url}...`);
const response = await fetch(url, {
headers: { 'User-Agent': userAgent }
});
if (!response.ok) {
throw new Error(`Response status: ${response.status}`);
}
const json = await response.json();
json.forEach(p => p._metaSourceUrl = url);
mergedData.push(...json);
console.log(` -> Merged ${json.length} plugins.`);
} catch (error) {
console.error(`Error processing ${url}: ${error.message}`);
}
}
return mergedData;
}
async function clearImagesFolder() {
try {
await fs.rm(imagesDir, { recursive: true, force: true });
await fs.mkdir(imagesDir, { recursive: true });
} catch (err) {
console.error('Error clearing images folder:', err);
}
}
async function downloadImage(url, filename) {
console.log(`Downloading image: ${url} as ${filename}`);
try {
const res = await fetch(url, { headers: { 'User-Agent': userAgent } });
if (!res.ok) throw new Error(`Failed to fetch image: ${res.status}`);
const buffer = await res.arrayBuffer();
await fs.writeFile(path.join(imagesDir, filename), Buffer.from(buffer));
return true;
} catch (err) {
console.error(`Error downloading image ${url}:`, err.message);
return false;
}
}
function getImageExtension(url) {
const ext = path.extname(new URL(url).pathname);
return ext || '.png';
}
function getPluginId(plugin) {
return plugin.id || plugin.Id || plugin.pluginId || plugin.name || null;
}
function hashString(str) {
return crypto.createHash('md5').update(str).digest('hex');
}
function findGithubUrl(obj) {
if (!obj) return null;
for (const key in obj) {
if (typeof obj[key] === 'string') {
const match = obj[key].match(/https?:\/\/github\.com\/[^\/]+\/[^\/]+/);
if (match) {
return match[0];
}
} else if (typeof obj[key] === 'object' && obj[key] !== null) {
const url = findGithubUrl(obj[key]);
if (url) return url;
}
}
return null;
}
async function processDescriptions(pluginData) {
try {
const genTime = new Date().toISOString().substring(11, 16) + ' UTC';
for (const plugin of pluginData) {
const repoUrl = findGithubUrl(plugin);
const sourceUrl = plugin._metaSourceUrl || 'Unknown';
delete plugin._metaSourceUrl;
let appendText = ` \n \nUniversal Repo: \nGenerated: ${genTime} \nSource: ${sourceUrl}`;
if (repoUrl) {
appendText += ` \nGithub: ${repoUrl}`;
}
const descriptionProp = ['description', 'Description', 'overview'].find(p => plugin[p]);
if (descriptionProp) {
if (!plugin[descriptionProp].includes("Universal Repo:")) {
plugin[descriptionProp] += appendText;
}
} else {
plugin.description = appendText.trim();
}
}
console.log(`Sucessfully injected source URLs`);
} catch (err) {
console.error('Error processing descriptions:', err);
}
}
async function processImages(pluginData) {
for (const plugin of pluginData) {
if (plugin.imageUrl) {
const ext = getImageExtension(plugin.imageUrl);
let pluginId = getPluginId(plugin);
if (!pluginId) {
pluginId = hashString(plugin.imageUrl);
}
const filename = `${pluginId}${ext}`;
const success = await downloadImage(plugin.imageUrl, filename);
if (success) {
plugin.imageUrl = imageBaseUrl + filename;
console.log(` -> Updated manifest imageUrl for plugin ${pluginId}`);
}
}
}
}
async function writeManifest(dataToWrite, outputFile){
if (!dataToWrite || dataToWrite.length === 0) {
console.log(`No data to write to manifest ${outputFile}. Aborting.`);
return;
}
try {
const manifestJson = JSON.stringify(dataToWrite, null, 2);
await fs.writeFile(outputFile, manifestJson);
} catch (err) {
console.error(`Error writing manifest file ${outputFile}:`, err);
}
console.log(`\nSuccessfully created ${outputFile} with ${dataToWrite.length} total plugins`);
}
async function processList(sourceFile, outputFile) {
const plugins = await getSources(sourceFile);
if (plugins.length > 0) {
await processDescriptions(plugins);
await processImages(plugins);
await writeManifest(plugins, outputFile);
}
}
async function main() {
userAgent = `Jellyfin-Server/${await getLatestJellyfinVersion()}`;
await clearImagesFolder();
await processList('sources.txt', 'manifest.json');
await processList('sourcesnsfw.txt', 'manifestnsfw.json');
}
main();