Compare commits
1 commit
Author | SHA1 | Date | |
---|---|---|---|
0d485bc015 |
29 changed files with 75 additions and 1075 deletions
1
.env
Normal file
1
.env
Normal file
|
@ -0,0 +1 @@
|
||||||
|
PORT=3000
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,3 +1,2 @@
|
||||||
node_modules
|
node_modules
|
||||||
abtmtr.db
|
abtmtr.db
|
||||||
.env
|
|
Binary file not shown.
Before Width: | Height: | Size: 184 B |
|
@ -7,7 +7,7 @@ h1, h2, h3, h4, h5, h6, p, li, br {
|
||||||
|
|
||||||
h1, h2, h3, h4, h5, h6 {
|
h1, h2, h3, h4, h5, h6 {
|
||||||
margin-block: 1.5rem;
|
margin-block: 1.5rem;
|
||||||
padding-inline: 1ch;
|
padding-inline: 0.5rem;
|
||||||
font-size: 1rem;
|
font-size: 1rem;
|
||||||
background-color: var(--color-text);
|
background-color: var(--color-text);
|
||||||
color: var(--color-bg);
|
color: var(--color-bg);
|
||||||
|
@ -19,20 +19,14 @@ h3, h4, h5, h6 {
|
||||||
|
|
||||||
h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {
|
h1 a, h2 a, h3 a, h4 a, h5 a, h6 a {
|
||||||
display: block;
|
display: block;
|
||||||
margin-inline: -1ch;
|
margin-inline: -0.5rem;
|
||||||
padding-inline: 1ch;
|
padding-inline: 0.5rem;
|
||||||
background-color: var(--color-accent);
|
background-color: var(--color-accent);
|
||||||
color: var(--color-bg);
|
color: var(--color-bg);
|
||||||
}
|
}
|
||||||
|
|
||||||
a.noblock {
|
|
||||||
display: inline;
|
|
||||||
margin-inline: 0;
|
|
||||||
padding-block: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin-inline: 2ch;
|
margin-inline-start: 1rem;
|
||||||
margin-block: 1.5rem;
|
margin-block: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,7 +36,7 @@ p.nomargin {
|
||||||
|
|
||||||
ul {
|
ul {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding-inline-start: 4ch;
|
padding-inline-start: 2rem;
|
||||||
list-style-type: "- ";
|
list-style-type: "- ";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -50,26 +44,13 @@ ul li {
|
||||||
margin-block: 1.5rem;
|
margin-block: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
details > summary {
|
div {
|
||||||
list-style-type: '► ';
|
|
||||||
}
|
|
||||||
|
|
||||||
details[open] > summary {
|
|
||||||
list-style-type: '▼ ';
|
|
||||||
}
|
|
||||||
|
|
||||||
summary > h1, summary > h2, summary > h3, summary > h4, summary > h5, summary > h6 {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
section {
|
|
||||||
margin-block: 1.5em;
|
margin-block: 1.5em;
|
||||||
margin-inline-start: 2ch;
|
margin-inline-start: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
img {
|
img {
|
||||||
vertical-align: middle;
|
vertical-align: middle;
|
||||||
background-color: #fff8;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
a {
|
a {
|
||||||
|
@ -79,44 +60,29 @@ a {
|
||||||
|
|
||||||
code {
|
code {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
max-width: 100%;
|
|
||||||
box-sizing: border-box;
|
|
||||||
font: inherit;
|
font: inherit;
|
||||||
background-color: var(--color-code);
|
background-color: var(--color-code);
|
||||||
padding-inline: 1ch;
|
padding-inline: 0.5rem;
|
||||||
color: var(--color-code-text);
|
color: var(--color-code-text);
|
||||||
user-select: all;
|
user-select: all;
|
||||||
word-break: break-word;
|
|
||||||
overflow: auto;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button, input, textarea, select {
|
button, input {
|
||||||
border: none;
|
border: none;
|
||||||
background: transparent;
|
background: transparent;
|
||||||
color: inherit;
|
color: inherit;
|
||||||
font: inherit;
|
font: inherit;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
line-height: 1.5rem;
|
outline: none;
|
||||||
resize: none;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
select {
|
input[type=text], input[type=url] {
|
||||||
height: 1.5rem;
|
padding-inline: 0.5rem;
|
||||||
}
|
border-inline: 0.5em solid var(--color-accent);
|
||||||
|
|
||||||
:focus {
|
|
||||||
outline: 1ch solid var(--color-text);
|
|
||||||
}
|
|
||||||
|
|
||||||
input[type=text], input[type=url], textarea, select {
|
|
||||||
padding-inline: 1ch;
|
|
||||||
border-inline: 1ch solid var(--color-accent);
|
|
||||||
background-color: var(--color-bg-scrim);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
button, input[type=submit] {
|
button, input[type=submit] {
|
||||||
padding-inline: 1ch;
|
padding-inline: 0.5rem;
|
||||||
background-color: var(--color-accent);
|
background-color: var(--color-accent);
|
||||||
color: var(--color-bg);
|
color: var(--color-bg);
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
@ -126,34 +92,3 @@ button:active, input[type=submit]:active {
|
||||||
background-color: transparent;
|
background-color: transparent;
|
||||||
color: var(--color-accent);
|
color: var(--color-accent);
|
||||||
}
|
}
|
||||||
|
|
||||||
marquee {
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
|
|
||||||
.clearfix:after {
|
|
||||||
content: "";
|
|
||||||
display: table;
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
|
|
||||||
progress {
|
|
||||||
border: none;
|
|
||||||
border-radius: 0;
|
|
||||||
border-inline: 1ch solid var(--color-text);
|
|
||||||
background-color: var(--color-bg-scrim);
|
|
||||||
height: 1.5rem;
|
|
||||||
vertical-align: top;
|
|
||||||
}
|
|
||||||
progress::-webkit-progress-bar {
|
|
||||||
border: none;
|
|
||||||
border-radius: 0;
|
|
||||||
border-inline: 1ch solid var(--color-text);
|
|
||||||
background-color: var(--color-bg-scrim);
|
|
||||||
}
|
|
||||||
progress::-webkit-progress-value {
|
|
||||||
background-color: var(--color-accent);
|
|
||||||
}
|
|
||||||
progress::-moz-progress-bar {
|
|
||||||
background-color: var(--color-accent);
|
|
||||||
}
|
|
|
@ -5,5 +5,5 @@
|
||||||
}
|
}
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--font-main: "dos", monospace;
|
--font-main: "dos";
|
||||||
}
|
}
|
BIN
assets/matkap/me.jpg
Normal file
BIN
assets/matkap/me.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 137 KiB |
|
@ -22,7 +22,8 @@ html {
|
||||||
|
|
||||||
body {
|
body {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0.1px 2ch;
|
padding: 0.1px 1rem;
|
||||||
|
padding-block-end: 1.5rem;
|
||||||
min-height: 100%;
|
min-height: 100%;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
@ -31,29 +32,3 @@ body {
|
||||||
color: var(--color-text);
|
color: var(--color-text);
|
||||||
font-family: var(--font-main);
|
font-family: var(--font-main);
|
||||||
}
|
}
|
||||||
|
|
||||||
.matkap_ascii {
|
|
||||||
display: inline-block;
|
|
||||||
position: fixed;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
width: 80ch;
|
|
||||||
white-space: pre;
|
|
||||||
user-select: none;
|
|
||||||
pointer-events: none;
|
|
||||||
/* z-index: -1; */
|
|
||||||
opacity: 0.1;
|
|
||||||
line-height: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.entity_box {
|
|
||||||
margin: 3rem 1ch;
|
|
||||||
padding-inline: 1ch;
|
|
||||||
padding-block: 0.1px;
|
|
||||||
/* border: 1ch 1rem solid var(--color-accent); */
|
|
||||||
border-image: url("/assets/accents/9s_chr.png");
|
|
||||||
border-image-slice: 12 6;
|
|
||||||
border-image-width: 1rem 1ch;
|
|
||||||
border-image-repeat: stretch;
|
|
||||||
border-image-outset: 1.25rem 1ch;
|
|
||||||
}
|
|
492
index.js
492
index.js
|
@ -5,13 +5,6 @@ import SQLite from 'better-sqlite3';
|
||||||
import { Kysely, SqliteDialect } from 'kysely';
|
import { Kysely, SqliteDialect } from 'kysely';
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { JSDOM } from "jsdom";
|
import { JSDOM } from "jsdom";
|
||||||
import Parser from "rss-parser";
|
|
||||||
import nacl from "tweetnacl";
|
|
||||||
import expressBasicAuth from "express-basic-auth";
|
|
||||||
import { getRelativeTime } from "./modules/relativeTime.js";
|
|
||||||
import { fromHex } from "./modules/fromHex.js";
|
|
||||||
|
|
||||||
import sitemap from "./sitemap.json" assert { type: "json" };
|
|
||||||
|
|
||||||
dotenvConfig();
|
dotenvConfig();
|
||||||
|
|
||||||
|
@ -25,35 +18,27 @@ const db = new Kysely({
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
const rssParser = new Parser();
|
|
||||||
|
|
||||||
rawDB.exec(`CREATE TABLE IF NOT EXISTS blurbs( 'id' TEXT, 'site' TEXT, 'blurb' TEXT, 'verified' INTEGER, 'time' INTEGER );`);
|
rawDB.exec(`CREATE TABLE IF NOT EXISTS blurbs( 'id' TEXT, 'site' TEXT, 'blurb' TEXT, 'verified' INTEGER, 'time' INTEGER );`);
|
||||||
rawDB.exec(`CREATE TABLE IF NOT EXISTS mininq( 'id' TEXT, 'name' TEXT, 'ip' TEXT, 'msg' TEXT, 'url' TEXT, 'public' INTEGER, 'reply' TEXT, 'time' INTEGER );`);
|
|
||||||
rawDB.exec(`CREATE TABLE IF NOT EXISTS blacklist( 'domain' TEXT );`);
|
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
|
|
||||||
|
|
||||||
app.set('trust proxy', true);
|
|
||||||
app.set('view engine', 'ejs');
|
app.set('view engine', 'ejs');
|
||||||
app.use('/assets', express.static('assets'));
|
app.use('/assets', express.static('assets'));
|
||||||
app.set('views', path.join(__dirname, "views", "pages"));
|
app.set('views', path.join(__dirname, "views", "pages"));
|
||||||
app.use(express.urlencoded({
|
|
||||||
extended: true
|
|
||||||
}))
|
|
||||||
|
|
||||||
app.locals.siteMap = sitemap;
|
// app.use(async (req, res, next) => {
|
||||||
app.locals.curCommit = "0000000000";
|
// const buttonsJson = await fetch("https://cdn.abtmtr.link/site_content/buttons.json")
|
||||||
|
// .catch(() => res.status(500).send())
|
||||||
async function getCurCommit() {
|
// .then((res) => res.json())
|
||||||
const curCommit = await fetch("https://git.abtmtr.link/api/v1/repos/MeowcaTheoRange/abtmtr-v13/branches/main")
|
// .catch(() => res.status(500).send());
|
||||||
.catch(() => res.status(500).send())
|
// const followingJson = await fetch("https://cdn.abtmtr.link/site_content/following.json")
|
||||||
.then((res) => res.json());
|
// .catch(() => res.status(500).send())
|
||||||
|
// .then((res) => res.json())
|
||||||
app.locals.curCommit = curCommit.commit.id.substr(0, 10);
|
// .catch(() => res.status(500).send());
|
||||||
}
|
// app.locals.buttons = buttonsJson;
|
||||||
|
// app.locals.following = followingJson;
|
||||||
getCurCommit();
|
// next();
|
||||||
|
// })
|
||||||
|
|
||||||
app.get('/', async (req, res) => {
|
app.get('/', async (req, res) => {
|
||||||
const statusesJson = await fetch("https://cdn.abtmtr.link/site_content/v13/statuses.json")
|
const statusesJson = await fetch("https://cdn.abtmtr.link/site_content/v13/statuses.json")
|
||||||
|
@ -90,400 +75,9 @@ app.get('/about', async (req, res) => {
|
||||||
const linksJson = await fetch("https://cdn.abtmtr.link/site_content/v13/links.json")
|
const linksJson = await fetch("https://cdn.abtmtr.link/site_content/v13/links.json")
|
||||||
.catch(() => res.status(500).send())
|
.catch(() => res.status(500).send())
|
||||||
.then((res) => res.json());
|
.then((res) => res.json());
|
||||||
const currentlyListeningJson = await fetch(`https://ws.audioscrobbler.com/2.0/?method=user.getrecenttracks&user=MeowcaTheoRange&api_key=${process.env.LFM_API_KEY}&format=json&limit=2&extended=1`)
|
|
||||||
.catch(() => res.status(500).send())
|
|
||||||
.then((res) => res.json());
|
|
||||||
|
|
||||||
res.render('about', {
|
res.render('about', {
|
||||||
links: linksJson,
|
links: linksJson
|
||||||
cl: currentlyListeningJson.recenttracks.track[0],
|
|
||||||
getRelativeTime
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('/characters', async (req, res) => {
|
|
||||||
const charactersJson = await fetch("https://cdn.abtmtr.link/site_content/v13/characters.json")
|
|
||||||
.catch(() => res.status(500).send())
|
|
||||||
.then((res) => res.json());
|
|
||||||
|
|
||||||
res.render('characters', { characters: charactersJson });
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('/mininq', async (req, res) => {
|
|
||||||
const mailbox = await db
|
|
||||||
.selectFrom('mininq')
|
|
||||||
.select('id')
|
|
||||||
.where('public', '=', 0)
|
|
||||||
.where('url', '=', '')
|
|
||||||
.orderBy('time', "desc")
|
|
||||||
.limit(process.env.MININQ_MAILBOX_MAX)
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
const verifiedMailbox = await db
|
|
||||||
.selectFrom('mininq')
|
|
||||||
.select('id')
|
|
||||||
.where('public', '=', 0)
|
|
||||||
.where('url', '!=', '')
|
|
||||||
.orderBy('time', "desc")
|
|
||||||
.limit(process.env.MININQ_VERIFIED_MAILBOX_MAX)
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
const isIpAlreadyMininq = await db
|
|
||||||
.selectFrom('mininq')
|
|
||||||
.select('ip')
|
|
||||||
.where('ip', '=', req.ip)
|
|
||||||
.where('public', '=', 0)
|
|
||||||
.executeTakeFirst();
|
|
||||||
|
|
||||||
const { publicKey, secretKey } = nacl.box.keyPair();
|
|
||||||
|
|
||||||
const data = await db
|
|
||||||
.selectFrom('mininq')
|
|
||||||
.selectAll()
|
|
||||||
.where('public', '=', 1)
|
|
||||||
.orderBy('time', "desc")
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
res.render('mininq', {
|
|
||||||
keypair: {
|
|
||||||
publicKey: Buffer.from(publicKey).toString('hex'),
|
|
||||||
secretKey: Buffer.from(secretKey).toString('hex'),
|
|
||||||
},
|
|
||||||
data,
|
|
||||||
mailboxCount: mailbox.length,
|
|
||||||
isIpAlreadyMininq,
|
|
||||||
mailboxVerifiedCount: verifiedMailbox.length,
|
|
||||||
mailboxMaximum: process.env.MININQ_MAILBOX_MAX,
|
|
||||||
mailboxVerifiedMaximum: process.env.MININQ_VERIFIED_MAILBOX_MAX
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
async function checkIsMtr(user, pass, cb) {
|
|
||||||
try {
|
|
||||||
let publicKeyWorks = await checkPublicKey({
|
|
||||||
skey: pass,
|
|
||||||
pkeyurl: process.env.VERIF_URL
|
|
||||||
});
|
|
||||||
|
|
||||||
return cb(null, publicKeyWorks);
|
|
||||||
} catch (err) {
|
|
||||||
return cb(null, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
app.get('/mininq/mbox', expressBasicAuth({
|
|
||||||
authorizer: checkIsMtr,
|
|
||||||
authorizeAsync: true,
|
|
||||||
challenge: true
|
|
||||||
}), async (req, res) => {
|
|
||||||
const mailbox = await db
|
|
||||||
.selectFrom('mininq')
|
|
||||||
.selectAll()
|
|
||||||
.where('public', '=', 0)
|
|
||||||
.where('url', '=', '')
|
|
||||||
.orderBy('time', "desc")
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
const verifiedMailbox = await db
|
|
||||||
.selectFrom('mininq')
|
|
||||||
.selectAll()
|
|
||||||
.where('public', '=', 0)
|
|
||||||
.where('url', '!=', '')
|
|
||||||
.orderBy('time', "desc")
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
res.render('mininqbox', {
|
|
||||||
mail: verifiedMailbox.concat(mailbox),
|
|
||||||
mailboxCount: mailbox.length,
|
|
||||||
mailboxVerifiedCount: verifiedMailbox.length,
|
|
||||||
mailboxMaximum: process.env.MININQ_MAILBOX_MAX,
|
|
||||||
mailboxVerifiedMaximum: process.env.MININQ_VERIFIED_MAILBOX_MAX
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
app.post('/mininq/mbox/r', async (req, res) => {
|
|
||||||
const body = req.body;
|
|
||||||
|
|
||||||
if (typeof body.skey != "string") return res.status(400).send("skey is not a string");
|
|
||||||
if (body.skey.length != 64) return res.status(400).send("skey must be a valid secret key");
|
|
||||||
|
|
||||||
try {
|
|
||||||
let publicKeyWorks = await checkPublicKey({
|
|
||||||
skey: body.skey,
|
|
||||||
pkeyurl: process.env.VERIF_URL
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!publicKeyWorks) return res.status(401).send("No?");
|
|
||||||
} catch (err) {
|
|
||||||
return res.status(400).send(err.message);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof body.id != "string") return res.status(400).send("id is not a string");
|
|
||||||
if (body.id.length != 32) return res.status(400).send("id must be a mininq ID");
|
|
||||||
|
|
||||||
if (typeof body.action != "string") return res.status(400).send("action is not a string");
|
|
||||||
if (body.action.length > 10) return res.status(400).send("action must be valid");
|
|
||||||
|
|
||||||
switch (body.action) {
|
|
||||||
case "public":
|
|
||||||
if (process.env.MININQ_WEBHOOK != null) {
|
|
||||||
const mininq_pub = await db
|
|
||||||
.selectFrom('mininq')
|
|
||||||
.selectAll()
|
|
||||||
.where('id', '=', body.id)
|
|
||||||
.executeTakeFirst();
|
|
||||||
|
|
||||||
await fetch(process.env.MININQ_WEBHOOK, {
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify({
|
|
||||||
"content": "New mininq:",
|
|
||||||
"embeds": [
|
|
||||||
{
|
|
||||||
"description": mininq_pub.msg,
|
|
||||||
"color": 12298956,
|
|
||||||
// TODO: make this smaller
|
|
||||||
"author": mininq_pub.url != '' ? {
|
|
||||||
"name": `${mininq_pub.name} @ ${new URL(mininq_pub.url).host} sent:`,
|
|
||||||
"url": `https://abtmtr.link/mininq/#${mininq.id}`
|
|
||||||
} : {
|
|
||||||
"name": `${mininq_pub.name} sent:`,
|
|
||||||
"url": `https://abtmtr.link/mininq/#${mininq.id}`
|
|
||||||
},
|
|
||||||
"timestamp": new Date(mininq_pub.time).toISOString()
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"attachments": []
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await db
|
|
||||||
.updateTable('mininq')
|
|
||||||
.set({
|
|
||||||
public: 1
|
|
||||||
})
|
|
||||||
.where('id', '=', body.id)
|
|
||||||
.where('public', '=', 0)
|
|
||||||
.executeTakeFirst();
|
|
||||||
break;
|
|
||||||
case "delete":
|
|
||||||
await db
|
|
||||||
.deleteFrom('mininq')
|
|
||||||
.where('id', '=', body.id)
|
|
||||||
.executeTakeFirst();
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
if (typeof body.msg != "string") return res.status(400).send("msg is not a string");
|
|
||||||
if (body.msg.length < 1) return res.status(400).send("msg is required");
|
|
||||||
if (body.msg.length > 4000) return res.status(400).send("msg must not be longer than 4000 characters");
|
|
||||||
|
|
||||||
if (process.env.MININQ_WEBHOOK != null) {
|
|
||||||
const mininq = await db
|
|
||||||
.selectFrom('mininq')
|
|
||||||
.selectAll()
|
|
||||||
.where('id', '=', body.id)
|
|
||||||
.executeTakeFirst();
|
|
||||||
|
|
||||||
await fetch(process.env.MININQ_WEBHOOK, {
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify({
|
|
||||||
"content": "New mininq:",
|
|
||||||
"embeds": [
|
|
||||||
{
|
|
||||||
"description": mininq.msg,
|
|
||||||
"color": 12298956,
|
|
||||||
"author": mininq.url != '' ? {
|
|
||||||
"name": `${mininq.name} @ ${new URL(mininq.url).host} sent:`,
|
|
||||||
"url": `https://abtmtr.link/mininq/#${mininq.id}`
|
|
||||||
} : {
|
|
||||||
"name": `${mininq.name} sent:`
|
|
||||||
},
|
|
||||||
"timestamp": new Date(mininq.time).toISOString()
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"description": body.msg,
|
|
||||||
"color": 13421772,
|
|
||||||
"author": {
|
|
||||||
"name": "sysadmin reply:"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"attachments": []
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
await db
|
|
||||||
.updateTable('mininq')
|
|
||||||
.set({
|
|
||||||
public: 1,
|
|
||||||
reply: body.msg
|
|
||||||
})
|
|
||||||
.where('id', '=', body.id)
|
|
||||||
.where('public', '=', 0)
|
|
||||||
.executeTakeFirst();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
res.redirect('/mininq/mbox');
|
|
||||||
});
|
|
||||||
|
|
||||||
async function checkPublicKey(body) {
|
|
||||||
if (typeof body.skey != "string") throw ("skey is not a string");
|
|
||||||
if (body.skey.length != 64) throw ("skey must be a valid secret key");
|
|
||||||
|
|
||||||
if (body.pkeyurl.length < 1) throw ("pkeyurl is required");
|
|
||||||
if (body.pkeyurl.length > 512) throw ("pkeyurl must not be longer than 512 characters");
|
|
||||||
const site = await fetch(body.pkeyurl).then(async x => ({ s: x.status, t: await x.text() })).catch(_ => _);
|
|
||||||
if (site.s != 200) throw ("pkeyurl's site is not online");
|
|
||||||
try {
|
|
||||||
const domain = new URL(body.pkeyurl).hostname;
|
|
||||||
const isBadSite = await db
|
|
||||||
.selectFrom('blacklist')
|
|
||||||
.selectAll()
|
|
||||||
.where('domain', '=', domain)
|
|
||||||
.executeTakeFirst();
|
|
||||||
|
|
||||||
if (isBadSite != null)
|
|
||||||
throw ({
|
|
||||||
error: "Forbidden",
|
|
||||||
message: "\u{1f595} (or don't authenticate, i guess)"
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
throw ({
|
|
||||||
error: "Internal Server Error",
|
|
||||||
message: "URL CONSTRUCTOR FAILED???? i think this might be your fault...."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
let relationMeta;
|
|
||||||
try {
|
|
||||||
let webDom = new JSDOM(site.t);
|
|
||||||
relationMeta = webDom.window.document.querySelector(
|
|
||||||
`meta[name="abtmtr-mininq-key"]`
|
|
||||||
);
|
|
||||||
} catch (err) {
|
|
||||||
throw ("Something went wrong parsing your site's DOM");
|
|
||||||
}
|
|
||||||
let publicKey;
|
|
||||||
if (relationMeta != null) publicKey = relationMeta.content;
|
|
||||||
else throw ("Can't find public key");
|
|
||||||
|
|
||||||
let pkMatches;
|
|
||||||
try {
|
|
||||||
const generatedPublicKey = nacl.box.keyPair.fromSecretKey(fromHex(body.skey));
|
|
||||||
pkMatches = Buffer.from(generatedPublicKey.publicKey).toString('hex') == publicKey;
|
|
||||||
} catch (err) {
|
|
||||||
throw ("Something went wrong verifying your keypair");
|
|
||||||
}
|
|
||||||
|
|
||||||
return pkMatches;
|
|
||||||
}
|
|
||||||
|
|
||||||
app.post('/mininq/send', async (req, res) => {
|
|
||||||
|
|
||||||
const isIpAlreadyMininq = await db
|
|
||||||
.selectFrom('mininq')
|
|
||||||
.select('ip')
|
|
||||||
.where('ip', '=', req.ip)
|
|
||||||
.where('public', '=', 0)
|
|
||||||
.executeTakeFirst();
|
|
||||||
|
|
||||||
if (isIpAlreadyMininq != null) return res.status(403).send("IP is already in mailbox");
|
|
||||||
|
|
||||||
const body = req.body;
|
|
||||||
|
|
||||||
let newObject = {
|
|
||||||
url: '',
|
|
||||||
id: nanoid(32),
|
|
||||||
public: 0,
|
|
||||||
reply: '',
|
|
||||||
ip: req.ip
|
|
||||||
};
|
|
||||||
|
|
||||||
if (typeof body.name != "string") return res.status(400).send("name is not a string");
|
|
||||||
if (body.name.length < 1) return res.status(400).send("name is required");
|
|
||||||
if (body.name.length > 40) return res.status(400).send("name must not be longer than 40 characters");
|
|
||||||
|
|
||||||
newObject.name = body.name;
|
|
||||||
|
|
||||||
if (typeof body.msg != "string") return res.status(400).send("msg is not a string");
|
|
||||||
if (body.msg.length < 1) return res.status(400).send("msg is required");
|
|
||||||
if (body.msg.length > 2000) return res.status(400).send("msg must not be longer than 2000 characters");
|
|
||||||
|
|
||||||
newObject.msg = body.msg;
|
|
||||||
|
|
||||||
if (typeof body.pkeyurl == "string") {
|
|
||||||
try {
|
|
||||||
let publicKeyWorks = await checkPublicKey(body);
|
|
||||||
|
|
||||||
if (publicKeyWorks) {
|
|
||||||
newObject.url = body.pkeyurl;
|
|
||||||
|
|
||||||
const isUrlAlreadyMininq = await db
|
|
||||||
.selectFrom('mininq')
|
|
||||||
.select('url')
|
|
||||||
.where('url', '=', newObject.url)
|
|
||||||
.where('public', '=', 0)
|
|
||||||
.executeTakeFirst();
|
|
||||||
|
|
||||||
if (isUrlAlreadyMininq != null) return res.status(403).send("Pubkey is already in mailbox");
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
// do nothing lol
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const mailbox = await db
|
|
||||||
.selectFrom('mininq')
|
|
||||||
.select('id')
|
|
||||||
.where('public', '=', 0)
|
|
||||||
.where('url', '=', '')
|
|
||||||
.orderBy('time', "desc")
|
|
||||||
.limit(process.env.MININQ_MAILBOX_MAX)
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
const verifiedMailbox = await db
|
|
||||||
.selectFrom('mininq')
|
|
||||||
.select('id')
|
|
||||||
.where('public', '=', 0)
|
|
||||||
.where('url', '!=', '')
|
|
||||||
.orderBy('time', "desc")
|
|
||||||
.limit(process.env.MININQ_VERIFIED_MAILBOX_MAX)
|
|
||||||
.execute();
|
|
||||||
|
|
||||||
if (newObject.url) {
|
|
||||||
if (verifiedMailbox.length >= process.env.MININQ_VERIFIED_MAILBOX_MAX)
|
|
||||||
return res.status(403).send("Mailbox is full");
|
|
||||||
} else {
|
|
||||||
if (mailbox.length >= process.env.MININQ_MAILBOX_MAX)
|
|
||||||
return res.status(403).send("Mailbox is full");
|
|
||||||
}
|
|
||||||
|
|
||||||
newObject.time = Date.now();
|
|
||||||
|
|
||||||
try {
|
|
||||||
await db.insertInto('mininq')
|
|
||||||
.values(newObject)
|
|
||||||
.executeTakeFirstOrThrow();
|
|
||||||
} catch (err) {
|
|
||||||
console.log(err)
|
|
||||||
return res.status(500).send('Internal Server Error');
|
|
||||||
}
|
|
||||||
|
|
||||||
res.redirect('/mininq/');
|
|
||||||
});
|
|
||||||
|
|
||||||
app.get('/updates', async (req, res) => {
|
|
||||||
const rssXML = await fetch("https://cdn.abtmtr.link/site_content/rss.xml")
|
|
||||||
.catch(() => res.status(500).send())
|
|
||||||
.then((res) => res.text());
|
|
||||||
|
|
||||||
const rssParsed = await rssParser.parseString(rssXML);
|
|
||||||
|
|
||||||
res.render('updates', {
|
|
||||||
rss: rssParsed
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -547,28 +141,7 @@ app.get('/blurbs/send', async (req, res) => {
|
||||||
message: errors.join(", ")
|
message: errors.join(", ")
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
|
||||||
const domain = new URL(body.site).hostname;
|
|
||||||
const isBadSite = await db
|
|
||||||
.selectFrom('blacklist')
|
|
||||||
.selectAll()
|
|
||||||
.where('domain', '=', domain)
|
|
||||||
.executeTakeFirst();
|
|
||||||
|
|
||||||
if (isBadSite != null)
|
|
||||||
return res.status(403).json({
|
|
||||||
error: "Forbidden",
|
|
||||||
message: "\u{1f595}"
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
return res.status(500).json({
|
|
||||||
error: "Internal Server Error",
|
|
||||||
message: "URL CONSTRUCTOR FAILED???? i think this might be your fault...."
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const postId = nanoid(32);
|
const postId = nanoid(32);
|
||||||
const timestamp = Date.now();
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await db.insertInto('blurbs')
|
await db.insertInto('blurbs')
|
||||||
|
@ -577,7 +150,7 @@ app.get('/blurbs/send', async (req, res) => {
|
||||||
site: body.site,
|
site: body.site,
|
||||||
blurb: body.text,
|
blurb: body.text,
|
||||||
verified: 0,
|
verified: 0,
|
||||||
time: timestamp
|
time: Date.now()
|
||||||
})
|
})
|
||||||
.executeTakeFirstOrThrow();
|
.executeTakeFirstOrThrow();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
@ -605,37 +178,14 @@ app.get('/blurbs/check', async (req, res) => {
|
||||||
const relationLink = dom.window.document.querySelector(
|
const relationLink = dom.window.document.querySelector(
|
||||||
`[rel=me][href="https://abtmtr.link/blurbs/#${blurbFromId.id}"]`
|
`[rel=me][href="https://abtmtr.link/blurbs/#${blurbFromId.id}"]`
|
||||||
);
|
);
|
||||||
if (relationLink != null && blurbFromId.verified != 1) {
|
if (relationLink != null) await db
|
||||||
await db
|
|
||||||
.updateTable('blurbs')
|
.updateTable('blurbs')
|
||||||
.set({
|
.set({
|
||||||
verified: 1
|
verified: 1
|
||||||
})
|
})
|
||||||
.where('id', '=', body.id)
|
.where('id', '=', body.id)
|
||||||
.executeTakeFirst();
|
.executeTakeFirst();
|
||||||
|
else {
|
||||||
if (process.env.BLURBS_WEBHOOK != null) {
|
|
||||||
await fetch(process.env.BLURBS_WEBHOOK, {
|
|
||||||
method: "POST",
|
|
||||||
headers: { "Content-Type": "application/json" },
|
|
||||||
body: JSON.stringify({
|
|
||||||
"content": "New blurb:",
|
|
||||||
"embeds": [
|
|
||||||
{
|
|
||||||
"description": blurbFromId.blurb,
|
|
||||||
"color": 12298956,
|
|
||||||
"author": {
|
|
||||||
"name": `${new URL(blurbFromId.site).host}`,
|
|
||||||
"url": `https://abtmtr.link/blurbs/#${blurbFromId.id}`
|
|
||||||
},
|
|
||||||
"timestamp": new Date(blurbFromId.time).toISOString()
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"attachments": []
|
|
||||||
})
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (relationLink == null) {
|
|
||||||
await db
|
await db
|
||||||
.updateTable('blurbs')
|
.updateTable('blurbs')
|
||||||
.set({
|
.set({
|
||||||
|
@ -654,14 +204,6 @@ app.get("/favicon.ico", (req, res) => {
|
||||||
res.redirect("https://cdn.abtmtr.link/site_content/favicon.ico")
|
res.redirect("https://cdn.abtmtr.link/site_content/favicon.ico")
|
||||||
})
|
})
|
||||||
|
|
||||||
app.get(["/rss", "/rss.xml"], (req, res) => {
|
|
||||||
res.redirect("https://cdn.abtmtr.link/site_content/rss.xml")
|
|
||||||
})
|
|
||||||
|
|
||||||
app.all('*', (req, res) => {
|
|
||||||
res.status(404).render('404');
|
|
||||||
})
|
|
||||||
|
|
||||||
app.listen(process.env.PORT, () => {
|
app.listen(process.env.PORT, () => {
|
||||||
const url = new URL("http://localhost/");
|
const url = new URL("http://localhost/");
|
||||||
url.port = process.env.PORT;
|
url.port = process.env.PORT;
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
const MAP_HEX = {
|
|
||||||
0: 0, 1: 1, 2: 2, 3: 3, 4: 4, 5: 5, 6: 6,
|
|
||||||
7: 7, 8: 8, 9: 9, a: 10, b: 11, c: 12, d: 13,
|
|
||||||
e: 14, f: 15, A: 10, B: 11, C: 12, D: 13,
|
|
||||||
E: 14, F: 15
|
|
||||||
};
|
|
||||||
|
|
||||||
export function fromHex(hexString) {
|
|
||||||
const bytes = new Uint8Array(Math.floor((hexString || "").length / 2));
|
|
||||||
let i;
|
|
||||||
for (i = 0; i < bytes.length; i++) {
|
|
||||||
const a = MAP_HEX[hexString[i * 2]];
|
|
||||||
const b = MAP_HEX[hexString[i * 2 + 1]];
|
|
||||||
if (a === undefined || b === undefined) {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
bytes[i] = (a << 4) | b;
|
|
||||||
}
|
|
||||||
return i === bytes.length ? bytes : bytes.slice(0, i);
|
|
||||||
}
|
|
|
@ -1,19 +0,0 @@
|
||||||
var units = {
|
|
||||||
year: 24 * 60 * 60 * 1000 * 365,
|
|
||||||
month: 24 * 60 * 60 * 1000 * 365 / 12,
|
|
||||||
day: 24 * 60 * 60 * 1000,
|
|
||||||
hour: 60 * 60 * 1000,
|
|
||||||
minute: 60 * 1000,
|
|
||||||
second: 1000
|
|
||||||
}
|
|
||||||
|
|
||||||
var rtf = new Intl.RelativeTimeFormat('en', { numeric: 'auto' })
|
|
||||||
|
|
||||||
export function getRelativeTime(d1, d2 = new Date()) {
|
|
||||||
var elapsed = d1 - d2
|
|
||||||
|
|
||||||
// "Math.abs" accounts for both "past" & "future" scenarios
|
|
||||||
for (var u in units)
|
|
||||||
if (Math.abs(elapsed) > units[u] || u == 'second')
|
|
||||||
return rtf.format(Math.round(elapsed / units[u]), u)
|
|
||||||
}
|
|
85
package-lock.json
generated
85
package-lock.json
generated
|
@ -13,13 +13,10 @@
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"ejs": "^3.1.10",
|
"ejs": "^3.1.10",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"express-basic-auth": "^1.2.1",
|
|
||||||
"jsdom": "^25.0.0",
|
"jsdom": "^25.0.0",
|
||||||
"kysely": "^0.27.4",
|
"kysely": "^0.27.4",
|
||||||
"nanoid": "^5.0.7",
|
"nanoid": "^5.0.7",
|
||||||
"pg": "^8.12.0",
|
"pg": "^8.12.0"
|
||||||
"rss-parser": "^3.13.0",
|
|
||||||
"tweetnacl": "^1.0.3"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"nodemon": "^3.1.4"
|
"nodemon": "^3.1.4"
|
||||||
|
@ -146,24 +143,6 @@
|
||||||
],
|
],
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/basic-auth": {
|
|
||||||
"version": "2.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz",
|
|
||||||
"integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"safe-buffer": "5.1.2"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">= 0.8"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/basic-auth/node_modules/safe-buffer": {
|
|
||||||
"version": "5.1.2",
|
|
||||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
|
|
||||||
"integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
|
|
||||||
"license": "MIT"
|
|
||||||
},
|
|
||||||
"node_modules/better-sqlite3": {
|
"node_modules/better-sqlite3": {
|
||||||
"version": "11.2.1",
|
"version": "11.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.2.1.tgz",
|
||||||
|
@ -700,15 +679,6 @@
|
||||||
"node": ">= 0.10.0"
|
"node": ">= 0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/express-basic-auth": {
|
|
||||||
"version": "1.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/express-basic-auth/-/express-basic-auth-1.2.1.tgz",
|
|
||||||
"integrity": "sha512-L6YQ1wQ/mNjVLAmK3AG1RK6VkokA1BIY6wmiH304Xtt/cLTps40EusZsU1Uop+v9lTDPxdtzbFmdXfFO3KEnwA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"basic-auth": "^2.0.1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/file-uri-to-path": {
|
"node_modules/file-uri-to-path": {
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
|
||||||
|
@ -1835,25 +1805,6 @@
|
||||||
"integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==",
|
"integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/rss-parser": {
|
|
||||||
"version": "3.13.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/rss-parser/-/rss-parser-3.13.0.tgz",
|
|
||||||
"integrity": "sha512-7jWUBV5yGN3rqMMj7CZufl/291QAhvrrGpDNE4k/02ZchL0npisiYYqULF71jCEKoIiHvK/Q2e6IkDwPziT7+w==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"entities": "^2.0.3",
|
|
||||||
"xml2js": "^0.5.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/rss-parser/node_modules/entities": {
|
|
||||||
"version": "2.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/entities/-/entities-2.2.0.tgz",
|
|
||||||
"integrity": "sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==",
|
|
||||||
"license": "BSD-2-Clause",
|
|
||||||
"funding": {
|
|
||||||
"url": "https://github.com/fb55/entities?sponsor=1"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/safe-buffer": {
|
"node_modules/safe-buffer": {
|
||||||
"version": "5.2.1",
|
"version": "5.2.1",
|
||||||
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
|
||||||
|
@ -1880,12 +1831,6 @@
|
||||||
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/sax": {
|
|
||||||
"version": "1.4.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
|
|
||||||
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
|
|
||||||
"license": "ISC"
|
|
||||||
},
|
|
||||||
"node_modules/saxes": {
|
"node_modules/saxes": {
|
||||||
"version": "6.0.0",
|
"version": "6.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
|
||||||
|
@ -2207,12 +2152,6 @@
|
||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/tweetnacl": {
|
|
||||||
"version": "1.0.3",
|
|
||||||
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz",
|
|
||||||
"integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==",
|
|
||||||
"license": "Unlicense"
|
|
||||||
},
|
|
||||||
"node_modules/type-is": {
|
"node_modules/type-is": {
|
||||||
"version": "1.6.18",
|
"version": "1.6.18",
|
||||||
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
|
||||||
|
@ -2388,28 +2327,6 @@
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/xml2js": {
|
|
||||||
"version": "0.5.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.5.0.tgz",
|
|
||||||
"integrity": "sha512-drPFnkQJik/O+uPKpqSgr22mpuFHqKdbS835iAQrUC73L2F5WkboIRd63ai/2Yg6I1jzifPFKH2NTK+cfglkIA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"dependencies": {
|
|
||||||
"sax": ">=0.6.0",
|
|
||||||
"xmlbuilder": "~11.0.0"
|
|
||||||
},
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/xmlbuilder": {
|
|
||||||
"version": "11.0.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-11.0.1.tgz",
|
|
||||||
"integrity": "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA==",
|
|
||||||
"license": "MIT",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=4.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/xmlchars": {
|
"node_modules/xmlchars": {
|
||||||
"version": "2.2.0",
|
"version": "2.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
|
||||||
|
|
|
@ -22,13 +22,10 @@
|
||||||
"dotenv": "^16.4.5",
|
"dotenv": "^16.4.5",
|
||||||
"ejs": "^3.1.10",
|
"ejs": "^3.1.10",
|
||||||
"express": "^4.19.2",
|
"express": "^4.19.2",
|
||||||
"express-basic-auth": "^1.2.1",
|
|
||||||
"jsdom": "^25.0.0",
|
"jsdom": "^25.0.0",
|
||||||
"kysely": "^0.27.4",
|
"kysely": "^0.27.4",
|
||||||
"nanoid": "^5.0.7",
|
"nanoid": "^5.0.7",
|
||||||
"pg": "^8.12.0",
|
"pg": "^8.12.0"
|
||||||
"rss-parser": "^3.13.0",
|
|
||||||
"tweetnacl": "^1.0.3"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"nodemon": "^3.1.4"
|
"nodemon": "^3.1.4"
|
||||||
|
|
30
sitemap.json
30
sitemap.json
|
@ -1,30 +0,0 @@
|
||||||
[
|
|
||||||
{
|
|
||||||
"link": "blurbs",
|
|
||||||
"description": "what are other sites saying about abtmtr.link?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"link": "characters",
|
|
||||||
"description": "about abtmtr.link's mascots and other such characters"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"link": "updates",
|
|
||||||
"description": "what's going on with abtmtr.link?"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"link": "mininq",
|
|
||||||
"description": "slowchat, for sysadmins who aren't quite equine."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"link": "servers",
|
|
||||||
"description": "about the services on abtmtr.link and the machines that serve them."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"link": "sites",
|
|
||||||
"description": "a collection of other sites in the form of 88x31s."
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"link": "about",
|
|
||||||
"description": "who runs abtmtr.link?"
|
|
||||||
}
|
|
||||||
]
|
|
|
@ -1,9 +1,6 @@
|
||||||
<footer>
|
<footer>
|
||||||
<h1>abtmtr.link v13-2 (c. <%= curCommit %>)</h1>
|
<h1>abtmtr.link v13-2</h1>
|
||||||
<p>(c) MeowcaTheoRange 2023-24</p>
|
<p class="nomargin">(c) MeowcaTheoRange 2023-24</p>
|
||||||
<p>
|
<p class="nomargin">licensed under the <a href="https://git.abtmtr.link/MeowcaTheoRange/KarkatPublicLicense/">KKPL</a>.</p>
|
||||||
licensed under the <a href="https://git.abtmtr.link/MeowcaTheoRange/KarkatPublicLicense/">KKPL</a> v2.2.<br />
|
<p class="nomargin">fork <a href="https://git.abtmtr.link/MeowcaTheoRange/abtmtr-v13/">MeowcaTheoRange/abtmtr-v13</a> with git</p>
|
||||||
fork <a href="https://git.abtmtr.link/MeowcaTheoRange/abtmtr-v13/">MeowcaTheoRange/abtmtr-v13</a> with git
|
|
||||||
</p>
|
|
||||||
<p>font is <a href="https://int10h.org/oldschool-pc-fonts/fontlist/font?dos-v_re_jpn12">DOS/V re. JPN12</a> from <a href="https://int10h.org/oldschool-pc-fonts/">THE OLDSCHOOL PC FONT RESOURCE</a></p>
|
|
||||||
</footer>
|
</footer>
|
|
@ -1,15 +0,0 @@
|
||||||
<h3>
|
|
||||||
<%= mininq.name %>
|
|
||||||
<% if (mininq.url != '') { %>
|
|
||||||
@ <a href="<%= mininq.url %>" target="_blank" class="noblock"><%= new URL(mininq.url).host %></a>
|
|
||||||
<% } %>
|
|
||||||
sent:
|
|
||||||
</h3>
|
|
||||||
<p style="white-space: pre-line;"><%= mininq.msg %></p>
|
|
||||||
<% if (mininq.reply != '') { %>
|
|
||||||
<section>
|
|
||||||
<h3>sysadmin reply:</h3>
|
|
||||||
<p style="white-space: pre-line;"><%= mininq.reply %></p>
|
|
||||||
</section>
|
|
||||||
<% } %>
|
|
||||||
<p style="opacity: 0.5;"><%= new Date(mininq.time).toLocaleString('en-us', { timeZone: "America/Chicago" }) %> Central Time</p>
|
|
|
@ -3,11 +3,5 @@
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||||
<title>abtmtr.link</title>
|
<title>abtmtr.link</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<meta name="abtmtr-mininq-key" content="cf28339198953cab0c635c03030fa23d63e88db92126fab5d358b7cb1aded015">
|
|
||||||
<link rel="stylesheet" href="/assets/style.css" />
|
<link rel="stylesheet" href="/assets/style.css" />
|
||||||
<link rel="me" href="https://abtmtr.link/about">
|
|
||||||
<link rel="me" href="https://abtmtr.link/blurbs/#Hx7CuB4_zIWMAsOZlyyqsUm2upXEEYEl">
|
|
||||||
<link rel="me" href="https://abtmtr.link/blurbs/#aQArHxCaViG-Qw0aFkBRhQWbRsoxUqsB">
|
|
||||||
<link rel="alternate" type="application/rss+xml" href="https://abtmtr.link/rss.xml" title="RSS Feed">
|
|
||||||
<link rel="alternate" type="application/rss+xml" href="https://abtmtr.link/rss" title="RSS Feed">
|
|
||||||
</head>
|
</head>
|
|
@ -1 +0,0 @@
|
||||||
<span class="matkap_ascii" aria-hidden="true" hidden><%- include("../misc/MATKAP_ASCII.txt") %></span>
|
|
|
@ -1,36 +0,0 @@
|
||||||
|
|
||||||
▄▄▄
|
|
||||||
▄▀. ▀██▄
|
|
||||||
▄█▀ ▀█▄
|
|
||||||
▄██▀ `██▄
|
|
||||||
_███└ ▀██▄
|
|
||||||
_▄__ ▄██▀ ▄_ ▀██▄
|
|
||||||
▀█_ ²T══█▄▄J_ ▐█▀▄≡ º██
|
|
||||||
▀▄ _²²T▀ `█▄» └█_ ▄
|
|
||||||
_▄▄æ═ ▀█░_ █▌═└█
|
|
||||||
,█▀▀ ▀██▄▄ ██░_ ▐▌░ █
|
|
||||||
█ ▄A▀└ ▀█▄v _ ▐▌░╤▄_
|
|
||||||
,█▀ ▀▀██░ █▌░ █`
|
|
||||||
▄▄██▄▄▄ _▄▄___ ▀▀█▄▄▄▄▄▄▄▀
|
|
||||||
▄██▀▀░░░░▀█▄ ▄████▀▀▀██▄ .,. ▀▀▀▀▀▀▀▀▀▀▀█
|
|
||||||
██▀░░░░░░░░░▀█_ ▄▄█▀▀░░░░░░░▀██▄ █_
|
|
||||||
╒██▄▄░▄▄▄▄▄▄▄▄██▄██▄▄▄▄▄▄░░░░░░██ █_
|
|
||||||
▄▄▄▄▄▄██ █▌ ███▌ '└└▀█▀└█r _ ▐█
|
|
||||||
███████ █▌ █▌█▌ ▀ ▐█∩ █▌ █▀
|
|
||||||
███████ ▀█▄═ _█▀º░█▄ ┌█▀ ██▄▄▄═T└ █▌
|
|
||||||
`█████▌ º░▀█▄▄J;¿▄█▀░º ░▀█▄ ▄▄▀░` _▄═██░ _██
|
|
||||||
╘███▀ º²╜▀░░²º `░░▀▀▀▀▀▀▀░ ███░ j█▀
|
|
||||||
╘██▄ `ººº ╒███░ ▀═▄▄██▀
|
|
||||||
▀██¿ ═ ▄▄███▄▄▄▄ ¡▄███─
|
|
||||||
▀██▄ _▄▀ ╓███░░ *▄▄▄████¡ ▐▌
|
|
||||||
└▀█▄_ ▄▄█▌ ⁿ▄▄████░∩..⌐─▀▀:░∩ █▌▌█
|
|
||||||
▀███▄_ _▄█▀▀└ _▄███▀░░░█░░░█░▀█æ╧▀ ▐█
|
|
||||||
└▀█████▄▄▄██▀▀└ _▄▄▄██▀█.▀█º░░▀▄_▐▌ ▀█∩
|
|
||||||
┌█▀ ,²▀▀██▀█████████▀▀▀▀▀▀█▄▌ ª█▄░ '. ▀█▄▄▄═▄
|
|
||||||
▀▀ ▄▄░░▀░░██░░░░░░░░░░░░░▄█░ █: └ █
|
|
||||||
j█▀ `ººº └▀█Ñ▄_░░░░░░░█▀ ▄"█▄▀▄Y▄ █▀
|
|
||||||
██ ▐▄_ ²▀▀▀▀╤███▀ ,█: ▀██▀▀▄▄ ══▀:
|
|
||||||
███ ²▀╤▄_ '' ¬▀_ ▀██.º█∩ █▌
|
|
||||||
,██¡ ▀█░░█▄ ╘█
|
|
||||||
█▀ ▀█▀ ▀▄_└█▄
|
|
||||||
█▀ ▀█ └└
|
|
|
@ -1,13 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<%- include("../components/page-head.ejs") %>
|
|
||||||
<body>
|
|
||||||
<main>
|
|
||||||
<h1>404 Not Found</h1>
|
|
||||||
<p>it seems the resource you're looking for doesn't exist.<br />try again later?</p>
|
|
||||||
<p><< <a href="/">go back home</a></p>
|
|
||||||
</main>
|
|
||||||
<%- include("../components/footer.ejs") %>
|
|
||||||
<%- include("../components/post-main.ejs") %>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -4,10 +4,9 @@
|
||||||
<body>
|
<body>
|
||||||
<main>
|
<main>
|
||||||
<h1>about</h1>
|
<h1>about</h1>
|
||||||
<p>who runs abtmtr.link?</p>
|
|
||||||
<h2>MeowcaTheoRange's h-card</h2>
|
|
||||||
<p>hey, i'm MeowcaTheoRange.<br />i run abtmtr.link, for the most part.</p>
|
<p>hey, i'm MeowcaTheoRange.<br />i run abtmtr.link, for the most part.</p>
|
||||||
<section class="h-card">
|
<h2>MeowcaTheoRange's h-card</h2>
|
||||||
|
<div class="h-card">
|
||||||
<h3><span class="p-name">Theo Range</span>
|
<h3><span class="p-name">Theo Range</span>
|
||||||
(<span class="p-nickname">MeowcaTheoRange</span>)</h3>
|
(<span class="p-nickname">MeowcaTheoRange</span>)</h3>
|
||||||
<p class="nomargin">(<span class="p-honorific-prefix">Mx.</span>
|
<p class="nomargin">(<span class="p-honorific-prefix">Mx.</span>
|
||||||
|
@ -17,7 +16,7 @@
|
||||||
<span class="p-gender-identity">Non-binary</span>,
|
<span class="p-gender-identity">Non-binary</span>,
|
||||||
<span class="p-pronouns">they/them</span>
|
<span class="p-pronouns">they/them</span>
|
||||||
</p>
|
</p>
|
||||||
<p><img class="u-photo" src="https://abtmtr.link/favicon.ico" height="72" width="72"></p>
|
<p><img class="u-photo" src="https://abtmtr.link/favicon.ico" height="72"></p>
|
||||||
<p class="nomargin">
|
<p class="nomargin">
|
||||||
<a class="u-url" href="https://abtmtr.link/">website</a>,
|
<a class="u-url" href="https://abtmtr.link/">website</a>,
|
||||||
<a class="u-email" href="mailto:me@abtmtr.link">email</a>
|
<a class="u-email" href="mailto:me@abtmtr.link">email</a>
|
||||||
|
@ -29,34 +28,7 @@
|
||||||
<span class="p-region">Minnesota</span>,
|
<span class="p-region">Minnesota</span>,
|
||||||
<span class="p-country-name">USA</span>
|
<span class="p-country-name">USA</span>
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</div>
|
||||||
<h2>about MeowcaTheoRange</h2>
|
|
||||||
<section class="entity_box">
|
|
||||||
<h3>cheesy nicknames</h3>
|
|
||||||
<p class="nomargin">none <a href="/mininq">(yet)</a></p>
|
|
||||||
<h3>favourite hobby</h3>
|
|
||||||
<p class="nomargin">server management</p>
|
|
||||||
<h3>favourite sport</h3>
|
|
||||||
<p class="nomargin">server management</p>
|
|
||||||
<h3>favourite website</h3>
|
|
||||||
<p class="nomargin">https://abtmtr.link</p>
|
|
||||||
<h3>favourite artists</h3>
|
|
||||||
<p class="nomargin">mostly HALLEY LABS, some dariacore</p>
|
|
||||||
<h3>flavour</h3>
|
|
||||||
<p class="nomargin">would NOT taste good</p>
|
|
||||||
</section>
|
|
||||||
<% if (cl["@attr"] != null && cl["@attr"].nowplaying) { %>
|
|
||||||
<h2>MeowcaTheoRange is currently listening to</h2>
|
|
||||||
<% } else { %>
|
|
||||||
<h2>MeowcaTheoRange last listened to (<%= getRelativeTime(cl.date.uts * 1000) %>)</h2>
|
|
||||||
<% } %>
|
|
||||||
<p style="float: inline-start; margin-block: 0; margin-inline-end: 2ch;"><img class="u-photo" src="<%= cl.image[2]["#text"] %>" height="72" width="72"></p>
|
|
||||||
<p class="clearfix">
|
|
||||||
<%= cl.name %><br />
|
|
||||||
<span style="opacity: 0.5;"><%= cl.album["#text"] %></span><br />
|
|
||||||
<%= cl.artist.name %><br />
|
|
||||||
<a href="<%= cl.url %>" target="_blank">See on Last.fm</a>
|
|
||||||
</p>
|
|
||||||
<h2>MeowcaTheoRange's links</h2>
|
<h2>MeowcaTheoRange's links</h2>
|
||||||
<p>where else is MeowcaTheoRange?</p>
|
<p>where else is MeowcaTheoRange?</p>
|
||||||
<ul>
|
<ul>
|
||||||
|
@ -68,6 +40,5 @@
|
||||||
</ul>
|
</ul>
|
||||||
</main>
|
</main>
|
||||||
<%- include("../components/footer.ejs") %>
|
<%- include("../components/footer.ejs") %>
|
||||||
<%- include("../components/post-main.ejs") %>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
|
@ -20,21 +20,15 @@
|
||||||
</p>
|
</p>
|
||||||
<p style="opacity: 0.5;">by clicking this button and successfully submitting a blurb, you agree that your input may be displayed on this site and stored on abtmtr.link servers.</p>
|
<p style="opacity: 0.5;">by clicking this button and successfully submitting a blurb, you agree that your input may be displayed on this site and stored on abtmtr.link servers.</p>
|
||||||
<p style="opacity: 0.5;">you'll also need to verify your blurb by leaving a X/HTML snippet on your site. further instructions will be given to do this after you submit your blurb. thanks!</p>
|
<p style="opacity: 0.5;">you'll also need to verify your blurb by leaving a X/HTML snippet on your site. further instructions will be given to do this after you submit your blurb. thanks!</p>
|
||||||
<p style="opacity: 0.5;">rules: no slurs, no politics, no nsfw, no spam. if you break these rules outside of plausible deniability, your domain will be blacklisted.</p>
|
|
||||||
</form>
|
</form>
|
||||||
<h2>current blurbs (<%= data.length %>)</h2>
|
<h2>current blurbs</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<% data.forEach((blurb) => { %>
|
<% data.forEach((blurb) => { %>
|
||||||
<li class="blurb" id="<%= blurb.id %>">
|
<li class="blurb" id="<%= blurb.id %>"><a href="<%= blurb.site %>" target="_blank"><%= new URL(blurb.site).host %></a>: <%= blurb.blurb %> <a class="removePopup" href="/blurbs/check?id=<%= blurb.id %>">remove</a></li>
|
||||||
<a href="<%= blurb.site %>" target="_blank"><%= new URL(blurb.site).host %></a><% if (blurb.time < 1724994580263) { %> <span style="opacity: 0.5;">(pre-v13-2)</span><% } %>:
|
|
||||||
<%= blurb.blurb %>
|
|
||||||
<a class="removePopup" href="/blurbs/check?id=<%= blurb.id %>">remove</a>
|
|
||||||
</li>
|
|
||||||
<% }) %>
|
<% }) %>
|
||||||
</ul>
|
</ul>
|
||||||
</main>
|
</main>
|
||||||
<%- include("../components/footer.ejs") %>
|
<%- include("../components/footer.ejs") %>
|
||||||
<%- include("../components/post-main.ejs") %>
|
|
||||||
<style>
|
<style>
|
||||||
.blurb .removePopup {
|
.blurb .removePopup {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
|
|
|
@ -21,7 +21,6 @@
|
||||||
<p style="opacity: 0.5;">can't/don't want to add the snippet? <a href="/about" target="_blank">contact me</a>.</p>
|
<p style="opacity: 0.5;">can't/don't want to add the snippet? <a href="/about" target="_blank">contact me</a>.</p>
|
||||||
</main>
|
</main>
|
||||||
<%- include("../components/footer.ejs") %>
|
<%- include("../components/footer.ejs") %>
|
||||||
<%- include("../components/post-main.ejs") %>
|
|
||||||
<style>
|
<style>
|
||||||
.blurb .removePopup {
|
.blurb .removePopup {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
|
|
|
@ -1,29 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<%- include("../components/page-head.ejs") %>
|
|
||||||
<body>
|
|
||||||
<main>
|
|
||||||
<h1>characters</h1>
|
|
||||||
<p>about abtmtr.link's mascots and other such characters</p>
|
|
||||||
<h2>entity index</h2>
|
|
||||||
<% characters.forEach((entity) => { %>
|
|
||||||
<section class="entity_box">
|
|
||||||
<p style="float: left; margin-inline: 0 2ch;" class="nomargin">
|
|
||||||
<img src="<%= entity.img %>" height="72" width="72">
|
|
||||||
</p>
|
|
||||||
<h3><%= entity.name.toLowerCase() %> / <%= entity.pronouns.toLowerCase() %></h3>
|
|
||||||
<p class="nomargin"><%= entity.age %> / <%= entity.species %></p>
|
|
||||||
<p class="nomargin"><%= entity.sexuality %> / <%= entity.gender %></p>
|
|
||||||
<p class="clearfix nomargin" style="opacity: 0.5;"><%= entity.blurb %></p>
|
|
||||||
<br />
|
|
||||||
<% entity.rows.forEach(([k, v]) => { %>
|
|
||||||
<h4><%= k %></h4>
|
|
||||||
<p class="nomargin"><%= v %></p>
|
|
||||||
<% }) %>
|
|
||||||
</section>
|
|
||||||
<% }) %>
|
|
||||||
</main>
|
|
||||||
<%- include("../components/footer.ejs") %>
|
|
||||||
<%- include("../components/post-main.ejs") %>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -7,15 +7,24 @@
|
||||||
<p>abtmtr.link is a domain for all kinds of services,<br />from web search to live-streaming.</p>
|
<p>abtmtr.link is a domain for all kinds of services,<br />from web search to live-streaming.</p>
|
||||||
<h2>site links</h2>
|
<h2>site links</h2>
|
||||||
<ul>
|
<ul>
|
||||||
<% siteMap.forEach((crumb) => { %>
|
|
||||||
<li>
|
<li>
|
||||||
<h3><a href="/<%= crumb.link %>/"><%= crumb.link %></a></h3>
|
<h3><a href="/blurbs/">blurbs</a></h3>
|
||||||
<p class="nomargin"><%= crumb.description %></p>
|
<p class="nomargin">what are other sites saying about abtmtr.link?</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<h3><a href="/servers/">servers</a></h3>
|
||||||
|
<p class="nomargin">about the services on abtmtr.link and the machines that serve them.</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<h3><a href="/sites/">sites</a></h3>
|
||||||
|
<p class="nomargin">a collection of other sites in the form of 88x31s.</p>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<h3><a href="/about/">about</a></h3>
|
||||||
|
<p class="nomargin">who runs abtmtr.link?</p>
|
||||||
</li>
|
</li>
|
||||||
<% }) %>
|
|
||||||
</ul>
|
</ul>
|
||||||
</main>
|
</main>
|
||||||
<%- include("../components/footer.ejs") %>
|
<%- include("../components/footer.ejs") %>
|
||||||
<%- include("../components/post-main.ejs") %>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
|
@ -1,86 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<%- include("../components/page-head.ejs") %>
|
|
||||||
<body>
|
|
||||||
<main>
|
|
||||||
<h1>mininq</h1>
|
|
||||||
<p>slowchat, for sysadmins who aren't quite equine.</p>
|
|
||||||
<h2>submit a mininq</h2>
|
|
||||||
<p>
|
|
||||||
MAILBOX:
|
|
||||||
<progress value="<%= mailboxCount %>" max="<%= mailboxMaximum %>"
|
|
||||||
style="width: <%= mailboxMaximum * 2 %>ch;"></progress>
|
|
||||||
<%= mailboxCount %>/<%= mailboxMaximum %>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
MAILBOX (PRIORITY):
|
|
||||||
<progress value="<%= mailboxVerifiedCount %>" max="<%= mailboxVerifiedMaximum %>"
|
|
||||||
style="width: <%= mailboxVerifiedMaximum * 2 %>ch;"></progress>
|
|
||||||
<%= mailboxVerifiedCount %>/<%= mailboxVerifiedMaximum %>
|
|
||||||
</p>
|
|
||||||
<p>mininq (<code>[min]i [inq]uiry</code>) is a ping-ponging non-realtime chat interface made in the early 2000s to satisfy the needs of the single abtmtr.link sysadmin writing it.</p>
|
|
||||||
<p>either way, mininq was conceptualized, developed, and deployed without any second consideration. now, here you are.</p>
|
|
||||||
<% if (!isIpAlreadyMininq) { %>
|
|
||||||
<% if (mailboxCount < mailboxMaximum || mailboxVerifiedCount < mailboxVerifiedMaximum) { %>
|
|
||||||
<form action="/mininq/send" method="post">
|
|
||||||
<p>
|
|
||||||
<label for="username">Name:</label>
|
|
||||||
<input type="text" name="name" id="username" required>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<label for="message">Message:</label><br />
|
|
||||||
<textarea name="msg" id="message" rows="4" cols="30" required></textarea>
|
|
||||||
</p>
|
|
||||||
<% if (mailboxVerifiedCount < mailboxVerifiedMaximum) { %>
|
|
||||||
<div class="entity_box">
|
|
||||||
<% if (mailboxCount < mailboxMaximum) { %>
|
|
||||||
<h3>optional keyventure</h3>
|
|
||||||
<% } else { %>
|
|
||||||
<h3>mandatory keyventure</h3>
|
|
||||||
<p>uh-oh! looks like the non-priority mailbox is full. if you still want to submit a mininq, you'll have to follow the instructions below.<br />
|
|
||||||
or, wait until the non-priority mailbox has emptied.</p>
|
|
||||||
<% } %>
|
|
||||||
<p>this keypair has been generated for you to put on your site.</p>
|
|
||||||
<p>put this snippet in your document's head: <code><meta name="abtmtr-mininq-key" content="<%= keypair.publicKey %>"></code></p>
|
|
||||||
<p>then, if you want to make a mininq, you can enter your secret key and your site's URL here, and your domain will appear beside your name.<br />
|
|
||||||
you'll also get priority in the mininq inbox.</p>
|
|
||||||
<p>
|
|
||||||
<label for="secretkey">Secret Key:</label>
|
|
||||||
<input type="text" name="skey" id="secretkey" value="<%= keypair.secretKey %>"><br />
|
|
||||||
<span style="opacity: 0.5;">(keep this somewhere safe!)</span>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<label for="pubkeylocation">Website URL:</label>
|
|
||||||
<input type="url" name="pkeyurl" id="pubkeylocation">
|
|
||||||
</p>
|
|
||||||
<p></p>
|
|
||||||
</div>
|
|
||||||
<% } else { %>
|
|
||||||
<p>uh-oh! there was a keyventure here, but it looks like the priority mailbox is full. if you still want to submit a mininq, you'll have to appear anonymously.<br />
|
|
||||||
or, wait until the priority mailbox has emptied.</p>
|
|
||||||
<% } %>
|
|
||||||
<p>
|
|
||||||
<input type="submit" value="Submit Mininq">
|
|
||||||
</p>
|
|
||||||
<p style="opacity: 0.5;">by clicking this button and successfully submitting a mininq, you agree that your input may be displayed on this site and stored on abtmtr.link servers.</p>
|
|
||||||
<p style="opacity: 0.5;">rules: no slurs, no politics, no nsfw, no spam. if you break these rules outside of plausible deniability, your domain will be blacklisted.</p>
|
|
||||||
</form>
|
|
||||||
<% } else { %>
|
|
||||||
<p>uh-oh! looks like both mailboxes are full. you'll have to wait until the mailbox has emptied - then you can submit your mininq.</p>
|
|
||||||
<% } %>
|
|
||||||
<% } else { %>
|
|
||||||
<p>you've submitted a mininq!</p>
|
|
||||||
<% } %>
|
|
||||||
<h2>current mininqs</h2>
|
|
||||||
<ul>
|
|
||||||
<% data.forEach((mininq) => { %>
|
|
||||||
<li class="mininq" id="<%= mininq.id %>">
|
|
||||||
<%- include("../components/mininq.ejs", {mininq}) %>
|
|
||||||
</li>
|
|
||||||
<% }) %>
|
|
||||||
</ul>
|
|
||||||
</main>
|
|
||||||
<%- include("../components/footer.ejs") %>
|
|
||||||
<%- include("../components/post-main.ejs") %>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -1,58 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<%- include("../components/page-head.ejs") %>
|
|
||||||
<body>
|
|
||||||
<main>
|
|
||||||
<h1>mininq mailbox</h1>
|
|
||||||
<p>slowchat, for sysadmins who aren't quite equine.</p>
|
|
||||||
<h2>mailboxes</h2>
|
|
||||||
<p>
|
|
||||||
MAILBOX:
|
|
||||||
<progress value="<%= mailboxCount %>" max="<%= mailboxMaximum %>"
|
|
||||||
style="width: <%= mailboxMaximum * 2 %>ch;"></progress>
|
|
||||||
<%= mailboxCount %>/<%= mailboxMaximum %>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
MAILBOX (PRIORITY):
|
|
||||||
<progress value="<%= mailboxVerifiedCount %>" max="<%= mailboxVerifiedMaximum %>"
|
|
||||||
style="width: <%= mailboxVerifiedMaximum * 2 %>ch;"></progress>
|
|
||||||
<%= mailboxVerifiedCount %>/<%= mailboxVerifiedMaximum %>
|
|
||||||
</p>
|
|
||||||
<h2>current mininqs</h2>
|
|
||||||
<ul>
|
|
||||||
<% mail.forEach((mininq) => { %>
|
|
||||||
<li class="mininq" id="<%= mininq.id %>">
|
|
||||||
<%- include("../components/mininq.ejs", {mininq}) %>
|
|
||||||
<details>
|
|
||||||
<summary><h3>reply...</h3></summary>
|
|
||||||
<form action="/mininq/mbox/r" method="post">
|
|
||||||
<input type="hidden" name="id" value="<%= mininq.id %>">
|
|
||||||
<p>
|
|
||||||
<label for="action">action:</label>
|
|
||||||
<select name="action" id="action" required>
|
|
||||||
<option value="reply" selected>Reply & make public</option>
|
|
||||||
<option value="public" selected>Make public</option>
|
|
||||||
<option value="delete" selected>Delete mininq</option>
|
|
||||||
</select>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<label for="skey">Secret Key:</label>
|
|
||||||
<input type="text" name="skey" id="skey" required>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<label for="message">Message:</label><br />
|
|
||||||
<textarea name="msg" id="message" rows="4" cols="30"></textarea>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<input type="submit" value="Submit">
|
|
||||||
</p>
|
|
||||||
</form>
|
|
||||||
</details>
|
|
||||||
</li>
|
|
||||||
<% }) %>
|
|
||||||
</ul>
|
|
||||||
</main>
|
|
||||||
<%- include("../components/footer.ejs") %>
|
|
||||||
<%- include("../components/post-main.ejs") %>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
|
@ -28,6 +28,5 @@
|
||||||
</ul>
|
</ul>
|
||||||
</main>
|
</main>
|
||||||
<%- include("../components/footer.ejs") %>
|
<%- include("../components/footer.ejs") %>
|
||||||
<%- include("../components/post-main.ejs") %>
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
|
@ -4,8 +4,8 @@
|
||||||
<body>
|
<body>
|
||||||
<main>
|
<main>
|
||||||
<h1>sites</h1>
|
<h1>sites</h1>
|
||||||
<p>a collection of other sites in the form of 88x31s.</p>
|
<p>a large collection of 88x31s.</p>
|
||||||
<h2>friends of abtmtr.link (<%= b.length %>)</h2>
|
<h2>friends (<%= b.length %>)</h2>
|
||||||
<div>
|
<div>
|
||||||
<% b.forEach((button) => { %>
|
<% b.forEach((button) => { %>
|
||||||
<% if (button.img != null) { %>
|
<% if (button.img != null) { %>
|
||||||
|
@ -15,7 +15,7 @@
|
||||||
<% } %>
|
<% } %>
|
||||||
<% }) %>
|
<% }) %>
|
||||||
</div>
|
</div>
|
||||||
<h2>sites of interest (<%= f.length %>)</h2>
|
<h2>following (<%= f.length %>)</h2>
|
||||||
<div>
|
<div>
|
||||||
<% f.forEach((button) => { %>
|
<% f.forEach((button) => { %>
|
||||||
<% if (button.img != null) { %>
|
<% if (button.img != null) { %>
|
||||||
|
@ -27,7 +27,6 @@
|
||||||
</div>
|
</div>
|
||||||
</main>
|
</main>
|
||||||
<%- include("../components/footer.ejs") %>
|
<%- include("../components/footer.ejs") %>
|
||||||
<%- include("../components/post-main.ejs") %>
|
|
||||||
<style>
|
<style>
|
||||||
@property --hue {
|
@property --hue {
|
||||||
syntax: '<number>';
|
syntax: '<number>';
|
||||||
|
@ -55,6 +54,7 @@
|
||||||
|
|
||||||
.web-button {
|
.web-button {
|
||||||
position: relative;
|
position: relative;
|
||||||
|
filter: grayscale(1);
|
||||||
left: 0;
|
left: 0;
|
||||||
top: 0;
|
top: 0;
|
||||||
transition: box-shadow 0.5s ease-out, left 0.5s ease-out, top 0.5s ease-out;
|
transition: box-shadow 0.5s ease-out, left 0.5s ease-out, top 0.5s ease-out;
|
||||||
|
@ -87,6 +87,7 @@
|
||||||
left: -4px;
|
left: -4px;
|
||||||
top: -4px;
|
top: -4px;
|
||||||
z-index: 999;
|
z-index: 999;
|
||||||
|
filter: grayscale(0);
|
||||||
animation: hue-shift 1s linear infinite;
|
animation: hue-shift 1s linear infinite;
|
||||||
transition: all 0.0625s ease-out;
|
transition: all 0.0625s ease-out;
|
||||||
--shadow: hsl(var(--hue), 50%, 75%);
|
--shadow: hsl(var(--hue), 50%, 75%);
|
||||||
|
|
|
@ -1,23 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
<%- include("../components/page-head.ejs") %>
|
|
||||||
<body>
|
|
||||||
<main>
|
|
||||||
<h1>updates</h1>
|
|
||||||
<p>what's going on with abtmtr.link?</p>
|
|
||||||
<h2>items</h2>
|
|
||||||
<p>subscribe to the <a href="/rss">rss feed</a>!</p>
|
|
||||||
<ul>
|
|
||||||
<% rss.items.forEach((item) => { %>
|
|
||||||
<li>
|
|
||||||
<h3><a href="<%= item.link %>"><%= item.title %></a></h3>
|
|
||||||
<p class="nomargin" style="opacity: 0.5">by <%= item.author %> / <%= new Date(item.isoDate).toLocaleString(rss.language, { timeZone: "America/Chicago" }) %> Central Time</p>
|
|
||||||
<p style="white-space: pre-line;"><%= item.content.trim() %></p>
|
|
||||||
</li>
|
|
||||||
<% }) %>
|
|
||||||
</ul>
|
|
||||||
</main>
|
|
||||||
<%- include("../components/footer.ejs") %>
|
|
||||||
<%- include("../components/post-main.ejs") %>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
Loading…
Reference in a new issue