Compare commits
8 Commits
e23fb5ff0e
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7134437e00 | ||
|
|
5730d6a5ac | ||
|
|
ba9e0336b3 | ||
|
|
4bdd791647 | ||
|
|
e13f9189df | ||
|
|
063dd2e33e | ||
|
|
e858921ca7 | ||
|
|
168dac7aa8 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -37,3 +37,5 @@ __screenshots__/
|
|||||||
|
|
||||||
# Vite
|
# Vite
|
||||||
*.timestamp-*-*.mjs
|
*.timestamp-*-*.mjs
|
||||||
|
|
||||||
|
public/covers/*
|
||||||
21
Dockerfile
Normal file
21
Dockerfile
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
FROM node:20-slim AS base
|
||||||
|
ENV PNPM_HOME="/pnpm"
|
||||||
|
ENV PATH="$PNPM_HOME:$PATH"
|
||||||
|
RUN corepack enable
|
||||||
|
COPY . /app
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
FROM base AS prod-deps
|
||||||
|
ARG CI=true
|
||||||
|
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile
|
||||||
|
|
||||||
|
FROM base AS build
|
||||||
|
ARG CI=true
|
||||||
|
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
|
||||||
|
RUN pnpm run build
|
||||||
|
|
||||||
|
FROM base
|
||||||
|
COPY --from=prod-deps /app/node_modules /app/node_modules
|
||||||
|
COPY --from=build /app/dist /app/dist
|
||||||
|
EXPOSE 8000
|
||||||
|
CMD [ "pnpm", "start" ]
|
||||||
6
docker-compose.yml
Normal file
6
docker-compose.yml
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
services:
|
||||||
|
mva-backend:
|
||||||
|
image: mva-backend:latest
|
||||||
|
container_name: mva-backend
|
||||||
|
ports:
|
||||||
|
- 6767:3000
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "tsx watch src/index.ts",
|
"dev": "tsx watch src/index.ts",
|
||||||
"build": "tsc src/index.ts --outDir dist",
|
"build": "tsc",
|
||||||
"start": "NODE_ENV=production node dist/index.js"
|
"start": "NODE_ENV=production node dist/index.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import axios from "axios";
|
import axios from "axios";
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
export async function download_image_from_caa(mbid: string) {
|
export async function download_image_from_caa(mbid: string) {
|
||||||
console.log("[DEBUG]: download_image_from_caa:", mbid);
|
console.log("[DEBUG]: download_image_from_caa:", mbid);
|
||||||
@@ -14,7 +15,14 @@ export async function download_image_from_caa(mbid: string) {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
fs.writeFileSync(`public/covers/mb/${mbid}.png`, img_response.data);
|
const mb_dist = path.join(
|
||||||
|
process.cwd(),
|
||||||
|
"public",
|
||||||
|
"covers",
|
||||||
|
"mb",
|
||||||
|
`${mbid}.png`,
|
||||||
|
);
|
||||||
|
fs.writeFileSync(mb_dist, img_response.data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching data:", error);
|
console.error("Error fetching data:", error);
|
||||||
throw error;
|
throw error;
|
||||||
@@ -32,14 +40,20 @@ export async function itunes_cover_url(artist: string, album: string) {
|
|||||||
.replace(/[-_,"':;|]/g, " ")
|
.replace(/[-_,"':;|]/g, " ")
|
||||||
.toLowerCase();
|
.toLowerCase();
|
||||||
|
|
||||||
const apple_music_results = await fetch("https://itunes.apple.com/search", {
|
const response = await fetch("https://itunes.apple.com/search", {
|
||||||
body: new URLSearchParams({
|
body: new URLSearchParams({
|
||||||
term: query,
|
term: query,
|
||||||
entity: "song",
|
entity: "song",
|
||||||
limit: "10",
|
limit: "10",
|
||||||
}),
|
}),
|
||||||
method: "POST",
|
method: "POST",
|
||||||
}).then((res) => res.json());
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`iTunes API failed with status ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const apple_music_results = await response.json();
|
||||||
|
|
||||||
for (let result of apple_music_results.results) {
|
for (let result of apple_music_results.results) {
|
||||||
if (artist == result.artistName.toLowerCase()) {
|
if (artist == result.artistName.toLowerCase()) {
|
||||||
@@ -49,27 +63,35 @@ export async function itunes_cover_url(artist: string, album: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function download_image_from_itunes(
|
export async function download_image_from_itunes(
|
||||||
artist: string,
|
rawArtist: string,
|
||||||
album: string,
|
rawAlbum: string,
|
||||||
|
safeArtist: string,
|
||||||
|
safeAlbum: string,
|
||||||
) {
|
) {
|
||||||
console.log("[DEBUG]: download_image_from_itunes:", artist, album);
|
console.log("[DEBUG]: download_image_from_itunes:", rawArtist, rawAlbum);
|
||||||
try {
|
try {
|
||||||
const itunes_url = await itunes_cover_url(artist, album);
|
const itunes_url = await itunes_cover_url(rawArtist, rawAlbum);
|
||||||
|
|
||||||
if (!itunes_url) {
|
if (!itunes_url) {
|
||||||
throw new Error(`iTunes URL not found for ${artist} - ${album}`);
|
throw new Error(`iTunes URL not found for ${rawArtist} - ${rawAlbum}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const artist_dir = `public/covers/itunes/${artist}`;
|
const artist_dist = path.join(
|
||||||
if (!fs.existsSync(artist_dir)) {
|
process.cwd(),
|
||||||
fs.mkdirSync(artist_dir, { recursive: true });
|
"public",
|
||||||
|
"covers",
|
||||||
|
"itunes",
|
||||||
|
safeArtist,
|
||||||
|
);
|
||||||
|
if (!fs.existsSync(artist_dist)) {
|
||||||
|
fs.mkdirSync(artist_dist, { recursive: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
const img_response = await axios.get(itunes_url, {
|
const img_response = await axios.get(itunes_url, {
|
||||||
responseType: "arraybuffer",
|
responseType: "arraybuffer",
|
||||||
});
|
});
|
||||||
|
|
||||||
fs.writeFileSync(`${artist_dir}/${album}.png`, img_response.data);
|
fs.writeFileSync(`${artist_dist}/${safeAlbum}.png`, img_response.data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error fetching data:", error);
|
console.error("Error fetching data:", error);
|
||||||
throw error;
|
throw error;
|
||||||
|
|||||||
44
src/index.ts
44
src/index.ts
@@ -1,6 +1,7 @@
|
|||||||
import { Elysia } from "elysia";
|
import { Elysia } from "elysia";
|
||||||
import { cors } from "@elysiajs/cors";
|
import { cors } from "@elysiajs/cors";
|
||||||
import { node } from "@elysiajs/node";
|
import { node } from "@elysiajs/node";
|
||||||
|
import path from "node:path";
|
||||||
|
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
|
|
||||||
@@ -18,7 +19,7 @@ app.get("/cover/:artist/:album/:id", ({ params: { artist, album, id } }) =>
|
|||||||
get_image_from_server(artist, album, id),
|
get_image_from_server(artist, album, id),
|
||||||
);
|
);
|
||||||
|
|
||||||
app.listen(3000, ({ hostname, port }) => {
|
app.listen({ hostname: "0.0.0.0", port: 3000 }, ({ hostname, port }) => {
|
||||||
console.log(`🦊 Elysia is running at ${hostname}:${port}`);
|
console.log(`🦊 Elysia is running at ${hostname}:${port}`);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -27,22 +28,45 @@ async function get_image_from_server(
|
|||||||
album: string,
|
album: string,
|
||||||
mbid: string,
|
mbid: string,
|
||||||
) {
|
) {
|
||||||
const MBFilePath = `public/covers/mb/${mbid}.png`;
|
const safeArtist = artist
|
||||||
const iTunesFilePath = `public/covers/itunes/${artist}/${album}.png`;
|
.replace(/[^a-zA-Z0-9_\-\.\u0400-\u04FF\s]/g, "")
|
||||||
|
.trim();
|
||||||
|
const safeAlbum = album
|
||||||
|
.replace(/[^a-zA-Z0-9_\-\.\u0400-\u04FF\s]/g, "")
|
||||||
|
.trim();
|
||||||
|
const safeMbid = mbid.replace(/[^a-zA-Z0-9\-]/g, "");
|
||||||
|
|
||||||
|
const MBFilePath = path.join(
|
||||||
|
process.cwd(),
|
||||||
|
"public",
|
||||||
|
"covers",
|
||||||
|
"mb",
|
||||||
|
`${safeMbid}.png`,
|
||||||
|
);
|
||||||
|
const iTunesFilePath = path.join(
|
||||||
|
process.cwd(),
|
||||||
|
"public",
|
||||||
|
"covers",
|
||||||
|
"itunes",
|
||||||
|
safeArtist,
|
||||||
|
`${safeAlbum}.png`,
|
||||||
|
);
|
||||||
|
|
||||||
let resultFilePath = MBFilePath;
|
let resultFilePath = MBFilePath;
|
||||||
|
|
||||||
if (fs.existsSync(MBFilePath) || fs.existsSync(iTunesFilePath)) {
|
if (fs.existsSync(MBFilePath)) {
|
||||||
if (fs.existsSync(MBFilePath)) resultFilePath = MBFilePath;
|
resultFilePath = MBFilePath;
|
||||||
else resultFilePath = iTunesFilePath;
|
} else if (fs.existsSync(iTunesFilePath)) {
|
||||||
console.log("[DEBUG]: cover found on server");
|
resultFilePath = iTunesFilePath;
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
await download_image_from_itunes(artist, album);
|
await download_image_from_itunes(artist, album, safeArtist, safeAlbum);
|
||||||
resultFilePath = iTunesFilePath;
|
resultFilePath = iTunesFilePath;
|
||||||
} catch (first_error) {
|
} catch (first_error) {
|
||||||
try {
|
try {
|
||||||
await download_image_from_caa(mbid);
|
if (safeMbid && safeMbid != "0")
|
||||||
|
await download_image_from_caa(safeMbid);
|
||||||
|
else throw first_error;
|
||||||
resultFilePath = MBFilePath;
|
resultFilePath = MBFilePath;
|
||||||
} catch (secong_error) {
|
} catch (secong_error) {
|
||||||
console.error(
|
console.error(
|
||||||
@@ -50,7 +74,7 @@ async function get_image_from_server(
|
|||||||
first_error,
|
first_error,
|
||||||
secong_error,
|
secong_error,
|
||||||
);
|
);
|
||||||
resultFilePath = "public/covers/0.png";
|
return new Response("Cover not found", { status: 404 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,8 +2,8 @@
|
|||||||
// Visit https://aka.ms/tsconfig to read more about this file
|
// Visit https://aka.ms/tsconfig to read more about this file
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
// File Layout
|
// File Layout
|
||||||
// "rootDir": "./src",
|
"rootDir": "./src",
|
||||||
// "outDir": "./dist",
|
"outDir": "./dist",
|
||||||
// Environment Settings
|
// Environment Settings
|
||||||
// See also https://aka.ms/tsconfig/module
|
// See also https://aka.ms/tsconfig/module
|
||||||
"target": "esnext",
|
"target": "esnext",
|
||||||
|
|||||||
Reference in New Issue
Block a user