LastFMDownloader/index.js

230 lines
6.3 KiB
JavaScript
Raw Normal View History

2024-02-19 22:36:23 +00:00
require('dotenv').config();
2024-02-19 23:16:29 +00:00
const { Readable } = require("stream");
const { finished } = require("stream/promises");
const { searchMusics } = require('fix-esm').require("node-youtube-music");
2024-02-19 22:36:23 +00:00
const { GetListByKeyword } = require("youtube-search-api");
const skewered = require("skewered");
2024-02-19 23:16:29 +00:00
const { createClient } = require("fix-esm").require("webdav");
2024-02-19 22:36:23 +00:00
const path = require("path");
2024-02-19 23:16:29 +00:00
const { readFileSync, writeFileSync } = require("fs");
const io = require('socket.io-client');
const { program } = require('commander');
2024-02-19 22:36:23 +00:00
function pathGenerator({ url, name, channelName }) {
return path.join(process.env.NEXTCLOUD_FOLDER, `${url}-${skewered(`${name} - ${channelName}`)}.mp3`)
}
2024-02-19 23:16:29 +00:00
function checkLastSong({ name, channelName }) {
const curSong = readFileSync(path.join(__dirname, "last_song.txt"), 'utf8');
return (curSong === `${name} - ${channelName}`);
}
function writeLastSong({ name, channelName }) {
2024-02-19 23:23:47 +00:00
writeFileSync(path.join(__dirname, "last_song.txt"), `${name} - ${channelName}`, { recursive: true, encoding: 'utf-8' });
2024-02-19 23:16:29 +00:00
return null;
}
async function getVideo({ msName, msArtist }) {
let trackData;
if (msName != null && msArtist != null) {
trackData = {
name: msName,
artist: {
name: msArtist
}
};
} else {
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]);
}
2024-02-19 22:36:23 +00:00
2024-02-19 23:16:29 +00:00
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
});
}
2024-02-19 22:36:23 +00:00
let selectedVideo = {
url: "",
name: "",
channelName: ""
};
const musicList = await searchMusics(`${trackData.artist.name} - ${trackData.name}`);
const youtubeMusicVideo = musicList.find((song) => {
return skewered(song.title).includes(skewered(trackData.name));
});
2024-02-19 22:36:23 +00:00
console.log(youtubeMusicVideo);
if (youtubeMusicVideo == null) {
const videoList = await GetListByKeyword(`${trackData.artist.name} - ${trackData.name}`, false, 1, [
{type: "video"}
]);
2024-02-19 22:36:23 +00:00
const musicVideo = videoList.items[0];
selectedVideo = {
url: musicVideo.id,
name: musicVideo.title,
channelName: musicVideo.channelTitle
};
}
else selectedVideo = {
url: youtubeMusicVideo.youtubeId,
name: youtubeMusicVideo.title,
channelName: youtubeMusicVideo.artists[0].name
}
selectedVideo.channelName = selectedVideo.channelName.replace(/\s-\sTopic/ig, "");
return selectedVideo;
2024-02-19 22:36:23 +00:00
}
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 });
await finished(fileStream.pipe(nextcloudClient.createWriteStream(fileName)));
2024-02-19 22:36:23 +00:00
return fileName;
}
async function main() {
program
.option('-s, --song <value>', 'Look up a song', null)
.option('-a, --artist <value>', 'Look up an artist', null);
program.parse(process.argv);
const options = program.opts();
const socket = io.connect(`http://localhost:${process.env.NOTIFICATION_SERVER_PORT}`, {reconnect: true});
socket.on('connect', function (s) {
console.log('Successfully connected to notification server');
});
const video = await getVideo({
msName: options.song,
msArtist: options.artist
});
2024-02-19 22:36:23 +00:00
console.log(video);
if (video == null) return dismantle(socket);
2024-02-19 23:16:29 +00:00
2024-02-19 22:36:23 +00:00
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 dismantle(socket);
socket.emit('nodeMessage', {
message: `New song found - ${video.name} from ${video.channelName}`,
password: process.env.NOTIFICATION_SERVER_PASSWORD
})
2024-02-19 22:36:23 +00:00
const cobalt = await downloadFromCobalt(video);
console.log(cobalt);
socket.emit('nodeMessage', {
message: `Nextcloud upload running - ${cobalt.name} from ${cobalt.channelName}`,
password: process.env.NOTIFICATION_SERVER_PASSWORD
});
2024-02-19 22:36:23 +00:00
const nextcloud = await uploadToNextcloud(cobalt, nextcloudClient);
console.log(nextcloud);
socket.emit('nodeMessage', {
message: `Nextcloud upload finished - ${cobalt.name} from ${cobalt.channelName}`,
password: process.env.NOTIFICATION_SERVER_PASSWORD
})
return dismantle(socket);
2024-02-19 22:36:23 +00:00
}
main();
function dismantle(socket) {
socket.disconnect();
return null;
}