added rzeczy

This commit is contained in:
Patryk Koreń
2025-12-26 20:45:38 +01:00
parent 375779de9c
commit b551cce62a
14 changed files with 326 additions and 91 deletions

36
src/commands/forceplay.ts Normal file
View File

@@ -0,0 +1,36 @@
import { CacheType, ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js';
import { connectToChannelByInteraction } from '../util/helpers';
import { forceRequestSong } from '../playback';
import { getPlayMsg } from '../messages';
const name = "forceplay"
function register() {
return new SlashCommandBuilder()
.setName(name)
.setDescription('YT')
.addStringOption((option) => option.setName("url")
.setDescription("YT url")
.setRequired(true))
}
async function execute(interaction: ChatInputCommandInteraction<CacheType>) {
try {
connectToChannelByInteraction(interaction)
const url = interaction.options.getString("url")!;
await forceRequestSong(interaction, url)
const msg = getPlayMsg(url)
await interaction.reply(msg);
} catch (error) {
console.error(error);
await interaction.reply('Coś poszło nie tak :/');
}
}
export default {
name,
register,
execute
}

View File

@@ -1,8 +1,20 @@
import forceplay from "./forceplay"
import help from "./help"
import pause from "./pause"
import ping from "./ping"
import play from "./play"
import queue from "./queue"
import resume from "./resume"
import stop from "./stop"
export const commands: {[key: string]: Command} = {
export const commands: { [key: string]: Command } = {
ping,
play,
forceplay,
queue,
help,
resume,
stop,
pause,
}

View File

@@ -1,21 +0,0 @@
import { CacheType, ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js';
import { connectToChannel } from '../util/helpers';
import { joinVoiceChannel } from '@discordjs/voice';
const name = "ping"
function register() {
return new SlashCommandBuilder()
.setName(name)
.setDescription('Replies with Pong!')
}
async function execute(message: ChatInputCommandInteraction<CacheType>) {
const channelId = "515300847790325790"
}
export default {
name,
register,
execute
}

27
src/commands/pause.ts Normal file
View File

@@ -0,0 +1,27 @@
import { CacheType, ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js';
import { pause_playback } from '../playback';
import { player } from '../main';
const name = "pause"
function register() {
return new SlashCommandBuilder()
.setName(name)
.setDescription('pause playback')
}
async function execute(interaction: ChatInputCommandInteraction<CacheType>) {
try {
pause_playback(player)
} catch (error) {
console.error(error);
await interaction.reply('Coś poszło nie tak :/');
}
}
export default {
name,
register,
execute
}

View File

@@ -1,7 +1,7 @@
import { CacheType, ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js';
import { connectToChannel, playSong } from '../util/helpers';
import { player } from '../main';
import { get_audio } from '../util/downloader';
import { connectToChannelByInteraction } from '../util/helpers';
import { requestSong } from '../playback';
import { getPlayMsg } from '../messages';
const name = "play"
@@ -16,27 +16,14 @@ function register() {
async function execute(interaction: ChatInputCommandInteraction<CacheType>) {
try {
// await interaction.reply(`${interaction}`);
const member = await interaction.guild?.members.fetch(interaction.user.id);
if (!member) throw new Error("ale chuj")
const voiceChannel = member.voice.channel;
if (!voiceChannel) throw new Error("ale chuj 2")
connectToChannelByInteraction(interaction)
const connection = await connectToChannel(voiceChannel);
connection.subscribe(player);
await interaction.reply('https://gitea.papryk.com/Papryk/dj-spangebob pull requesty milewidziane XD');
const url = interaction.options.getString("url")!;
const path = await get_audio(url)
// const path = "/home/patryk/Papryk/dbot/data/Kino - Summer is ending Кино - Кончится лето [6VqiMQoMXmw].opus"
console.log(path)
await playSong(player, path);
await requestSong(interaction, url)
const msg = getPlayMsg(url)
await interaction.reply(msg);
} catch (error) {
/**
* The song isn't ready to play for some reason :(
*/
console.error(error);
await interaction.reply('Coś poszło nie tak :/');
}

24
src/commands/queue.ts Normal file
View File

@@ -0,0 +1,24 @@
import { CacheType, ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js';
import { getQueue } from '../playback';
const name = "queue"
function register() {
return new SlashCommandBuilder()
.setName(name)
.setDescription('Shows queue')
}
async function execute(interaction: ChatInputCommandInteraction<CacheType>) {
console.log(interaction.member)
const queue = getQueue()
await interaction.reply(`Current: ${queue.current}
Queue:
${queue.songList.join('\n ')}`);
}
export default {
name,
register,
execute
}

27
src/commands/resume.ts Normal file
View File

@@ -0,0 +1,27 @@
import { CacheType, ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js';
import { resume_playback } from '../playback';
import { player } from '../main';
const name = "resume"
function register() {
return new SlashCommandBuilder()
.setName(name)
.setDescription('resume playback')
}
async function execute(interaction: ChatInputCommandInteraction<CacheType>) {
try {
resume_playback(player)
} catch (error) {
console.error(error);
await interaction.reply('Coś poszło nie tak :/');
}
}
export default {
name,
register,
execute
}

27
src/commands/stop.ts Normal file
View File

@@ -0,0 +1,27 @@
import { CacheType, ChatInputCommandInteraction, SlashCommandBuilder } from 'discord.js';
import { stop_playback } from '../playback';
import { player } from '../main';
const name = "stop"
function register() {
return new SlashCommandBuilder()
.setName(name)
.setDescription('stop playback')
}
async function execute(interaction: ChatInputCommandInteraction<CacheType>) {
try {
stop_playback(player)
} catch (error) {
console.error(error);
await interaction.reply('Coś poszło nie tak :/');
}
}
export default {
name,
register,
execute
}

5
src/global.d.ts vendored
View File

@@ -3,3 +3,8 @@ type Command = {
register: () => any,
execute: (interaction: ChatInputCommandInteraction<CacheType>) => Promise<void>,
}
type Queue = {
songList: string[],
current: string | null,
}

View File

@@ -2,12 +2,19 @@
import { Client, Events, GatewayIntentBits, MessageFlags, REST, Routes } from 'discord.js';
import dotenv from 'dotenv';
import { commands } from "./commands";
import { createAudioPlayer } from '@discordjs/voice';
import { AudioPlayerState, createAudioPlayer } from '@discordjs/voice';
import { connectToChannel, playSong } from './util/helpers';
import { updatePlayer } from './playback';
dotenv.config()
export const DATA_DIR = "./data";
// AUDIO
export const player = createAudioPlayer();
player.on('stateChange', (oldState: AudioPlayerState, newState: AudioPlayerState) => {
updatePlayer()
});
// Create a new client instance
const client = new Client({
@@ -29,11 +36,17 @@ 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)
const data = await rest.put(Routes.applicationCommands(
await rest.put(Routes.applicationGuildCommands(
process.env.DISCORD_APP_ID!,
process.env.GUILD_ID!
), {
body: registry
});
// await rest.put(Routes.applicationCommands(
// process.env.DISCORD_APP_ID!,
// ), {
// body: registry
// });
}

17
src/messages.ts Normal file
View File

@@ -0,0 +1,17 @@
import { getQueue } from "./playback";
export function getPlayMsg(url: string): string {
return `
Request: ${url}
https://gitea.papryk.com/Papryk/dj-spangebob pull requesty milewidziane XD\n` + getQueueMsg()
}
export function getCurrentSongMsg(): string {
return 'TODO-1'
}
export function getQueueMsg(): string {
const queue = getQueue()
return `Current: ${queue.current}
Queue:
${queue.songList.join('\n ')}`
}

57
src/playback.ts Normal file
View File

@@ -0,0 +1,57 @@
import { AudioPlayer, AudioPlayerStatus } from "@discordjs/voice";
import { player } from "./main";
import { CacheType, ChatInputCommandInteraction } from "discord.js";
import { getAudioFile } from "./util/downloader";
import { playSong } from "./util/helpers";
const queue: Queue = {
songList: [],
current: null
}
export async function requestSong(
interaction: ChatInputCommandInteraction<CacheType>,
url: string) {
const path = await getAudioFile(url)
queue.songList.push(path)
updatePlayer()
}
export async function forceRequestSong(
interaction: ChatInputCommandInteraction<CacheType>,
url: string) {
const path = await getAudioFile(url)
queue.songList.push(path)
playSong(player, path);
}
export async function updatePlayer() {
if (player.state.status === AudioPlayerStatus.Idle) {
const nextSong = queue.songList.shift()
if (!nextSong) {
queue.current = null
return
};
playSong(player, nextSong);
}
}
export function pause_playback(player: AudioPlayer) {
player.pause()
}
export function stop_playback(player: AudioPlayer) {
player.stop()
}
export function resume_playback(player: AudioPlayer) {
player.unpause()
}
export function getQueue(): Queue {
return queue
}

View File

@@ -1,34 +1,48 @@
import { spawn } from "node:child_process";
import { DATA_DIR } from "../main";
export async function get_audio(url: string): Promise<string> {
const ytDlpBin = process.env.YT_DLP_BIN_PATH! ?? "yt-dlp";
const dataDir = "./data";
export async function getAudioFile(url: string): Promise<string> {
const id = extractId(url)
let path: string;
try {
path = await findFileById(id)
} catch (e) {
await downloadYTVideo(url)
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
}
// ---- run yt-dlp ----
export async function downloadYTVideo(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-quality", "0",
"--no-playlist",
"--paths", dataDir,
"--paths", 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")));
});
}
// ---- find the file ----
export async function findFileById(id: string): Promise<string> {
return await new Promise<string>((resolve, reject) => {
const find = spawn("find", [
dataDir,
DATA_DIR,
"-type", "f",
"-iname", `*${id}*.opus`,
"-exec", "readlink", "-f", "{}", ";",

View File

@@ -7,8 +7,18 @@ import {
joinVoiceChannel,
type AudioPlayer,
} from '@discordjs/voice';
import type { VoiceBasedChannel } from 'discord.js';
import type { CacheType, ChatInputCommandInteraction, VoiceBasedChannel } from 'discord.js';
import { createDiscordJSAdapter } from './adapter.js';
import { player } from '../main.js';
export async function connectToChannelByInteraction(interaction: ChatInputCommandInteraction<CacheType>): Promise<void> {
const member = await interaction.guild?.members.fetch(interaction.user.id);
if (!member) throw new Error("ale chuj")
const voiceChannel = member.voice.channel;
if (!voiceChannel) throw new Error("ale chuj 2")
const connection = await connectToChannel(voiceChannel);
connection.subscribe(player);
}
export async function connectToChannel(channel: VoiceBasedChannel) {
/**