Last active
January 28, 2025 19:06
-
-
Save chelming/b8e4e58e6ad6d99f3380dda36f5de626 to your computer and use it in GitHub Desktop.
quickadd macro for interfacing with Komga
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const notice = (msg) => new Notice(msg, 5000); | |
const log = (msg) => console.log(msg); | |
const API_URL_OPTION = "Komga URL"; | |
const API_USER_OPTION = "Username"; | |
const API_PASS_OPTION = "Password"; | |
const COVER_PATH_OPTION = "Path to save covers"; | |
module.exports = { | |
entry: start, | |
settings: { | |
name: "Komga Importer", | |
author: "Chris Helming", | |
options: { | |
[API_URL_OPTION]: { | |
type: "text", | |
defaultValue: "", | |
placeholder: "https://komga.example.com", | |
}, | |
[API_USER_OPTION]: { | |
type: "text", | |
defaultValue: "", | |
placeholder: "Username", | |
}, | |
[API_PASS_OPTION]: { | |
type: "text", | |
defaultValue: "", | |
placeholder: "Password", | |
secret: true, | |
}, | |
[COVER_PATH_OPTION]: { | |
type: "text", | |
defaultValue: "/", | |
placeholder: "/fake/path/", | |
}, | |
}, | |
}, | |
}; | |
let QuickAdd; | |
let Settings; | |
let auth; | |
async function start(params, settings) { | |
QuickAdd = params; | |
Settings = settings; | |
if (!Settings[API_URL_OPTION] || !Settings[API_USER_OPTION] || !Settings[API_PASS_OPTION]) { | |
notice("Please fill out the macro API settings."); | |
throw new Error("No macro selected."); | |
} | |
var Base64 = {_keyStr:"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",encode:function(e){var t="";var n,r,i,s,o,u,a;var f=0;e=Base64._utf8_encode(e);while(f<e.length){n=e.charCodeAt(f++);r=e.charCodeAt(f++);i=e.charCodeAt(f++);s=n>>2;o=(n&3)<<4|r>>4;u=(r&15)<<2|i>>6;a=i&63;if(isNaN(r)){u=a=64}else if(isNaN(i)){a=64}t=t+this._keyStr.charAt(s)+this._keyStr.charAt(o)+this._keyStr.charAt(u)+this._keyStr.charAt(a)}return t},decode:function(e){var t="";var n,r,i;var s,o,u,a;var f=0;e=e.replace(/[^A-Za-z0-9\+\/\=]/g,"");while(f<e.length){s=this._keyStr.indexOf(e.charAt(f++));o=this._keyStr.indexOf(e.charAt(f++));u=this._keyStr.indexOf(e.charAt(f++));a=this._keyStr.indexOf(e.charAt(f++));n=s<<2|o>>4;r=(o&15)<<4|u>>2;i=(u&3)<<6|a;t=t+String.fromCharCode(n);if(u!=64){t=t+String.fromCharCode(r)}if(a!=64){t=t+String.fromCharCode(i)}}t=Base64._utf8_decode(t);return t},_utf8_encode:function(e){e=e.replace(/\r\n/g,"\n");var t="";for(var n=0;n<e.length;n++){var r=e.charCodeAt(n);if(r<128){t+=String.fromCharCode(r)}else if(r>127&&r<2048){t+=String.fromCharCode(r>>6|192);t+=String.fromCharCode(r&63|128)}else{t+=String.fromCharCode(r>>12|224);t+=String.fromCharCode(r>>6&63|128);t+=String.fromCharCode(r&63|128)}}return t},_utf8_decode:function(e){var t="";var n=0;var r=c1=c2=0;while(n<e.length){r=e.charCodeAt(n);if(r<128){t+=String.fromCharCode(r);n++}else if(r>191&&r<224){c2=e.charCodeAt(n+1);t+=String.fromCharCode((r&31)<<6|c2&63);n+=2}else{c2=e.charCodeAt(n+1);c3=e.charCodeAt(n+2);t+=String.fromCharCode((r&15)<<12|(c2&63)<<6|c3&63);n+=3}}return t}}; | |
const b = Base64.encode(Settings[API_USER_OPTION] + ":" + Settings[API_PASS_OPTION]); | |
auth = "Basic " + b; | |
Settings[API_URL_OPTION] = Settings[API_URL_OPTION].replace(/\/?$/, '/'); | |
const query = await QuickAdd.quickAddApi.inputPrompt( | |
"Comic name (blank for recent): " | |
); | |
let results; | |
if (!query) { | |
results = await getRecents(); | |
} | |
else { | |
results = await getByQuery(query); | |
} | |
const choice = await QuickAdd.quickAddApi.suggester( | |
results.map(formatTitleForSuggestion), | |
results | |
); | |
if (!choice) { | |
notice("No choice selected."); | |
throw new Error("No choice selected."); | |
} | |
log(choice); | |
const colorers = linkifyList(getAuthors(choice, 'colorist')); | |
const comicStatus = choice.metadata.status.charAt(0) + choice.metadata.status.substring(1).toLowerCase() | |
const coverArtists = linkifyList(getAuthors(choice, 'cover')); | |
const editors = linkifyList(getAuthors(choice, 'editor')); | |
const fileName = replaceIllegalFileNameCharactersInString(choice.name); | |
const inkers = linkifyList(getAuthors(choice, 'inker')); | |
const pencillers = linkifyList(getAuthors(choice, 'penciller')); | |
const writers = linkifyList(getAuthors(choice, 'writer')); | |
thumbnailGet(fileName, choice.id); | |
QuickAdd.variables = { | |
...choice, | |
colorersLinks: colorers || ' ', | |
comicStatus: comicStatus || ' ', | |
coverArtistsLinks: coverArtists || ' ', | |
editorsLinks: editors || ' ', | |
fileName: fileName || ' ', | |
inkersLinks: inkers || ' ', | |
komgaURL: `${Settings[API_URL_OPTION]}series/${choice.id}`, | |
pencillersLinks: pencillers || ' ', | |
publisherLink: `[[${choice.metadata.publisher ? choice.metadata.publisher : "Unknown Publisher"}]]`, | |
summary: choice.booksMetadata.summary.replace(/\*List of covers and their creators:\*.*$(\n.*)*/gm,'').trim() || ' ', | |
title: `${choice.metadata.title.replace(/ \(\d{1,2}\)/,'')}`, | |
writersLinks: writers || ' ', | |
year: choice.booksMetadata.releaseDate?.split('-')[0] || ' ', | |
}; | |
} | |
function getAuthors(choice, role) { | |
const roleArray = [...new Set(choice.booksMetadata.authors.filter(a => a.role == role).map(a => `@${a.name}`))] | |
return roleArray.sort((a, b) => (a.name > b.name) ? 1 : -1);; | |
} | |
function formatTitleForSuggestion(resultItem) { | |
const name = resultItem.name; | |
const issue = `${resultItem.booksCount} ${resultItem.booksCount == 1 ? "issue" : 'issues'}`; | |
const pub = resultItem.metadata.publisher ? resultItem.metadata.publisher : "Unknown"; | |
return `${name} (${issue}) (${pub})`; | |
} | |
async function getRecents() { | |
const endpoint = 'api/v1/books?read_status=READ&read_status=IN_PROGRESS&sort=readProgress.readDate%2Cdesc' | |
const searchResults = await apiGet(Settings[API_URL_OPTION] + endpoint, {}); | |
if (!searchResults.content || !searchResults.content.length) { | |
notice("No results found."); | |
throw new Error("No results found."); | |
} | |
const seriesIds = searchResults.content.map(a => a.seriesId); | |
const uniqueSeries = [...new Set(seriesIds)] | |
let seriesResults = []; | |
for (const series of uniqueSeries) { | |
seriesResults.push(await getSeries(series)); | |
} | |
return seriesResults; | |
} | |
async function getSeries(id) { | |
const endpoint = 'api/v1/series/' + id; | |
const searchResults = await apiGet(Settings[API_URL_OPTION] + endpoint, {}); | |
return searchResults; | |
} | |
async function getByQuery(query) { | |
const endpoint = 'api/v1/series'; | |
const searchResults = await apiGet(Settings[API_URL_OPTION] + endpoint, { | |
search: query, | |
unpaged: true, | |
}); | |
if (!searchResults.content || !searchResults.content.length) { | |
notice("No results found."); | |
throw new Error("No results found."); | |
} | |
return searchResults.content.sort((a, b) => (a.name > b.name) ? 1 : -1); | |
} | |
function linkifyList(list) { | |
if (list.length === 0) return ""; | |
if (list.length === 1) return `[[${list[0]}]]`; | |
return list.map((item) => `[[${item.trim()}]]`).join(", "); | |
} | |
function replaceIllegalFileNameCharactersInString(string) { | |
return string.replace(/[\\,#%&\{\}\/*<>$\'\":@]*/g, ""); | |
} | |
async function apiGet(url, data) { | |
let finalURL = new URL(url); | |
if (data) | |
Object.keys(data).forEach((key) => | |
finalURL.searchParams.append(key, data[key]) | |
); | |
const res = await request({ | |
url: finalURL.href, | |
method: "GET", | |
cache: "no-cache", | |
headers: { | |
Authorization: auth | |
} | |
}); | |
return JSON.parse(res); | |
} | |
function thumbnailGet(fileName, seriesId) { | |
var request = require('https'); | |
request.get({ | |
path: `/api/v1/series/${seriesId}/thumbnail`, | |
host: new URL(Settings[API_URL_OPTION]).host, | |
headers: { | |
Authorization: auth, | |
'User-Agent': 'Mozilla/5.0', | |
}, | |
encoding: null | |
}, (res) => { | |
let str = []; | |
res.setEncoding('binary'); | |
res.on('data', function (body) { | |
str.push(body); | |
}); | |
res.on('end', function(){ | |
var buf = Buffer.from(str.join(''), 'binary'); | |
app.vault.createBinary(Settings[COVER_PATH_OPTION]+fileName+'.jpg', buf); | |
}); | |
}); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment