first commit
This commit is contained in:
commit
974aa4706a
4 changed files with 166 additions and 0 deletions
20
.env.example
Normal file
20
.env.example
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Last.fm
|
||||||
|
|
||||||
|
LASTFM_INSTANCE= # ws.audioscrobbler.com
|
||||||
|
|
||||||
|
LASTFM_USERNAME= # meowcatheorange
|
||||||
|
LASTFM_API_KEY= # cccccccccccccccccc...
|
||||||
|
LASTFM_SHARED_SECRET= # cccccccccccccccccc...
|
||||||
|
|
||||||
|
# Cobalt
|
||||||
|
|
||||||
|
COBALT_INSTANCE= # co.wuk.sh
|
||||||
|
|
||||||
|
# Nextcloud
|
||||||
|
|
||||||
|
NEXTCLOUD_INSTANCE= # next.abtmtr.link
|
||||||
|
|
||||||
|
NEXTCLOUD_USERNAME= # meowcatheorange
|
||||||
|
NEXTCLOUD_PASSWORD= # ccccc-ccccc-ccccc-ccccc-ccccc
|
||||||
|
|
||||||
|
NEXTCLOUD_FOLDER= # /Music/LastFMDownloader/
|
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
node_modules/
|
||||||
|
package-lock.json
|
||||||
|
.env
|
124
index.js
Normal file
124
index.js
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
require('dotenv').config();
|
||||||
|
const process = require('process');
|
||||||
|
const { Readable } = require('stream');
|
||||||
|
// const { searchMusics } = require("fix-esm").require('node-youtube-music');
|
||||||
|
const { GetListByKeyword } = require("youtube-search-api");
|
||||||
|
const skewered = require("skewered");
|
||||||
|
const { createClient } = require('fix-esm').require("webdav");
|
||||||
|
const path = require("path");
|
||||||
|
|
||||||
|
function pathGenerator({ url, name, channelName }) {
|
||||||
|
return path.join(process.env.NEXTCLOUD_FOLDER, `${url}-${skewered(`${name} - ${channelName}`)}.mp3`)
|
||||||
|
}
|
||||||
|
|
||||||
|
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]);
|
||||||
|
|
||||||
|
// const musics = await searchMusics(`${trackData.artist.name} - ${trackData.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);
|
||||||
|
|
||||||
|
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();
|
19
package.json
Normal file
19
package.json
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"name": "lastfm-downloader",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"dotenv": "^16.4.4",
|
||||||
|
"fix-esm": "^1.0.1",
|
||||||
|
"skewered": "^1.0.0",
|
||||||
|
"webdav": "^5.3.2",
|
||||||
|
"youtube-search-api": "^1.2.1"
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue