const express = require('express'); const showdown = require('showdown') const crypto = require('crypto'); // For encrypting passwords const { fromUnixTime, format, getUnixTime } = require("date-fns") const fs = require('fs'); const users = require('./users.js'); const posts = require('./posts.js'); const comments = require('./comments.js'); const config = require('./config.js'); 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 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 += `${tags[tag_index]}` 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", `


`) .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, ">> $1") .replaceAll("\n", "
") comment_content += `
${comment['name']} ${unix_time_to_date_format(comment['pubdate'])} No. ${comment['id']}
${comment['content']}

` } 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 = ` ${config.site_name} ${config.site_url} ${config.site_description} ` for (let i = posts.posts.length-1; i >= 0; i--) { rss_content += ` ${posts.posts[i]["title"]} ${config.site_url}/post/${i} ${config.site_url}/post/${i} ${unix_time_to_rss_date(posts.posts[i]['pubdate'])}` for (let j = 0; j < posts.posts[i]['tags'].length; j++) { rss_content += `` }; rss_content += "" } rss_content += ` ` 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(`
${posts_div}
`); }); 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(`
${posts_div}
`); }); 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(`
${post_div}
`); }); 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(`
${page_content}
`); }); app.get("/post", (req,res) => { res.send(`






* Markdown supported
`); }); 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(`






* Markdown supported
`); }); 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}`); });