492 lines
20 KiB
JavaScript
492 lines
20 KiB
JavaScript
"use strict";
|
|
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
if (k2 === undefined) k2 = k;
|
|
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
}
|
|
Object.defineProperty(o, k2, desc);
|
|
}) : (function(o, m, k, k2) {
|
|
if (k2 === undefined) k2 = k;
|
|
o[k2] = m[k];
|
|
}));
|
|
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
}) : function(o, v) {
|
|
o["default"] = v;
|
|
});
|
|
var __importStar = (this && this.__importStar) || (function () {
|
|
var ownKeys = function(o) {
|
|
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
var ar = [];
|
|
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
return ar;
|
|
};
|
|
return ownKeys(o);
|
|
};
|
|
return function (mod) {
|
|
if (mod && mod.__esModule) return mod;
|
|
var result = {};
|
|
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
__setModuleDefault(result, mod);
|
|
return result;
|
|
};
|
|
})();
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.upsertListEntry = upsertListEntry;
|
|
exports.getUserList = getUserList;
|
|
exports.deleteListEntry = deleteListEntry;
|
|
exports.getSingleListEntry = getSingleListEntry;
|
|
exports.getActiveAccessToken = getActiveAccessToken;
|
|
exports.isConnected = isConnected;
|
|
exports.getUserListByFilter = getUserListByFilter;
|
|
const database_1 = require("../../shared/database");
|
|
const extensions_1 = require("../../shared/extensions");
|
|
const animeService = __importStar(require("../anime/anime.service"));
|
|
const booksService = __importStar(require("../books/books.service"));
|
|
const aniListService = __importStar(require("../anilist/anilist.service"));
|
|
const USER_DB = 'userdata';
|
|
async function upsertListEntry(entry) {
|
|
const { user_id, entry_id, source, entry_type, status, progress, score, start_date, end_date, repeat_count, notes, is_private } = entry;
|
|
let prev = null;
|
|
try {
|
|
prev = await getSingleListEntry(user_id, entry_id, source, entry_type);
|
|
}
|
|
catch {
|
|
prev = null;
|
|
}
|
|
const isNew = !prev;
|
|
if (!isNew && prev?.progress != null && progress < prev.progress) {
|
|
return { changes: 0, ignored: true };
|
|
}
|
|
const today = new Date().toISOString().slice(0, 10);
|
|
if (prev?.start_date && !entry.start_date) {
|
|
entry.start_date = prev.start_date;
|
|
}
|
|
if (!prev?.start_date && progress === 1) {
|
|
entry.start_date = today;
|
|
}
|
|
const total = prev?.total_episodes ??
|
|
prev?.total_chapters ??
|
|
null;
|
|
if (total && progress >= total) {
|
|
entry.status = 'COMPLETED';
|
|
entry.end_date = today;
|
|
}
|
|
if (source === 'anilist') {
|
|
const token = await getActiveAccessToken(user_id);
|
|
if (token) {
|
|
try {
|
|
const result = await aniListService.updateAniListEntry(token, {
|
|
mediaId: entry.entry_id,
|
|
status: entry.status,
|
|
progress: entry.progress,
|
|
score: entry.score,
|
|
start_date: entry.start_date,
|
|
end_date: entry.end_date,
|
|
repeat_count: entry.repeat_count,
|
|
notes: entry.notes,
|
|
is_private: entry.is_private
|
|
});
|
|
return { changes: 0, external: true, anilistResult: result };
|
|
}
|
|
catch (err) {
|
|
console.error("Error actualizando AniList:", err);
|
|
}
|
|
}
|
|
}
|
|
const sql = `
|
|
INSERT INTO ListEntry
|
|
(
|
|
user_id, entry_id, source, entry_type, status,
|
|
progress, score,
|
|
start_date, end_date, repeat_count, notes, is_private,
|
|
updated_at
|
|
)
|
|
VALUES
|
|
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
|
ON CONFLICT(user_id, entry_id) DO UPDATE SET
|
|
source = EXCLUDED.source,
|
|
entry_type = EXCLUDED.entry_type,
|
|
status = EXCLUDED.status,
|
|
progress = EXCLUDED.progress,
|
|
score = EXCLUDED.score,
|
|
start_date = EXCLUDED.start_date,
|
|
end_date = EXCLUDED.end_date,
|
|
repeat_count = EXCLUDED.repeat_count,
|
|
notes = EXCLUDED.notes,
|
|
is_private = EXCLUDED.is_private,
|
|
updated_at = CURRENT_TIMESTAMP;
|
|
`;
|
|
const params = [
|
|
entry.user_id,
|
|
entry.entry_id,
|
|
entry.source,
|
|
entry.entry_type,
|
|
entry.status,
|
|
entry.progress,
|
|
entry.score ?? null,
|
|
entry.start_date || null,
|
|
entry.end_date || null,
|
|
entry.repeat_count ?? 0,
|
|
entry.notes || null,
|
|
entry.is_private ?? 0
|
|
];
|
|
try {
|
|
const result = await (0, database_1.run)(sql, params, USER_DB);
|
|
return { changes: result.changes, lastID: result.lastID, external: false };
|
|
}
|
|
catch (error) {
|
|
console.error("Error al guardar la entrada de lista:", error);
|
|
throw new Error("Error en la base de datos al guardar la entrada.");
|
|
}
|
|
}
|
|
async function getUserList(userId) {
|
|
const sql = `
|
|
SELECT * FROM ListEntry
|
|
WHERE user_id = ?
|
|
ORDER BY updated_at DESC;
|
|
`;
|
|
try {
|
|
const dbList = await (0, database_1.queryAll)(sql, [userId], USER_DB);
|
|
const connected = await isConnected(userId);
|
|
let finalList = [...dbList];
|
|
if (connected) {
|
|
const anilistEntries = await aniListService.getUserAniList(userId);
|
|
const localWithoutAnilist = dbList.filter(entry => entry.source !== 'anilist');
|
|
finalList = [...anilistEntries, ...localWithoutAnilist];
|
|
}
|
|
const enrichedListPromises = finalList.map(async (entry) => {
|
|
if (entry.source === 'anilist' && connected) {
|
|
let finalTitle = entry.title;
|
|
if (typeof finalTitle === 'object' && finalTitle !== null) {
|
|
finalTitle =
|
|
finalTitle.userPreferred ||
|
|
finalTitle.english ||
|
|
finalTitle.romaji ||
|
|
'Unknown Title';
|
|
}
|
|
return {
|
|
...entry,
|
|
title: finalTitle,
|
|
poster: entry.poster || 'https://placehold.co/400x600?text=No+Cover',
|
|
};
|
|
}
|
|
let contentDetails = null;
|
|
const id = entry.entry_id;
|
|
const type = entry.entry_type;
|
|
const ext = (0, extensions_1.getExtension)(entry.source);
|
|
try {
|
|
if (type === 'ANIME') {
|
|
if (entry.source === 'anilist') {
|
|
const anime = await animeService.getAnimeById(id);
|
|
contentDetails = {
|
|
title: anime?.title.english || 'Unknown Anime Title',
|
|
poster: anime?.coverImage?.extraLarge || '',
|
|
total_episodes: anime?.episodes || 0,
|
|
};
|
|
}
|
|
else {
|
|
const anime = await animeService.getAnimeInfoExtension(ext, id.toString());
|
|
contentDetails = {
|
|
title: anime?.title || 'Unknown Anime Title',
|
|
poster: anime?.image || 'https://placehold.co/400x600?text=No+Cover',
|
|
total_episodes: anime?.episodes || 0,
|
|
};
|
|
}
|
|
}
|
|
else if (type === 'MANGA' || type === 'NOVEL') {
|
|
if (entry.source === 'anilist') {
|
|
const book = await booksService.getBookById(id);
|
|
contentDetails = {
|
|
title: book?.title.english || 'Unknown Book Title',
|
|
poster: book?.coverImage?.extraLarge || 'https://placehold.co/400x600?text=No+Cover',
|
|
total_chapters: book?.chapters || book?.volumes * 10 || 0,
|
|
};
|
|
}
|
|
else {
|
|
const book = await booksService.getBookInfoExtension(ext, id.toString());
|
|
contentDetails = {
|
|
title: book?.title || 'Unknown Book Title',
|
|
poster: book?.image || '',
|
|
total_chapters: book?.chapters || book?.volumes * 10 || 0,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
catch {
|
|
contentDetails = {
|
|
title: 'Error Loading Details',
|
|
poster: 'https://placehold.co/400x600?text=No+Cover',
|
|
};
|
|
}
|
|
let finalTitle = contentDetails?.title || 'Unknown Title';
|
|
let finalPoster = contentDetails?.poster || 'https://placehold.co/400x600?text=No+Cover';
|
|
if (typeof finalTitle === 'object' && finalTitle !== null) {
|
|
finalTitle =
|
|
finalTitle.userPreferred ||
|
|
finalTitle.english ||
|
|
finalTitle.romaji ||
|
|
'Unknown Title';
|
|
}
|
|
return {
|
|
...entry,
|
|
title: finalTitle,
|
|
poster: finalPoster,
|
|
total_episodes: contentDetails?.total_episodes,
|
|
total_chapters: contentDetails?.total_chapters,
|
|
};
|
|
});
|
|
return await Promise.all(enrichedListPromises);
|
|
}
|
|
catch (error) {
|
|
console.error("Error al obtener la lista del usuario:", error);
|
|
throw new Error("Error getting list.");
|
|
}
|
|
}
|
|
async function deleteListEntry(userId, entryId, source) {
|
|
if (source === 'anilist') {
|
|
const token = await getActiveAccessToken(userId);
|
|
if (token) {
|
|
try {
|
|
await aniListService.deleteAniListEntry(token, Number(entryId));
|
|
return { success: true, external: true };
|
|
}
|
|
catch (err) {
|
|
console.error("Error borrando en AniList:", err);
|
|
}
|
|
}
|
|
}
|
|
const sql = `
|
|
DELETE FROM ListEntry
|
|
WHERE user_id = ? AND entry_id = ?;
|
|
`;
|
|
const result = await (0, database_1.run)(sql, [userId, entryId], USER_DB);
|
|
return { success: result.changes > 0, changes: result.changes, external: false };
|
|
}
|
|
async function getSingleListEntry(userId, entryId, source, entryType) {
|
|
const localSql = `
|
|
SELECT * FROM ListEntry
|
|
WHERE user_id = ? AND entry_id = ? AND source = ? AND entry_type = ?;
|
|
`;
|
|
const localResult = await (0, database_1.queryAll)(localSql, [userId, entryId, source, entryType], USER_DB);
|
|
if (localResult.length > 0) {
|
|
const entry = localResult[0];
|
|
const contentDetails = entryType === 'ANIME'
|
|
? await animeService.getAnimeById(entryId).catch(() => null)
|
|
: await booksService.getBookById(entryId).catch(() => null);
|
|
let finalTitle = contentDetails?.title || 'Unknown';
|
|
let finalPoster = contentDetails?.coverImage?.extraLarge ||
|
|
contentDetails?.image ||
|
|
'https://placehold.co/400x600?text=No+Cover';
|
|
if (typeof finalTitle === 'object') {
|
|
finalTitle =
|
|
finalTitle.userPreferred ||
|
|
finalTitle.english ||
|
|
finalTitle.romaji ||
|
|
'Unknown';
|
|
}
|
|
return {
|
|
...entry,
|
|
title: finalTitle,
|
|
poster: finalPoster,
|
|
total_episodes: contentDetails?.episodes,
|
|
total_chapters: contentDetails?.chapters,
|
|
};
|
|
}
|
|
if (source === 'anilist') {
|
|
const connected = await isConnected(userId);
|
|
if (!connected)
|
|
return null;
|
|
const sql = `
|
|
SELECT access_token
|
|
FROM UserIntegration
|
|
WHERE user_id = ? AND platform = 'AniList';
|
|
`;
|
|
const integration = await (0, database_1.queryOne)(sql, [userId], USER_DB);
|
|
if (!integration?.access_token)
|
|
return null;
|
|
if (entryType === 'NOVEL') {
|
|
entryType = 'MANGA';
|
|
}
|
|
const aniEntry = await aniListService.getSingleAniListEntry(integration.access_token, Number(entryId), entryType);
|
|
if (!aniEntry)
|
|
return null;
|
|
const contentDetails = entryType === 'ANIME'
|
|
? await animeService.getAnimeById(entryId).catch(() => null)
|
|
: await booksService.getBookById(entryId).catch(() => null);
|
|
let finalTitle = contentDetails?.title || 'Unknown';
|
|
let finalPoster = contentDetails?.coverImage?.extraLarge ||
|
|
contentDetails?.image ||
|
|
'https://placehold.co/400x600?text=No+Cover';
|
|
if (typeof finalTitle === 'object') {
|
|
finalTitle =
|
|
finalTitle.userPreferred ||
|
|
finalTitle.english ||
|
|
finalTitle.romaji ||
|
|
'Unknown';
|
|
}
|
|
return {
|
|
user_id: userId,
|
|
...aniEntry,
|
|
title: finalTitle,
|
|
poster: finalPoster,
|
|
total_episodes: contentDetails?.episodes,
|
|
total_chapters: contentDetails?.chapters,
|
|
};
|
|
}
|
|
return null;
|
|
}
|
|
async function getActiveAccessToken(userId) {
|
|
const sql = `
|
|
SELECT access_token, expires_at
|
|
FROM UserIntegration
|
|
WHERE user_id = ? AND platform = 'AniList';
|
|
`;
|
|
try {
|
|
const integration = await (0, database_1.queryOne)(sql, [userId], USER_DB);
|
|
if (!integration) {
|
|
return null;
|
|
}
|
|
const expiryDate = new Date(integration.expires_at);
|
|
const now = new Date();
|
|
const fiveMinutes = 5 * 60 * 1000;
|
|
if (expiryDate.getTime() < (now.getTime() + fiveMinutes)) {
|
|
console.log(`AniList token for user ${userId} expired or near expiry.`);
|
|
return null;
|
|
}
|
|
return integration.access_token;
|
|
}
|
|
catch (error) {
|
|
console.error("Error al verificar la integración de AniList:", error);
|
|
return null;
|
|
}
|
|
}
|
|
async function isConnected(userId) {
|
|
const token = await getActiveAccessToken(userId);
|
|
return !!token;
|
|
}
|
|
async function getUserListByFilter(userId, status, entryType) {
|
|
let sql = `
|
|
SELECT * FROM ListEntry
|
|
WHERE user_id = ?
|
|
ORDER BY updated_at DESC;
|
|
`;
|
|
const params = [userId];
|
|
try {
|
|
const dbList = await (0, database_1.queryAll)(sql, params, USER_DB);
|
|
const connected = await isConnected(userId);
|
|
const statusMap = {
|
|
watching: 'CURRENT',
|
|
reading: 'CURRENT',
|
|
completed: 'COMPLETED',
|
|
paused: 'PAUSED',
|
|
dropped: 'DROPPED',
|
|
planning: 'PLANNING'
|
|
};
|
|
const mappedStatus = status ? statusMap[status.toLowerCase()] : null;
|
|
let finalList = [];
|
|
const filteredLocal = dbList.filter((entry) => {
|
|
if (mappedStatus && entry.status !== mappedStatus)
|
|
return false;
|
|
if (entryType) {
|
|
if (entryType === 'MANGA') {
|
|
if (!['MANGA', 'NOVEL'].includes(entry.entry_type))
|
|
return false;
|
|
}
|
|
else {
|
|
if (entry.entry_type !== entryType)
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
let filteredAniList = [];
|
|
if (connected) {
|
|
const anilistEntries = await aniListService.getUserAniList(userId);
|
|
filteredAniList = anilistEntries.filter((entry) => {
|
|
if (mappedStatus && entry.status !== mappedStatus)
|
|
return false;
|
|
if (entryType) {
|
|
if (entryType === 'MANGA') {
|
|
if (!['MANGA', 'NOVEL'].includes(entry.entry_type))
|
|
return false;
|
|
}
|
|
else {
|
|
if (entry.entry_type !== entryType)
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
});
|
|
}
|
|
finalList = [...filteredAniList, ...filteredLocal];
|
|
const enrichedListPromises = finalList.map(async (entry) => {
|
|
if (entry.source === 'anilist') {
|
|
let finalTitle = entry.title;
|
|
if (typeof finalTitle === 'object' && finalTitle !== null) {
|
|
finalTitle =
|
|
finalTitle.userPreferred ||
|
|
finalTitle.english ||
|
|
finalTitle.romaji ||
|
|
'Unknown Title';
|
|
}
|
|
return {
|
|
...entry,
|
|
title: finalTitle,
|
|
poster: entry.poster || 'https://placehold.co/400x600?text=No+Cover',
|
|
};
|
|
}
|
|
let contentDetails = null;
|
|
const id = entry.entry_id;
|
|
const type = entry.entry_type;
|
|
const ext = (0, extensions_1.getExtension)(entry.source);
|
|
try {
|
|
if (type === 'ANIME') {
|
|
const anime = await animeService.getAnimeInfoExtension(ext, id.toString());
|
|
contentDetails = {
|
|
title: anime?.title || 'Unknown Anime Title',
|
|
poster: anime?.image || '',
|
|
total_episodes: anime?.episodes || 0,
|
|
};
|
|
}
|
|
else if (type === 'MANGA' || type === 'NOVEL') {
|
|
const book = await booksService.getBookInfoExtension(ext, id.toString());
|
|
contentDetails = {
|
|
title: book?.title || 'Unknown Book Title',
|
|
poster: book?.image || '',
|
|
total_chapters: book?.chapters || book?.volumes * 10 || 0,
|
|
};
|
|
}
|
|
}
|
|
catch {
|
|
contentDetails = {
|
|
title: 'Error Loading Details',
|
|
poster: 'https://placehold.co/400x600?text=No+Cover',
|
|
};
|
|
}
|
|
let finalTitle = contentDetails?.title || 'Unknown Title';
|
|
let finalPoster = contentDetails?.poster || 'https://placehold.co/400x600?text=No+Cover';
|
|
if (typeof finalTitle === 'object' && finalTitle !== null) {
|
|
finalTitle =
|
|
finalTitle.userPreferred ||
|
|
finalTitle.english ||
|
|
finalTitle.romaji ||
|
|
'Unknown Title';
|
|
}
|
|
return {
|
|
...entry,
|
|
title: finalTitle,
|
|
poster: finalPoster,
|
|
total_episodes: contentDetails?.total_episodes,
|
|
total_chapters: contentDetails?.total_chapters,
|
|
};
|
|
});
|
|
return await Promise.all(enrichedListPromises);
|
|
}
|
|
catch (error) {
|
|
console.error("Error al filtrar la lista del usuario:", error);
|
|
throw new Error("Error en la base de datos al obtener la lista filtrada.");
|
|
}
|
|
}
|