feat(komga): add script to make a Series from a Read List
All checks were successful
Discord / discord commits (push) Has been skipped
All checks were successful
Discord / discord commits (push) Has been skipped
This commit is contained in:
parent
fb75377969
commit
4cdd8c0d43
14 changed files with 2076 additions and 3 deletions
|
@ -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
3
apps/list2series/.envrc
Normal file
|
@ -0,0 +1,3 @@
|
|||
use flake $FLAKE#node
|
||||
(cd ../config; npm ci)
|
||||
npm ci
|
1
apps/list2series/.gitignore
vendored
Normal file
1
apps/list2series/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
.env
|
11
apps/list2series/default.nix
Normal file
11
apps/list2series/default.nix
Normal 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.
|
||||
'';
|
||||
}
|
3
apps/list2series/eslint.config.ts
Normal file
3
apps/list2series/eslint.config.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
import eslintConf from 'eslint-conf';
|
||||
|
||||
export default eslintConf;
|
1812
apps/list2series/package-lock.json
generated
Normal file
1812
apps/list2series/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
21
apps/list2series/package.json
Normal file
21
apps/list2series/package.json
Normal 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
204
apps/list2series/src/app.ts
Normal 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();
|
10
apps/list2series/tsconfig.json
Normal file
10
apps/list2series/tsconfig.json
Normal file
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"$schema": "https://json.schemastore.org/tsconfig",
|
||||
"extends": "../config/tsconfig.base.json",
|
||||
"includes": [
|
||||
"*.ts",
|
||||
"**/*.ts",
|
||||
"*.js",
|
||||
"**/*.js"
|
||||
]
|
||||
}
|
|
@ -8,6 +8,7 @@
|
|||
listToAttrs (map (x: nameValuePair x (callPackage ./${x})) [
|
||||
"extract-subs"
|
||||
"gen-docs"
|
||||
"list2series"
|
||||
"mc-mods"
|
||||
"pin-inputs"
|
||||
"update-sources"
|
||||
|
|
|
@ -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;
|
||||
|
|
4
justfile
4
justfile
|
@ -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 -- "$@"
|
||||
|
|
|
@ -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 |
|
||||
|
|
|
@ -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 |
|
||||
|
|
Loading…
Add table
Reference in a new issue