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, + `${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 @@ - + + This is an iFrame how did you get here - <% if (opts.theme && opts.theme.toLowerCase() == 'masto-light'){ %> + <% if (opts.theme && opts.theme?.toLowerCase() === 'masto-light' || opts.theme?.toLowerCase() === 'mastodon-light'){ %> - <% } else if (opts.theme && opts.theme.toLowerCase() == 'masto-auto'){ %> + <% } else if (opts.theme && opts.theme?.toLowerCase() === 'masto-auto' || opts.theme?.toLowerCase() === 'mastodon-auto'){ %> - <% } else if (opts.theme && opts.theme.toLowerCase() == 'misskey-dark'){ %> + <% } else if (opts.theme && opts.theme?.toLowerCase() === 'misskey-dark' ){ %> - <% } else if (opts.theme && opts.theme.toLowerCase() == 'misskey-light'){ %> + <% } else if (opts.theme && opts.theme?.toLowerCase() === 'misskey-light'){ %> - <% } else if (opts.theme && opts.theme.toLowerCase() == 'misskey-auto'){ %> + <% } else if (opts.theme && opts.theme?.toLowerCase() === 'misskey-auto'){ %> - <% } else if (opts.theme && opts.theme.toLowerCase() == 'pleroma'){ %> + <% } else if (opts.theme && opts.theme?.toLowerCase() === 'pleroma'){ %> <% } else { %> @@ -36,7 +37,7 @@
<% if (meta.avatar){ %> - + avatar <% } %>
@@ -60,7 +61,7 @@ <% } %>
- + avatar
<%= item.author.displayName %> @@ -127,13 +128,13 @@ path: function(){ // need to query this DOM my damn self let pageLinks = document.querySelectorAll('.hacky_link'); - if (!pageLinks || pageLinks.length == 0){ + if (!pageLinks || pageLinks.length === 0){ console.log ('next page link could not be found'); return false; }else{ let finalLink = pageLinks[pageLinks.length-1].href; // make sure we don't load the same page twice - if (!finalLink || finalLink == window.location.href || finalLink == lastPageLoaded){ + if (!finalLink || finalLink === window.location.href || finalLink === lastPageLoaded){ console.log('this was the last page'); return false; }else{ diff --git a/package.json b/package.json index bb0028e..3a6eb83 100644 --- a/package.json +++ b/package.json @@ -4,8 +4,6 @@ "cors": "2.8.5", "ejs": "3.1.8", "express": "4.18.1", - "feedparser": "2.2.10", - "helmet": "5.1.1", "megalodon": "4.1.0", "morgan": "1.10.0", "serve-static": "1.15.0", diff --git a/public/css/masto-auto.css b/public/css/masto-auto.css index 17e21a0..6eb589c 100644 --- a/public/css/masto-auto.css +++ b/public/css/masto-auto.css @@ -381,3 +381,11 @@ body { display: none; } } + +img { + vertical-align: middle; +} + +.item-content img { + margin: 0; +} diff --git a/public/css/masto-dark.css b/public/css/masto-dark.css index f9fda69..899bdd6 100644 --- a/public/css/masto-dark.css +++ b/public/css/masto-dark.css @@ -197,3 +197,15 @@ input[type="checkbox"]:not(:checked) ~ label::after { input[type="checkbox"]:not(:checked) ~ div { display: none; } + +img { + vertical-align: middle; +} + +img .item-content { + margin: 0; +} + +.item-content img { + margin: 0; +} diff --git a/public/css/masto-light.css b/public/css/masto-light.css index 8da7b9d..a3ac690 100644 --- a/public/css/masto-light.css +++ b/public/css/masto-light.css @@ -205,3 +205,11 @@ html, body { font-weight: normal; } + +img { + vertical-align: middle; +} + +.item-content img { + margin: 0; +} diff --git a/public/css/misskey-auto.css b/public/css/misskey-auto.css index 4d70160..08bd504 100644 --- a/public/css/misskey-auto.css +++ b/public/css/misskey-auto.css @@ -426,3 +426,11 @@ body { display: none; } } + +img { + vertical-align: middle; +} + +.item-content img { + margin: 0; +} diff --git a/public/css/misskey-dark.css b/public/css/misskey-dark.css index 5f82cf1..73ca395 100644 --- a/public/css/misskey-dark.css +++ b/public/css/misskey-dark.css @@ -207,3 +207,11 @@ input[type="checkbox"]:not(:checked) ~ label::after { input[type="checkbox"]:not(:checked) ~ div { display: none; } + +img { + vertical-align: middle; +} + +.item-content img { + margin: 0; +} diff --git a/public/css/misskey-light.css b/public/css/misskey-light.css index 37e4744..eefa618 100644 --- a/public/css/misskey-light.css +++ b/public/css/misskey-light.css @@ -214,3 +214,11 @@ html, body { font-weight: normal; } + +img { + vertical-align: middle; +} + +.item-content img { + margin: 0; +} diff --git a/public/css/pleroma.css b/public/css/pleroma.css index 6676cbf..482e4c1 100644 --- a/public/css/pleroma.css +++ b/public/css/pleroma.css @@ -197,3 +197,11 @@ input[type="checkbox"]:not(:checked) ~ label::after { input[type="checkbox"]:not(:checked) ~ div { display: none; } + +img { + vertical-align: middle; +} + +.item-content img { + margin: 0; +} diff --git a/public/css/stylesheet.css b/public/css/stylesheet.css index c347176..c3dcc64 100644 --- a/public/css/stylesheet.css +++ b/public/css/stylesheet.css @@ -204,3 +204,11 @@ button:hover, a:hover { background-color: #394150; } + +img { + vertical-align: middle; +} + +.item-content img { + margin: 0; +} diff --git a/public/index.html b/public/index.html index 3fb1697..39c326e 100644 --- a/public/index.html +++ b/public/index.html @@ -12,7 +12,7 @@

Fedifeed

-

Embedded ActivityPub feeds for blogs, websites, etc.

+

Embedded Mastodon/Pleroma/Misskey feeds for blogs, websites, etc.

Fork on Gitea auto-auto (depends on instance, automatic) -