format everything, make now playying actually good
This commit is contained in:
parent
4a3b3aabcf
commit
fca2614603
5 changed files with 313 additions and 173 deletions
98
.env.example
98
.env.example
|
|
@ -1,4 +1,100 @@
|
|||
WEBHOOK_URL=https://discord.com/api/webhooks/no/no
|
||||
import { Client, GatewayIntentBits } from "discord.js";
|
||||
import crypto from "crypto";
|
||||
import { existsSync, readFileSync, writeFileSync } from "fs";
|
||||
|
||||
/* ================= CONFIG ================= */
|
||||
const REFRESH_MS = Number(process.env.REFRESH_MS ?? 15_000);
|
||||
const STATE_FILE = "./state.json";
|
||||
|
||||
/* ================= DISCORD ================= */
|
||||
const client = new Client({
|
||||
intents: [GatewayIntentBits.Guilds],
|
||||
});
|
||||
|
||||
/* ================= STATE ================= */
|
||||
let messageId = null;
|
||||
let lastSignature = null;
|
||||
|
||||
if (existsSync(STATE_FILE)) {
|
||||
try {
|
||||
const state = JSON.parse(readFileSync(STATE_FILE, "utf8"));
|
||||
messageId = state.messageId ?? null;
|
||||
lastSignature = state.lastSignature ?? null;
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function saveState() {
|
||||
writeFileSync(
|
||||
STATE_FILE,
|
||||
JSON.stringify({ messageId, lastSignature }, null, 2)
|
||||
);
|
||||
}
|
||||
|
||||
/* ================= NAVIDROME AUTH ================= */
|
||||
function navidromeAuth() {
|
||||
const salt = crypto.randomBytes(6).toString("hex");
|
||||
const token = crypto
|
||||
.createHash("md5")
|
||||
.update(process.env.NAVIDROME_PASS + salt)
|
||||
.digest("hex");
|
||||
|
||||
return { salt, token };
|
||||
}
|
||||
|
||||
function nowPlayingUrl() {
|
||||
const { salt, token } = navidromeAuth();
|
||||
return `${process.env.NAVIDROME_URL}/rest/getNowPlaying` +
|
||||
`?u=${process.env.NAVIDROME_USER}` +
|
||||
`&t=${token}&s=${salt}&f=json&v=1.16.1&c=discord-bot`;
|
||||
}
|
||||
|
||||
function coverFetchUrl(id) {
|
||||
const { salt, token } = navidromeAuth();
|
||||
return `${process.env.NAVIDROME_URL}/rest/getCoverArt` +
|
||||
`?u=${process.env.NAVIDROME_USER}` +
|
||||
`&t=${token}&s=${salt}&v=1.16.1&c=discord-bot` +
|
||||
`&id=${id}&size=96`;
|
||||
}
|
||||
|
||||
/* ================= UTIL ================= */
|
||||
function colorFromString(str) {
|
||||
let hash = 0;
|
||||
for (const c of str) hash = (hash << 5) - hash + c.charCodeAt(0);
|
||||
return Math.abs(hash) % 0xffffff;
|
||||
}
|
||||
|
||||
function signature(entries) {
|
||||
return entries
|
||||
.map(e => `${e.username}:${e.id}:${e.minutesAgo ?? 0}`)
|
||||
.join("|");
|
||||
}
|
||||
|
||||
/* ================= NAVIDROME ================= */
|
||||
async function getNowPlaying() {
|
||||
const res = await fetch(nowPlayingUrl());
|
||||
if (!res.ok) throw new Error("Failed to fetch now playing");
|
||||
|
||||
const json = await res.json();
|
||||
return json["subsonic-response"].nowPlaying?.entry ?? [];
|
||||
}
|
||||
|
||||
/* ================= MESSAGE BUILD ================= */
|
||||
async function buildMessage(entries) {
|
||||
if (!entries.length) {
|
||||
return {
|
||||
embeds: [{
|
||||
title: "Nothing playing",
|
||||
description: "No active listeners",
|
||||
color: 0x808080,
|
||||
}],
|
||||
files: [],
|
||||
};
|
||||
}
|
||||
|
||||
const embeds = [];
|
||||
const files = [];
|
||||
|
||||
for (let i = 0; i < e
|
||||
DISCORD_TOKEN="no.no"
|
||||
LASTFM_API_KEY=no
|
||||
|
||||
|
|
|
|||
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -2,5 +2,5 @@
|
|||
deezer_cache
|
||||
orpheus_links.txt
|
||||
discord_send.txt
|
||||
|
||||
state.json
|
||||
node_modules
|
||||
|
|
|
|||
|
|
@ -1,112 +1,164 @@
|
|||
import { Client, GatewayIntentBits } from "discord.js";
|
||||
import crypto from "crypto";
|
||||
import { existsSync, readFileSync, writeFileSync } from "fs";
|
||||
import crypto from "crypto"
|
||||
|
||||
const REFRESH_MS = Number(process.env.REFRESH_MS ?? 15_000);
|
||||
const STATE_FILE = "./state.json";
|
||||
|
||||
const client = new Client({
|
||||
intents: [GatewayIntentBits.Guilds],
|
||||
});
|
||||
|
||||
let messageId = null;
|
||||
let lastSignature = null;
|
||||
|
||||
if (existsSync(STATE_FILE)) {
|
||||
try {
|
||||
const state = JSON.parse(readFileSync(STATE_FILE, "utf8"));
|
||||
messageId = state.messageId ?? null;
|
||||
lastSignature = state.lastSignature ?? null;
|
||||
} catch {}
|
||||
}
|
||||
|
||||
function saveState() {
|
||||
writeFileSync(
|
||||
STATE_FILE,
|
||||
JSON.stringify({ messageId, lastSignature }, null, 2),
|
||||
);
|
||||
}
|
||||
|
||||
function navidromeAuth() {
|
||||
const salt = crypto.randomBytes(6).toString("hex");
|
||||
const token = crypto
|
||||
.createHash("md5")
|
||||
.update(process.env.NAVIDROME_PASS + salt)
|
||||
.digest("hex");
|
||||
|
||||
|
||||
const NOW_PLAYING_URL = `${process.env.NAVIDROME_URL}/rest/getNowPlaying?u=${process.env.NAVIDROME_USER}&t=${token}&s=${salt}&f=json&v=0.0.1&c=now-playing`;
|
||||
const COVER_FETCH_URL = (id) =>
|
||||
`${process.env.NAVIDROME_URL}/rest/getCoverArt?u=${process.env.NAVIDROME_USER}&t=${token}&s=${salt}&f=json&v=0.0.1&c=now-playing&id=${id}&size=80`;
|
||||
|
||||
const WEBHOOK_URL = process.env.WEBHOOK_URL;
|
||||
const REFRESH_MS = 15_000;
|
||||
const STATE_FILE = "./state.json";
|
||||
|
||||
let messageId = null;
|
||||
|
||||
if (existsSync(STATE_FILE)) {
|
||||
try {
|
||||
const state = JSON.parse(readFileSync(STATE_FILE, "utf8"));
|
||||
messageId = state.messageId ?? null;
|
||||
} catch {
|
||||
// will be made later
|
||||
}
|
||||
return { salt, token };
|
||||
}
|
||||
|
||||
function saveState() {
|
||||
writeFileSync(STATE_FILE, JSON.stringify({ messageId }, null, 2));
|
||||
function nowPlayingUrl() {
|
||||
const { salt, token } = navidromeAuth();
|
||||
return (
|
||||
`${process.env.NAVIDROME_URL}/rest/getNowPlaying` +
|
||||
`?u=${process.env.NAVIDROME_USER}` +
|
||||
`&t=${token}&s=${salt}&f=json&v=1.16.1&c=discord-bot`
|
||||
);
|
||||
}
|
||||
|
||||
function coverFetchUrl(id) {
|
||||
const { salt, token } = navidromeAuth();
|
||||
return (
|
||||
`${process.env.NAVIDROME_URL}/rest/getCoverArt` +
|
||||
`?u=${process.env.NAVIDROME_USER}` +
|
||||
`&t=${token}&s=${salt}&v=1.16.1&c=discord-bot` +
|
||||
`&id=${id}&size=96`
|
||||
);
|
||||
}
|
||||
|
||||
function colorFromString(str) {
|
||||
let hash = 0;
|
||||
for (let i = 0; i < str.length; i++) {
|
||||
hash = (hash << 5) - hash + str.charCodeAt(i);
|
||||
hash |= 0;
|
||||
}
|
||||
for (const c of str) hash = (hash << 5) - hash + c.charCodeAt(0);
|
||||
return Math.abs(hash) % 0xffffff;
|
||||
}
|
||||
|
||||
function signature(entries) {
|
||||
return entries
|
||||
.map((e) => `${e.username}:${e.id}:${e.minutesAgo ?? 0}`)
|
||||
.join("|");
|
||||
}
|
||||
|
||||
async function getNowPlaying() {
|
||||
const res = await fetch(NOW_PLAYING_URL);
|
||||
const res = await fetch(nowPlayingUrl());
|
||||
if (!res.ok) throw new Error("Failed to fetch now playing");
|
||||
|
||||
const json = await res.json();
|
||||
return json["subsonic-response"].nowPlaying?.entry ?? [];
|
||||
}
|
||||
|
||||
async function buildPayload(entries) {
|
||||
if (entries.length === 0) {
|
||||
return { embeds: [{ title: "Nothing playing", description: "No active listeners", color: 0x808080 }], files: [] };
|
||||
async function buildMessage(entries) {
|
||||
if (!entries.length) {
|
||||
return {
|
||||
embeds: [
|
||||
{
|
||||
title: "Nothing playing",
|
||||
description: "No active listeners",
|
||||
color: 0x808080,
|
||||
},
|
||||
],
|
||||
files: [],
|
||||
};
|
||||
}
|
||||
|
||||
const embeds = [];
|
||||
const files = [];
|
||||
|
||||
for (const e of entries) {
|
||||
const filename = `cover_${e.id}.jpg`;
|
||||
const imgRes = await fetch(COVER_FETCH_URL(e.coverArt));
|
||||
for (let i = 0; i < entries.length; i++) {
|
||||
const e = entries[i];
|
||||
const filename = `cover_${i}.jpg`;
|
||||
|
||||
const imgRes = await fetch(coverFetchUrl(e.coverArt));
|
||||
if (imgRes.ok) {
|
||||
const buf = await imgRes.arrayBuffer();
|
||||
files.push({ name: filename, data: new Blob([buf]) });
|
||||
const buf = Buffer.from(await imgRes.arrayBuffer());
|
||||
files.push({ attachment: buf, name: filename });
|
||||
}
|
||||
|
||||
embeds.push({
|
||||
title: e.title,
|
||||
description: `**${e.artist}** — *${e.album}*`,
|
||||
color: colorFromString(e.username),
|
||||
thumbnail: { url: `attachment://${filename}` },
|
||||
thumbnail: files.length ? { url: `attachment://${filename}` } : undefined,
|
||||
fields: [
|
||||
{ name: "User", value: e.username, inline: true },
|
||||
{ name: "Player", value: e.playerName, inline: true },
|
||||
{ name: "Duration", value: `${Math.floor(e.duration / 60)}:${String(e.duration % 60).padStart(2, "0")}`, inline: false },
|
||||
{
|
||||
name: "Duration",
|
||||
value: `${Math.floor(e.duration / 60)}:${String(
|
||||
e.duration % 60,
|
||||
).padStart(2, "0")}`,
|
||||
},
|
||||
],
|
||||
footer: { text: "now playing!" },
|
||||
footer: { text: "now playing" },
|
||||
});
|
||||
}
|
||||
|
||||
return { embeds, files };
|
||||
}
|
||||
|
||||
async function sendOrResend(payload) {
|
||||
// delete previous message if exists
|
||||
if (messageId) {
|
||||
await fetch(`${WEBHOOK_URL}/messages/${messageId}`, { method: "DELETE" }).catch(() => {});
|
||||
messageId = null;
|
||||
async function updateMessage() {
|
||||
const entries = await getNowPlaying();
|
||||
const sig = signature(entries);
|
||||
|
||||
if (sig === lastSignature) return;
|
||||
|
||||
const payload = await buildMessage(entries);
|
||||
const channel = await client.channels.fetch(process.env.DISCORD_CHANNEL_ID);
|
||||
|
||||
if (!messageId) {
|
||||
const msg = await channel.send(payload);
|
||||
messageId = msg.id;
|
||||
} else {
|
||||
try {
|
||||
const msg = await channel.messages.fetch(messageId);
|
||||
await msg.edit(payload);
|
||||
} catch {
|
||||
const msg = await channel.send(payload);
|
||||
messageId = msg.id;
|
||||
}
|
||||
}
|
||||
|
||||
const form = new FormData();
|
||||
payload.files.forEach((f, i) => form.append(`files[${i}]`, f.data, f.name));
|
||||
form.append("payload_json", JSON.stringify({ embeds: payload.embeds }));
|
||||
|
||||
const res = await fetch(`${WEBHOOK_URL}?wait=true`, { method: "POST", body: form });
|
||||
if (!res.ok) throw new Error("Webhook send failed");
|
||||
const data = await res.json();
|
||||
messageId = data.id;
|
||||
lastSignature = sig;
|
||||
saveState();
|
||||
}
|
||||
|
||||
async function tick() {
|
||||
try {
|
||||
const entries = await getNowPlaying();
|
||||
const payload = await buildPayload(entries);
|
||||
await sendOrResend(payload);
|
||||
} catch (err) {
|
||||
console.error("Update failed:", err.message);
|
||||
}
|
||||
}
|
||||
client.once("ready", async () => {
|
||||
console.log(`Logged in as ${client.user.tag}`);
|
||||
await updateMessage();
|
||||
setInterval(() => {
|
||||
updateMessage().catch((err) =>
|
||||
console.error("Update failed:", err.message),
|
||||
);
|
||||
}, REFRESH_MS);
|
||||
});
|
||||
|
||||
await tick();
|
||||
setInterval(tick, REFRESH_MS);
|
||||
client.login(process.env.DISCORD_BOT_TOKEN);
|
||||
|
|
|
|||
|
|
@ -1,12 +1,10 @@
|
|||
import https from "https";
|
||||
import { createWriteStream } from "fs";
|
||||
|
||||
// ======== COLOR UTILS ==========
|
||||
const C = {
|
||||
info: msg => console.log("\x1b[36m[i]\x1b[0m " + msg),
|
||||
ok: msg => console.log("\x1b[32m[✓]\x1b[0m " + msg),
|
||||
warn: msg => console.log("\x1b[33m[!]\x1b[0m " + msg),
|
||||
err: msg => console.log("\x1b[31m[✗]\x1b[0m " + msg),
|
||||
info: (msg) => console.log("\x1b[36m[i]\x1b[0m " + msg),
|
||||
ok: (msg) => console.log("\x1b[32m[✓]\x1b[0m " + msg),
|
||||
warn: (msg) => console.log("\x1b[33m[!]\x1b[0m " + msg),
|
||||
err: (msg) => console.log("\x1b[31m[✗]\x1b[0m " + msg),
|
||||
};
|
||||
|
||||
// ======== DOWNLOAD WITH PROGRESS ==========
|
||||
|
|
@ -15,20 +13,24 @@ function download(url, filename, referer = url) {
|
|||
let file = null; // will open later
|
||||
|
||||
function doReq(link, ref) {
|
||||
C.info(`Requesting: ${link}`);
|
||||
|
||||
https.get(link, {
|
||||
https
|
||||
.get(
|
||||
link,
|
||||
{
|
||||
headers: {
|
||||
"User-Agent": "Mozilla/5.0",
|
||||
"Referer": ref
|
||||
}
|
||||
}, res => {
|
||||
|
||||
Referer: ref,
|
||||
},
|
||||
},
|
||||
(res) => {
|
||||
// --- Redirect handler ---
|
||||
if ([301, 302, 303, 307, 308].includes(res.statusCode)) {
|
||||
const loc = res.headers.location;
|
||||
if (!loc) return reject(new Error("Redirect without Location header"));
|
||||
const nextURL = loc.startsWith("http") ? loc : new URL(loc, link).href;
|
||||
if (!loc)
|
||||
return reject(new Error("Redirect without Location header"));
|
||||
const nextURL = loc.startsWith("http")
|
||||
? loc
|
||||
: new URL(loc, link).href;
|
||||
C.warn(`↪ Redirect (${res.statusCode}): ${link} → ${nextURL}`);
|
||||
return doReq(nextURL, link);
|
||||
}
|
||||
|
|
@ -46,18 +48,25 @@ function download(url, filename, referer = url) {
|
|||
let downloaded = 0;
|
||||
const start = Date.now();
|
||||
|
||||
res.on("data", chunk => {
|
||||
res.on("data", (chunk) => {
|
||||
downloaded += chunk.length;
|
||||
|
||||
if (total) {
|
||||
const percent = ((downloaded / total) * 100).toFixed(1);
|
||||
const speed = (downloaded / 1024 / ((Date.now() - start) / 1000)).toFixed(1);
|
||||
const speed = (
|
||||
downloaded /
|
||||
1024 /
|
||||
((Date.now() - start) / 1000)
|
||||
).toFixed(1);
|
||||
const barSize = 30;
|
||||
const filled = Math.round(percent / 100 * barSize);
|
||||
const bar = "[" + "#".repeat(filled) + "-".repeat(barSize - filled) + "]";
|
||||
const filled = Math.round((percent / 100) * barSize);
|
||||
const bar =
|
||||
"[" + "#".repeat(filled) + "-".repeat(barSize - filled) + "]";
|
||||
process.stdout.write(`\r${bar} ${percent}% ${speed}KB/s`);
|
||||
} else {
|
||||
process.stdout.write(`\rDownloaded ${Math.round(downloaded / 1024)}KB`);
|
||||
process.stdout.write(
|
||||
`\rDownloaded ${Math.round(downloaded / 1024)}KB`,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
|
|
@ -68,15 +77,13 @@ function download(url, filename, referer = url) {
|
|||
C.ok("Download complete!");
|
||||
resolve();
|
||||
});
|
||||
|
||||
}).on("error", err => reject(err));
|
||||
},
|
||||
)
|
||||
.on("error", (err) => reject(err));
|
||||
}
|
||||
|
||||
doReq(url, referer);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// ======== MAIN FUNCTION ==========
|
||||
(async () => {
|
||||
const target = process.argv[2];
|
||||
|
|
@ -88,9 +95,9 @@ function download(url, filename, referer = url) {
|
|||
|
||||
C.info(`Fetching webpage: ${target}`);
|
||||
|
||||
let html;
|
||||
try {
|
||||
html = await fetch(target, { headers: { "User-Agent": "Mozilla/5.0" } }).then(r => r.text());
|
||||
html = await fetch(target, {
|
||||
headers: { "User-Agent": "Mozilla/5.0" },
|
||||
}).then((r) => r.text());
|
||||
} catch (e) {
|
||||
C.err("Failed to fetch the page");
|
||||
console.error(e);
|
||||
|
|
@ -105,16 +112,12 @@ function download(url, filename, referer = url) {
|
|||
C.ok("Found data[] block");
|
||||
|
||||
// Safer eval (only our captured data)
|
||||
let arr;
|
||||
try {
|
||||
arr = eval("[" + match[1] + "]");
|
||||
} catch (e) {
|
||||
C.err("❌ Failed to parse data[] as JavaScript");
|
||||
console.error(e);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const item = arr.find(x => x?.type === "data" && x?.data?.filename);
|
||||
const item = arr.find((x) => x?.type === "data" && x?.data?.filename);
|
||||
if (!item) {
|
||||
C.err("❌ Could not find downloadable file info in data[]");
|
||||
process.exit(1);
|
||||
|
|
|
|||
|
|
@ -12,25 +12,23 @@ const client = new Client({
|
|||
});
|
||||
|
||||
const command = new SlashCommandBuilder()
|
||||
.setName("request")
|
||||
.setDescription("Request a new artist or submit a SendGB link")
|
||||
.addStringOption(opt =>
|
||||
.addStringOption((opt) =>
|
||||
opt
|
||||
.setName("artist")
|
||||
.setDescription("Artist name (Last.fm)")
|
||||
.setRequired(false)
|
||||
.setRequired(false),
|
||||
)
|
||||
.addStringOption(opt =>
|
||||
.addStringOption((opt) =>
|
||||
opt
|
||||
.setName("url")
|
||||
.setDescription("SendGB URL (https://sendgb.com/...)")
|
||||
.setRequired(false)
|
||||
.setRequired(false),
|
||||
)
|
||||
.addStringOption(opt =>
|
||||
.addStringOption((opt) =>
|
||||
opt
|
||||
.setName("description")
|
||||
.setDescription("Description for the SendGB link")
|
||||
.setRequired(false)
|
||||
.setRequired(false),
|
||||
);
|
||||
|
||||
function isValidSendGbUrl(url) {
|
||||
|
|
@ -43,14 +41,11 @@ function isValidDeezerUrl(url) {
|
|||
const rest = new REST({ version: "10" }).setToken(process.env.DISCORD_TOKEN);
|
||||
|
||||
await rest.put(
|
||||
Routes.applicationGuildCommands(
|
||||
(await rest.get(Routes.oauth2CurrentApplication())).id,
|
||||
process.env.GUILD_ID
|
||||
process.env.GUILD_ID,
|
||||
),
|
||||
{ body: [command.toJSON()] }
|
||||
{ body: [command.toJSON()] },
|
||||
);
|
||||
|
||||
|
||||
async function getDeezerAlbum(url) {
|
||||
const match = url.match(/album\/(\d+)/);
|
||||
if (!match) return null;
|
||||
|
|
@ -65,9 +60,8 @@ async function getDeezerAlbum(url) {
|
|||
title: data.title,
|
||||
artist: data.artist.name,
|
||||
link: data.link,
|
||||
cover: data.cover_medium,
|
||||
releaseDate: data.release_date,
|
||||
tracks: data.tracks?.data?.map(t => t.title).join(", ") || "No tracks info",
|
||||
tracks:
|
||||
data.tracks?.data?.map((t) => t.title).join(", ") || "No tracks info",
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -91,9 +85,6 @@ async function getLastFmArtist(artist) {
|
|||
console.error("Last.fm response was not JSON:");
|
||||
console.error(text.slice(0, 500));
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
if (!data.artist) return null;
|
||||
return data.artist;
|
||||
}
|
||||
|
|
@ -112,9 +103,7 @@ async function navidromeHasArtist(artist) {
|
|||
t: token,
|
||||
s: salt,
|
||||
v: "1.16.1",
|
||||
c: "discord-bot",
|
||||
query: artist,
|
||||
f: "json"
|
||||
f: "json",
|
||||
});
|
||||
|
||||
const res = await fetch(url);
|
||||
|
|
@ -127,18 +116,12 @@ async function navidromeHasArtist(artist) {
|
|||
console.error("Navidrome response was not JSON:");
|
||||
console.error(text.slice(0, 500));
|
||||
return false;
|
||||
const artists = json["subsonic-response"]?.searchResult3?.artist ?? [];
|
||||
|
||||
return artists.some((a) => a.name.toLowerCase() === artist.toLowerCase());
|
||||
}
|
||||
|
||||
const artists =
|
||||
json["subsonic-response"]?.searchResult3?.artist ?? [];
|
||||
|
||||
return artists.some(
|
||||
a => a.name.toLowerCase() === artist.toLowerCase()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
client.on("interactionCreate", async interaction => {
|
||||
client.on("interactionCreate", async (interaction) => {
|
||||
if (!interaction.isChatInputCommand()) return;
|
||||
if (interaction.commandName !== "request") return;
|
||||
|
||||
|
|
@ -175,9 +158,10 @@ client.on("interactionCreate", async interaction => {
|
|||
embeds: [
|
||||
{
|
||||
title: "📦 External Upload",
|
||||
description,
|
||||
fields: [
|
||||
{ name: "Download", value: url+"#"+encodeURIComponent(description) },
|
||||
{
|
||||
name: "Download",
|
||||
value: url + "#" + encodeURIComponent(description),
|
||||
},
|
||||
{ name: "Requested by", value: `<@${interaction.user.id}>` },
|
||||
],
|
||||
},
|
||||
|
|
@ -208,9 +192,9 @@ client.on("interactionCreate", async interaction => {
|
|||
],
|
||||
},
|
||||
],
|
||||
return interaction.editReply({
|
||||
content: "✅ Deezer album info submitted.",
|
||||
});
|
||||
|
||||
return interaction.editReply({ content: "✅ Deezer album info submitted." });
|
||||
}
|
||||
|
||||
return interaction.editReply({
|
||||
|
|
@ -225,9 +209,9 @@ client.on("interactionCreate", async interaction => {
|
|||
});
|
||||
}
|
||||
|
||||
const artist = await getLastFmArtist(artistName);
|
||||
if (!artist) {
|
||||
return interaction.editReply({ content: "❌ Artist not found on Last.fm." });
|
||||
return interaction.editReply({
|
||||
content: "❌ Artist not found on Last.fm.",
|
||||
});
|
||||
}
|
||||
|
||||
await channel.send({
|
||||
|
|
@ -235,17 +219,22 @@ client.on("interactionCreate", async interaction => {
|
|||
{
|
||||
title: artist.name,
|
||||
url: artist.url,
|
||||
description: artist.bio?.summary?.replace(/<[^>]*>/g, "") ?? "No description available.",
|
||||
description:
|
||||
artist.bio?.summary?.replace(/<[^>]*>/g, "") ??
|
||||
"No description available.",
|
||||
thumbnail: {
|
||||
url: artist.image?.find(i => i.size === "extralarge")?.["#text"] ?? null,
|
||||
url:
|
||||
artist.image?.find((i) => i.size === "extralarge")?.["#text"] ??
|
||||
null,
|
||||
},
|
||||
fields: [
|
||||
{ name: "Listeners", value: artist.stats.listeners, inline: true },
|
||||
{ name: "Playcount", value: artist.stats.playcount, inline: true },
|
||||
{ name: "Tags", value: artist.tags?.tag?.map(t => t.name).join(", ") || "None" },
|
||||
{ name: "Requested by", value: `<@${interaction.user.id}>` },
|
||||
],
|
||||
{
|
||||
name: "Tags",
|
||||
value: artist.tags?.tag?.map((t) => t.name).join(", ") || "None",
|
||||
},
|
||||
|
||||
],
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue