132 lines
3.5 KiB
JavaScript
132 lines
3.5 KiB
JavaScript
if (process.env.D!="BUG") console.debug = () => {};
|
|
import "./util.js";
|
|
import Koa from "koa";
|
|
import Router from "@koa/router";
|
|
import send from "koa-send";
|
|
import { cachedVRCYoutubeSearch } from "./VRCYoutubeSearch.js"
|
|
import { getImageSheet } from "./imagesheet.js";
|
|
import { resolveVrcUrl } from "./vrcurl.js";
|
|
import { getVideoCaptionsCached } from "./youtube-captions.js";
|
|
import { stringToBoolean } from "./util.js";
|
|
import shorturlmap from "./shorturlmap.json" assert { type: "json" };
|
|
|
|
var app = new Koa();
|
|
var router = new Router();
|
|
|
|
|
|
router.get(["/search", "/trending"], async ctx => {
|
|
if (ctx.path == "/trending") {
|
|
var query = {"type":"trending"};
|
|
} else {
|
|
var query = ctx.querystring.match(/[?&]input=(.*)/i)?.[1];
|
|
if (!query) {
|
|
ctx.status = 400;
|
|
ctx.body = "missing search query";
|
|
return;
|
|
}
|
|
query = decodeURIComponent(query).replace(/^.*→/, '').trim();
|
|
}
|
|
|
|
if (!ctx.query.pool || !/^[a-z-_]+\d*$/.test(ctx.query.pool)) {
|
|
ctx.status = 400;
|
|
ctx.body = "invalid pool";
|
|
return;
|
|
}
|
|
|
|
var options = {
|
|
thumbnails: stringToBoolean(ctx.query.thumbnails),
|
|
icons: stringToBoolean(ctx.query.icons),
|
|
captions: stringToBoolean(ctx.query.captions)
|
|
};
|
|
|
|
ctx.body = await cachedVRCYoutubeSearch(ctx.query.pool, query, options);
|
|
});
|
|
|
|
|
|
router.get("/vrcurl/:pool/:num", async ctx => {
|
|
var dest = await resolveVrcUrl(ctx.params.pool, ctx.params.num);
|
|
if (!dest) {
|
|
ctx.status = 404;
|
|
return;
|
|
}
|
|
switch (dest.type) {
|
|
case "redirect":
|
|
ctx.redirect(dest.url);
|
|
break;
|
|
case "imagesheet":
|
|
let buf = await getImageSheet(ctx.params.pool, ctx.params.num);
|
|
if (!buf) {
|
|
ctx.status = 404;
|
|
return;
|
|
}
|
|
ctx.body = buf;
|
|
ctx.type = "image/png";
|
|
break;
|
|
case "continuation":
|
|
ctx.body = await cachedVRCYoutubeSearch(ctx.params.pool, {type: "continuation", for: dest.for, continuationData: dest.continuationData}, dest.options);
|
|
break;
|
|
case "trending":
|
|
ctx.body = await cachedVRCYoutubeSearch(ctx.params.pool, {type: "trending", bp: dest.bp}, dest.options);
|
|
break;
|
|
case "captions":
|
|
ctx.body = await getVideoCaptionsCached(dest.videoId);
|
|
break;
|
|
default:
|
|
console.error("unknown vrcurl type", dest.type);
|
|
ctx.status = 500;
|
|
}
|
|
});
|
|
|
|
|
|
router.get("/robots.txt", ctx => {
|
|
ctx.body = `User-agent: *\nDisallow: /`;
|
|
});
|
|
|
|
router.get("/test.html", async ctx => {
|
|
await send(ctx, "test.html");
|
|
});
|
|
|
|
router.get("/", ctx => {
|
|
ctx.redirect("https://www.u2b.cx/");
|
|
});
|
|
|
|
|
|
|
|
|
|
|
|
// short urls to work around https://feedback.vrchat.com/udon/p/vrcurlinputfield-incorrect-focus-issue-on-quest
|
|
app.use(async (ctx, next) => {
|
|
var subdomain = ctx.hostname.match(/(.*).u2b.cx$/i)?.[1];
|
|
if (subdomain && !["api","api2","dev"].includes(subdomain)) {
|
|
if (shorturlmap[subdomain]) {
|
|
ctx.url = shorturlmap[subdomain] + ctx.url.slice(1);
|
|
} else {
|
|
ctx.status = 404;
|
|
return;
|
|
}
|
|
}
|
|
await next();
|
|
});
|
|
|
|
// work around vrchat json parser bug https://feedback.vrchat.com/udon/p/braces-inside-strings-in-vrcjson-can-fail-to-deserialize
|
|
app.use(async (ctx, next) => {
|
|
await next();
|
|
if (ctx.type != "application/json") return;
|
|
ctx.body = structuredClone(ctx.body);
|
|
(function iterateObject(obj) {
|
|
for (var key in obj) {
|
|
if (typeof obj[key] == "string") {
|
|
obj[key] = obj[key].replace(/[\[\]{}]/g, chr => "\\u" + chr.charCodeAt(0).toString(16).padStart(4, '0'));
|
|
} else if (typeof obj[key] == "object") {
|
|
iterateObject(obj[key]);
|
|
}
|
|
}
|
|
})(ctx.body);
|
|
ctx.body = JSON.stringify(ctx.body).replaceAll("\\\\u", "\\u");
|
|
});
|
|
|
|
|
|
app.use(router.routes());
|
|
app.use(router.allowedMethods());
|
|
|
|
app.listen(process.env.PORT || 8142, process.env.ADDRESS); |