- offer to create tag when none is found while adding a link from cli - fix tag display in search
259 lines
6.6 KiB
JavaScript
259 lines
6.6 KiB
JavaScript
import fs from "fs-extra";
|
|
import path from "path";
|
|
import https from "https";
|
|
import inquirer from "inquirer";
|
|
import { fileURLToPath } from "url";
|
|
import { loadConfig, MEDIA_TYPES } from "./config.js";
|
|
import { sanitizeMediaString } from "./sanitize.js";
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const downloadImage = (url, dest) =>
|
|
new Promise((resolve, reject) => {
|
|
const file = fs.createWriteStream(dest);
|
|
|
|
https
|
|
.get(url, (response) => {
|
|
if (response.statusCode !== 200)
|
|
return reject(new Error(`Failed to download. Status: ${response.statusCode}`));
|
|
|
|
response.pipe(file);
|
|
file.on("finish", () => file.close(resolve));
|
|
})
|
|
.on("error", reject);
|
|
});
|
|
const isValidTMDBUrl = (val) =>
|
|
/^https:\/\/image\.tmdb\.org\/t\/p\//.test(val) || "❌ Must be a valid TMDB image url";
|
|
const overwriteImageDownloadPrompt = async (url, finalPath, fileName) => {
|
|
await fs.ensureDir(path.dirname(finalPath));
|
|
|
|
if (await fs.pathExists(finalPath)) {
|
|
const { overwrite } = await inquirer.prompt({
|
|
type: "confirm",
|
|
name: "overwrite",
|
|
message: `${fileName} already exists. Overwrite?`,
|
|
default: false
|
|
});
|
|
|
|
if (!overwrite) {
|
|
console.log(`⚠️ Skipped existing file: ${fileName}`);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
await downloadImage(url, finalPath);
|
|
|
|
console.log(`✅ Saved to: ${finalPath}`);
|
|
};
|
|
|
|
export const downloadWatchingImages = async () => {
|
|
const config = await loadConfig();
|
|
const { mediaType } = await inquirer.prompt({
|
|
type: "list",
|
|
name: "mediaType",
|
|
message: "Movie or a tv show?",
|
|
choices: MEDIA_TYPES.map(({ key, label }) => ({
|
|
name: label,
|
|
value: key
|
|
}))
|
|
});
|
|
const { tmdbId } = await inquirer.prompt({
|
|
name: "tmdbId",
|
|
message: "Enter the TMDB ID:"
|
|
});
|
|
|
|
if (!tmdbId) {
|
|
console.warn("⚠️ TMDB ID is required.");
|
|
|
|
return;
|
|
}
|
|
|
|
const { posterUrl, backdropUrl } = await inquirer.prompt([
|
|
{
|
|
name: "posterUrl",
|
|
message: "Enter the poster url:",
|
|
validate: (val) => {
|
|
if (!val) return true;
|
|
|
|
return isValidTMDBUrl(val);
|
|
}
|
|
},
|
|
{
|
|
name: "backdropUrl",
|
|
message: "Enter the backdrop url:",
|
|
validate: (val) => {
|
|
if (!val) return true;
|
|
|
|
return isValidTMDBUrl(val);
|
|
}
|
|
}
|
|
]);
|
|
const types = [
|
|
{ type: "poster", url: posterUrl },
|
|
{ type: "backdrop", url: backdropUrl }
|
|
];
|
|
|
|
for (const { type, url } of types) {
|
|
if (!url) continue;
|
|
|
|
const ext = path.extname(new URL(url).pathname) || ".jpg";
|
|
const fileName = `${type}-${tmdbId}${ext}`;
|
|
const targetSubPath = config.mediaPaths[mediaType][type];
|
|
|
|
if (!targetSubPath) {
|
|
console.error(`❌ Missing path for ${mediaType}/${type}. Check your config.`);
|
|
|
|
continue;
|
|
}
|
|
|
|
const targetDir = path.join(config.storageDir, targetSubPath);
|
|
const finalPath = path.join(targetDir, fileName);
|
|
|
|
await overwriteImageDownloadPrompt(url, finalPath, fileName);
|
|
}
|
|
};
|
|
|
|
export const downloadArtistImage = async () => {
|
|
const config = await loadConfig();
|
|
const { artistName } = await inquirer.prompt({
|
|
name: "artistName",
|
|
message: "Enter the artist name:"
|
|
});
|
|
|
|
if (!artistName) {
|
|
console.warn("⚠️ Artist name is required.");
|
|
|
|
return;
|
|
}
|
|
|
|
const { imageUrl } = await inquirer.prompt({
|
|
name: "imageUrl",
|
|
message: "Enter the artist image url:",
|
|
validate: (val) => {
|
|
try {
|
|
new URL(val);
|
|
|
|
return true;
|
|
} catch {
|
|
return "❌ Must be a valid url.";
|
|
}
|
|
}
|
|
});
|
|
|
|
const sanitizedName = sanitizeMediaString(artistName);
|
|
const ext = path.extname(new URL(imageUrl).pathname) || ".jpg";
|
|
const fileName = `${sanitizedName}${ext}`;
|
|
const targetDir = path.join(config.storageDir, "artists");
|
|
const finalPath = path.join(targetDir, fileName);
|
|
|
|
await overwriteImageDownloadPrompt(imageUrl, finalPath, fileName);
|
|
};
|
|
|
|
export const downloadAlbumImage = async () => {
|
|
const config = await loadConfig();
|
|
|
|
const { artistName } = await inquirer.prompt({
|
|
name: "artistName",
|
|
message: "Enter the artist name:"
|
|
});
|
|
|
|
if (!artistName) {
|
|
console.warn("⚠️ Artist name is required.");
|
|
|
|
return;
|
|
}
|
|
|
|
const { albumName } = await inquirer.prompt({
|
|
name: "albumName",
|
|
message: "Enter the album name:"
|
|
});
|
|
|
|
if (!albumName) {
|
|
console.warn("⚠️ Album name is required.");
|
|
|
|
return;
|
|
}
|
|
|
|
const { imageUrl } = await inquirer.prompt({
|
|
name: "imageUrl",
|
|
message: "Enter the album image url:",
|
|
validate: (val) => {
|
|
try {
|
|
new URL(val);
|
|
|
|
return true;
|
|
} catch {
|
|
return "❌ Must be a valid url.";
|
|
}
|
|
}
|
|
});
|
|
|
|
const artistSlug = sanitizeMediaString(artistName);
|
|
const albumSlug = sanitizeMediaString(albumName);
|
|
const ext = path.extname(new URL(imageUrl).pathname) || ".jpg";
|
|
const fileName = `${artistSlug}-${albumSlug}${ext}`;
|
|
const targetDir = path.join(config.storageDir, config.albumPath);
|
|
const finalPath = path.join(targetDir, fileName);
|
|
|
|
await overwriteImageDownloadPrompt(imageUrl, finalPath, fileName);
|
|
};
|
|
|
|
export const downloadBookImage = async () => {
|
|
const config = await loadConfig();
|
|
const { isbn } = await inquirer.prompt({
|
|
name: "isbn",
|
|
message: "Enter the ISBN (no spaces):",
|
|
validate: (val) =>
|
|
/^[a-zA-Z0-9-]+$/.test(val) || "ISBN must contain only letters, numbers, or hyphens"
|
|
});
|
|
const { bookTitle } = await inquirer.prompt({
|
|
name: "bookTitle",
|
|
message: "Enter the book title:"
|
|
});
|
|
|
|
if (!bookTitle) {
|
|
console.warn("⚠️ Book title is required.");
|
|
|
|
return;
|
|
}
|
|
|
|
const { imageUrl } = await inquirer.prompt({
|
|
name: "imageUrl",
|
|
message: "Enter the book cover image URL:",
|
|
validate: (val) => {
|
|
try {
|
|
new URL(val);
|
|
|
|
return true;
|
|
} catch {
|
|
return "Must be a valid URL";
|
|
}
|
|
}
|
|
});
|
|
const titleSlug = sanitizeMediaString(bookTitle);
|
|
const ext = path.extname(new URL(imageUrl).pathname) || ".jpg";
|
|
const fileName = `${isbn}-${titleSlug}${ext}`;
|
|
const targetDir = path.join(config.storageDir, config.bookPath);
|
|
const finalPath = path.join(targetDir, fileName);
|
|
|
|
await overwriteImageDownloadPrompt(imageUrl, finalPath, fileName);
|
|
};
|
|
|
|
export const downloadAsset = async () => {
|
|
const { type } = await inquirer.prompt({
|
|
type: "list",
|
|
name: "type",
|
|
message: "What type of asset are you downloading?",
|
|
choices: ["movie/tv show", "artist", "album", "book"]
|
|
});
|
|
|
|
if (type === "artist") {
|
|
await downloadArtistImage();
|
|
} else if (type === "album") {
|
|
await downloadAlbumImage();
|
|
} else if (type === "book") {
|
|
await downloadBookImage();
|
|
} else {
|
|
await downloadWatchingImages();
|
|
}
|
|
};
|