blaze/index.ts
2023-06-21 02:26:24 +02:00

190 lines
5.1 KiB
TypeScript

import express from "express";
import { Readability, isProbablyReaderable } from "@mozilla/readability";
import got from "got";
import path from "path";
import { fileURLToPath } from "url";
import "dotenv/config";
import { parseHTML, parseJSON } from "linkedom";
// @ts-ignore
import XHR2 from "xhr2";
const XMLHttpRequest = XHR2.XMLHttpRequest;
import { minify } from "html-minifier";
import { blazeUrl, injectBlazeToPageLinks } from "./utils.js";
const app = express();
const port = 8888;
const minifierOptions = {
collapseWhitespace: true,
removeComments: true,
removeOptionalTags: true,
removeRedundantAttributes: true,
removeScriptTypeAttributes: true,
removeTagWhitespace: true,
useShortDoctype: true,
minifyCSS: true,
};
// @ts-ignore
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
app.get("/", async (req, res) => {
const searchEngine = "https://api.search.brave.com/res/v1/web/search";
const query = req.query.q as string;
if (!query) {
return res.sendFile(path.join(__dirname, "/dist/index.html"));
}
const key = process.env.CYCLIC_BRAVE_KEY;
if (!key) {
throw new Error("No brave key found");
}
try {
const xhr = new XMLHttpRequest();
xhr.open("GET", `${searchEngine}?q=${query}&safesearch=moderate`, true);
xhr.setRequestHeader("Accept", "*/*");
xhr.setRequestHeader("X-Subscription-Token", key);
xhr.onreadystatechange = () => {
if (xhr.readyState !== 4) {
return;
}
if (xhr.status !== 200) {
console.error("XHR request failed:", xhr.status, xhr.statusText);
return;
}
const data = JSON.parse(xhr.responseText);
// @ts-ignore
const results = data.web.results.map(
(result: any) => `
<article>
<a href="${blazeUrl}/blazed?url=${result.url}">
<h2>${result.title}</h2>
</a>
<span>${result.meta_url.hostname}</span>
<p>${result.description}</p>
</article>
<hr />
`
);
const html = `
<html>
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Blaze - ${query}</title>
<style>
body {font-family:sans-serif}
h2 {margin-bottom:0}
span {font-size:.9rem}
</style>
</head>
<body>
${results.join("")}
</body>
</html>
`;
const minifiedSerp = minify(html, minifierOptions);
res.send(minifiedSerp);
};
xhr.send();
} catch (err) {
console.error(err);
}
});
app.get("/blazed", async (req, res) => {
const pageToBlaze = req.query.url as string;
try {
const response = await got(pageToBlaze, {
headers: { Accept: "text/html" },
});
const { document } = parseHTML(response.body);
// TODO: missing handling of 404. The idea is to send the blaze 404 page, otherwise will show error page on client
if (!isProbablyReaderable(document)) {
// TODO: send minimalized version of the page instead the read mode
// implementation draft:
// document.querySelectorAll("link").forEach((l) => {
// l.remove();
// });
// document.querySelectorAll("style").forEach((s) => {
// s.remove;
// });
// document.querySelectorAll("script").forEach((s) => {
// s.remove();
// });
// // @ts-ignore
// const jsonDocument = document.toJSON();
// const cleanDocument = parseJSON(jsonDocument);
// return res.send(document.toString());
return res.sendFile(path.join(__dirname, "/dist/not_blazed.html"));
}
//TODO: find if there are more performant ways to remove images or evaluate if is the case to remove images
document.querySelectorAll("img").forEach((img) => img.remove());
const reader = new Readability(document);
const article = reader.parse();
if (!article) {
return res.send("Something went wrong");
}
const blazedPage = `<html><head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<style>body {font-family: sans-serif}</style>
</head>
<body>
${article.content}
<script>
${injectBlazeToPageLinks}
const url = "${blazeUrl}"
const currentUrl = "${req.query.url}"
injectBlazeToPageLinks(url, currentUrl)
</script>
</body></html>
`;
const minifiedBlazedPage = minify(blazedPage, minifierOptions);
res.send(minifiedBlazedPage);
} catch (err) {
console.log(err);
}
});
app.get("/info", (_, res) => {
res.sendFile(path.join(__dirname + "/dist/info.html"));
});
app.get("/ooops", (_, res) => {
res.sendFile(path.join(__dirname + "/dist/info_not_blazed.html"));
});
app.get("/favicon.svg", (_, res) => {
res.sendFile(path.join(__dirname + "/favicon.svg"));
});
app.listen(port, () => {
console.log(`Got request`);
});