newgrounds-api
Version:
A simple Node.js library for interacting with the Newgrounds data.
464 lines • 19.8 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.Newgrounds = void 0;
const Audio_js_1 = require("./Audio.js");
const index_js_1 = require("../utils/index.js");
const cheerio = __importStar(require("cheerio"));
const puppeteer_1 = __importDefault(require("puppeteer"));
const Playlist_js_1 = require("./Playlist.js");
/**
* Represents the Newgrounds API
* @class Newgrounds
*/
class Newgrounds {
options;
puppeteer;
constructor() {
this.options = {
ua: "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:138.0) Gecko/20100101 Firefox/138.0",
};
this.puppeteer = puppeteer_1.default;
}
/**
* Search for audio on Newgrounds
* @param {string} terms The search terms
* @param {SearchOptions} options The search options
* @returns {Promise<AudioSearchResult[]>}
*/
async searchAudio(terms, options = {
page: 1,
sort_by: "relevance",
}) {
const url = `https://www.newgrounds.com/search/conduct/audio?terms=${encodeURIComponent(terms)}&page=${options.page}&sort=${options.sort_by}`;
const response = await fetch(url, {
headers: {
"User-Agent": this.options.ua,
},
});
if (!response.ok) {
throw new Error(`Failed to fetch audio search results: ${response.status} ${response.statusText}`);
}
const $ = cheerio.load(await response.text());
const searchResults = $(".audio-wrapper");
let res = [];
searchResults.each((i, el) => {
const mainAnchor = $(el).find(".item-audiosubmission");
const title = mainAnchor.attr("title") ?? "";
const link = mainAnchor.attr("href")?.trim() ?? "";
const id = link.substring(link.lastIndexOf("/") + 1);
const thumbnail = mainAnchor.find(".item-icon img").attr("src") ?? "";
const artist = mainAnchor.find(".detail-title").find("strong").text().trim();
const short_description = mainAnchor.find(".detail-description").text().trim();
const metaSection = mainAnchor.find(".item-details-meta");
const scoreText = metaSection.find(".star-score").attr("title");
let score = null;
if (scoreText) {
const parts = scoreText.split(" ");
if (parts.length > 1) {
const scoreValue = parts[1].split("/")[0];
if (!isNaN(parseFloat(scoreValue))) {
score = parseFloat(scoreValue);
}
}
}
const ddElements = metaSection.find("dl dd");
let type;
let genre;
let views = null;
if (ddElements.length === 1) {
const viewsText = ddElements.text();
views = viewsText ? Number(viewsText.split(",").join("").replace(" Views", "")) : null;
}
else if (ddElements.length === 2) {
const viewsText = ddElements.eq(1).text();
views = viewsText ? Number(viewsText.split(",").join("").replace(" Views", "")) : null;
}
else if (ddElements.length >= 3) {
type = ddElements.eq(0).text();
genre = ddElements.eq(1).text();
const viewsText = ddElements.eq(2).text();
views = viewsText ? Number(viewsText.split(",").join("").replace(" Views", "")) : null;
}
res.push({
title: title,
link: link,
id: id,
thumbnail: thumbnail,
artist: artist,
short_description: short_description,
score: score,
type: type,
genre: genre,
views: views,
});
});
return res;
}
/**
* Get details of an audio.
* @param {string} id audio id
* @returns {Promise<Audio>}
*/
async getAudio(id) {
const $ = cheerio.load(await (await fetch(`https://www.newgrounds.com/audio/listen/${id}`)).text());
const url = `https://www.newgrounds.com/audio/listen/${id}`;
const icon = $("meta[property='og:image']").attr("content");
const title = $("meta[property='og:title']").attr("content");
const caption = $("meta[property='og:description']").attr("content");
const credits = [];
$(".authorlinks .item-user").each((_i, el) => {
const $el = $(el);
const artist = $el.find(".item-details-main h4 a").text().trim();
const url = $el.find(".item-details-main h4 a").attr("href");
const icon = $el.find(".user-icon-bordered image").attr("href");
credits.push({ artist, url, icon });
});
const info = {};
$("dl.sidestats").each((_dlIndex, dlElement) => {
const $dl = $(dlElement);
$dl.find("dt").each((_dtIndex, dtElement) => {
const infoKey = $(dtElement)
.text()
.toLowerCase()
.replace(/\s/g, "_")
.replace(":", "")
.trim();
const values = [];
let currentSibling = $(dtElement).next();
while (currentSibling.length > 0 &&
!currentSibling.is("dt") &&
currentSibling.parent().is($dl)) {
if (currentSibling.is("dd")) {
const clonedDd = currentSibling.clone();
clonedDd.find("script").remove();
clonedDd.find("br").replaceWith(" ");
clonedDd.find("a").each((_k, aEl) => {
$(aEl).replaceWith($(aEl).text());
});
clonedDd.find("span").each((_k, spanEl) => {
$(spanEl).replaceWith($(spanEl).text());
});
clonedDd.find("ul li").each((_k, liEl) => {
const liText = $(liEl).text().trim().replace(/\s+/g, " ");
if (liText) {
values.push(liText);
}
});
let value = clonedDd.text().trim();
value = value.replace(/\s+/g, " ").trim();
if (!clonedDd.hasClass("tags") && value) {
values.push(value);
}
}
currentSibling = currentSibling.next();
}
if (infoKey === "tags" && values.length > 0) {
info[infoKey] = values.join(", ");
}
else if (values.length > 1) {
info[infoKey] = values;
}
else if (values.length === 1) {
info[infoKey] = values[0];
}
else {
info[infoKey] = undefined;
}
});
});
if (info.listens) {
info.listens = Number(info.listens.replace(/,/g, ""));
}
if (info.faves) {
info.faves = {
count: Number(info.faves.replace(/,/g, "")),
view_url: $("#faves_load").attr("href"),
};
}
if (info.downloads) {
info.downloads = Number(info.downloads.replace(/,/g, ""));
}
if (info.votes) {
info.votes = Number(info.votes.replace(/,/g, ""));
}
if (info.score && info.score.includes("/")) {
info.score = Number(info.score.split("/")[0]);
}
if (info.uploaded) {
info.uploaded = new Date(info.uploaded.join(" ")).toISOString();
}
if (info.genre) {
info.genre = {
id: new URL($(".sidestats.flex-1 dd a").attr("href") || "").searchParams.get("genre"),
name: info.genre,
browse_url: $(".sidestats.flex-1 dd a").attr("href"),
};
}
if (info.file_info) {
info.file_info = {
type: info.file_info[0],
size: info.file_info[1],
duration: (0, index_js_1.convertTimeFormatToSeconds)(info.file_info[2]),
};
}
if (info.tags) {
info.tags = info.tags.split(", ");
}
const trophies = [];
$("ul.trophies li").each((_i, el) => {
const $el = $(el);
const title = $el.find("strong").text().trim();
const time = $el.find("div.flex-1").contents().filter(function () {
return this.nodeType === 3;
}).text().trim();
const url = $el.find("a").attr("href");
trophies.push({ title, time, url: url ? `https://www.newgrounds.com${url}` : undefined });
});
const appearances = {
label: $("ul.itemlist.alternating > li > span").text(),
url: $("ul.itemlist.alternating > li > span > a").attr("href"),
};
const related = [];
$("div.pod-body.audio-view > ul > li").each((i, e) => {
const $e = $(e);
const url = $e.find("a.item-link").attr("href");
const id = url?.substring(url?.lastIndexOf("/") + 1);
const title = $e.find("a.item-link > h4 > span").eq(0).text().trim();
const artist = $e.find("a.item-link > h4 span strong").text().trim();
related.push({
id: id,
title: title,
url: url,
artist: artist,
});
});
const licensing_terms = (0, index_js_1.convertHtmlToMarkdown)($("div#creative_commons .pod-body.creative-commons").html() ?? "");
const audio = {
rating: $("div[itemprop='itemReviewed'] > h2").attr("class")?.split("-")[1].toUpperCase(),
download_url: $("a.icon-download").attr("href"),
file_url: $("meta[property='og:audio']").attr("content"),
share_url: $("a.icon-share").attr("href"),
};
const author_comments = (0, index_js_1.convertHtmlToMarkdown)($("div#author_comments").html() ?? "");
const reviews = [];
$(".pod-body.review").each((_i, el) => {
const $el = $(el);
const user = $el.find(".review-meta div a span").text().trim();
const time = $el.find(".review-meta time").text().trim();
const content = $el.find(".review-body.text-content p").text().trim();
const scoreText = $el.find(".review-meta .score .star-score").attr("title");
let rated_score = 0;
if (scoreText) {
const match = scoreText.match(/Score: (\d+\.?\d*)\/5\.00/);
if (match && match[1]) {
rated_score = parseFloat(match[1]);
}
}
reviews.push({ user, time, content, rated_score });
});
return new Audio_js_1.Audio(this, {
id: id,
title: title,
caption: caption,
url: url,
icon: icon,
credits: credits,
info: {
listens: info.listens,
faves: info.faves,
downloads: info.downloads,
votes: info.votes,
score: info.score,
uploaded: info.uploaded,
genre: info.genre,
file_info: info.file_info,
tags: info.tags,
trophies: trophies,
},
appearances: appearances,
related: related,
licensing_terms: licensing_terms,
audio: audio,
author_comments: author_comments,
reviews: reviews,
});
}
/**
* Get details of a playlist.
* @param {string} id playlist id
* @returns {Promise<Playlist>}
*/
async getPlaylist(id) {
const browser = await puppeteer_1.default.launch();
const page = await browser.newPage();
await page.setViewport({
width: 1280,
height: 720,
});
await page.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36");
await page.goto(`https://www.newgrounds.com/playlist/${id}`, {
waitUntil: ["networkidle0", "networkidle2"],
});
const playlistData = await page.evaluate(function () {
const urlPath = window.location.pathname;
const playlistIdMatch = urlPath.match(/^\/playlist\/([^\/]+\/[^\/]+)/);
const playlistId = playlistIdMatch ? playlistIdMatch[1] : "";
// Extract playlist title
const playlistTitleElem = document.querySelector("#playlist_outer .pod-head h2");
const playlistTitle = playlistTitleElem ? playlistTitleElem.textContent?.trim() : "";
const iconElem = document.querySelector("img.playlist-icon-large");
const playlistIcon = iconElem ? iconElem.src : "";
const authorElem = document.querySelector("ul.authorlinks li div.item-user");
let author = "";
let authorIcon = "";
let authorId = "";
let authorUrl = "";
if (authorElem) {
const authorLink = authorElem.querySelector("a.item-icon");
if (authorLink) {
authorUrl = authorLink.href || "";
try {
const urlObj = new URL(authorUrl);
authorId = "";
}
catch (e) {
authorId = "";
}
let iconImg = authorLink.querySelector("img");
if (!iconImg) {
const svgImage = authorLink.querySelector("svg image");
authorIcon = svgImage
? svgImage.getAttribute("href") || svgImage.getAttribute("xlink:href") || ""
: "";
}
else {
authorIcon = iconImg.src;
}
}
const authorNameElem = authorElem.querySelector("h4 a");
author = authorNameElem ? authorNameElem.textContent?.trim() : "";
}
const playlistItems = [];
const list = document.querySelector("#playlist_list");
if (!list) {
return {
playlistId,
playlistIcon,
author,
authorIcon,
authorId,
authorUrl,
items: playlistItems,
};
}
const items = list.querySelectorAll("li");
items.forEach((li) => {
const audioWrapperElem = li.querySelector(".audio-wrapper");
if (!audioWrapperElem)
return;
const link = audioWrapperElem.querySelector("a.item-audiosubmission");
if (!link)
return;
const url = link.href;
const title = link.getAttribute("title") || "";
const authorElem = link.querySelector(".item-details-main .detail-title span strong");
const author = authorElem ? authorElem.textContent?.trim() : "";
const descriptionElem = link.querySelector(".detail-description");
const description = descriptionElem ? descriptionElem.textContent?.trim() : "";
const viewsElem = link.querySelector(".item-details-meta dl dd:nth-child(3)");
const views = viewsElem ? viewsElem.textContent?.trim().replace("Views", "").replace(",", "") : "";
const scoreElem = link.querySelector(".star-score");
let score = "";
if (scoreElem && scoreElem.title) {
score = scoreElem.title.replace("Score: ", "").trim().split("/")[0];
}
const genreElem = link.querySelector(".item-details-meta dl dd:nth-child(2)");
const genre = genreElem ? genreElem.textContent?.trim() : "";
let id = "";
try {
const urlObj = new URL(url);
const pathParts = urlObj.pathname.split("/");
if (pathParts.length >= 4) {
id = pathParts[3];
}
}
catch (e) {
id = "";
}
const iconElem = link.querySelector(".item-icon img");
const icon = iconElem ? iconElem.src : "";
playlistItems.push({
id,
title,
author,
description,
url,
views: views !== "" ? Number(views) : null,
score: score !== "" ? Number(score) : null,
genre: genre !== "" ? genre : undefined,
icon,
});
});
return {
playlistId,
playlistTitle,
playlistIcon,
author,
authorIcon,
authorUrl,
items: playlistItems,
};
});
await browser.close();
return new Playlist_js_1.Playlist(this, {
id: playlistData.playlistId,
title: playlistData.playlistTitle,
url: `https://www.newgrounds.com/playlist/${id}`,
thumbnail: playlistData.playlistIcon,
author: {
name: playlistData.author,
url: playlistData.authorUrl,
icon: playlistData.authorIcon,
},
items: playlistData.items,
});
}
}
exports.Newgrounds = Newgrounds;
//# sourceMappingURL=Newgrounds.js.map