Files
MangaCircus/greatest/index.js
T

45 lines
1.9 KiB
JavaScript

'use strict';
const fs = require('fs');
const path = require('path');
const RATINGS = Object.freeze(['flame', 'good', 'neutral', 'skip']);
const WEIGHT = Object.freeze({ flame: 3, good: 1, neutral: 0, skip: -2 });
const normalize = value => String(value || '').normalize('NFKD').toLowerCase().replace(/[^a-z0-9]+/g, '');
function loadCatalog(file = path.join(__dirname, 'catalog.json')) {
const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
if (parsed.schema !== 1 || !Array.isArray(parsed.entries)) throw new Error('Invalid Greatest of All Time catalog');
const ids = new Set();
for (const entry of parsed.entries) {
if (!/^[a-z0-9-]+$/.test(entry.id) || ids.has(entry.id) || !entry.title || !entry.period || !Number.isInteger(entry.rank))
throw new Error(`Invalid or duplicate Greatest of All Time entry: ${entry.id}`);
if (entry.cover && !/^https:\/\//.test(entry.cover)) throw new Error(`Invalid cover URL for ${entry.id}`);
if (entry.source?.url && !/^https:\/\//.test(entry.source.url)) throw new Error(`Invalid source URL for ${entry.id}`);
ids.add(entry.id);
}
return parsed;
}
function findOwned(entry, mangas) {
const names = new Set([entry.title, ...(entry.aliases || [])].map(normalize));
return mangas.find(m => {
const alt = m.meta?.altTitles;
return [m.title, ...(Array.isArray(alt) ? alt : alt ? [alt] : [])].some(name => names.has(normalize(name)));
}) || null;
}
function countsFor(entryId, votes, users) {
const counts = { flame: 0, good: 0, neutral: 0, skip: 0 };
for (const name of Object.keys(users)) {
const rating = votes[name]?.[entryId];
if (RATINGS.includes(rating)) counts[rating]++;
}
return counts;
}
function scoreFor(counts) {
return RATINGS.reduce((sum, rating) => sum + counts[rating] * WEIGHT[rating], 0);
}
module.exports = { RATINGS, WEIGHT, normalize, loadCatalog, findOwned, countsFor, scoreFor };