require('dotenv').config(); const { Readable } = require("stream"); const { GetListByKeyword } = require("youtube-search-api"); const skewered = require("skewered"); const { createClient } = require("fix-esm").require("webdav"); const path = require("path"); const { readFileSync, writeFileSync } = require("fs"); function pathGenerator({ url, name, channelName }) { return path.join(process.env.NEXTCLOUD_FOLDER, `${url}-${skewered(`${name} - ${channelName}`)}.mp3`) } function checkLastSong({ name, channelName }) { const curSong = readFileSync(path.join(__dirname, "last_song.txt"), 'utf8'); return (curSong === `${name} - ${channelName}`); } function writeLastSong({ name, channelName }) { writeFileSync(path.join(__dirname, "last_song.txt"), `${name} - ${channelName}`, 'utf8'); return null; } async function getVideo() { const trackData = await fetch( `https://${process.env.LASTFM_INSTANCE}/2.0/?method=user.getrecenttracks&user=${process.env.LASTFM_USERNAME}&api_key=${process.env.LASTFM_API_KEY}&format=json&limit=1&extended=1` ).then(x => x.json()).then(data => data.recenttracks.track[0]); if (checkLastSong({ name: trackData.name, channelName: trackData.artist.name })) { console.log("Last song check failed"); return null; } else { console.log("Last song check succeeded, writing file"); writeLastSong({ name: trackData.name, channelName: trackData.artist.name }); } const videoList = await GetListByKeyword(`${trackData.artist.name} - ${trackData.name}`, false, 1, [ {type: "video"} ]); const musicVideo = videoList.items[0]; return { url: musicVideo.id, name: musicVideo.title, channelName: musicVideo.channelTitle }; } async function checkNextcloud({ url, name, channelName }, nextcloudClient) { const fileName = pathGenerator({ url, name, channelName }); return await nextcloudClient.exists(fileName); } async function downloadFromCobalt({ url, name, channelName }) { const processor = await fetch(`https://${process.env.COBALT_INSTANCE}/api/json`, { method: "POST", body: JSON.stringify({ url: `https://youtu.be/${url}`, vCodec: "h264", vQuality: "144", aFormat: "mp3", filenamePattern: "basic", isAudioOnly: true, disableMetadata: false }), headers: [ ["Accept", "application/json"], ["Content-Type", "application/json"], ] }).then(x => x.json()); let processorURL; switch (processor.status) { case 'error': case 'rate-limit': throw new Error(`Error! (${processor.text})`); case 'redirect': processorURL = processor.audio || processor.url; break; case 'picker': throw new Error("Can't handle picker!"); case 'stream': const streamProcessor = await fetch(`${processor.url}&p=1`).then(x => x.json()); if (streamProcessor.status === "continue") processorURL = processor.audio || processor.url; else processorURL = processor.audio || processor.url; break; default: processorURL = processor.audio || processor.url; break; } const { body } = await fetch(processorURL); return { fileStream: Readable.fromWeb(body), url, name, channelName }; } async function uploadToNextcloud({ fileStream, url, name, channelName }, nextcloudClient) { await nextcloudClient.createDirectory(process.env.NEXTCLOUD_FOLDER, { recursive: true, }); const fileName = pathGenerator({ url, name, channelName }); fileStream.pipe(nextcloudClient.createWriteStream(fileName)); return fileName; } async function main() { const video = await getVideo(); console.log(video); if (video == null) return null; const nextcloudClient = createClient( `https://${process.env.NEXTCLOUD_INSTANCE}/remote.php/dav/files/${process.env.NEXTCLOUD_USERNAME}/`, { username: process.env.NEXTCLOUD_USERNAME, password: process.env.NEXTCLOUD_PASSWORD, } ); if (await checkNextcloud(video, nextcloudClient)) return null; const cobalt = await downloadFromCobalt(video); console.log(cobalt); const nextcloud = await uploadToNextcloud(cobalt, nextcloudClient); console.log(nextcloud); return nextcloud; } main();