feat: newgrounds video & audio support

This commit is contained in:
hyperdefined 2024-08-08 13:17:26 -04:00
parent a3ee3d9c16
commit 985bedf5fb
No known key found for this signature in database
GPG key ID: EB0B55B31E88AB03
7 changed files with 218 additions and 2 deletions

View file

@ -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 | ✅ | ✅ | ✅ | ❌ | ❌ |

View file

@ -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;

View file

@ -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"

View file

@ -62,6 +62,9 @@ export const services = {
"url_shortener/:shortLink"
],
},
newgrounds: {
patterns: [":type/:method/:id"]
},
reddit: {
patterns: [
"r/:sub/comments/:id/:title",

View file

@ -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,
}

View 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' };
}

View file

@ -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"
}
}]
}