diff --git a/index.js b/index.js index e73ee23..07380c2 100644 --- a/index.js +++ b/index.js @@ -6,17 +6,9 @@ import cors from "cors"; import errorPage from "./lib/errorPage.js"; import morgan from "morgan"; import { detector } from "megalodon"; -import helmet from "helmet"; const app = Express(); -app.use( - helmet({ - contentSecurityPolicy: false, - crossOriginEmbedderPolicy: false, - crossOriginResourcePolicy: false, - frameguard: false, - }) -); +app.disable("x-powered-by"); const logger = morgan(":method :url :status via :referrer - :response-time ms"); @@ -44,13 +36,17 @@ app.get("/api/feed", cors(), logger, function (req, res) { return; } - const userUrl = feedUrl.replace(/\.atom.*/i, ""); + let userUrl = ""; + + if (typeof feedUrl === "string") { + userUrl = feedUrl.replace(/\.atom.*/i, ""); + } const redirectUrl = "/api/v1/feed?"; const qs = ["userurl=" + encodeURIComponent(userUrl), "api=v1"]; ["size", "theme", "boosts", "replies"].forEach((key) => { - if (typeof req.query[key] != "undefined") { + if (typeof req.query[key] !== "undefined") { qs.push(key + "=" + encodeURIComponent(req.query[key])); } }); @@ -64,78 +60,43 @@ app.get("/api/v1/feed", cors(), logger, async function (req, res) { // userUrl let type = req.query.instance_type; let userUrl = req.query.userurl; - if (userUrl === "" || userUrl === undefined) { + if (!userUrl) { const user = req.query.user; const instance = req.query.instance; - if (type === "" || type === undefined) { + if (!type) { type = await detector(instance).catch(() => ""); } - if (type === "mastodon" || type === "pleroma") - userUrl = instance + "/users/" + user; - else if (type === "misskey") userUrl = instance + "/@" + user; - else { - res - .status(400) - .send(errorPage(400, "You need to specify a user URL", null)); - return; + switch (type) { + case "mastodon": + case "pleroma": + userUrl = instance + "/users/" + user; + break; + case "misskey": + userUrl = instance + "/@" + user; + break; + default: + res + .status(400) + .send(errorPage(400, "You need to specify a user URL", null)); + return; } } const feedUrl = req.query.feedurl; const opts = {}; - if (req.query.size) { - opts.size = req.query.size; - } - if (req.query.theme) { - opts.theme = req.query.theme; - if (opts.theme === "auto-auto") { - switch (type) { - case "mastodon": - opts.theme = "masto-auto"; - break; - case "pleroma": - opts.theme = "pleroma"; - break; - case "misskey": - opts.theme = "misskey-auto"; - break; - default: - break; - } - } else if (opts.theme === "auto-light") { - switch (type) { - case "mastodon": - opts.theme = "masto-light"; - break; - case "misskey": - opts.theme = "misskey-light"; - break; - case "pleroma": - opts.theme = "pleroma-light"; - break; - default: - break; - } - } else if (opts.theme === "auto-dark") { - switch (type) { - case "mastodon": - opts.theme = "masto-dark"; - break; - case "misskey": - opts.theme = "misskey-dark"; - break; - case "pleroma": - opts.theme = "pleroma-dark"; - break; - default: - break; - } - } - } - if (req.query.header) opts.header = req.query.header.toLowerCase() === "true"; - if (req.query.boosts) opts.boosts = req.query.boosts.toLowerCase() === "true"; - if (req.query.replies) + opts.size = req.query.size; + + opts.theme = req.query.theme; + + opts.theme = opts.theme.replace("auto-", `${type}-`); + opts.theme ??= "auto-auto"; + + if (typeof req.query.header === "string") + opts.header = req.query.header.toLowerCase() === "true"; + if (typeof req.query.boosts === "string") + opts.boosts = req.query.boosts.toLowerCase() === "true"; + if (typeof req.query.replies === "string") opts.replies = req.query.replies.toLowerCase() === "true"; opts.instance_type = type; opts.userUrl = userUrl; @@ -152,10 +113,28 @@ app.get("/api/v1/feed", cors(), logger, async function (req, res) { .status(500) .send(errorPage(500, null, { theme: opts.theme, size: opts.size })); // TODO log the error - console.error(er, er.stack); + console.error("error:", er, er.stack); }); }); +// eslint-disable-next-line no-unused-vars +app.use(function (req, res, _next) { + // respond with html page + if (req.accepts("html")) { + res.status(404).send("Not found"); + return; + } + + // respond with json + if (req.accepts("json")) { + res.status(404).json({ error: "Not found" }); + return; + } + + // default to plain-text. send() + res.status(404).type("txt").send("Not found"); +}); + app.listen(process.env.PORT || 8000, "127.0.0.1", function () { console.log("Server started, listening on " + (process.env.PORT || 8000)); }); diff --git a/lib/convert.js b/lib/convert.js index 9c5944b..37e3275 100644 --- a/lib/convert.js +++ b/lib/convert.js @@ -38,18 +38,15 @@ async function promiseSome(proms) { prom // it's already been called .then(resolve) .catch(() => { - // console.warn(e);// for debugging resolve(null); }); }); } - return await Promise.all(proms.map(noRejectWrap)); + return Promise.all(proms.map(noRejectWrap)); } export default async function (opts) { - // let opts = opts; - let feedUrl = opts.feedUrl; let userUrl = opts.userUrl; let isIndex = false; @@ -72,7 +69,7 @@ export default async function (opts) { let outbox = await apGet(user.outbox); // outbox.first can be a string for a URL, or an object with stuffs in it - if (typeof outbox.first == "object") { + if (typeof outbox.first === "object") { feed = outbox.first; } else { feed = await apGet(outbox.first); @@ -107,9 +104,9 @@ async function itemsForFeed(opts, user, feed) { // yes, I have to fetch all the fucking boosts for this whole feed apparently >:/ let boostData = []; let boostUrls = feed.orderedItems - .filter((i) => i.type == "Announce") + .filter((i) => i.type === "Announce") .map((i) => i.object); - // console.log(boostUrls); + boostData = await promiseSome(boostUrls.map(apGet)); // now get user data for each of those @@ -126,8 +123,6 @@ async function itemsForFeed(opts, user, feed) { // some URLs may have failed but IDGAF - // console.log(boostData[0]); - boostData.forEach((boostToot) => { if (!boostToot) { // failed request @@ -137,31 +132,31 @@ async function itemsForFeed(opts, user, feed) { // inject in-place into items let index = -1; - for (var i = 0; i < items.length; i++) { - if (items[i].object == boostToot.id) { + for (let i = 0; i < items.length; i++) { + if (items[i].object === boostToot.id) { index = i; break; } } - if (index == -1) { + if (index === -1) { console.warn(`warning: couldn't match boost to item: ${boostToot}`); return; } boostToot.object = boostToot; // this lets the later stage parser access object without errors :) - items[i] = boostToot; + items[index] = boostToot; }); } return items .filter((item) => { - return typeof item.object == "object"; // handle weird cases + return typeof item.object === "object"; // handle weird cases }) .map((item) => { let enclosures = (item.object.attachment || []) .filter((a) => { - return a.type == "Document"; + return a.type === "Document"; }) .map((a) => { return { @@ -173,6 +168,18 @@ async function itemsForFeed(opts, user, feed) { let op = item._userdata ? item._userdata : user; + let content = + item.object && item.object.content ? item.object.content : ""; //TODO sanitize then render without entity escapes + + item.object.tag.forEach((tag) => { + if (tag.type === "Emoji") { + content = content.replace( + tag.name, + `` + ); + } + }); + return { isBoost: !!item._userdata, title: item._userdata @@ -183,7 +190,7 @@ async function itemsForFeed(opts, user, feed) { isReply: !!(item.object && item.object.inReplyTo), hasCw: item.object.sensitive || false, cw: item.object.summary, - content: item.object && item.object.content ? item.object.content : "", //TODO sanitize then render without entity escapes + content: content, atomHref: item.published ? item.published.replace(/\W+/g, "") : Math.random().toString().replace("./g", ""), // used for IDs @@ -202,7 +209,7 @@ async function itemsForFeed(opts, user, feed) { }); } -function getNextPage(opts, user, feed) { +function getNextPage(opts, _user, feed) { //based on feed.next if (!feed.next) { return null; @@ -220,7 +227,7 @@ function getNextPage(opts, user, feed) { // add other params to the end ["theme", "header", "size", "boosts", "replies"].forEach((k) => { - if (typeof opts[k] != "undefined") { + if (typeof opts[k] !== "undefined") { ret += `&${k}=${opts[k].toString()}`; } }); @@ -230,7 +237,6 @@ function getNextPage(opts, user, feed) { // utilities below function getTimeDisplay(d) { - // let d = d; if (typeof d !== "object") { d = new Date(d); } diff --git a/lib/errorPage.js b/lib/errorPage.js index 57b9785..ba0d688 100644 --- a/lib/errorPage.js +++ b/lib/errorPage.js @@ -6,7 +6,7 @@ export default function (code, message, displayOptions) { let msg; // const displayOptions = displayOptions || {}; - if (code == 500 && !message) { + if (code === 500 && !message) { msg = "
Sorry, we are having trouble fetching posts for this user. Please try again later.
If the issue persists, please open an issue on Gitea, or message sam@froth.zone
"; } else { diff --git a/lib/template.ejs b/lib/template.ejs index fcfee6c..f477131 100644 --- a/lib/template.ejs +++ b/lib/template.ejs @@ -1,21 +1,22 @@ - + +