UNPKG

koneko-cli

Version:

Your CLI for reading manga from the terminal

325 lines (324 loc) • 15.2 kB
"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; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.loadEnabledPluginServers = loadEnabledPluginServers; const inquirer_1 = __importDefault(require("inquirer")); const ora_1 = __importDefault(require("ora")); const open_1 = __importDefault(require("open")); const fs_extra_1 = __importDefault(require("fs-extra")); const path_1 = __importDefault(require("path")); const SettingsConfig_1 = __importDefault(require("../SettingsConfig")); const DebugLogger_1 = require("./DebugLogger"); const Loader_1 = __importDefault(require("../../modules/mangakakalot/Loader")); const child_process_1 = require("child_process"); async function loadEnabledPluginServers() { const pluginFolder = path_1.default.join(process.env.HOME || process.env.USERPROFILE || '.', '.config', 'koneko', 'plugins'); DebugLogger_1.logger.debug('šŸ” Loading plugins from:', pluginFolder); if (!(await fs_extra_1.default.pathExists(pluginFolder))) { DebugLogger_1.logger.debug('āš ļø Plugin folder does not exist.'); return {}; } const pluginDirs = await fs_extra_1.default.readdir(pluginFolder); DebugLogger_1.logger.debug('šŸ” Plugin directories found:', pluginDirs); const servers = {}; for (const pluginName of pluginDirs) { try { const pluginMainPath = path_1.default.join(pluginFolder, pluginName, 'index.js'); const packageJsonPath = path_1.default.join(pluginFolder, pluginName, 'package.json'); if (!(await fs_extra_1.default.pathExists(pluginMainPath))) { DebugLogger_1.logger.debug(`āš ļø Skipping plugin "${pluginName}": index.js not found.`); continue; } if (await fs_extra_1.default.pathExists(packageJsonPath)) { DebugLogger_1.logger.debug(`šŸ” Found package.json for plugin "${pluginName}". Checking for main entry...`); DebugLogger_1.logger.debug(`šŸ” Loading package.json for plugin "${pluginName}"`); try { (0, child_process_1.execSync)(`npm install`, { cwd: path_1.default.join(pluginFolder, pluginName), stdio: 'ignore' }); DebugLogger_1.logger.debug(`āœ… Successfully installed dependencies for plugin "${pluginName}".`); } catch (error) { DebugLogger_1.logger.debug(`āŒ Failed to install dependencies for plugin "${pluginName}":`, error); continue; } } DebugLogger_1.logger.debug(`šŸ” Importing plugin: ${pluginName}`); const pluginModule = await Promise.resolve(`${pluginMainPath}`).then(s => __importStar(require(s))); if (pluginModule) { servers[pluginName] = { name: pluginName, searchManga: (query) => { var _a, _b, _c; return pluginModule.search ? pluginModule.search(query, (_c = (_b = (_a = SettingsConfig_1.default.data) === null || _a === void 0 ? void 0 : _a.Plugins) === null || _b === void 0 ? void 0 : _b[pluginName]) !== null && _c !== void 0 ? _c : {}) : Promise.resolve([]); }, getMangaDetail: (id) => pluginModule.getMangaDetail ? pluginModule.getMangaDetail(id) : Promise.resolve(null), getChapterList: (id) => pluginModule.getChapters ? pluginModule.getChapters(id) : Promise.resolve([]), getImagesAndCreatePDF: (url, mangaTitle, chapterTitle) => pluginModule.getImagesAndCreatePDF ? pluginModule.getImagesAndCreatePDF(url, mangaTitle, chapterTitle) : Promise.reject(new Error("Not implemented")), }; DebugLogger_1.logger.debug(`āœ… Plugin "${pluginName}" loaded.`); } else { DebugLogger_1.logger.debug(`āš ļø Plugin "${pluginName}" did not export anything.`); } } catch (error) { DebugLogger_1.logger.debug(`āŒ Failed to load plugin "${pluginName}":`, error); } } return servers; } class MangaEventHandler { constructor() { this.lastDownloadedFolder = null; this.currentServer = null; this.allServers = { MangaKonekuto: Loader_1.default, }; } parseChapterNumber(title) { const match = title.match(/chapter\s*(\d+(\.\d+)?)/i); if (match && match[1]) return parseFloat(match[1]); return Number.MAX_SAFE_INTEGER; } async cleanupPreviousDownload() { var _a; const shouldDelete = (_a = (await SettingsConfig_1.default.get("Settings.DeletePrevious"))) !== null && _a !== void 0 ? _a : false; if (shouldDelete && this.lastDownloadedFolder) { try { await fs_extra_1.default.remove(this.lastDownloadedFolder); } catch (_b) { console.log(`āš ļø Failed to delete ${this.lastDownloadedFolder}`); } } } async setupServer() { const enabledServers = (await SettingsConfig_1.default.get("Settings.Server")) || []; DebugLogger_1.logger.debug('šŸ” Enabled servers from settings:', enabledServers); const pluginServers = await loadEnabledPluginServers(); DebugLogger_1.logger.debug('šŸ” Plugin servers loaded:', Object.keys(pluginServers)); const allServers = Object.assign(Object.assign({}, this.allServers), pluginServers); DebugLogger_1.logger.debug('šŸ” All servers after merging built-ins and plugins:', Object.keys(allServers)); // Normalize keys for case-insensitive match const allServersLowerCaseMap = new Map(); for (const key of Object.keys(allServers)) { allServersLowerCaseMap.set(key.toLowerCase(), key); } for (const serverName of enabledServers) { DebugLogger_1.logger.debug(`šŸ”Ž Checking if server "${serverName}" exists in allServers...`); const lower = serverName.toLowerCase(); if (allServersLowerCaseMap.has(lower)) { const realKey = allServersLowerCaseMap.get(lower); DebugLogger_1.logger.debug(`āœ… Server found: "${realKey}". Setting as current server.`); this.currentServer = allServers[realKey]; return true; } else { DebugLogger_1.logger.debug(`āŒ Server "${serverName}" not found in loaded servers.`); } } console.log('\nāš ļø No supported servers enabled in your settings.'); console.log('Please enable a server in Settings -> Enable Server.'); return false; } async manage() { var _a, _b; if (!(await this.setupServer())) return; console.log(`\n🌐 Using ${(_b = (_a = this.currentServer) === null || _a === void 0 ? void 0 : _a.name) !== null && _b !== void 0 ? _b : "unknown"} as your manga source.`); const searchAnswer = await inquirer_1.default.prompt([ { type: 'input', name: 'query', message: 'Enter the manga title to search for:', }, ]); const searchSpinner = (0, ora_1.default)('Searching...').start(); const searchResults = await this.currentServer.searchManga(searchAnswer.query); searchSpinner.stop(); if (searchResults.length === 0) { console.log('No results found.'); return; } const mangaChoices = searchResults.map((manga) => ({ name: `${manga.title} - Author: ${manga.author}`, value: manga, })); const selectAnswer = await inquirer_1.default.prompt([ { type: 'rawlist', name: 'selectedManga', message: 'Select a manga:', choices: mangaChoices, }, ]); const selectedManga = selectAnswer.selectedManga; const infoSpinner = (0, ora_1.default)('Loading manga info...').start(); const mangaInfo = await this.currentServer.getMangaDetail(selectedManga.id); infoSpinner.stop(); if (!mangaInfo) { console.log('Failed to load manga details.'); return; } console.log(`\n=== Manga Information ===`); console.log(`Title: ${mangaInfo.title}`); console.log(`Image URL: ${mangaInfo.imageUrl}`); console.log(`Summary:\n${mangaInfo.summary}\n`); const actionAnswer = await inquirer_1.default.prompt([ { type: 'list', name: 'action', message: `What would you like to do?`, choices: [ { name: 'Select Chapter To Read', value: 'read' }, { name: 'Back to Search', value: 'back' }, ], }, ]); if (actionAnswer.action === 'read') { await this.readChapters(selectedManga.id, mangaInfo.title); } else { await this.manage(); } } async readChapters(mangaId, mangaTitle) { const chapterSpinner = (0, ora_1.default)(`Loading chapters for "${mangaTitle}"...`).start(); let chapters = await this.currentServer.getChapterList(mangaId); chapterSpinner.stop(); if (chapters.length === 0) { console.log('No chapters found.'); return; } chapters.sort((a, b) => this.parseChapterNumber(a.title) - this.parseChapterNumber(b.title)); let currentIndex; while (true) { if (typeof currentIndex !== 'number') { const chapterChoices = chapters.map((ch, idx) => ({ name: `${ch.title} (${ch.date})`, value: idx, })); const chapterAnswer = await inquirer_1.default.prompt([ { type: 'list', name: 'chapterIndex', message: `Select a chapter to read:`, choices: chapterChoices, }, ]); currentIndex = chapterAnswer.chapterIndex; } if (typeof currentIndex === 'number' && currentIndex < chapters.length) { const selectedChapter = chapters[currentIndex]; console.log(`\nšŸ“– Now reading: ${selectedChapter.title} (${selectedChapter.date}) [${currentIndex + 1} / ${chapters.length}]`); const safeMangaTitle = mangaTitle.replace(/[^a-z0-9]/gi, '_').toLowerCase(); const safeChapterTitle = selectedChapter.title.replace(/[^a-z0-9]/gi, '_').toLowerCase(); await this.cleanupPreviousDownload(); const pdfPath = await this.currentServer.getImagesAndCreatePDF(selectedChapter.url, safeMangaTitle, safeChapterTitle); this.lastDownloadedFolder = path_1.default.dirname(pdfPath); await (0, open_1.default)(pdfPath); const nextAction = await inquirer_1.default.prompt([ { type: 'list', name: 'action', message: `What do you want to do now?`, choices: [ { name: 'Read Next Chapter', value: 'next' }, { name: 'Back to Chapter List', value: 'back' }, { name: 'Back to Manga Search', value: 'search' }, ], }, ]); if (nextAction.action === 'next') { currentIndex++; if (currentIndex >= chapters.length) { console.log('\nšŸš€ No more chapters available.'); const backAnswer = await inquirer_1.default.prompt([ { type: 'confirm', name: 'goBack', message: 'Would you like to go back to the chapter list?', default: true, }, ]); if (backAnswer.goBack) { currentIndex = undefined; continue; } else { break; } } } else if (nextAction.action === 'back') { currentIndex = undefined; } else { break; } } else { console.log('\nšŸš€ No more chapters available.'); const backAnswer = await inquirer_1.default.prompt([ { type: 'confirm', name: 'goBack', message: 'Would you like to go back to the chapter list?', default: true, }, ]); if (backAnswer.goBack) { currentIndex = undefined; continue; } else { break; } } } } } exports.default = MangaEventHandler;