Flatlogic Bot ca3a32f23e V 4
2026-02-16 06:55:36 +00:00

564 lines
28 KiB
JavaScript

"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __generator = (this && this.__generator) || function (thisArg, body) {
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
function verb(n) { return function (v) { return step([n, v]); }; }
function step(op) {
if (f) throw new TypeError("Generator is already executing.");
while (g && (g = 0, op[0] && (_ = 0)), _) try {
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
if (y = 0, t) op = [op[0] & 2, t.value];
switch (op[0]) {
case 0: case 1: t = op; break;
case 4: _.label++; return { value: op[1], done: false };
case 5: _.label++; y = op[1]; op = [0]; continue;
case 7: op = _.ops.pop(); _.trys.pop(); continue;
default:
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
if (t[2]) _.ops.pop();
_.trys.pop(); continue;
}
op = body.call(thisArg, _);
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
}
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Util = void 0;
var fs = require("fs");
var path = require("path");
var index_1 = require("./index");
var stream_1 = require("stream");
var child_process_1 = require("child_process");
var temp = 0;
var FFMPEG = { checked: false, path: "" };
var SOURCES = [
function () {
var _a;
var ffmpeg = require("ffmpeg-static");
return (_a = ffmpeg === null || ffmpeg === void 0 ? void 0 : ffmpeg.path) !== null && _a !== void 0 ? _a : ffmpeg;
},
function () { return "ffmpeg"; },
function () { return "./ffmpeg"; },
];
var Util = /** @class */ (function () {
function Util(api) {
var _this = this;
this.api = api;
this.tracks = new index_1.Tracks(this.api);
this.users = new index_1.Users(this.api);
this.playlists = new index_1.Playlists(this.api);
this.resolveTrack = function (trackResolvable) { return __awaiter(_this, void 0, void 0, function () {
var _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
if (!(typeof trackResolvable === "string")) return [3 /*break*/, 2];
return [4 /*yield*/, this.tracks.get(trackResolvable)];
case 1:
_a = _b.sent();
return [3 /*break*/, 3];
case 2:
_a = trackResolvable;
_b.label = 3;
case 3: return [2 /*return*/, _a];
}
});
}); };
this.sortTranscodings = function (trackResolvable, protocol) { return __awaiter(_this, void 0, void 0, function () {
var track, transcodings;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.resolveTrack(trackResolvable)];
case 1:
track = _a.sent();
transcodings = track.media.transcodings.sort(function (t) { return (t.quality === "hq" ? -1 : 1); });
if (!protocol)
return [2 /*return*/, transcodings];
return [2 /*return*/, transcodings.filter(function (t) { return t.format.protocol === protocol; })];
}
});
}); };
this.getStreamLink = function (transcoding) { return __awaiter(_this, void 0, void 0, function () {
var url, client_id, headers, connect, _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
if (!(transcoding === null || transcoding === void 0 ? void 0 : transcoding.url))
return [2 /*return*/, null];
url = transcoding.url;
return [4 /*yield*/, this.api.getClientId()];
case 1:
client_id = _b.sent();
headers = this.api.headers;
connect = url.includes("?") ? "&client_id=".concat(client_id) : "?client_id=".concat(client_id);
_b.label = 2;
case 2:
_b.trys.push([2, 3, , 5]);
return [2 /*return*/, fetch(url + connect, { headers: headers }).then(function (r) { return r.json(); }).then(function (r) { return r.url; })];
case 3:
_a = _b.sent();
return [4 /*yield*/, this.api.getClientId(true)];
case 4:
client_id = _b.sent();
connect = url.includes("?") ? "&client_id=".concat(client_id) : "?client_id=".concat(client_id);
try {
return [2 /*return*/, fetch(url + connect, { headers: headers }).then(function (r) { return r.json(); }).then(function (r) { return r.url; })];
}
catch (_c) {
return [2 /*return*/, null];
}
return [3 /*break*/, 5];
case 5: return [2 /*return*/];
}
});
}); };
/**
* Gets the direct streaming link of a track.
*/
this.streamLink = function (trackResolvable, protocol) { return __awaiter(_this, void 0, void 0, function () {
var track, transcodings;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.resolveTrack(trackResolvable)];
case 1:
track = _a.sent();
return [4 /*yield*/, this.sortTranscodings(track, protocol)];
case 2:
transcodings = _a.sent();
if (!transcodings.length)
return [2 /*return*/, null];
return [2 /*return*/, this.getStreamLink(transcodings[0])];
}
});
}); };
this.mergeFiles = function (files, outputFile) { return __awaiter(_this, void 0, void 0, function () {
var outStream, ret, _loop_1, _i, files_1, file;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
outStream = fs.createWriteStream(outputFile);
ret = new Promise(function (resolve, reject) {
outStream.on("finish", resolve);
outStream.on("error", reject);
});
_loop_1 = function (file) {
return __generator(this, function (_b) {
switch (_b.label) {
case 0: return [4 /*yield*/, new Promise(function (resolve, reject) {
fs.createReadStream(file).on("error", reject).on("end", resolve).pipe(outStream, { end: false });
})];
case 1:
_b.sent();
return [2 /*return*/];
}
});
};
_i = 0, files_1 = files;
_a.label = 1;
case 1:
if (!(_i < files_1.length)) return [3 /*break*/, 4];
file = files_1[_i];
return [5 /*yield**/, _loop_1(file)];
case 2:
_a.sent();
_a.label = 3;
case 3:
_i++;
return [3 /*break*/, 1];
case 4:
outStream.end();
return [2 /*return*/, ret];
}
});
}); };
this.checkFFmpeg = function () {
var _a;
if (FFMPEG.checked)
return true;
for (var _i = 0, SOURCES_1 = SOURCES; _i < SOURCES_1.length; _i++) {
var fn = SOURCES_1[_i];
try {
var command = fn();
var result = (0, child_process_1.spawnSync)(command, ["-h"], { windowsHide: true, shell: true, encoding: "utf-8" });
if (result.error)
throw result.error;
if (result.stderr && !result.stdout)
throw new Error(result.stderr);
var output = result.output.filter(Boolean).join("\n");
var version = (_a = /version (.+) Copyright/im.exec(output)) === null || _a === void 0 ? void 0 : _a[1];
if (!version)
throw new Error("Malformed FFmpeg command using ".concat(command));
FFMPEG.path = command;
}
catch (_b) { }
}
FFMPEG.checked = true;
if (!FFMPEG.path) {
console.warn("FFmpeg not found, please install ffmpeg-static or add ffmpeg to your PATH.");
console.warn("Download m4a (hq) is disabled, use mp3 (sq) instead.");
}
return true;
};
this.spawnFFmpeg = function (argss) {
try {
(0, child_process_1.spawnSync)(FFMPEG.path, argss, { windowsHide: true, shell: false });
}
catch (e) {
console.error(e);
throw "FFmpeg error";
}
};
/**
* Readable stream of m3u playlists.
*/
this.m3uReadableStream = function (trackResolvable) { return __awaiter(_this, void 0, void 0, function () {
var track, transcodings, transcoding, _i, transcodings_1, t, headers, client_id, connect, m3uLink, destDir, output, m3u, urls, chunks, i, arrayBuffer, chunkPath, stream;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.resolveTrack(trackResolvable)];
case 1:
track = _a.sent();
return [4 /*yield*/, this.sortTranscodings(track, "hls")];
case 2:
transcodings = _a.sent();
if (!transcodings.length)
throw "No transcodings found";
for (_i = 0, transcodings_1 = transcodings; _i < transcodings_1.length; _i++) {
t = transcodings_1[_i];
if (t.format.mime_type.startsWith('audio/mp4; codecs="mp4a') && this.checkFFmpeg()) {
transcoding = { url: t.url, type: "m4a" };
break;
}
if (t.format.mime_type.startsWith("audio/mpeg")) {
transcoding = { url: t.url, type: "mp3" };
break;
}
}
if (!transcoding) {
console.log("Support for this track is not yet implemented, please open an issue on GitHub.\n\n URL: ".concat(track.permalink_url, ".\n\n Type: ").concat(track.media.transcodings.map(function (t) { return t.format.mime_type; }).join(" | ")));
throw "No supported transcodings found";
}
headers = this.api.headers;
return [4 /*yield*/, this.api.getClientId()];
case 3:
client_id = _a.sent();
connect = transcoding.url.includes("?") ? "&client_id=".concat(client_id) : "?client_id=".concat(client_id);
return [4 /*yield*/, fetch(transcoding.url + connect, { headers: this.api.headers }).then(function (r) { return r.json(); }).then(function (r) { return r.url; })];
case 4:
m3uLink = _a.sent();
destDir = path.join(__dirname, "tmp_".concat(temp++));
if (!fs.existsSync(destDir))
fs.mkdirSync(destDir, { recursive: true });
output = path.join(destDir, "out.".concat(transcoding.type));
if (!(transcoding.type === "m4a")) return [3 /*break*/, 5];
try {
this.spawnFFmpeg(["-y", "-loglevel", "warning", "-i", m3uLink, "-bsf:a", "aac_adtstoasc", "-vcodec", "copy", "-c", "copy", "-crf", "50", output]);
}
catch (_b) {
console.warn("Failed to transmux to m4a (hq), download as mp3 (hq) instead.");
FFMPEG.path = null;
return [2 /*return*/, this.m3uReadableStream(trackResolvable)];
}
return [3 /*break*/, 12];
case 5: return [4 /*yield*/, fetch(m3uLink, { headers: headers }).then(function (r) { return r.text(); })];
case 6:
m3u = _a.sent();
urls = m3u.match(/(http).*?(?=\s)/gm);
chunks = [];
i = 0;
_a.label = 7;
case 7:
if (!(i < urls.length)) return [3 /*break*/, 10];
return [4 /*yield*/, fetch(urls[i], { headers: headers }).then(function (r) { return r.arrayBuffer(); })];
case 8:
arrayBuffer = _a.sent();
chunkPath = path.join(destDir, "".concat(i, ".").concat(transcoding.type));
fs.writeFileSync(chunkPath, Buffer.from(arrayBuffer));
chunks.push(chunkPath);
_a.label = 9;
case 9:
i++;
return [3 /*break*/, 7];
case 10: return [4 /*yield*/, this.mergeFiles(chunks, output)];
case 11:
_a.sent();
_a.label = 12;
case 12:
stream = stream_1.Readable.from(fs.readFileSync(output));
Util.removeDirectory(destDir);
return [2 /*return*/, { stream: stream, type: transcoding.type }];
}
});
}); };
this.webToNodeStream = function (webStream) {
var reader = webStream.getReader();
return new stream_1.Readable({
read: function () {
return __awaiter(this, void 0, void 0, function () {
var _a, done, value;
return __generator(this, function (_b) {
switch (_b.label) {
case 0: return [4 /*yield*/, reader.read()];
case 1:
_a = _b.sent(), done = _a.done, value = _a.value;
if (done) {
this.push(null);
}
else {
this.push(value);
}
return [2 /*return*/];
}
});
});
}
});
};
/**
* Downloads the mp3 stream of a track.
*/
this.downloadTrackStream = function (trackResolvable, title, dest) { return __awaiter(_this, void 0, void 0, function () {
var result, track, transcodings, transcoding, url, headers, stream_2, type, stream, fileName, writeStream;
var _this = this;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.resolveTrack(trackResolvable)];
case 1:
track = _a.sent();
return [4 /*yield*/, this.sortTranscodings(track, "progressive")];
case 2:
transcodings = _a.sent();
if (!!transcodings.length) return [3 /*break*/, 4];
return [4 /*yield*/, this.m3uReadableStream(trackResolvable)];
case 3:
result = _a.sent();
return [3 /*break*/, 7];
case 4:
transcoding = transcodings[0];
return [4 /*yield*/, this.getStreamLink(transcoding)];
case 5:
url = _a.sent();
headers = this.api.headers;
return [4 /*yield*/, fetch(url, { headers: headers }).then(function (r) { return _this.webToNodeStream(r.body); })];
case 6:
stream_2 = _a.sent();
type = transcoding.format.mime_type.startsWith('audio/mp4; codecs="mp4a') ? "m4a" : "mp3";
result = { stream: stream_2, type: type };
_a.label = 7;
case 7:
stream = result.stream;
fileName = path.extname(dest) ? dest : path.join(dest, "".concat(title, ".").concat(result.type));
writeStream = fs.createWriteStream(fileName);
stream.pipe(writeStream);
return [4 /*yield*/, new Promise(function (resolve) { return stream.on("end", function () { return resolve(); }); })];
case 8:
_a.sent();
return [2 /*return*/, fileName];
}
});
}); };
/**
* Downloads a track on Soundcloud.
*/
this.downloadTrack = function (trackResolvable, dest) { return __awaiter(_this, void 0, void 0, function () {
var folder, track, downloadObj, result, arrayBuffer, _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
if (!dest)
dest = "./";
folder = path.extname(dest) ? path.dirname(dest) : dest;
if (!fs.existsSync(folder))
fs.mkdirSync(folder, { recursive: true });
return [4 /*yield*/, this.resolveTrack(trackResolvable)];
case 1:
track = _b.sent();
if (!(track.downloadable === true)) return [3 /*break*/, 8];
_b.label = 2;
case 2:
_b.trys.push([2, 6, , 7]);
return [4 /*yield*/, this.api.getV2("/tracks/".concat(track.id, "/download"))];
case 3:
downloadObj = _b.sent();
return [4 /*yield*/, fetch(downloadObj.redirectUri)];
case 4:
result = _b.sent();
dest = path.extname(dest) ? dest : path.join(dest, "".concat(track.title.replace(/[\\/:*?\"<>|]/g, ""), ".").concat(result.headers["x-amz-meta-file-type"]));
return [4 /*yield*/, result.arrayBuffer()];
case 5:
arrayBuffer = _b.sent();
fs.writeFileSync(dest, Buffer.from(arrayBuffer, "binary"));
return [2 /*return*/, dest];
case 6:
_a = _b.sent();
return [2 /*return*/, this.downloadTrackStream(track, track.title.replace(/[\\/:*?\"<>|]/g, ""), dest)];
case 7: return [3 /*break*/, 9];
case 8: return [2 /*return*/, this.downloadTrackStream(track, track.title.replace(/[\\/:*?\"<>|]/g, ""), dest)];
case 9: return [2 /*return*/];
}
});
}); };
/**
* Downloads an array of tracks.
*/
this.downloadTracks = function (tracks, dest, limit) { return __awaiter(_this, void 0, void 0, function () {
var resultArray, i, result, _a;
return __generator(this, function (_b) {
switch (_b.label) {
case 0:
if (!limit)
limit = tracks.length;
resultArray = [];
i = 0;
_b.label = 1;
case 1:
if (!(i < limit)) return [3 /*break*/, 6];
_b.label = 2;
case 2:
_b.trys.push([2, 4, , 5]);
return [4 /*yield*/, this.downloadTrack(tracks[i], dest)];
case 3:
result = _b.sent();
resultArray.push(result);
return [3 /*break*/, 5];
case 4:
_a = _b.sent();
return [3 /*break*/, 5];
case 5:
i++;
return [3 /*break*/, 1];
case 6: return [2 /*return*/, resultArray];
}
});
}); };
/**
* Downloads all the tracks from the search query.
*/
this.downloadSearch = function (query, dest, limit) { return __awaiter(_this, void 0, void 0, function () {
var tracks;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.tracks.search({ q: query })];
case 1:
tracks = _a.sent();
return [2 /*return*/, this.downloadTracks(tracks.collection, dest, limit)];
}
});
}); };
/**
* Download all liked tracks by a user.
*/
this.downloadLikes = function (userResolvable, dest, limit) { return __awaiter(_this, void 0, void 0, function () {
var tracks;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.users.likes(userResolvable, limit)];
case 1:
tracks = _a.sent();
return [2 /*return*/, this.downloadTracks(tracks, dest, limit)];
}
});
}); };
/**
* Downloads all the tracks in a playlist.
*/
this.downloadPlaylist = function (playlistResolvable, dest, limit) { return __awaiter(_this, void 0, void 0, function () {
var playlist;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.playlists.get(playlistResolvable)];
case 1:
playlist = _a.sent();
return [2 /*return*/, this.downloadTracks(playlist.tracks, dest, limit)];
}
});
}); };
/**
* Returns a readable stream to the track.
*/
this.streamTrack = function (trackResolvable) { return __awaiter(_this, void 0, void 0, function () {
var url, readable;
return __generator(this, function (_a) {
switch (_a.label) {
case 0: return [4 /*yield*/, this.streamLink(trackResolvable, "progressive")];
case 1:
url = _a.sent();
if (!url)
return [2 /*return*/, this.m3uReadableStream(trackResolvable).then(function (r) { return r.stream; })];
return [4 /*yield*/, fetch(url, { headers: this.api.headers }).then(function (r) { return r.body; })];
case 2:
readable = _a.sent();
return [2 /*return*/, this.webToNodeStream(readable)];
}
});
}); };
/**
* Downloads a track's song cover.
*/
this.downloadSongCover = function (trackResolvable, dest, noDL) { return __awaiter(_this, void 0, void 0, function () {
var folder, track, artwork, title, client_id, url, arrayBuffer;
return __generator(this, function (_a) {
switch (_a.label) {
case 0:
if (!dest)
dest = "./";
folder = dest;
if (!fs.existsSync(folder))
fs.mkdirSync(folder, { recursive: true });
return [4 /*yield*/, this.resolveTrack(trackResolvable)];
case 1:
track = _a.sent();
artwork = (track.artwork_url ? track.artwork_url : track.user.avatar_url).replace(".jpg", ".png").replace("-large", "-t500x500");
title = track.title.replace(/[\\/:*?\"<>|]/g, "");
dest = path.extname(dest) ? dest : path.join(folder, "".concat(title, ".png"));
return [4 /*yield*/, this.api.getClientId()];
case 2:
client_id = _a.sent();
url = "".concat(artwork, "?client_id=").concat(client_id);
if (noDL)
return [2 /*return*/, url];
return [4 /*yield*/, fetch(url).then(function (r) { return r.arrayBuffer(); })];
case 3:
arrayBuffer = _a.sent();
fs.writeFileSync(dest, Buffer.from(arrayBuffer));
return [2 /*return*/, dest];
}
});
}); };
}
Util.removeDirectory = function (dir) {
if (!fs.existsSync(dir))
return;
fs.readdirSync(dir).forEach(function (file) {
var current = path.join(dir, file);
if (fs.lstatSync(current).isDirectory()) {
Util.removeDirectory(current);
}
else {
fs.unlinkSync(current);
}
});
try {
fs.rmdirSync(dir);
}
catch (error) {
console.error(error);
}
};
return Util;
}());
exports.Util = Util;