feat(komga): add script to make a Series from a Read List
All checks were successful
Discord / discord commits (push) Has been skipped

This commit is contained in:
matt1432 2025-03-24 13:18:27 -04:00
parent fb75377969
commit 4cdd8c0d43
14 changed files with 2076 additions and 3 deletions

View file

@ -8,6 +8,7 @@ This directory contains every derivations for apps exposed by this flake.
| ---- | ----------- |
| `extract-subs` | Extract all `srt` subtitle files from a `mkv` video with the appropriate name. |
| `gen-docs` | Generates the READMEs in this repository from nix attributes. |
| `list2series` | Converts a Komga read list into a comics series for reading with mihon. |
| `mc-mods` | Checks if a list of mods have a version available for a specific Minecraft version and a specific loader. |
| `pin-inputs` | Takes a list of inputs to pin to their current rev in `flake.lock`. |
| `update-sources` | Updates all derivation sources in this repository and generates a commit message for the changes made. |

3
apps/list2series/.envrc Normal file
View file

@ -0,0 +1,3 @@
use flake $FLAKE#node
(cd ../config; npm ci)
npm ci

1
apps/list2series/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
.env

View file

@ -0,0 +1,11 @@
{buildApp, ...}:
buildApp {
src = ./.;
npmDepsHash = "sha256-GrGSXKAH8w068rIOFwmQoM1Zn68ESkazBiDaoE0mrQw=";
runtimeInputs = [];
meta.description = ''
Converts a Komga read list into a comics series for reading with mihon.
'';
}

View file

@ -0,0 +1,3 @@
import eslintConf from 'eslint-conf';
export default eslintConf;

1812
apps/list2series/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,21 @@
{
"name": "list2series",
"version": "0.0.0",
"bin": "out/bin/app.cjs",
"type": "module",
"scripts": {
"build": "node_ver=$(node -v); esbuild src/app.ts --bundle --platform=node --target=\"node${node_ver:1:2}\" --outfile=out/bin/app.cjs"
},
"dependencies": {
"@types/node": "22.13.11",
"axios": "^1.8.4",
"esbuild": "0.25.1",
"eslint": "9.23.0",
"jiti": "2.4.2",
"pkg-types": "2.1.0",
"typescript": "5.8.2"
},
"devDependencies": {
"eslint-conf": "file:../config"
}
}

204
apps/list2series/src/app.ts Normal file
View file

@ -0,0 +1,204 @@
import axios from 'axios';
import { copyFileSync, mkdirSync, readFileSync, rmSync } from 'fs';
import { basename } from 'path';
// eslint-disable-next-line
type Book = any;
const API = JSON.parse(
readFileSync(`${process.env.FLAKE}/apps/list2series/.env`, { encoding: 'utf-8' }),
).API;
// Examples of calling this script:
// $ just l2s copy 0K65Q482KK7SD
// $ just l2s meta 0K65Q482KK7SD
const LIST_ID = process.argv[3];
const getListInfo = async() => {
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 getSeriesBooks = async(listName: string, seriesPath: string): Promise<Book[]> => {
const series = 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: 'isNot',
value: '',
},
},
}),
});
const seriesId = (series.data.content as Book[]).find((s) => s.url === seriesPath).id;
// Reset Series metadata
axios.request({
method: 'patch',
maxBodyLength: Infinity,
url: `https://komga.nelim.org/api/v1/series/${seriesId}/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: seriesId,
},
},
}),
});
return books.data.content;
};
const getBookInfo = async(id: string) => {
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;
};
const scanLibrary = async() => {
return await 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) => {
source.metadata.title = `${source.seriesTitle} Issue #${source.number}`;
source.metadata.number = i.toString();
source.metadata.numberSort = i;
const metadata = JSON.stringify(source.metadata);
const res = await 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,
});
return res;
};
const main = async() => {
const list = await getListInfo();
const ids = list.bookIds as string[];
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(`copying ${basename(bookPath)}`);
copyFileSync(bookPath, inListPath);
}
await 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();

View file

@ -0,0 +1,10 @@
{
"$schema": "https://json.schemastore.org/tsconfig",
"extends": "../config/tsconfig.base.json",
"includes": [
"*.ts",
"**/*.ts",
"*.js",
"**/*.js"
]
}

View file

@ -8,6 +8,7 @@
listToAttrs (map (x: nameValuePair x (callPackage ./${x})) [
"extract-subs"
"gen-docs"
"list2series"
"mc-mods"
"pin-inputs"
"update-sources"

View file

@ -30,7 +30,9 @@ in {
baseUri = "https://komga.nelim.org";
metadataUpdate.default = {
libraryType = "COMIC";
overrideExistingCovers = false;
bookCovers = true;
seriesCovers = true;
overrideExistingCovers = true;
overrideComicInfo = true;
postProcessing = {
seriesTitle = true;

View file

@ -15,6 +15,10 @@ genflake:
alejandra -q "$FLAKE"/flake.nix
[positional-arguments]
l2s action list:
nix run "$FLAKE"#list2series -- "$@"
[positional-arguments]
mc-mods version action='check':
nix run "$FLAKE"#mc-mods -- "$@"

View file

@ -12,7 +12,8 @@ This directory contains every derivations for packages exposed by this flake.
| `gsr-kms-server` | Small program giving safe KMS access to gpu-screen-recorder when recording a monitor. This is the only part of gpu-screen-recorder that could require root permissions. | https://git.dec05eba.com/gpu-screen-recorder/about |
| `homepage` | Highly customisable dashboard with Docker and service API integrations. | https://gethomepage.dev |
| `jmusicbot` | Discord music bot that's easy to set up and run yourself | https://github.com/jagrosh/MusicBot |
| `komf` | komf is a tool that fetches metadata and thumbnails for your digital comic book library. | https://github.com/Snd-R/komf |
| `kapowarr` | Kapowarr is a software to build and manage a comic book library, fitting in the \*arr suite of software | https://casvt.github.io/Kapowarr |
| `komf` | Komf is a tool that fetches metadata and thumbnails for your digital comic book library. | https://github.com/Snd-R/komf |
| `libratbag` | Configuration library for gaming mice | https://github.com/libratbag/libratbag |
| `librespot-auth` | A simple program for populating a credentials.json via Spotify's zeroconf authentication. | https://github.com/dspearson/librespot-auth |
| `nbted` | Command-line NBT editor written in Rust. It does precisely one thing: convert NBT files to a pretty text format, and reverse the pretty text format back into NBT. | https://github.com/C4K3/nbted |

View file

@ -27,7 +27,6 @@ Every extensions I use in my firefox module.
| `bitwarden` | At home, at work, or on the go, Bitwarden easily secures all your passwords, passkeys, and sensitive information. | https://bitwarden.com |
| `darkreader` | Dark mode for every website. Take care of your eyes, use dark theme for night and daily browsing. | https://darkreader.org/ |
| `floccus` | Sync your bookmarks and tabs across browsers via Nextcloud, any WebDAV service, any Git service, via a local file, via Google Drive. | https://floccus.org |
| `frankerfacez` | The Twitch Enhancement Suite - Get custom emotes and tons of new features you'll never want to go without. | https://www.frankerfacez.com |
| `google-container` | THIS IS NOT AN OFFICIAL ADDON FROM MOZILLA! It is a fork of the Facebook Container addon. Prevent Google from tracking you around the web. The Google Container extension helps you take control and isolate your web activity from Google. | https://github.com/containers-everywhere/contain-google |
| `image-search-options` | A customizable reverse image search tool that conveniently presents a variety of top image search engines. | http://saucenao.com/ |
| `istilldontcareaboutcookies` | Community version of the popular extension "I don't care about cookies" https://github.com/OhMyGuus/I-Dont-Care-About-Cookies | https://github.com/OhMyGuus/I-Dont-Care-About-Cookies |