|
|
|
'use strict';
|
|
|
|
|
|
|
|
const { Posts, Modlogs } = require(__dirname+'/../../db/')
|
|
|
|
, Permissions = require(__dirname+'/../../lib/permission/permissions.js')
|
|
|
|
, { createHash } = require('crypto')
|
|
|
|
, Mongo = require(__dirname+'/../../db/db.js')
|
|
|
|
, { prepareMarkdown } = require(__dirname+'/../../lib/post/markdown/markdown.js')
|
|
|
|
, messageHandler = require(__dirname+'/../../lib/post/message.js')
|
|
|
|
, nameHandler = require(__dirname+'/../../lib/post/name.js')
|
|
|
|
, getFilterStrings = require(__dirname+'/../../lib/post/getfilterstrings.js')
|
|
|
|
, filterActions = require(__dirname+'/../../lib/post/filteractions.js')
|
|
|
|
, config = require(__dirname+'/../../lib/misc/config.js')
|
|
|
|
, buildQueue = require(__dirname+'/../../lib/build/queue.js')
|
|
|
|
, dynamicResponse = require(__dirname+'/../../lib/misc/dynamic.js')
|
|
|
|
, { buildThread } = require(__dirname+'/../../lib/build/tasks.js');
|
|
|
|
|
|
|
|
module.exports = async (req, res) => {
|
|
|
|
|
|
|
|
/*
|
|
|
|
todo: handle some more situations
|
|
|
|
- last activity date
|
|
|
|
- correct bump date when editing thread or last post in a thread
|
|
|
|
- allow for regular users (OP ONLY) and option for staff to disable in board settings
|
|
|
|
- different permission levels for historical posts when remarked up (or not, fuck that)
|
|
|
|
*/
|
|
|
|
|
|
|
|
const { filterBanAppealable, previewReplies, strictFiltering } = config.get;
|
|
|
|
const { board, post } = res.locals;
|
|
|
|
|
|
|
|
//filters
|
|
|
|
if (!res.locals.permissions.get(Permissions.BYPASS_FILTERS)) {
|
|
|
|
//only global filters are checked, because anybody who could edit bypasses board filters
|
|
|
|
const { filters, filterMode, filterBanDuration } = config.get;
|
|
|
|
if (filters.length > 0 && filterMode > 0) {
|
|
|
|
let hitGlobalFilter = false;
|
|
|
|
const { strictCombinedString } = getFilterStrings(req, res, strictFiltering);
|
|
|
|
hitGlobalFilter = filters.some(filter => { return strictCombinedString.includes(filter.toLowerCase()); });
|
|
|
|
//block/ban edit
|
|
|
|
if (hitGlobalFilter) {
|
|
|
|
return filterActions(req, res, hitGlobalFilter, 0, filterMode,
|
|
|
|
0, filterBanDuration, filterBanAppealable, null);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
//message hash
|
|
|
|
let messageHash = null;
|
|
|
|
if (req.body.message && req.body.message.length > 0) {
|
|
|
|
const noQuoteMessage = req.body.message.replace(/>>\d+/g, '').replace(/>>>\/\w+(\/\d*)?/gm, '').trim();
|
|
|
|
messageHash = createHash('sha256').update(noQuoteMessage).digest('base64');
|
|
|
|
}
|
|
|
|
//new name, trip and cap
|
|
|
|
const { name, tripcode, capcode } = await nameHandler(req.body.name, res.locals.permissions,
|
|
|
|
board.settings, board.owner, board.staff, res.locals.user ? res.locals.user.username : null);
|
|
|
|
//new message and quotes
|
|
|
|
const nomarkup = prepareMarkdown(req.body.message, false);
|
|
|
|
const { message, quotes, crossquotes } = await messageHandler(nomarkup, req.body.board, post.thread, res.locals.permissions);
|
|
|
|
//todo: email and subject (probably dont need any transformation since staff bypass limits on forceanon, and it doesnt have to account for sage/etc
|
|
|
|
|
|
|
|
//intersection/difference of quotes sets for linking and unlinking
|
|
|
|
const oldQuoteIds = post.quotes.map(q => q._id.toString());
|
|
|
|
const oldQuotesSet = new Set(oldQuoteIds);
|
|
|
|
const newQuoteIds = quotes.map(q => q._id.toString());
|
|
|
|
const newQuotesSet = new Set(newQuoteIds);
|
|
|
|
|
|
|
|
const addedQuotesSet = new Set(newQuoteIds.filter(qid => !oldQuotesSet.has(qid)).map(Mongo.ObjectId));
|
|
|
|
const removedQuotesSet = new Set(oldQuoteIds.filter(qid => !newQuotesSet.has(qid)).map(Mongo.ObjectId));
|
|
|
|
|
|
|
|
//linking new added quotes
|
|
|
|
if (addedQuotesSet.size > 0) {
|
|
|
|
await Posts.db.updateMany({
|
|
|
|
'_id': {
|
|
|
|
'$in': [...addedQuotesSet]
|
|
|
|
}
|
|
|
|
}, {
|
|
|
|
'$push': {
|
|
|
|
'backlinks': { _id: post._id, postId: post.postId }
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
//unlinking removed quotes
|
|
|
|
if (removedQuotesSet.size > 0) {
|
|
|
|
await Posts.db.updateMany({
|
|
|
|
'_id': {
|
|
|
|
'$in': [...removedQuotesSet]
|
|
|
|
}
|
|
|
|
}, {
|
|
|
|
'$pull': {
|
|
|
|
'backlinks': {
|
|
|
|
'postId': post.postId
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
//update the post
|
|
|
|
await Posts.db.updateOne({
|
|
|
|
board: req.body.board,
|
|
|
|
postId: post.postId
|
|
|
|
}, {
|
|
|
|
'$set': {
|
|
|
|
edited: {
|
|
|
|
username: req.body.hide_name ? 'Hidden User' : req.session.user,
|
|
|
|
date: new Date(),
|
|
|
|
},
|
|
|
|
nomarkup,
|
|
|
|
message,
|
|
|
|
'messagehash': messageHash || null,
|
|
|
|
quotes,
|
|
|
|
crossquotes,
|
|
|
|
name,
|
|
|
|
tripcode,
|
|
|
|
capcode,
|
|
|
|
email: req.body.email,
|
|
|
|
subject: req.body.subject,
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
//add the edit to the modlog
|
|
|
|
await Modlogs.insertOne({
|
|
|
|
board: board._id,
|
|
|
|
showLinks: true,
|
|
|
|
postLinks: [{
|
|
|
|
postId: post.postId,
|
|
|
|
thread: post.thread,
|
|
|
|
}],
|
|
|
|
actions: 'Edit',
|
|
|
|
date: new Date(),
|
|
|
|
showUser: req.body.hide_name ? false : true,
|
|
|
|
message: req.body.log_message || null,
|
|
|
|
user: req.session.user,
|
|
|
|
ip: {
|
|
|
|
cloak: res.locals.ip.cloak,
|
|
|
|
raw: res.locals.ip.raw,
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
const buildOptions = {
|
|
|
|
'threadId': post.thread || post.postId,
|
|
|
|
'board': res.locals.board
|
|
|
|
};
|
|
|
|
|
|
|
|
//build thread immediately for redirect
|
|
|
|
await buildThread(buildOptions);
|
|
|
|
|
|
|
|
dynamicResponse(req, res, 200, 'message', {
|
|
|
|
'title': 'Success',
|
|
|
|
'message': 'Post edited successfully',
|
|
|
|
'redirect': req.body.referer,
|
|
|
|
});
|
|
|
|
res.end();
|
|
|
|
|
|
|
|
//rebuild the modlogs
|
|
|
|
buildQueue.push({
|
|
|
|
'task': 'buildModLog',
|
|
|
|
'options': {
|
|
|
|
'board': board,
|
|
|
|
}
|
|
|
|
});
|
|
|
|
buildQueue.push({
|
|
|
|
'task': 'buildModLogList',
|
|
|
|
'options': {
|
|
|
|
'board': board,
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
//check if post is visible in preview posts
|
|
|
|
let postInPreviewPosts = false;
|
|
|
|
if (post.thread) {
|
|
|
|
const threadPreviewPosts = await Posts.db.find({
|
|
|
|
'thread': post.thread,
|
|
|
|
'board': board._id
|
|
|
|
},{
|
|
|
|
'projection': {
|
|
|
|
'postId': 1, //only get postId
|
|
|
|
}
|
|
|
|
}).sort({
|
|
|
|
'postId': -1
|
|
|
|
}).limit(previewReplies).toArray();
|
|
|
|
postInPreviewPosts = threadPreviewPosts.some(p => p.postId <= post.postId);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (post.thread === null || postInPreviewPosts) {
|
|
|
|
const threadPage = await Posts.getThreadPage(board._id, post.thread || post.postId);
|
|
|
|
//rebuild index page if its a thread or visible in preview posts
|
|
|
|
buildQueue.push({
|
|
|
|
'task': 'buildBoard',
|
|
|
|
'options': {
|
|
|
|
'board': res.locals.board,
|
|
|
|
'page': threadPage
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (post.thread === null) {
|
|
|
|
//rebuild catalog if its a thread to correct catalog tile
|
|
|
|
buildQueue.push({
|
|
|
|
'task': 'buildCatalog',
|
|
|
|
'options': {
|
|
|
|
'board': res.locals.board,
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
};
|