371 lines
14 KiB
JavaScript
Executable File
371 lines
14 KiB
JavaScript
Executable File
const fs = require('fs');
|
|
const express = require('express');
|
|
const showdown = require('showdown')
|
|
const crypto = require('crypto'); // For encrypting passwords
|
|
const { fromUnixTime, format, getUnixTime } = require("date-fns")
|
|
|
|
if (process.argv[2] == "--first-time") {
|
|
initialise()
|
|
}
|
|
|
|
let users
|
|
let posts
|
|
let comments
|
|
let config
|
|
|
|
try {
|
|
users = require('./users.js');
|
|
posts = require('./posts.js');
|
|
comments = require('./comments.js');
|
|
config = require('./config.js');
|
|
}
|
|
catch (error) {
|
|
console.log("A file is missing!")
|
|
console.log("Run with --first-time to initialise the program")
|
|
console.log(error)
|
|
process.exit(1)
|
|
}
|
|
let converter = new showdown.Converter({
|
|
simpleLineBreaks: true,
|
|
tables: true,
|
|
strikethrough: true,
|
|
tasklists: true,
|
|
encodeEmails: true
|
|
})
|
|
const app = express();
|
|
|
|
let footer_div = config.site_wide_footer
|
|
footer_div = replace_format_indicators(footer_div)
|
|
|
|
app.use(express.urlencoded({ extended: true }));
|
|
app.use(express.json());
|
|
app.use(express.static(config.root_path));
|
|
|
|
function initialise() {
|
|
try {
|
|
const users = require("./users.js");
|
|
}
|
|
catch (error) {
|
|
console.log("Creating users file")
|
|
fs.writeFileSync(`${__dirname}/users.js`, `export const users = []`)
|
|
}
|
|
try {
|
|
const posts = require("./posts.js");
|
|
}
|
|
catch (error) {
|
|
console.log("Creating posts file")
|
|
fs.writeFileSync(`${__dirname}/posts.js`, `export const posts = []`)
|
|
}
|
|
try {
|
|
const users = require("./comments.js");
|
|
}
|
|
catch (error) {
|
|
console.log("Creating comments file")
|
|
fs.writeFileSync(`${__dirname}/comments.js`, `export const comments = []\nexport const counter = 0`)
|
|
}
|
|
try {
|
|
const users = require("./config.js");
|
|
}
|
|
catch (error) {
|
|
console.log("Copying the example config to config.js")
|
|
console.log("!!! PLEASE MODIFY config.js TO YOUR NEEDS !!!")
|
|
fs.copyFile('example-config.js', 'config.js')
|
|
}
|
|
process.exit()
|
|
}
|
|
|
|
function get_userID(username) {
|
|
for (let i = 0; i < users.users.length; i++) {
|
|
if (users.users[i]['username'] == username) {
|
|
return i
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
function unix_time_to_date_format(unix_time) {
|
|
date = fromUnixTime(unix_time)
|
|
formatted_date = format(date, config.date_format)
|
|
|
|
return formatted_date
|
|
}
|
|
|
|
function unix_time_to_rss_date(unix_time) {
|
|
date = fromUnixTime(unix_time)
|
|
formatted_date = format(date, "EEE, dd MMM yyyy HH:mm:ss")
|
|
return `${formatted_date} ${config.time_zone}`
|
|
}
|
|
|
|
function hyperlink_tags(tags) {
|
|
string = ""
|
|
for (let tag_index = 0; tag_index < tags.length; tag_index++) {
|
|
string += `<a href="/tag/${tags[tag_index]}">${tags[tag_index]}</a>`
|
|
if (tag_index < tags.length - 1) {
|
|
string += ", ";
|
|
}
|
|
}
|
|
return string
|
|
}
|
|
|
|
function replace_format_indicators(input_string, post_index=0, tag_name="tag") {
|
|
post_object = posts.posts[post_index]
|
|
output_string = input_string
|
|
.replaceAll("%%", "%")
|
|
.replaceAll("%A", (post_object["tags"]))
|
|
.replaceAll("%B", (hyperlink_tags(post_object["tags"])))
|
|
.replaceAll("%C", converter.makeHtml(post_object["content"]))
|
|
.replaceAll("%D", unix_time_to_date_format(post_object["pubdate"]))
|
|
.replaceAll("%E", unix_time_to_date_format(post_object["editdate"]))
|
|
.replaceAll("%F", users.users[post_object["userID"]]['prettyname'])
|
|
.replaceAll("%G", tag_name)
|
|
.replaceAll("%I", users.users[post_object['userID']]['description'])
|
|
.replaceAll("%L", `/post/${post_index}`)
|
|
.replaceAll("%M", return_comments(post_index))
|
|
.replaceAll("%N", users.users[post_object["userID"]]['username'])
|
|
.replaceAll("%P", "/post")
|
|
.replaceAll("%O", `/edit/${post_index}`)
|
|
.replaceAll("%R", "/rss")
|
|
.replaceAll("%S", config.seperator)
|
|
.replaceAll("%T", post_object["title"])
|
|
.replaceAll("%U", `/user/${users.users[post_object["userID"]]['username']}`)
|
|
.replaceAll("%X", `<form method="POST" action="/submit_comment">
|
|
<input type="hidden" name="post_index" value="${post_index}">
|
|
<input placeholder="username" name="name"><br/>
|
|
<textarea placeholder="comment" name="content"></textarea><br/>
|
|
<button type="submit">Submit</button>
|
|
</form>`)
|
|
.replaceAll("%Y", config.site_name)
|
|
.replaceAll("%W", config.site_description)
|
|
.replaceAll("%Z", config.attribution)
|
|
if (config.enable_hitcount == true) {
|
|
output_string = output_string
|
|
.replaceAll("%H", fs.readFileSync('hitcount.txt'))
|
|
}
|
|
|
|
return output_string
|
|
}
|
|
|
|
function escape_input(input) {
|
|
let output = input
|
|
.replaceAll("<", "<")
|
|
.replaceAll(">", ">")
|
|
.replaceAll("\\", "\")
|
|
.replaceAll('"', """)
|
|
.replaceAll("'", "'")
|
|
.replaceAll("/", "/")
|
|
return output
|
|
}
|
|
|
|
// TODO make the formatting customisable
|
|
function return_comments(post_id) {
|
|
const post_comments = comments.comments[post_id]
|
|
let comment_content = ""
|
|
for (let comment_index = 0; comment_index < post_comments.length; comment_index++) {
|
|
let comment = {...post_comments[comment_index]};
|
|
comment['content'] = comment['content']
|
|
.replaceAll(/>> ([0-9]*)/g, "<a href='#$1'>>> $1</a>")
|
|
.replaceAll("\n", "<br/>")
|
|
comment_content += `<div id="${comment['id']}">${comment['name']} ${unix_time_to_date_format(comment['pubdate'])} No. ${comment['id']}<br/>${comment['content']}</div><br/>`
|
|
}
|
|
return comment_content
|
|
}
|
|
|
|
// RSS protocol gets
|
|
app.get(config.rss_path, (req,res) => {
|
|
if (config.rss == false) {
|
|
res.send("Sorry, RSS is disabled!")
|
|
}
|
|
else {
|
|
let rss_content = `<?xml version="1.0" encoding="UTF-8" ?>
|
|
<rss version="2.0">
|
|
<channel>
|
|
<title>${config.site_name}</title>
|
|
<link>${config.site_url}</link>
|
|
<description>${config.site_description}</description>
|
|
`
|
|
for (let i = posts.posts.length-1; i >= 0; i--) {
|
|
rss_content += `
|
|
<item>
|
|
<title>${posts.posts[i]["title"]}</title>
|
|
<link>${config.site_url}/post/${i}</link>
|
|
<description><![CDATA[${converter.makeHtml(posts.posts[i]["content"])}]]></description>
|
|
<guid isPermaLink="true">${config.site_url}/post/${i}</guid>
|
|
<pubDate>${unix_time_to_rss_date(posts.posts[i]['pubdate'])}</pubDate>`
|
|
for (let j = 0; j < posts.posts[i]['tags'].length; j++) {
|
|
rss_content += `<category><![CDATA[${posts.posts[i]['tags'][j]}]]></category>`
|
|
};
|
|
rss_content += "</item>"
|
|
}
|
|
rss_content += `
|
|
</channel>
|
|
</rss>`
|
|
res.setHeader('content-type', 'application/rss+xml');
|
|
res.send(rss_content)
|
|
};
|
|
});
|
|
|
|
app.get("/", (req,res) => {
|
|
if (config.enable_hitcount) {
|
|
let hitcount = parseInt(fs.readFileSync('hitcount.txt'))
|
|
hitcount += 1
|
|
console.log(`/ Is loaded, hitcount: ${hitcount}`)
|
|
fs.writeFileSync(`${__dirname}/hitcount.txt`, `${hitcount}`, 'utf-8');
|
|
}
|
|
|
|
header_div = config.timeline_header
|
|
header_div = replace_format_indicators(header_div);
|
|
posts_div = "";
|
|
counter = posts.posts.length - 1;
|
|
while ((counter >= 0) && (counter > (posts.posts.length - (config.timeline_length + 1))))
|
|
{
|
|
let post = config.timeline_post_format;
|
|
posts_div += replace_format_indicators(post, counter);
|
|
counter -= 1;
|
|
}
|
|
res.send(`<html><head><meta charset="${config.charset}"><style>${config.css}</style></head><body><div id="header">${header_div}</div><div id="posts">${posts_div}</div></body><footer>${footer_div}</footer></html>`);
|
|
});
|
|
app.get("/user/:username", (req, res) => {
|
|
header_div = config.user_page_header
|
|
header_div = replace_format_indicators(header_div)
|
|
posts_div = "";
|
|
for (let post_index = posts.posts.length-1; post_index >= 0; post_index--) {
|
|
if (users.users[posts.posts[post_index]["userID"]]["username"] == req.params.username) {
|
|
let post = config.user_post_format;
|
|
posts_div += replace_format_indicators(post, post_index);
|
|
}
|
|
}
|
|
res.send(`<html><head><meta charset="${config.charset}"><style>${config.css}</style></head><body><div id="header">${header_div}</div><div id="posts">${posts_div}</div></body><footer>${footer_div}</footer></html>`);
|
|
});
|
|
app.get("/post/:post_index", (req, res) => {
|
|
post_div = "";
|
|
let post = config.post_page_format;
|
|
post_div += replace_format_indicators(post, req.params.post_index);
|
|
res.send(`<html><head><meta charset="${config.charset}"><style>${config.css}</style></head><body><div id="posts">${post_div}</div></body><footer>${footer_div}</footer></html>`);
|
|
});
|
|
app.get("/tag/:tag", (req,res) => {
|
|
const tag = req.params.tag
|
|
let header_div = config.tag_page_header
|
|
header_div = replace_format_indicators(header_div,0,tag)
|
|
let page_content = ""
|
|
for (let i = posts.posts.length-1; i >= 0; i--) {
|
|
if (posts.posts[i]['tags'].includes(tag)) {
|
|
let post = config.tag_post_format;
|
|
page_content += replace_format_indicators(post, i);
|
|
};
|
|
};
|
|
res.send(`<html><style>${config.css}</style><body><div id="header">${header_div}</div><div id="posts">${page_content}</div></body><footer>${footer_div}</footer></html>`);
|
|
});
|
|
|
|
app.get("/post", (req,res) => {
|
|
res.send(`</html><head><meta charset="${config.charset}"><style>${config.css}</style></head><form action="/submit_post" method="POST">
|
|
<input placeholder="username" required name="username"><br/>
|
|
<input placeholder="password" type="password" required id="password" name="password"><br/>
|
|
<input placeholder="title" required name="title"><br/>
|
|
<textarea placeholder="post content*" required name="content"></textarea><br/>
|
|
<input placeholder="Tags (comma seperated)" name="tags"><br/>
|
|
<input type="submit" value="Submit"><br/>
|
|
<small>* Markdown supported</small>
|
|
</form></html>`);
|
|
});
|
|
app.get("/edit/:post_id", (req,res) => {
|
|
const post_id = req.params.post_id
|
|
const post = posts.posts[post_id]
|
|
const user = users.users[post['userID']]
|
|
res.send(`</html><head><meta charset="${config.charset}"><style>${config.css}</style></head>
|
|
<form action="/submit_edit" method="POST" onsubmit="sha512password()">
|
|
<input name="userID" type="hidden" value="${post['userID']}">
|
|
<input name="postID" type="hidden" value="${post_id}">
|
|
<input placeholder="${user['prettyname']}'s password" type="password" required id="password" name="password"><br/>
|
|
<input placeholder="title" value="${post['title']}" required name="title"><br/>
|
|
<textarea placeholder="content" required name="content">${post['content']}</textarea><br/>
|
|
<input placeholder="tags (comma seperated)" value="${post['tags']}" name="tags"><br/>
|
|
<label>Delete forever (no undo): </label><input name="delete" type="checkbox"><br/>
|
|
<input type="submit" value="Submit"><br/>
|
|
<small>* Markdown supported</small>
|
|
</form></html>`);
|
|
});
|
|
|
|
app.post("/submit_comment", (req,res) => {
|
|
const unix_timestamp = getUnixTime(new Date())
|
|
let name = escape_input(req.body.name)
|
|
if (name == "") {
|
|
name = config.default_username
|
|
}
|
|
new_comment = {
|
|
"name": name,
|
|
"content": escape_input(req.body.content),
|
|
"id": comments.counter,
|
|
"pubdate": unix_timestamp
|
|
};
|
|
let counter = comments.counter+1;
|
|
comments.comments[req.body.post_index].push(new_comment);
|
|
fs.writeFileSync(`${__dirname}/comments.js`, `export const comments = ${JSON.stringify(comments.comments)}\nexport const counter = ${counter}`, 'utf-8');
|
|
|
|
res.redirect(301,`/post/${req.body.post_index}`)
|
|
});
|
|
app.post("/submit_edit", (req,res) => {
|
|
const password = crypto.createHash('sha512').update(req.body.password).digest('hex');
|
|
const postID = req.body.postID
|
|
const userID = req.body.userID
|
|
const title = req.body.title
|
|
const content = req.body.content
|
|
const tags = req.body.tags.split(',');
|
|
const delete_bool = req.body.delete
|
|
const unix_timestamp = getUnixTime(new Date())
|
|
console.log(users.users[userID]['prettyname'], "is editting the post titled:", title);
|
|
|
|
if (users.users[userID]['hash'] == password) { // password matches
|
|
let post = posts.posts[postID]
|
|
post['title'] = title
|
|
post['content'] = content
|
|
post['tags'] = tags
|
|
post['editdate'] = unix_timestamp
|
|
if (typeof delete_bool != "undefined") {
|
|
console.log("Deleting post!")
|
|
posts.posts.splice(postID,1)
|
|
comments.comments.splice(postID,1)
|
|
fs.writeFileSync(`${__dirname}/comments.js`, `export const comments = ${JSON.stringify(comments.comments)}\nexport const counter = ${comments.counter}`, 'utf-8');
|
|
}
|
|
fs.writeFileSync(`${__dirname}/posts.js`, `export const posts = ${JSON.stringify(posts.posts)}`, 'utf-8');
|
|
res.redirect(302, "/");
|
|
}
|
|
else {
|
|
res.send(`Invalid Password for user`,users.users[userID]['prettyname']);
|
|
}
|
|
});
|
|
app.post("/submit_post", (req,res) => {
|
|
const password = crypto.createHash('sha512').update(req.body.password).digest('hex');
|
|
const username = escape_input(req.body.username)
|
|
const title = escape_input(req.body.title)
|
|
const content = escape_input(req.body.content)
|
|
const tags = escape_input(req.body.tags).split(',');
|
|
const unix_timestamp = getUnixTime(new Date())
|
|
console.log(username, "is submitting a post titled:", title);
|
|
|
|
if (get_userID(username) == -1) {
|
|
res.send("User does not exist")
|
|
}
|
|
|
|
else if (users.users[get_userID(username)]['hash'] == password) { // Password matches
|
|
posts.posts.push({
|
|
"userID": get_userID(username),
|
|
"title": title,
|
|
"content": content,
|
|
"pubdate": unix_timestamp,
|
|
"editdate": unix_timestamp,
|
|
"tags": tags,
|
|
})
|
|
fs.writeFileSync(`${__dirname}/posts.js`, `export const posts = ${JSON.stringify(posts.posts)}`, 'utf-8');
|
|
comments.comments.push([])
|
|
fs.writeFileSync(`${__dirname}/comments.js`, `export const comments = ${JSON.stringify(comments.comments)}\nexport const counter = ${comments.counter}`)
|
|
res.redirect(302, "/");
|
|
}
|
|
else {
|
|
res.send(`Invalid Password for user`,username);
|
|
}
|
|
});
|
|
|
|
app.listen(config.port, () => {
|
|
console.log(`Server is running at http://localhost:${config.port} in ${config.root_path}`);
|
|
});
|