diff --git a/package.json b/package.json
index f0772552..f3387dcf 100644
--- a/package.json
+++ b/package.json
@@ -1,7 +1,7 @@
{
"name": "cobalt",
"description": "save what you love",
- "version": "4.8",
+ "version": "4.9-dev",
"author": "wukko",
"exports": "./src/cobalt.js",
"type": "module",
diff --git a/src/cobalt.js b/src/cobalt.js
index 74004f91..7512db78 100644
--- a/src/cobalt.js
+++ b/src/cobalt.js
@@ -6,7 +6,7 @@ import * as fs from "fs";
import rateLimit from "express-rate-limit";
import { shortCommit } from "./modules/sub/currentCommit.js";
-import { appName, genericUserAgent, version, internetExplorerRedirect } from "./modules/config.js";
+import { appName, genericUserAgent, version } from "./modules/config.js";
import { getJSON } from "./modules/api.js";
import renderPage from "./modules/pageRender/page.js";
import { apiJSON, checkJSONPost, languageCode } from "./modules/sub/utils.js";
@@ -57,6 +57,13 @@ if (fs.existsSync('./.env') && process.env.selfURL && process.env.streamSalt &&
}
next();
});
+ app.use((req, res, next) => {
+ if (req.header("user-agent") && req.header("user-agent").includes("Trident")) {
+ res.destroy()
+ }
+ next();
+ });
+
app.use('/api/json', express.json({
verify: (req, res, buf) => {
try {
@@ -150,20 +157,12 @@ if (fs.existsSync('./.env') && process.env.selfURL && process.env.streamSalt &&
res.redirect('/api/json')
});
app.get("/", (req, res) => {
- if (req.header("user-agent") && req.header("user-agent").includes("Trident")) {
- if (internetExplorerRedirect.newNT.includes(req.header("user-agent").split('NT ')[1].split(';')[0])) {
- res.redirect(internetExplorerRedirect.new)
- } else {
- res.redirect(internetExplorerRedirect.old)
- }
- } else {
- res.send(renderPage({
- "hash": commitHash,
- "type": "default",
- "lang": languageCode(req),
- "useragent": req.header('user-agent') ? req.header('user-agent') : genericUserAgent
- }))
- }
+ res.send(renderPage({
+ "hash": commitHash,
+ "type": "default",
+ "lang": languageCode(req),
+ "useragent": req.header('user-agent') ? req.header('user-agent') : genericUserAgent
+ }))
});
app.get("/favicon.ico", (req, res) => {
res.redirect('/icons/favicon.ico');
diff --git a/src/config.json b/src/config.json
index 4ce8c81f..fe59d601 100644
--- a/src/config.json
+++ b/src/config.json
@@ -2,7 +2,7 @@
"streamLifespan": 120000,
"maxVideoDuration": 7500000,
"maxAudioDuration": 7500000,
- "genericUserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/105.0.0.0 Safari/537.36",
+ "genericUserAgent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36",
"authorInfo": {
"name": "wukko",
"link": "https://wukko.me/",
@@ -18,11 +18,6 @@
}
}
},
- "internetExplorerRedirect": {
- "newNT": ["6.1", "6.2", "6.3", "10.0"],
- "old": "https://mypal-browser.org/",
- "new": "https://www.mozilla.org/firefox/new/"
- },
"donations": {
"crypto": {
"bitcoin": "bc1q59jyyjvrzj4c22rkk3ljeecq6jmpyscgz9spnd",
diff --git a/src/modules/api.js b/src/modules/api.js
index 70f84016..9a6db6c8 100644
--- a/src/modules/api.js
+++ b/src/modules/api.js
@@ -10,32 +10,43 @@ import match from "./processing/match.js";
export async function getJSON(originalURL, lang, obj) {
try {
let url = decodeURIComponent(originalURL);
- if (!url.includes('http://')) {
- let hostname = url.replace("https://", "").replace(' ', '').split('&')[0].split("/")[0].split("."),
- host = hostname[hostname.length - 2],
- patternMatch;
- if (host === "youtu") {
+ if (url.startsWith('http://')) {
+ return apiJSON(0, { t: errorUnsupported(lang) });
+ }
+ let hostname = url.replace("https://", "").replace(' ', '').split('&')[0].split("/")[0].split("."),
+ host = hostname[hostname.length - 2],
+ patternMatch;
+
+ // TO-DO: bring all tests into one unified module instead of placing them in several places
+ switch(host) {
+ case "youtu":
host = "youtube";
url = `https://youtube.com/watch?v=${url.replace("youtu.be/", "").replace("https://", "")}`;
- }
- if (host === "goo" && url.substring(0, 30) === "https://soundcloud.app.goo.gl/") {
- host = "soundcloud"
- url = `https://soundcloud.com/${url.replace("https://soundcloud.app.goo.gl/", "").split('/')[0]}`
- }
- if (host === "tumblr" && !url.includes("blog/view")) {
- if (url.slice(-1) == '/') url = url.slice(0, -1);
- url = url.replace(url.split('/')[5], '');
- }
- if (host && host.length < 20 && host in patterns && patterns[host]["enabled"]) {
- for (let i in patterns[host]["patterns"]) {
- patternMatch = new UrlPattern(patterns[host]["patterns"][i]).match(cleanURL(url, host).split(".com/")[1]);
- if (patternMatch) break;
+ break;
+ case "goo":
+ if (url.substring(0, 30) === "https://soundcloud.app.goo.gl/"){
+ host = "soundcloud"
+ url = `https://soundcloud.com/${url.replace("https://soundcloud.app.goo.gl/", "").split('/')[0]}`
}
- if (patternMatch) {
- return await match(host, patternMatch, url, lang, obj);
- } else return apiJSON(0, { t: errorUnsupported(lang) });
- } else return apiJSON(0, { t: errorUnsupported(lang) });
- } else return apiJSON(0, { t: errorUnsupported(lang) });
+ break;
+ case "tumblr":
+ if (!url.includes("blog/view")) {
+ if (url.slice(-1) == '/') url = url.slice(0, -1);
+ url = url.replace(url.split('/')[5], '');
+ }
+ break;
+ }
+ if (!(host && host.length < 20 && host in patterns && patterns[host]["enabled"])) {
+ return apiJSON(0, { t: errorUnsupported(lang) });
+ }
+ for (let i in patterns[host]["patterns"]) {
+ patternMatch = new UrlPattern(patterns[host]["patterns"][i]).match(cleanURL(url, host).split(".com/")[1]);
+ if (patternMatch) break;
+ }
+ if (!patternMatch) {
+ return apiJSON(0, { t: errorUnsupported(lang) });
+ }
+ return await match(host, patternMatch, url, lang, obj);
} catch (e) {
return apiJSON(0, { t: loc(lang, 'ErrorSomethingWentWrong') });
}
diff --git a/src/modules/build.js b/src/modules/build.js
index fdeebdab..0dd7279e 100644
--- a/src/modules/build.js
+++ b/src/modules/build.js
@@ -4,9 +4,9 @@ export async function buildFront() {
try {
await esbuild.build({
entryPoints: ['src/front/cobalt.js', 'src/front/cobalt.css'],
- outdir: `min/`,
+ outdir: 'min/',
minify: true,
- loader: { ".js": "js", ".css": "css" }
+ loader: { '.js': 'js', '.css': 'css' }
})
} catch (e) {
return;
diff --git a/src/modules/config.js b/src/modules/config.js
index 8f27e950..82a109ee 100644
--- a/src/modules/config.js
+++ b/src/modules/config.js
@@ -15,7 +15,6 @@ export const
repo = packageJson["bugs"]["url"].replace('/issues', ''),
authorInfo = config.authorInfo,
quality = config.quality,
- internetExplorerRedirect = config.internetExplorerRedirect,
donations = config.donations,
ffmpegArgs = config.ffmpegArgs,
supportedAudio = config.supportedAudio,
diff --git a/src/modules/services/bilibili.js b/src/modules/services/bilibili.js
index 5d1902f7..17d12f15 100644
--- a/src/modules/services/bilibili.js
+++ b/src/modules/services/bilibili.js
@@ -1,28 +1,37 @@
import { genericUserAgent, maxVideoDuration } from "../config.js";
+// TO-DO: quality picking
export default async function(obj) {
try {
let html = await fetch(`https://bilibili.com/video/${obj.id}`, {
headers: {"user-agent": genericUserAgent}
- }).then((r) => {return r.text()}).catch(() => {return false});
- if (!html) return { error: 'ErrorCouldntFetch' };
+ }).then((r) => { return r.text() }).catch(() => { return false });
+ if (!html) {
+ return { error: 'ErrorCouldntFetch' };
+ }
- if (html.includes('')[0]);
- if (streamData.data.timelength <= maxVideoDuration) {
- let video = streamData["data"]["dash"]["video"].filter((v) => {
- if (!v["baseUrl"].includes("https://upos-sz-mirrorcosov.bilivideo.com/")) return true;
- }).sort((a, b) => Number(b.bandwidth) - Number(a.bandwidth));
- let audio = streamData["data"]["dash"]["audio"].filter((a) => {
- if (!a["baseUrl"].includes("https://upos-sz-mirrorcosov.bilivideo.com/")) return true;
- }).sort((a, b) => Number(b.bandwidth) - Number(a.bandwidth));
- return { urls: [video[0]["baseUrl"], audio[0]["baseUrl"]], time: streamData.data.timelength, audioFilename: `bilibili_${obj.id}_audio`, filename: `bilibili_${obj.id}_${video[0]["width"]}x${video[0]["height"]}.mp4` };
- } else {
- return { error: ['ErrorLengthLimit', maxVideoDuration / 60000] };
- }
- } else {
+ if (!(html.includes('')[0]);
+ if (streamData.data.timelength > maxVideoDuration) {
+ return { error: ['ErrorLengthLimit', maxVideoDuration / 60000] };
+ }
+
+ let video = streamData["data"]["dash"]["video"].filter((v) => {
+ if (!v["baseUrl"].includes("https://upos-sz-mirrorcosov.bilivideo.com/")) return true;
+ }).sort((a, b) => Number(b.bandwidth) - Number(a.bandwidth));
+
+ let audio = streamData["data"]["dash"]["audio"].filter((a) => {
+ if (!a["baseUrl"].includes("https://upos-sz-mirrorcosov.bilivideo.com/")) return true;
+ }).sort((a, b) => Number(b.bandwidth) - Number(a.bandwidth));
+
+ return {
+ urls: [video[0]["baseUrl"], audio[0]["baseUrl"]],
+ time: streamData.data.timelength,
+ audioFilename: `bilibili_${obj.id}_audio`,
+ filename: `bilibili_${obj.id}_${video[0]["width"]}x${video[0]["height"]}.mp4`
+ };
} catch (e) {
return { error: 'ErrorBadFetch' };
}
diff --git a/src/modules/services/reddit.js b/src/modules/services/reddit.js
index 2323a028..3a8fe25f 100644
--- a/src/modules/services/reddit.js
+++ b/src/modules/services/reddit.js
@@ -1,26 +1,39 @@
import { maxVideoDuration } from "../config.js";
+// TO-DO: add support for gifs (#80)
export default async function(obj) {
try {
- let data = await fetch(`https://www.reddit.com/r/${obj.sub}/comments/${obj.id}/${obj.name}.json`).then((r) => {return r.json()}).catch(() => {return false});
- if (!data) return { error: 'ErrorCouldntFetch' };
+ let data = await fetch(`https://www.reddit.com/r/${obj.sub}/comments/${obj.id}/${obj.name}.json`).then((r) => { return r.json() }).catch(() => { return false });
+ if (!data) {
+ return { error: 'ErrorCouldntFetch' };
+ }
data = data[0]["data"]["children"][0]["data"];
- if ("reddit_video" in data["secure_media"] && data["secure_media"]["reddit_video"]["duration"] * 1000 < maxVideoDuration) {
- let video = data["secure_media"]["reddit_video"]["fallback_url"].split('?')[0],
- audio = video.match('.mp4') ? `${video.split('_')[0]}_audio.mp4` : `${data["secure_media"]["reddit_video"]["fallback_url"].split('DASH')[0]}audio`;
-
- await fetch(audio, {method: "HEAD"}).then((r) => {if (r.status != 200) audio = ''}).catch(() => {audio = ''});
-
- let id = data["secure_media"]["reddit_video"]["fallback_url"].split('/')[3]
- if (audio.length > 0) {
- return { typeId: 2, type: "render", urls: [video, audio], audioFilename: `reddit_${id}_audio`, filename: `reddit_${id}.mp4` };
- } else {
- return { typeId: 1, urls: video };
- }
- } else {
+ if (!"reddit_video" in data["secure_media"]) {
return { error: 'ErrorEmptyDownload' };
}
+ if (data["secure_media"]["reddit_video"]["duration"] * 1000 > maxVideoDuration) {
+ return { error: ['ErrorLengthLimit', maxVideoDuration / 60000] };
+ }
+ let video = data["secure_media"]["reddit_video"]["fallback_url"].split('?')[0],
+ audio = video.match('.mp4')
+ ? `${video.split('_')[0]}_audio.mp4`
+ : `${data["secure_media"]["reddit_video"]["fallback_url"].split('DASH')[0]}audio`;
+
+ await fetch(audio, {method: "HEAD"}).then((r) => {if (r.status != 200) audio = ''}).catch(() => {audio = ''});
+
+ let id = data["secure_media"]["reddit_video"]["fallback_url"].split('/')[3];
+
+ if (!audio.length > 0) {
+ return { typeId: 1, urls: video };
+ }
+ return {
+ typeId: 2,
+ type: "render",
+ urls: [video, audio],
+ audioFilename: `reddit_${id}_audio`,
+ filename: `reddit_${id}.mp4`
+ };
} catch (err) {
return { error: 'ErrorBadFetch' };
}
diff --git a/src/modules/services/soundcloud.js b/src/modules/services/soundcloud.js
index af26a92c..e6026cb5 100644
--- a/src/modules/services/soundcloud.js
+++ b/src/modules/services/soundcloud.js
@@ -4,32 +4,31 @@ let cachedID = {}
async function findClientID() {
try {
- let sc = await fetch('https://soundcloud.com/').then((r) => {return r.text()}).catch(() => {return false});
- let sc_version = String(sc.match(/')[0])
+ if (!json["media"]["transcodings"]) {
+ return { error: 'ErrorEmptyDownload' }
+ }
+ let clientId = await findClientID();
+ if (!clientId) {
+ return { error: 'ErrorSoundCloudNoClientId' }
+ }
+ let fileUrlBase = json.media.transcodings[0]["url"].replace("/hls", "/progressive")
+ let fileUrl = `${fileUrlBase}${fileUrlBase.includes("?") ? "&" : "?"}client_id=${clientId}&track_authorization=${json.track_authorization}`;
+ if (!fileUrl.substring(0, 54) === "https://api-v2.soundcloud.com/media/soundcloud:tracks:") {
+ return { error: 'ErrorEmptyDownload' }
+ }
+ if (json.duration > maxAudioDuration) {
+ return { error: ['ErrorLengthAudioConvert', maxAudioDuration / 60000] }
+ }
+ let file = await fetch(fileUrl).then(async (r) => { return (await r.json()).url }).catch(() => { return false });
+ if (!file) {
+ return { error: 'ErrorCouldntFetch' };
+ }
+ return {
+ urls: file,
+ audioFilename: `soundcloud_${json.id}`,
+ fileMetadata: {
+ title: json.title,
+ artist: json.user.username,
+ }
}
- if (!html) return { error: 'ErrorCouldntFetch'};
- if (html.includes('')[0])
- if (json["media"]["transcodings"]) {
- let clientId = await findClientID();
- if (clientId) {
- let fileUrlBase = json.media.transcodings[0]["url"].replace("/hls", "/progressive")
- let fileUrl = `${fileUrlBase}${fileUrlBase.includes("?") ? "&" : "?"}client_id=${clientId}&track_authorization=${json.track_authorization}`;
- if (fileUrl.substring(0, 54) === "https://api-v2.soundcloud.com/media/soundcloud:tracks:") {
- if (json.duration < maxAudioDuration) {
- let file = await fetch(fileUrl).then(async (r) => {return (await r.json()).url}).catch(() => {return false});
- if (!file) return { error: 'ErrorCouldntFetch' };
- return {
- urls: file,
- audioFilename: `soundcloud_${json.id}`,
- fileMetadata: {
- title: json.title,
- artist: json.user.username,
- }
- }
- } else return { error: ['ErrorLengthAudioConvert', maxAudioDuration / 60000] }
- }
- } else return { error: 'ErrorSoundCloudNoClientId' }
- } else return { error: 'ErrorEmptyDownload' }
- } else return { error: ['ErrorBrokenLink', 'soundcloud'] }
} catch (e) {
return { error: 'ErrorBadFetch' };
}
diff --git a/src/modules/services/tiktok.js b/src/modules/services/tiktok.js
index ec1b1ce9..ab30311f 100644
--- a/src/modules/services/tiktok.js
+++ b/src/modules/services/tiktok.js
@@ -12,18 +12,18 @@ let config = {
}
}
function selector(j, h, id) {
- if (j) {
- let t;
- switch (h) {
- case "tiktok":
- t = j["aweme_list"].filter((v) => { if (v["aweme_id"] == id) return true })
- break;
- case "douyin":
- t = j['item_list'].filter((v) => { if (v["aweme_id"] == id) return true })
- break;
- }
- if (t.length > 0) { return t[0] } else return false
- } else return false
+ if (!j) return false
+ let t;
+ switch (h) {
+ case "tiktok":
+ t = j["aweme_list"].filter((v) => { if (v["aweme_id"] == id) return true })
+ break;
+ case "douyin":
+ t = j['item_list'].filter((v) => { if (v["aweme_id"] == id) return true })
+ break;
+ }
+ if (!t.length > 0) return false
+ return t[0]
}
export default async function(obj) {
@@ -32,7 +32,7 @@ export default async function(obj) {
let html = await fetch(`${config[obj.host]["short"]}${obj.id}`, {
redirect: "manual",
headers: { "user-agent": userAgent }
- }).then((r) => {return r.text()}).catch(() => {return false});
+ }).then((r) => { return r.text() }).catch(() => { return false });
if (!html) return { error: 'ErrorCouldntFetch' };
if (html.slice(0, 17) === ' {return r.json()}).catch(() => {return false});
+ }).then((r) => { return r.json() }).catch(() => { return false });
detail = selector(detail, obj.host, obj.postId);
@@ -60,20 +62,19 @@ export default async function(obj) {
images = detail["images"] ? detail["images"] : false
}
if (!obj.isAudioOnly && !images) {
- video = obj.host === "tiktok" ? detail["video"]["play_addr"]["url_list"][0] : detail["video"]["play_addr"]["url_list"][0].replace("playwm", "play");
- videoFilename = `${filenameBase}_video_nw.mp4` // nw - no watermark
- if (!obj.noWatermark) {
- video = obj.host === "tiktok" ? detail["video"]["download_addr"]["url_list"][0] : detail['video']['play_addr']['url_list'][0]
- videoFilename = `${filenameBase}_video.mp4`
+ video = obj.host === "tiktok" ? detail["video"]["download_addr"]["url_list"][0] : detail['video']['play_addr']['url_list'][0]
+ videoFilename = `${filenameBase}_video.mp4`
+ if (obj.noWatermark) {
+ video = obj.host === "tiktok" ? detail["video"]["play_addr"]["url_list"][0] : detail["video"]["play_addr"]["url_list"][0].replace("playwm", "play");
+ videoFilename = `${filenameBase}_video_nw.mp4` // nw - no watermark
}
} else {
let fallback = obj.host === "douyin" ? detail["video"]["play_addr"]["url_list"][0].replace("playwm", "play") : detail["video"]["play_addr"]["url_list"][0];
+ audio = fallback;
+ audioFilename = `${filenameBase}_audio_fv`; // fv - from video
if (obj.fullAudio || fallback.includes("music")) {
audio = detail["music"]["play_url"]["url_list"][0]
audioFilename = `${filenameBase}_audio`
- } else {
- audio = fallback
- audioFilename = `${filenameBase}_audio_fv` // fv - from video
}
if (audio.slice(-4) === ".mp3") isMp3 = true;
}
diff --git a/src/modules/services/tumblr.js b/src/modules/services/tumblr.js
index e0bde0a1..372388ac 100644
--- a/src/modules/services/tumblr.js
+++ b/src/modules/services/tumblr.js
@@ -5,11 +5,12 @@ export default async function(obj) {
let user = obj.user ? obj.user : obj.url.split('.')[0].replace('https://', '');
let html = await fetch(`https://${user}.tumblr.com/post/${obj.id}`, {
headers: {"user-agent": genericUserAgent}
- }).then((r) => {return r.text()}).catch(() => {return false});
+ }).then((r) => { return r.text() }).catch(() => { return false });
if (!html) return { error: 'ErrorCouldntFetch' };
- if (html.includes('property="og:video" content="https://va.media.tumblr.com/')) {
- return { urls: `https://va.media.tumblr.com/${html.split('property="og:video" content="https://va.media.tumblr.com/')[1].split('"')[0]}`, audioFilename: `tumblr_${obj.id}_audio` }
- } else return { error: 'ErrorEmptyDownload' }
+ if (!html.includes('property="og:video" content="https://va.media.tumblr.com/')) {
+ return { error: 'ErrorEmptyDownload' }
+ }
+ return { urls: `https://va.media.tumblr.com/${html.split('property="og:video" content="https://va.media.tumblr.com/')[1].split('"')[0]}`, audioFilename: `tumblr_${obj.id}_audio` }
} catch (e) {
return { error: 'ErrorBadFetch' };
}
diff --git a/src/modules/services/twitter.js b/src/modules/services/twitter.js
index c9766c2e..32e7918a 100644
--- a/src/modules/services/twitter.js
+++ b/src/modules/services/twitter.js
@@ -36,23 +36,22 @@ export default async function(obj) {
req_status = await fetch(showURL, { headers: _headers }).then((r) => { return r.status == 200 ? r.json() : false;}).catch(() => {return false});
}
if (!req_status) return { error: 'ErrorCouldntFetch' }
- if (req_status["extended_entities"] && req_status["extended_entities"]["media"]) {
- let single, multiple = [], media = req_status["extended_entities"]["media"];
- media = media.filter((i) => { if (i["type"] === "video" || i["type"] === "animated_gif") return true })
- if (media.length > 1) {
- for (let i in media) { multiple.push({type: "video", thumb: media[i]["media_url_https"], url: bestQuality(media[i]["video_info"]["variants"])}) }
- } else if (media.length > 0) {
- single = bestQuality(media[0]["video_info"]["variants"])
- } else {
- return { error: 'ErrorNoVideosInTweet' }
- }
- if (single) {
- return { urls: single, filename: `twitter_${obj.id}.mp4`, audioFilename: `twitter_${obj.id}_audio` }
- } else if (multiple) {
- return { picker: multiple }
- } else {
- return { error: 'ErrorNoVideosInTweet' }
- }
+ if (!req_status["extended_entities"] && req_status["extended_entities"]["media"]) {
+ return { error: 'ErrorNoVideosInTweet' }
+ }
+ let single, multiple = [], media = req_status["extended_entities"]["media"];
+ media = media.filter((i) => { if (i["type"] === "video" || i["type"] === "animated_gif") return true })
+ if (media.length > 1) {
+ for (let i in media) { multiple.push({type: "video", thumb: media[i]["media_url_https"], url: bestQuality(media[i]["video_info"]["variants"])}) }
+ } else if (media.length === 1) {
+ single = bestQuality(media[0]["video_info"]["variants"])
+ } else {
+ return { error: 'ErrorNoVideosInTweet' }
+ }
+ if (single) {
+ return { urls: single, filename: `twitter_${obj.id}.mp4`, audioFilename: `twitter_${obj.id}_audio` }
+ } else if (multiple) {
+ return { picker: multiple }
} else {
return { error: 'ErrorNoVideosInTweet' }
}
@@ -67,34 +66,33 @@ export default async function(obj) {
return r.status == 200 ? r.json() : false;
}).catch((e) => {return false});
- if (AudioSpaceById) {
- if (AudioSpaceById.data.audioSpace.metadata.is_space_available_for_replay === true) {
- let streamStatus = await fetch(`https://twitter.com/i/api/1.1/live_video_stream/status/${AudioSpaceById.data.audioSpace.metadata.media_key}`, { headers: _headers }).then((r) => {return r.status == 200 ? r.json() : false;}).catch(() => {return false;});
- if (!streamStatus) return { error: 'ErrorCouldntFetch' };
-
- let participants = AudioSpaceById.data.audioSpace.participants.speakers
- let listOfParticipants = `Twitter Space speakers: `
- for (let i in participants) {
- listOfParticipants += `@${participants[i]["twitter_screen_name"]}, `
- }
- listOfParticipants = listOfParticipants.slice(0, -2);
- return {
- urls: streamStatus.source.noRedirectPlaybackUrl,
- audioFilename: `twitterspaces_${obj.spaceId}`,
- isAudioOnly: true,
- fileMetadata: {
- title: AudioSpaceById.data.audioSpace.metadata.title,
- artist: `Twitter Space by @${AudioSpaceById.data.audioSpace.metadata.creator_results.result.legacy.screen_name}`,
- comment: listOfParticipants,
- // cover: AudioSpaceById.data.audioSpace.metadata.creator_results.result.legacy.profile_image_url_https.replace("_normal", "")
- }
- }
- } else {
- return { error: 'TwitterSpaceWasntRecorded' };
- }
- } else {
+ if (!AudioSpaceById) {
return { error: 'ErrorEmptyDownload' }
}
+ if (!AudioSpaceById.data.audioSpace.metadata.is_space_available_for_replay === true) {
+ return { error: 'TwitterSpaceWasntRecorded' };
+ }
+ let streamStatus = await fetch(`https://twitter.com/i/api/1.1/live_video_stream/status/${AudioSpaceById.data.audioSpace.metadata.media_key}`,
+ { headers: _headers }).then((r) =>{return r.status == 200 ? r.json() : false;}).catch(() => {return false;});
+ if (!streamStatus) return { error: 'ErrorCouldntFetch' };
+
+ let participants = AudioSpaceById.data.audioSpace.participants.speakers
+ let listOfParticipants = `Twitter Space speakers: `
+ for (let i in participants) {
+ listOfParticipants += `@${participants[i]["twitter_screen_name"]}, `
+ }
+ listOfParticipants = listOfParticipants.slice(0, -2);
+ return {
+ urls: streamStatus.source.noRedirectPlaybackUrl,
+ audioFilename: `twitterspaces_${obj.spaceId}`,
+ isAudioOnly: true,
+ fileMetadata: {
+ title: AudioSpaceById.data.audioSpace.metadata.title,
+ artist: `Twitter Space by @${AudioSpaceById.data.audioSpace.metadata.creator_results.result.legacy.screen_name}`,
+ comment: listOfParticipants,
+ // cover: AudioSpaceById.data.audioSpace.metadata.creator_results.result.legacy.profile_image_url_https.replace("_normal", "")
+ }
+ }
}
} catch (err) {
return { error: 'ErrorBadFetch' };
diff --git a/src/modules/services/vimeo.js b/src/modules/services/vimeo.js
index a5e88f3c..fe1af72f 100644
--- a/src/modules/services/vimeo.js
+++ b/src/modules/services/vimeo.js
@@ -1,14 +1,14 @@
-import { quality, services } from "../config.js";
+import { maxVideoDuration, quality, services } from "../config.js";
export default async function(obj) {
try {
let api = await fetch(`https://player.vimeo.com/video/${obj.id}/config`).then((r) => {return r.json()}).catch(() => {return false});
if (!api) return { error: 'ErrorCouldntFetch' };
- let downloadType = "";
+ let downloadType = "dash";
if (JSON.stringify(api).includes('"progressive":[{')) {
downloadType = "progressive";
- } else if (JSON.stringify(api).includes('"files":{"dash":{"')) downloadType = "dash";
+ }
switch(downloadType) {
case "progressive":
@@ -19,10 +19,13 @@ export default async function(obj) {
let pref = parseInt(quality[obj.quality], 10)
for (let i in all) {
let currQuality = parseInt(all[i]["quality"].replace('p', ''), 10)
+ if (currQuality === pref) {
+ best = all[i];
+ break
+ }
if (currQuality < pref) {
- break;
- } else if (currQuality == pref) {
- best = all[i]
+ best = all[i-1];
+ break
}
}
}
@@ -31,45 +34,46 @@ export default async function(obj) {
}
return { urls: best["url"], filename: `tumblr_${obj.id}.mp4` };
case "dash":
+ if (api.video.duration > maxVideoDuration / 1000) {
+ return { error: ['ErrorLengthLimit', maxVideoDuration / 60000] };
+ }
let masterJSONURL = api["request"]["files"]["dash"]["cdns"]["akfire_interconnect_quic"]["url"];
let masterJSON = await fetch(masterJSONURL).then((r) => {return r.json()}).catch(() => {return false});
+
if (!masterJSON) return { error: 'ErrorCouldntFetch' };
- if (masterJSON.video) {
- let type = "";
- if (masterJSON.base_url.includes("parcel")) {
- type = "parcel"
- } else if (masterJSON.base_url == "../") {
- type = "chop"
- }
- let masterJSON_Video = masterJSON.video.sort((a, b) => Number(b.width) - Number(a.width));
- let masterJSON_Audio = masterJSON.audio.sort((a, b) => Number(b.bitrate) - Number(a.bitrate)).filter((a)=> {if (a['mime_type'] === "audio/mp4") return true;});
-
- let bestVideo = masterJSON_Video[0]
- let bestAudio = masterJSON_Audio[0]
- switch (type) {
- case "parcel":
- if (obj.quality != "max") {
- let pref = parseInt(quality[obj.quality], 10)
- for (let i in masterJSON_Video) {
- let currQuality = parseInt(services.vimeo.resolutionMatch[masterJSON_Video[i]["width"]], 10)
- if (currQuality < pref) {
- break;
- } else if (currQuality == pref) {
- bestVideo = masterJSON_Video[i]
- }
+ if (!masterJSON.video) {
+ return { error: 'ErrorEmptyDownload' }
+ }
+ let type = "parcel";
+ if (masterJSON.base_url == "../") {
+ type = "chop"
+ }
+ let masterJSON_Video = masterJSON.video.sort((a, b) => Number(b.width) - Number(a.width));
+ let masterJSON_Audio = masterJSON.audio.sort((a, b) => Number(b.bitrate) - Number(a.bitrate)).filter((a)=> {if (a['mime_type'] === "audio/mp4") return true;});
+
+ let bestVideo = masterJSON_Video[0]
+ let bestAudio = masterJSON_Audio[0]
+ switch (type) {
+ case "parcel":
+ if (obj.quality != "max") {
+ let pref = parseInt(quality[obj.quality], 10)
+ for (let i in masterJSON_Video) {
+ let currQuality = parseInt(services.vimeo.resolutionMatch[masterJSON_Video[i]["width"]], 10)
+ if (currQuality < pref) {
+ break;
+ } else if (currQuality == pref) {
+ bestVideo = masterJSON_Video[i]
}
}
- let baseUrl = masterJSONURL.split("/sep/")[0]
- let videoUrl = `${baseUrl}/parcel/video/${bestVideo.index_segment.split('?')[0]}`;
- let audioUrl = `${baseUrl}/parcel/audio/${bestAudio.index_segment.split('?')[0]}`;
+ }
+ let baseUrl = masterJSONURL.split("/sep/")[0]
+ let videoUrl = `${baseUrl}/parcel/video/${bestVideo.index_segment.split('?')[0]}`;
+ let audioUrl = `${baseUrl}/parcel/audio/${bestAudio.index_segment.split('?')[0]}`;
- return { urls: [videoUrl, audioUrl], audioFilename: `vimeo_${obj.id}_audio`, filename: `vimeo_${obj.id}_${bestVideo["width"]}x${bestVideo["height"]}.mp4` }
- case "chop": // TO-DO: support chop type of streams
- default:
- return { error: 'ErrorEmptyDownload' }
- }
- } else {
- return { error: 'ErrorEmptyDownload' }
+ return { urls: [videoUrl, audioUrl], audioFilename: `vimeo_${obj.id}_audio`, filename: `vimeo_${obj.id}_${bestVideo["width"]}x${bestVideo["height"]}.mp4` }
+ case "chop": // TO-DO: support chop type of streams
+ default:
+ return { error: 'ErrorEmptyDownload' }
}
default:
return { error: 'ErrorEmptyDownload' }
diff --git a/src/modules/services/vk.js b/src/modules/services/vk.js
index ca2d826e..0c4872be 100644
--- a/src/modules/services/vk.js
+++ b/src/modules/services/vk.js
@@ -9,49 +9,45 @@ export default async function(obj) {
headers: {"user-agent": genericUserAgent}
}).then((r) => {return r.text()}).catch(() => {return false});
if (!html) return { error: 'ErrorCouldntFetch' };
- if (html.includes(`{"lang":`)) {
- let js = JSON.parse('{"lang":' + html.split(`{"lang":`)[1].split(']);')[0]);
- if (js["mvData"]["is_active_live"] == '0') {
- if (js["mvData"]["duration"] <= maxVideoDuration / 1000) {
- let mpd = JSON.parse(xml2json(js["player"]["params"][0]["manifest"], { compact: true, spaces: 4 }));
-
- let repr = mpd["MPD"]["Period"]["AdaptationSet"]["Representation"];
- if (!mpd["MPD"]["Period"]["AdaptationSet"]["Representation"]) {
- repr = mpd["MPD"]["Period"]["AdaptationSet"][0]["Representation"];
- }
- let attr = repr[repr.length - 1]["_attributes"];
- let selectedQuality;
- let qualities = Object.keys(services.vk.quality_match);
- for (let i in qualities) {
- if (qualities[i] == attr["height"]) {
- selectedQuality = `url${attr["height"]}`;
- break;
- }
- if (qualities[i] == attr["width"]) {
- selectedQuality = `url${attr["width"]}`;
- break;
- }
- }
- let maxQuality = js["player"]["params"][0][selectedQuality].split('type=')[1].slice(0, 1)
- let userQuality = selectQuality('vk', obj.quality, Object.entries(services.vk.quality_match).reduce((r, [k, v]) => { r[v] = k; return r; })[maxQuality]);
- let userRepr = repr[services.vk.representation_match[userQuality]]["_attributes"];
- if (selectedQuality in js["player"]["params"][0]) {
- return {
- urls: js["player"]["params"][0][`url${userQuality}`],
- filename: `vk_${obj.userId}_${obj.videoId}_${userRepr["width"]}x${userRepr['height']}.mp4`
- };
- } else {
- return { error: 'ErrorEmptyDownload' };
- }
- } else {
- return { error: ['ErrorLengthLimit', maxVideoDuration / 60000] };
- }
- } else {
- return { error: 'ErrorLiveVideo' };
- }
- } else {
+ if (!html.includes(`{"lang":`)) {
return { error: 'ErrorEmptyDownload' };
}
+ let js = JSON.parse('{"lang":' + html.split(`{"lang":`)[1].split(']);')[0]);
+ if (!js["mvData"]["is_active_live"] == '0') {
+ return { error: 'ErrorLiveVideo' };
+ }
+ if (js["mvData"]["duration"] > maxVideoDuration / 1000) {
+ return { error: ['ErrorLengthLimit', maxVideoDuration / 60000] };
+ }
+ let mpd = JSON.parse(xml2json(js["player"]["params"][0]["manifest"], { compact: true, spaces: 4 }));
+
+ let repr = mpd["MPD"]["Period"]["AdaptationSet"]["Representation"];
+ if (!mpd["MPD"]["Period"]["AdaptationSet"]["Representation"]) {
+ repr = mpd["MPD"]["Period"]["AdaptationSet"][0]["Representation"];
+ }
+ let attr = repr[repr.length - 1]["_attributes"];
+ let selectedQuality;
+ let qualities = Object.keys(services.vk.quality_match);
+ for (let i in qualities) {
+ if (qualities[i] == attr["height"]) {
+ selectedQuality = `url${attr["height"]}`;
+ break;
+ }
+ if (qualities[i] == attr["width"]) {
+ selectedQuality = `url${attr["width"]}`;
+ break;
+ }
+ }
+ let maxQuality = js["player"]["params"][0][selectedQuality].split('type=')[1].slice(0, 1)
+ let userQuality = selectQuality('vk', obj.quality, Object.entries(services.vk.quality_match).reduce((r, [k, v]) => { r[v] = k; return r; })[maxQuality]);
+ let userRepr = repr[services.vk.representation_match[userQuality]]["_attributes"];
+ if (!selectedQuality in js["player"]["params"][0]) {
+ return { error: 'ErrorEmptyDownload' };
+ }
+ return {
+ urls: js["player"]["params"][0][`url${userQuality}`],
+ filename: `vk_${obj.userId}_${obj.videoId}_${userRepr["width"]}x${userRepr['height']}.mp4`
+ };
} catch (err) {
return { error: 'ErrorBadFetch' };
}
diff --git a/src/modules/services/youtube.js b/src/modules/services/youtube.js
index 6d247e4d..dfba7583 100644
--- a/src/modules/services/youtube.js
+++ b/src/modules/services/youtube.js
@@ -5,93 +5,88 @@ import selectQuality from "../stream/selectQuality.js";
export default async function(obj) {
try {
let infoInitial = await ytdl.getInfo(obj.id);
- if (infoInitial) {
- let info = infoInitial.formats;
- if (!info[0]["isLive"]) {
- let videoMatch = [], fullVideoMatch = [], video = [], audio = info.filter((a) => {
- if (!a["isHLS"] && !a["isDashMPD"] && a["hasAudio"] && !a["hasVideo"] && a["container"] == obj.format) return true;
- }).sort((a, b) => Number(b.bitrate) - Number(a.bitrate));
- if (!obj.isAudioOnly) {
- video = info.filter((a) => {
- if (!a["isHLS"] && !a["isDashMPD"] && a["hasVideo"] && a["container"] == obj.format) {
- if (obj.quality != "max") {
- if (a["hasAudio"] && mq[obj.quality] == a["height"]) {
- fullVideoMatch.push(a)
- } else if (!a["hasAudio"] && mq[obj.quality] == a["height"]) {
- videoMatch.push(a);
- }
- }
- return true
- }
- }).sort((a, b) => Number(b.bitrate) - Number(a.bitrate));
- if (obj.quality != "max") {
- if (videoMatch.length == 0) {
- let ss = selectQuality("youtube", obj.quality, video[0]["qualityLabel"].slice(0, 5).replace('p', '').trim())
- videoMatch = video.filter((a) => {
- if (a["qualityLabel"].slice(0, 5).replace('p', '').trim() == ss) return true;
- })
- } else if (fullVideoMatch.length > 0) {
- videoMatch = [fullVideoMatch[0]]
- }
- } else videoMatch = [video[0]];
- if (obj.quality == "los") videoMatch = [video[video.length - 1]];
- }
- let generalMeta = {
- title: infoInitial.videoDetails.title,
- artist: infoInitial.videoDetails.ownerChannelName.replace("- Topic", "").trim(),
- }
- if (audio[0]["approxDurationMs"] <= maxVideoDuration) {
- if (!obj.isAudioOnly && videoMatch.length > 0) {
- if (video.length > 0 && audio.length > 0) {
- if (videoMatch[0]["hasVideo"] && videoMatch[0]["hasAudio"]) {
- return {
- type: "bridge", urls: videoMatch[0]["url"], time: videoMatch[0]["approxDurationMs"],
- filename: `youtube_${obj.id}_${videoMatch[0]["width"]}x${videoMatch[0]["height"]}.${obj.format}`
- };
- } else {
- return {
- type: "render", urls: [videoMatch[0]["url"], audio[0]["url"]], time: videoMatch[0]["approxDurationMs"],
- filename: `youtube_${obj.id}_${videoMatch[0]["width"]}x${videoMatch[0]["height"]}.${obj.format}`
- };
- }
- } else {
- return { error: 'ErrorBadFetch' };
- }
- } else if (!obj.isAudioOnly) {
- return {
- type: "render", urls: [video[0]["url"], audio[0]["url"]], time: video[0]["approxDurationMs"],
- filename: `youtube_${obj.id}_${video[0]["width"]}x${video[0]["height"]}.${video[0]["container"]}`
- };
- } else if (audio.length > 0) {
- let r = {
- type: "render",
- isAudioOnly: true,
- urls: audio[0]["url"],
- audioFilename: `youtube_${obj.id}_audio`,
- fileMetadata: generalMeta
- };
- if (infoInitial.videoDetails.description) {
- let isAutoGenAudio = infoInitial.videoDetails.description.startsWith("Provided to YouTube by");
- if (isAutoGenAudio) {
- let descItems = infoInitial.videoDetails.description.split("\n\n")
- r.fileMetadata.album = descItems[2]
- r.fileMetadata.copyright = descItems[3]
- if (descItems[4].startsWith("Released on:")) r.fileMetadata.date = descItems[4].replace("Released on: ", '').trim();
- }
- }
- return r
- } else {
- return { error: 'ErrorBadFetch' };
- }
- } else {
- return { error: ['ErrorLengthLimit', maxVideoDuration / 60000] };
- }
- } else {
- return { error: 'ErrorLiveVideo' };
- }
- } else {
+ if (!infoInitial) {
return { error: 'ErrorCantConnectToServiceAPI' };
}
+ let info = infoInitial.formats;
+ if (info[0]["isLive"]) {
+ return { error: 'ErrorLiveVideo' };
+ }
+ let videoMatch = [], fullVideoMatch = [], video = [], audio = info.filter((a) => {
+ if (!a["isHLS"] && !a["isDashMPD"] && a["hasAudio"] && !a["hasVideo"] && a["container"] == obj.format) return true;
+ }).sort((a, b) => Number(b.bitrate) - Number(a.bitrate));
+ if (!obj.isAudioOnly) {
+ video = info.filter((a) => {
+ if (!a["isHLS"] && !a["isDashMPD"] && a["hasVideo"] && a["container"] == obj.format) {
+ if (obj.quality != "max") {
+ if (a["hasAudio"] && mq[obj.quality] == a["height"]) {
+ fullVideoMatch.push(a)
+ } else if (!a["hasAudio"] && mq[obj.quality] == a["height"]) {
+ videoMatch.push(a);
+ }
+ }
+ return true
+ }
+ }).sort((a, b) => Number(b.bitrate) - Number(a.bitrate));
+ if (obj.quality != "max") {
+ if (videoMatch.length == 0) {
+ let ss = selectQuality("youtube", obj.quality, video[0]["qualityLabel"].slice(0, 5).replace('p', '').trim())
+ videoMatch = video.filter((a) => {
+ if (a["qualityLabel"].slice(0, 5).replace('p', '').trim() == ss) return true;
+ })
+ } else if (fullVideoMatch.length > 0) {
+ videoMatch = [fullVideoMatch[0]]
+ }
+ } else videoMatch = [video[0]];
+ if (obj.quality == "los") videoMatch = [video[video.length - 1]];
+ }
+ let generalMeta = {
+ title: infoInitial.videoDetails.title,
+ artist: infoInitial.videoDetails.ownerChannelName.replace("- Topic", "").trim(),
+ }
+ if (audio[0]["approxDurationMs"] > maxVideoDuration) {
+ return { error: ['ErrorLengthLimit', maxVideoDuration / 60000] };
+ }
+ if (!obj.isAudioOnly && videoMatch.length > 0) {
+ if (video.length === 0 && audio.length === 0) {
+ return { error: 'ErrorBadFetch' };
+ }
+ if (videoMatch[0]["hasVideo"] && videoMatch[0]["hasAudio"]) {
+ return {
+ type: "bridge", urls: videoMatch[0]["url"], time: videoMatch[0]["approxDurationMs"],
+ filename: `youtube_${obj.id}_${videoMatch[0]["width"]}x${videoMatch[0]["height"]}.${obj.format}`
+ };
+ }
+ return {
+ type: "render", urls: [videoMatch[0]["url"], audio[0]["url"]], time: videoMatch[0]["approxDurationMs"],
+ filename: `youtube_${obj.id}_${videoMatch[0]["width"]}x${videoMatch[0]["height"]}.${obj.format}`
+ };
+ } else if (!obj.isAudioOnly) {
+ return {
+ type: "render", urls: [video[0]["url"], audio[0]["url"]], time: video[0]["approxDurationMs"],
+ filename: `youtube_${obj.id}_${video[0]["width"]}x${video[0]["height"]}.${video[0]["container"]}`
+ };
+ } else if (audio.length > 0) {
+ let r = {
+ type: "render",
+ isAudioOnly: true,
+ urls: audio[0]["url"],
+ audioFilename: `youtube_${obj.id}_audio`,
+ fileMetadata: generalMeta
+ };
+ if (infoInitial.videoDetails.description) {
+ let isAutoGenAudio = infoInitial.videoDetails.description.startsWith("Provided to YouTube by");
+ if (isAutoGenAudio) {
+ let descItems = infoInitial.videoDetails.description.split("\n\n")
+ r.fileMetadata.album = descItems[2]
+ r.fileMetadata.copyright = descItems[3]
+ if (descItems[4].startsWith("Released on:")) r.fileMetadata.date = descItems[4].replace("Released on: ", '').trim();
+ }
+ }
+ return r
+ } else {
+ return { error: 'ErrorBadFetch' };
+ }
} catch (e) {
return { error: 'ErrorBadFetch' };
}
diff --git a/src/modules/setup.js b/src/modules/setup.js
index 625d4beb..aa64370e 100644
--- a/src/modules/setup.js
+++ b/src/modules/setup.js
@@ -33,22 +33,15 @@ console.log(
)
rl.question(q, r1 => {
- if (r1) {
- ob['selfURL'] = `https://${r1}/`
- } else {
- ob['selfURL'] = `http://localhost`
- }
+ ob['selfURL'] = `http://localhost:9000/`
+ ob['port'] = 9000
+ if (r1) ob['selfURL'] = `https://${r1}/`
+
console.log(Bright("\nGreat! Now, what's the port it'll be running on? (9000)"))
+
rl.question(q, r2 => {
- if (!r1 && !r2) {
- ob['selfURL'] = `http://localhost:9000/`
- ob['port'] = 9000
- } else if (!r1 && r2) {
- ob['selfURL'] = `http://localhost:${r2}/`
- ob['port'] = r2
- } else {
- ob['port'] = r2
- }
+ if (r2) ob['port'] = r2
+ if (!r1 && r2) ob['selfURL'] = `http://localhost:${r2}/`
final()
});
})
diff --git a/src/modules/stream/manage.js b/src/modules/stream/manage.js
index 7da39d9c..065c8c2d 100644
--- a/src/modules/stream/manage.js
+++ b/src/modules/stream/manage.js
@@ -43,16 +43,14 @@ export function createStream(obj) {
export function verifyStream(ip, id, hmac, exp) {
try {
let streamInfo = streamCache.get(id);
- if (streamInfo) {
- let ghmac = sha256(`${id},${streamInfo.service},${ip},${exp}`, salt);
- if (hmac == ghmac && exp.toString() == streamInfo.exp && ghmac == streamInfo.hmac && ip == streamInfo.ip && exp > Math.floor(new Date().getTime())) {
- return streamInfo;
- } else {
- return { error: 'Unauthorized', status: 401 };
- }
- } else {
+ if (!streamInfo) {
return { error: 'this stream token does not exist', status: 400 };
}
+ let ghmac = sha256(`${id},${streamInfo.service},${ip},${exp}`, salt);
+ if (hmac == ghmac && exp.toString() == streamInfo.exp && ghmac == streamInfo.hmac && ip == streamInfo.ip && exp > Math.floor(new Date().getTime())) {
+ return streamInfo;
+ }
+ return { error: 'Unauthorized', status: 401 };
} catch (e) {
return { status: 500, body: { status: "error", text: "Internal Server Error" } };
}
diff --git a/src/modules/stream/selectQuality.js b/src/modules/stream/selectQuality.js
index d21a5f23..9ddf1fdc 100644
--- a/src/modules/stream/selectQuality.js
+++ b/src/modules/stream/selectQuality.js
@@ -1,5 +1,6 @@
import { services, quality as mq } from "../config.js";
+// TO-DO: remake entirety of this module to be more of how quality picking is done in vimeo module
function closest(goal, array) {
return array.sort().reduce(function (prev, curr) {
return (Math.abs(curr - goal) < Math.abs(prev - goal) ? curr : prev);
@@ -15,9 +16,7 @@ export default function(service, quality, maxQuality) {
if (quality >= maxQuality || quality == maxQuality) return maxQuality;
if (quality < maxQuality) {
- if (services[service]["quality"][quality]) {
- return quality
- } else {
+ if (!services[service]["quality"][quality]) {
let s = Object.keys(services[service]["quality_match"]).filter((q) => {
if (q <= quality) {
return true
@@ -25,5 +24,6 @@ export default function(service, quality, maxQuality) {
})
return closest(quality, s)
}
+ return quality
}
}
diff --git a/src/modules/stream/stream.js b/src/modules/stream/stream.js
index eb843086..7f9b42e6 100644
--- a/src/modules/stream/stream.js
+++ b/src/modules/stream/stream.js
@@ -5,24 +5,24 @@ import { streamAudioOnly, streamDefault, streamLiveRender, streamVideoOnly } fro
export default function(res, ip, id, hmac, exp) {
try {
let streamInfo = verifyStream(ip, id, hmac, exp);
- if (!streamInfo.error) {
- if (streamInfo.isAudioOnly && streamInfo.type !== "bridge") {
- streamAudioOnly(streamInfo, res);
- } else {
- switch (streamInfo.type) {
- case "render":
- streamLiveRender(streamInfo, res);
- break;
- case "mute":
- streamVideoOnly(streamInfo, res);
- break;
- default:
- streamDefault(streamInfo, res);
- break;
- }
- }
- } else {
+ if (streamInfo.error) {
res.status(streamInfo.status).json(apiJSON(0, { t: streamInfo.error }).body);
+ return;
+ }
+ if (streamInfo.isAudioOnly && streamInfo.type !== "bridge") {
+ streamAudioOnly(streamInfo, res);
+ return;
+ }
+ switch (streamInfo.type) {
+ case "render":
+ streamLiveRender(streamInfo, res);
+ break;
+ case "mute":
+ streamVideoOnly(streamInfo, res);
+ break;
+ default:
+ streamDefault(streamInfo, res);
+ break;
}
} catch (e) {
res.status(500).json({ status: "error", text: "Internal Server Error" });
diff --git a/src/modules/stream/types.js b/src/modules/stream/types.js
index ef6ac22c..50ce1389 100644
--- a/src/modules/stream/types.js
+++ b/src/modules/stream/types.js
@@ -27,39 +27,41 @@ export function streamDefault(streamInfo, res) {
}
export function streamLiveRender(streamInfo, res) {
try {
- if (streamInfo.urls.length === 2) {
- let format = streamInfo.filename.split('.')[streamInfo.filename.split('.').length - 1], args = [
- '-loglevel', '-8',
- '-i', streamInfo.urls[0],
- '-i', streamInfo.urls[1],
- '-map', '0:v',
- '-map', '1:a',
- ];
- args = args.concat(ffmpegArgs[format])
- if (streamInfo.time) args.push('-t', msToTime(streamInfo.time));
- args.push('-f', format, 'pipe:3');
- const ffmpegProcess = spawn(ffmpeg, args, {
- windowsHide: true,
- stdio: [
- 'inherit', 'inherit', 'inherit',
- 'pipe'
- ],
- });
- res.setHeader('Connection', 'keep-alive');
- res.setHeader('Content-Disposition', `attachment; filename="${streamInfo.filename}"`);
- ffmpegProcess.stdio[3].pipe(res);
- ffmpegProcess.on('disconnect', () => ffmpegProcess.kill());
- ffmpegProcess.on('close', () => ffmpegProcess.kill());
- ffmpegProcess.on('exit', () => ffmpegProcess.kill());
- res.on('finish', () => ffmpegProcess.kill());
- res.on('close', () => ffmpegProcess.kill());
- ffmpegProcess.on('error', (err) => {
- ffmpegProcess.kill();
- res.end();
- });
- } else {
+ if (!streamInfo.urls.length === 2) {
res.end();
+ return;
}
+ let format = streamInfo.filename.split('.')[streamInfo.filename.split('.').length - 1], args = [
+ '-loglevel', '-8',
+ '-i', streamInfo.urls[0],
+ '-i', streamInfo.urls[1],
+ '-map', '0:v',
+ '-map', '1:a',
+ ];
+ args = args.concat(ffmpegArgs[format])
+ if (streamInfo.time) args.push('-t', msToTime(streamInfo.time));
+ args.push('-f', format, 'pipe:3');
+ const ffmpegProcess = spawn(ffmpeg, args, {
+ windowsHide: true,
+ stdio: [
+ 'inherit', 'inherit', 'inherit',
+ 'pipe'
+ ],
+ });
+ res.setHeader('Connection', 'keep-alive');
+ res.setHeader('Content-Disposition', `attachment; filename="${streamInfo.filename}"`);
+ ffmpegProcess.stdio[3].pipe(res);
+
+ ffmpegProcess.on('disconnect', () => ffmpegProcess.kill());
+ ffmpegProcess.on('close', () => ffmpegProcess.kill());
+ ffmpegProcess.on('exit', () => ffmpegProcess.kill());
+ res.on('finish', () => ffmpegProcess.kill());
+ res.on('close', () => ffmpegProcess.kill());
+ ffmpegProcess.on('error', (err) => {
+ ffmpegProcess.kill();
+ res.end();
+ });
+
} catch (e) {
res.end();
}
@@ -93,6 +95,7 @@ export function streamAudioOnly(streamInfo, res) {
res.setHeader('Connection', 'keep-alive');
res.setHeader('Content-Disposition', `attachment; filename="${streamInfo.filename}.${streamInfo.audioFormat}"`);
ffmpegProcess.stdio[3].pipe(res);
+
ffmpegProcess.on('disconnect', () => ffmpegProcess.kill());
ffmpegProcess.on('close', () => ffmpegProcess.kill());
ffmpegProcess.on('exit', () => ffmpegProcess.kill());
@@ -125,6 +128,7 @@ export function streamVideoOnly(streamInfo, res) {
res.setHeader('Connection', 'keep-alive');
res.setHeader('Content-Disposition', `attachment; filename="${streamInfo.filename.split('.')[0]}_mute.${format}"`);
ffmpegProcess.stdio[3].pipe(res);
+
ffmpegProcess.on('disconnect', () => ffmpegProcess.kill());
ffmpegProcess.on('close', () => ffmpegProcess.kill());
ffmpegProcess.on('exit', () => ffmpegProcess.kill());
diff --git a/src/modules/sub/utils.js b/src/modules/sub/utils.js
index 6c65f5fb..78efaf14 100644
--- a/src/modules/sub/utils.js
+++ b/src/modules/sub/utils.js
@@ -103,25 +103,24 @@ export function checkJSONPost(obj) {
}
try {
let objKeys = Object.keys(obj);
- if (objKeys.length < 8 && obj.url) {
- let defKeys = Object.keys(def);
- for (let i in objKeys) {
- if (String(objKeys[i]) !== "url" && defKeys.includes(objKeys[i])) {
- if (apiVar.booleanOnly.includes(objKeys[i])) {
- def[objKeys[i]] = obj[objKeys[i]] ? true : false;
- } else {
- if (apiVar.allowed[objKeys[i]] && apiVar.allowed[objKeys[i]].includes(obj[objKeys[i]])) def[objKeys[i]] = String(obj[objKeys[i]])
- }
- }
- }
- obj["url"] = decodeURIComponent(String(obj["url"]))
- let hostname = obj["url"].replace("https://", "").replace(' ', '').split('&')[0].split("/")[0].split("."),
- host = hostname[hostname.length - 2]
- def["url"] = encodeURIComponent(cleanURL(obj["url"], host))
- return def
- } else {
+ if (!(objKeys.length < 8 && obj.url)) {
return false
}
+ let defKeys = Object.keys(def);
+ for (let i in objKeys) {
+ if (String(objKeys[i]) !== "url" && defKeys.includes(objKeys[i])) {
+ if (apiVar.booleanOnly.includes(objKeys[i])) {
+ def[objKeys[i]] = obj[objKeys[i]] ? true : false;
+ } else {
+ if (apiVar.allowed[objKeys[i]] && apiVar.allowed[objKeys[i]].includes(obj[objKeys[i]])) def[objKeys[i]] = String(obj[objKeys[i]])
+ }
+ }
+ }
+ obj["url"] = decodeURIComponent(String(obj["url"]))
+ let hostname = obj["url"].replace("https://", "").replace(' ', '').split('&')[0].split("/")[0].split("."),
+ host = hostname[hostname.length - 2]
+ def["url"] = encodeURIComponent(cleanURL(obj["url"], host))
+ return def
} catch (e) {
return false;
}