214 lines
6 KiB
TypeScript
214 lines
6 KiB
TypeScript
import axios from 'axios';
|
|
import { linkSync, mkdirSync, readFileSync, rmSync } from 'fs';
|
|
import { basename } from 'path';
|
|
|
|
import { type Book, type ReadList, type Series } from './types';
|
|
|
|
|
|
// Examples of calling this script:
|
|
// $ just l2s copy 0K65Q482KK7SD
|
|
// $ just l2s meta 0K65Q482KK7SD
|
|
const API = JSON.parse(
|
|
readFileSync(`${process.env.FLAKE}/apps/list2series/.env`, { encoding: 'utf-8' }),
|
|
).API;
|
|
|
|
const LIST_ID = process.argv[3];
|
|
|
|
const getListInfo = async(): Promise<ReadList> => {
|
|
const res = await axios.request({
|
|
method: 'get',
|
|
maxBodyLength: Infinity,
|
|
url: `https://komga.nelim.org/api/v1/readlists/${LIST_ID}`,
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
'X-API-Key': API,
|
|
},
|
|
});
|
|
|
|
return res.data;
|
|
};
|
|
|
|
const getSeries = async(seriesTitle: string, operator = true): Promise<Series[]> => {
|
|
return (await axios.request({
|
|
method: 'post',
|
|
maxBodyLength: Infinity,
|
|
url: 'https://komga.nelim.org/api/v1/series/list?unpaged=true',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json',
|
|
'X-API-Key': API,
|
|
},
|
|
data: JSON.stringify({
|
|
condition: {
|
|
title: {
|
|
operator: operator ? 'is' : 'isNot',
|
|
value: seriesTitle,
|
|
},
|
|
},
|
|
}),
|
|
})).data.content;
|
|
};
|
|
|
|
const getSeriesBooks = async(listName: string, seriesPath: string): Promise<Book[]> => {
|
|
const thisSeries = (await getSeries('', false)).find((s) => s.url === seriesPath);
|
|
|
|
if (!thisSeries) {
|
|
throw new Error('Series could not be found');
|
|
}
|
|
|
|
// Reset Series metadata
|
|
axios.request({
|
|
method: 'patch',
|
|
maxBodyLength: Infinity,
|
|
url: `https://komga.nelim.org/api/v1/series/${thisSeries.id}/metadata`,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-API-Key': API,
|
|
},
|
|
data: JSON.stringify({
|
|
ageRating: null,
|
|
ageRatingLock: true,
|
|
alternateTitles: null,
|
|
alternateTitlesLock: true,
|
|
genres: null,
|
|
genresLock: true,
|
|
language: null,
|
|
languageLock: true,
|
|
links: null,
|
|
linksLock: true,
|
|
publisherLock: true,
|
|
readingDirection: 'LEFT_TO_RIGHT',
|
|
readingDirectionLock: true,
|
|
sharingLabels: null,
|
|
sharingLabelsLock: true,
|
|
status: null,
|
|
statusLock: true,
|
|
summary: null,
|
|
summaryLock: true,
|
|
tags: null,
|
|
tagsLock: true,
|
|
title: listName,
|
|
titleLock: true,
|
|
titleSort: listName,
|
|
titleSortLock: true,
|
|
totalBookCountLock: true,
|
|
}),
|
|
});
|
|
|
|
const books = await axios.request({
|
|
method: 'post',
|
|
maxBodyLength: Infinity,
|
|
url: 'https://komga.nelim.org/api/v1/books/list?unpaged=true',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'Accept': 'application/json',
|
|
'X-API-Key': API,
|
|
},
|
|
data: JSON.stringify({
|
|
condition: {
|
|
seriesId: {
|
|
operator: 'is',
|
|
value: thisSeries.id,
|
|
},
|
|
},
|
|
}),
|
|
});
|
|
|
|
return books.data.content;
|
|
};
|
|
|
|
const getBookInfo = async(id: string): Promise<Book> => {
|
|
const res = await axios.request({
|
|
method: 'get',
|
|
maxBodyLength: Infinity,
|
|
url: `https://komga.nelim.org/api/v1/books/${id}`,
|
|
headers: {
|
|
'Accept': 'application/json',
|
|
'X-API-Key': API,
|
|
},
|
|
});
|
|
|
|
return res.data;
|
|
};
|
|
|
|
// There doesn't seem to be a way to wait for the scan to be done
|
|
const scanLibrary = (): void => {
|
|
axios.request({
|
|
method: 'post',
|
|
maxBodyLength: Infinity,
|
|
url: 'https://komga.nelim.org/api/v1/libraries/0K4QG58XA29DZ/scan',
|
|
headers: {
|
|
'X-API-Key': API,
|
|
},
|
|
});
|
|
};
|
|
|
|
const setBookMetadata = async(i: number, source: Book, target: Book): Promise<void> => {
|
|
const thisSeries = (await getSeries(source.seriesTitle))[0];
|
|
|
|
source.metadata.title = thisSeries.booksCount !== 1 ?
|
|
`${source.seriesTitle} Issue #${source.metadata.number}` :
|
|
source.metadata.title = source.seriesTitle;
|
|
|
|
source.metadata.number = i.toString();
|
|
source.metadata.numberSort = i;
|
|
|
|
const metadata = JSON.stringify(source.metadata);
|
|
|
|
axios.request({
|
|
method: 'patch',
|
|
maxBodyLength: Infinity,
|
|
url: `https://komga.nelim.org/api/v1/books/${target.id}/metadata`,
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
'X-API-Key': API,
|
|
},
|
|
data: metadata,
|
|
});
|
|
};
|
|
|
|
const main = async(): Promise<void> => {
|
|
const list = await getListInfo();
|
|
const ids = list.bookIds;
|
|
const seriesPath = `/data/comics/[List] ${list.name}`;
|
|
|
|
const listBooks = [] as Book[];
|
|
|
|
for (let i = 0; i < ids.length; i++) {
|
|
const book = await getBookInfo(ids[i]);
|
|
|
|
listBooks[i] = book;
|
|
};
|
|
|
|
if (process.argv[2] === 'copy') {
|
|
rmSync(seriesPath, { recursive: true, force: true });
|
|
mkdirSync(seriesPath, { recursive: true });
|
|
|
|
for (const book of listBooks) {
|
|
const bookPath = book.url;
|
|
const inListPath = `${seriesPath}/${basename(bookPath)}`;
|
|
|
|
console.log(`hardlinking ${basename(bookPath)}`);
|
|
linkSync(bookPath, inListPath);
|
|
}
|
|
|
|
scanLibrary();
|
|
}
|
|
|
|
else if (process.argv[2] === 'meta') {
|
|
const seriesBooks = await getSeriesBooks(`[List] ${list.name}`, seriesPath);
|
|
|
|
for (const target of seriesBooks) {
|
|
const source = listBooks.find((b) => basename(b.url) === basename(target.url));
|
|
|
|
if (source) {
|
|
const i = listBooks.indexOf(source) + 1;
|
|
|
|
console.log(`Setting metadata for ${source.name}`);
|
|
setBookMetadata(i, source, target);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
main();
|