Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: newgrounds video & audio support #682

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 | ✅ | ✅ | ✅ | ❌ | ❌ |
Expand Down
1 change: 1 addition & 0 deletions api/src/processing/match-action.js
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ export default function({ r, host, audioFormat, isAudioOnly, isAudioMuted, disab
case "ok":
case "vk":
case "tiktok":
case "newgrounds":
params = { type: "proxy" };
break;

Expand Down
12 changes: 12 additions & 0 deletions api/src/processing/match.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,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;

Expand Down Expand Up @@ -238,6 +239,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"
Expand Down
3 changes: 3 additions & 0 deletions api/src/processing/service-config.js
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ export const services = {
"url_shortener/:shortLink"
],
},
newgrounds: {
patterns: [":type/:method/:id"]
},
reddit: {
patterns: [
"r/:sub/comments/:id/:title",
Expand Down
5 changes: 5 additions & 0 deletions api/src/processing/service-patterns.js
Original file line number Diff line number Diff line change
Expand Up @@ -70,4 +70,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,
}
156 changes: 156 additions & 0 deletions api/src/processing/services/newgrounds.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { genericUserAgent } from "../../config.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: 'fetch.fail' };

let json;
try {
json = JSON.parse(req);
} catch { return { error: 'fetch.empty' }; }

const videoData = getQuality(json.sources, obj.quality);
if (videoData == null) {
return { error: 'fetch.empty' };
}
if (!videoData.type.includes('mp4')) {
return { error: 'fetch.empty' };
}

let fileMetadata = {
title: decodeURIComponent(json.title),
artist: 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: 'fetch.fail' };

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: 'fetch.empty' };
}

const title = titleMatch[1];
const artist = artistMatch[1];
const url = urlMatch[1].replace(/\\\//g, '/');
let fileMetadata = {
title: decodeURIComponent(title.trim()),
artist: 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: 'link.unsupported' };
}
42 changes: 42 additions & 0 deletions api/src/util/tests/newgrounds.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
[
{
"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