const express = require('express'); 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 config = require('./config.js'); const app = express(); const port = 8080; 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", markdown_to_html(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("%N", users.users[post_object["userID"]]['username']) .replaceAll("%P", "/post") .replaceAll("%O", `/edit/${post_index}`) .replaceAll("%R", "/rss") .replaceAll("%S", config.seperator) .replaceAll("%T", markdown_to_html(post_object["title"])) .replaceAll("%U", `/user/${users.users[post_object["userID"]]['username']}`) .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 } // Markdown supported: // *text* = bold // _text_ = italic // [page](url) = hyperlink // ```code``` = code function markdown_to_html(input) { // FIXME backticks before these should negate stuff let output = input .replaceAll(/\[(.*)\]\((https:\/\/|http:\/\/)(.*)\)/g, "$1") .replaceAll(/\*(.*)\*/g,"$1") .replaceAll(/_(.*)_/g,"$1") .replaceAll(/```(.*)```/gms,"$1") .replaceAll("\n","
") return output } 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.file_extension} ")}]]> ${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: *bold*, _italic_, (link)[url] and \`\`\`code block\`\`\`
`); }); 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(`





`); }); 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) } 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'); res.redirect(302, "/"); } else { res.send(`Invalid Password for user`,username); } }); app.listen(port, () => { console.log(`Server is running at http://localhost:${port} in ${config.root_path}`); });