feat: newgrounds video & audio support
This commit is contained in:
parent
a3ee3d9c16
commit
985bedf5fb
7 changed files with 218 additions and 2 deletions
|
@ -26,6 +26,7 @@ this list is not final and keeps expanding over time. if support for a service y
|
|||
| instagram | ✅ | ✅ | ✅ | ➖ | ➖ |
|
||||
| facebook | ✅ | ❌ | ✅ | ➖ | ➖ |
|
||||
| loom | ✅ | ❌ | ✅ | ✅ | ➖ |
|
||||
| newgrounds | ✅ | ✅ | ✅ | ✅ | ✅ |
|
||||
| ok.ru | ✅ | ❌ | ✅ | ✅ | ✅ |
|
||||
| pinterest | ✅ | ✅ | ✅ | ➖ | ➖ |
|
||||
| reddit | ✅ | ✅ | ✅ | ❌ | ❌ |
|
||||
|
|
|
@ -140,6 +140,7 @@ export default function({ r, host, audioFormat, isAudioOnly, isAudioMuted, disab
|
|||
case "ok":
|
||||
case "vk":
|
||||
case "tiktok":
|
||||
case "newgrounds":
|
||||
params = { type: "proxy" };
|
||||
break;
|
||||
|
||||
|
|
|
@ -29,6 +29,7 @@ import snapchat from "./services/snapchat.js";
|
|||
import loom from "./services/loom.js";
|
||||
import facebook from "./services/facebook.js";
|
||||
import bluesky from "./services/bluesky.js";
|
||||
import newgrounds from "./services/newgrounds.js";
|
||||
|
||||
let freebind;
|
||||
|
||||
|
@ -243,6 +244,17 @@ export default async function({ host, patternMatch, params }) {
|
|||
});
|
||||
break;
|
||||
|
||||
case "newgrounds":
|
||||
r = await newgrounds({
|
||||
type: patternMatch.type,
|
||||
method: patternMatch.method,
|
||||
id: patternMatch.id,
|
||||
quality: params.videoQuality,
|
||||
isAudioOnly,
|
||||
isAudioMuted
|
||||
});
|
||||
break;
|
||||
|
||||
default:
|
||||
return createResponse("error", {
|
||||
code: "error.api.service.unsupported"
|
||||
|
|
|
@ -62,6 +62,9 @@ export const services = {
|
|||
"url_shortener/:shortLink"
|
||||
],
|
||||
},
|
||||
newgrounds: {
|
||||
patterns: [":type/:method/:id"]
|
||||
},
|
||||
reddit: {
|
||||
patterns: [
|
||||
"r/:sub/comments/:id/:title",
|
||||
|
|
|
@ -73,4 +73,9 @@ export const testers = {
|
|||
|
||||
"bsky": pattern =>
|
||||
pattern.user?.length <= 128 && pattern.post?.length <= 128,
|
||||
|
||||
"newgrounds": (patternMatch) =>
|
||||
(patternMatch.type == 'portal' && patternMatch.method == 'view')
|
||||
|| (patternMatch.type == 'audio' && patternMatch.method == 'listen')
|
||||
&& patternMatch.id?.length >= 1,
|
||||
}
|
||||
|
|
157
api/src/processing/services/newgrounds.js
Normal file
157
api/src/processing/services/newgrounds.js
Normal file
|
@ -0,0 +1,157 @@
|
|||
import { genericUserAgent } from "../../config.js";
|
||||
import { cleanString } from "../../misc/utils.js";
|
||||
|
||||
const qualities = ["4k", "1440p", "1080p", "720p", "480p", "360p", "240p", "144p"];
|
||||
|
||||
const qualityMatch = {
|
||||
2160: "4k",
|
||||
1440: "1440p",
|
||||
1080: "1080p",
|
||||
720: "720p",
|
||||
480: "480p",
|
||||
360: "360p",
|
||||
240: "240p",
|
||||
144: "144p"
|
||||
}
|
||||
|
||||
function getQuality(sources, requestedQuality) {
|
||||
if (requestedQuality == "max") {
|
||||
for (let quality of qualities) {
|
||||
if (sources[quality]) {
|
||||
return {
|
||||
src: sources[quality][0].src,
|
||||
quality: quality,
|
||||
type: sources[quality][0].type,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let videoData = sources[qualityMatch[requestedQuality]];
|
||||
if (videoData) {
|
||||
return {
|
||||
src: videoData[0].src,
|
||||
quality: requestedQuality + "p",
|
||||
type: videoData[0].type,
|
||||
}
|
||||
}
|
||||
|
||||
const qualityIndex = qualities.indexOf(qualityMatch[requestedQuality]);
|
||||
if (qualityIndex !== -1) {
|
||||
for (let i = qualityIndex; i >= 0; i--) {
|
||||
if (sources[qualities[i]]) {
|
||||
return {
|
||||
src: sources[qualities[i]][0].src,
|
||||
quality: qualities[i],
|
||||
type: sources[qualities[i]][0].type,
|
||||
}
|
||||
}
|
||||
}
|
||||
for (let i = qualityIndex + 1; i < qualities.length; i++) {
|
||||
if (sources[qualities[i]]) {
|
||||
return {
|
||||
src: sources[qualities[i]][0].src,
|
||||
quality: qualities[i],
|
||||
type: sources[qualities[i]][0].type,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
async function getVideo(obj) {
|
||||
let req = await fetch(`https://www.newgrounds.com/portal/video/${obj.id}`, {
|
||||
headers: {
|
||||
'User-Agent': genericUserAgent,
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
}
|
||||
})
|
||||
.then(request => request.text())
|
||||
.catch(() => {});
|
||||
|
||||
if (!req) return { error: 'ErrorCouldntFetch' };
|
||||
|
||||
let json;
|
||||
try {
|
||||
json = JSON.parse(req);
|
||||
} catch { return { error: 'ErrorEmptyDownload' }; }
|
||||
|
||||
const videoData = getQuality(json.sources, obj.quality);
|
||||
if (videoData == null) {
|
||||
return { error: 'ErrorCouldntFetch' };
|
||||
}
|
||||
if (!videoData.type.includes('mp4')) {
|
||||
return { error: 'ErrorCouldntFetch' };
|
||||
}
|
||||
|
||||
let fileMetadata = {
|
||||
title: cleanString(decodeURIComponent(json.title)),
|
||||
artist: cleanString(decodeURIComponent(json.author)),
|
||||
}
|
||||
|
||||
return {
|
||||
urls: videoData.src,
|
||||
filenameAttributes: {
|
||||
service: "newgrounds",
|
||||
id: obj.id,
|
||||
title: fileMetadata.title,
|
||||
author: fileMetadata.artist,
|
||||
extension: 'mp4',
|
||||
qualityLabel: videoData.quality,
|
||||
resolution: videoData.quality
|
||||
},
|
||||
fileMetadata,
|
||||
}
|
||||
}
|
||||
|
||||
async function getMusic(obj) {
|
||||
let req = await fetch(`https://www.newgrounds.com/audio/listen/${obj.id}`, {
|
||||
headers: {
|
||||
'User-Agent': genericUserAgent,
|
||||
}
|
||||
})
|
||||
.then(request => request.text())
|
||||
.catch(() => {});
|
||||
|
||||
if (!req) return { error: 'ErrorCouldntFetch' };
|
||||
|
||||
const titleMatch = req.match(/"name"\s*:\s*"([^"]+)"/);
|
||||
const artistMatch = req.match(/"artist"\s*:\s*"([^"]+)"/);
|
||||
const urlMatch = req.match(/"filename"\s*:\s*"([^"]+)"/);
|
||||
|
||||
if (!titleMatch || !artistMatch || !urlMatch) {
|
||||
return { error: 'ErrorCouldntFetch' };
|
||||
}
|
||||
|
||||
const title = titleMatch[1];
|
||||
const artist = artistMatch[1];
|
||||
const url = urlMatch[1].replace(/\\\//g, '/');
|
||||
let fileMetadata = {
|
||||
title: cleanString(decodeURIComponent(title.trim())),
|
||||
artist: cleanString(decodeURIComponent(artist.trim())),
|
||||
}
|
||||
|
||||
return {
|
||||
urls: url,
|
||||
filenameAttributes: {
|
||||
service: "newgrounds",
|
||||
id: obj.id,
|
||||
title: fileMetadata.title,
|
||||
author: fileMetadata.artist,
|
||||
},
|
||||
fileMetadata,
|
||||
isAudioOnly: true
|
||||
}
|
||||
}
|
||||
|
||||
export default function(obj) {
|
||||
if (obj.type == 'portal') {
|
||||
return getVideo(obj);
|
||||
}
|
||||
if (obj.type == 'audio') {
|
||||
return getMusic(obj);
|
||||
}
|
||||
return { error: 'ErrorUnsupported' };
|
||||
}
|
|
@ -1493,5 +1493,42 @@
|
|||
"status": "error"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"newgrounds": [{
|
||||
"name": "regular video",
|
||||
"url": "https://www.newgrounds.com/portal/view/938050",
|
||||
"params": {},
|
||||
"expected": {
|
||||
"code": 200,
|
||||
"status": "tunnel"
|
||||
}
|
||||
}, {
|
||||
"name": "regular video (audio only)",
|
||||
"url": "https://www.newgrounds.com/portal/view/938050",
|
||||
"params": {
|
||||
"downloadMode": "audio"
|
||||
},
|
||||
"expected": {
|
||||
"code": 200,
|
||||
"status": "tunnel"
|
||||
}
|
||||
}, {
|
||||
"name": "regular video (muted)",
|
||||
"url": "https://www.newgrounds.com/portal/view/938050",
|
||||
"params": {
|
||||
"downloadMode": "mute"
|
||||
},
|
||||
"expected": {
|
||||
"code": 200,
|
||||
"status": "tunnel"
|
||||
}
|
||||
}, {
|
||||
"name": "regular music",
|
||||
"url": "https://www.newgrounds.com/audio/listen/500476",
|
||||
"params": {},
|
||||
"expected": {
|
||||
"code": 200,
|
||||
"status": "tunnel"
|
||||
}
|
||||
}]
|
||||
}
|
Loading…
Reference in a new issue