dodano obsłuugę linków ze spotify oraz soundcloud

This commit is contained in:
Patryk Koreń
2026-01-03 22:48:46 +01:00
parent 28b6546c25
commit 6ad7e01957
7 changed files with 1299 additions and 23 deletions

View File

@@ -1 +1,6 @@
DISCORD_TOKEN
DISCORD_TOKEN=
DISCORD_APP_ID=
DISCORD_CLIENT_SECRET=
GUILD_ID=
SPOTIFY_CLIENT_ID=
SPOTIFY_CLIENT_SECRET=

1214
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -22,6 +22,12 @@
"@types/node": "^25.0.3",
"discord.js": "^14.25.1",
"dotenv": "^17.2.3",
"typescript": "^5.9.3"
"spotify-web-api-node": "^5.0.2",
"typescript": "^5.9.3",
"yt-search": "^2.13.1"
},
"devDependencies": {
"@types/spotify-web-api-node": "^5.0.11",
"@types/yt-search": "^2.10.3"
}
}

View File

@@ -2,7 +2,7 @@ import { CacheType, ChatInputCommandInteraction, SlashCommandBuilder } from 'dis
import { connectToChannelByInteraction } from '../util/helpers';
import { requestSong } from '../playback';
import { getPlayMsg, msg_downloading, msg_play, msg_searching } from '../messages';
import { search } from '../util/downloader';
import { search, spotifyToYouTube } from '../util/downloader';
const name = "play"
@@ -23,7 +23,11 @@ async function execute(interaction: ChatInputCommandInteraction<CacheType>) {
const input = interaction.options.getString("url")!;
let url: string;
if (input.startsWith("http")) {
if (input.startsWith("https://open.spotify.com/")) {
await interaction.editReply(msg_searching(input));
url = await spotifyToYouTube(input)
}
else if (input.startsWith("http")) {
url = input
} else {
await interaction.editReply(msg_searching(input));

View File

@@ -1,11 +1,12 @@
// Require the necessary discord.js classes
import { Client, Events, GatewayIntentBits, MessageFlags, REST, Routes } from 'discord.js';
import dotenv from 'dotenv';
dotenv.config()
import { Client, Events, GatewayIntentBits, MessageFlags, REST, Routes } from 'discord.js';
import { commands } from "./commands";
import { AudioPlayerState, createAudioPlayer } from '@discordjs/voice';
import { connectToChannel, playSong } from './util/helpers';
import { updatePlayer } from './playback';
dotenv.config()
import SpotifyWebApi from 'spotify-web-api-node';
export const DATA_DIR = "./data";
@@ -24,6 +25,12 @@ const client = new Client({
GatewayIntentBits.GuildVoiceStates]
});
export const spotify = new SpotifyWebApi({
clientId: process.env.SPOTIFY_CLIENT_ID,
clientSecret: process.env.SPOTIFY_CLIENT_SECRET,
});
// When the client is ready, run this code (only once).
// The distinction between `client: Client<boolean>` and `readyClient: Client<true>` is important for TypeScript developers.
// It makes some properties non-nullable.
@@ -35,7 +42,7 @@ client.once(Events.ClientReady, (readyClient) => {
async function registerCommands() {
const rest = new REST().setToken(process.env.DISCORD_TOKEN!);
const registry = Object.values(commands).map((cmd) => cmd.register().toJSON())
console.log(registry)
console.log("komendy: \n", registry.map(r => `${r.name} : ${r.description}`).join("\n"))
await rest.put(Routes.applicationGuildCommands(
process.env.DISCORD_APP_ID!,
process.env.GUILD_ID!

View File

@@ -53,7 +53,7 @@ export function msg_play(): InteractionEditReplyOptions {
.setColor(PRIMARY_COLOR)
.addFields(
{ name: "Na Placku", value: current },
{ name: "Kolejka", value: queueSongs.length ? queueSongs.join("\n") : "Pusta Kolejka" }
{ name: "Kolejka", value: queueSongs.length ? queueSongs.join("\n") : "Pusto" }
)
.setTimestamp()
]

View File

@@ -1,41 +1,65 @@
import { spawn } from "node:child_process";
import { DATA_DIR } from "../main";
import { DATA_DIR, spotify } from "../main";
import ytSearch from 'yt-search';
export async function getAudioFile(url: string): Promise<string> {
const id = extractId(url)
const id = await extractId(url)
console.log(`ID: ${id}`);
let path: string;
try {
path = await findFileById(id)
} catch (e) {
await downloadYTVideo(url)
await downloadAudio(url)
console.log(`downloaded ${id}`)
path = await findFileById(id)
}
return path
}
export function extractId(url: string): string {
const idMatch = /[?&]v=([a-zA-Z0-9_-]{11})/.exec(url);
if (!idMatch) throw new Error("Cannot extract video ID");
const id = idMatch[1];
console.log(`ID: ${id}`);
return id
export async function extractId(url: string): Promise<string> {
if (url.startsWith("https://soundcloud")) { // soundcloud
const ytDlpBin = process.env.YT_DLP_BIN_PATH! ?? "yt-dlp";
return await new Promise<string>((resolve, reject) => {
const p = spawn(ytDlpBin, [
"--print", "%(id)s",
url,
]);
let out = ""
p.stderr.on("data", d => process.stderr.write(d));
p.stdout.on("data", d => out += d.toString());
p.on("close", code => code === 0 ? resolve(out.trim()) : reject(new Error("yt-dlp extract id failed")));
});
} else {
const idMatch = /[?&]v=([a-zA-Z0-9_-]{11})/.exec(url);
if (!idMatch) throw new Error("Cannot extract video ID");
const id = idMatch[1];
return id
}
}
export async function downloadYTVideo(url: string): Promise<void> {
export async function downloadAudio(url: string): Promise<void> {
const ytDlpBin = process.env.YT_DLP_BIN_PATH! ?? "yt-dlp";
await new Promise<void>((resolve, reject) => {
const p = spawn(ytDlpBin, [
"-x",
"--audio-format", "opus",
// "--audio-format", "opus",
"--audio-quality", "0",
"--no-playlist",
"--paths", DATA_DIR,
"--paths", `temp:/tmp`,
"--paths", `home:${DATA_DIR}`,
url,
]);
p.stderr.on("data", d => process.stderr.write(d));
p.on("close", code => code === 0 ? resolve() : reject(new Error("yt-dlp failed")));
p.stdout.on("data", d => process.stdout.write(d))
p.on("close", code => {
console.log("Pobieranie exit code: ", code)
code === 0 ? resolve() : reject(new Error("yt-dlp failed"))
});
});
}
@@ -44,7 +68,7 @@ export async function findFileById(id: string): Promise<string> {
const find = spawn("find", [
DATA_DIR,
"-type", "f",
"-iname", `*${id}*.opus`,
"-iname", `*${id}*`,
"-exec", "readlink", "-f", "{}", ";",
]);
@@ -93,3 +117,21 @@ export function formatFilePath(path: string): string {
.replace(/\[.*\].opus/, "")
.trim();
}
export async function spotifyToYouTube(spotifyUrl: string) {
const data = await spotify.clientCredentialsGrant()
// console.log(data, {
// clientId: process.env.SPOTIFY_CLIENT_ID,
// clientSecret: process.env.SPOTIFY_CLIENT_SECRET,
// }, spotify)
spotify.setAccessToken(data.body.access_token)
const trackId = spotifyUrl.split('/track/')[1].split('?')[0];
const track = await spotify.getTrack(trackId);
const query = `${track.body.name} ${track.body.artists[0].name}`;
const result = await ytSearch(query);
return result.videos[0].url;
}