Merge branch 'dev'

merge-requests/208/head
fatchan 4 years ago
commit 456c8fdfcb
  1. 13
      configs/main.js.example
  2. 2
      controllers/forms/actions.js
  3. 7
      controllers/forms/boardsettings.js
  4. 5
      controllers/forms/globalactions.js
  5. 3
      controllers/pages.js
  6. 10
      db/boards.js
  7. 56
      gulp/res/css/style.css
  8. 3
      gulp/res/css/themes/win95.css
  9. BIN
      gulp/res/icons/favicon2.ico
  10. 12
      gulp/res/js/captcha.js
  11. 60
      gulp/res/js/forms.js
  12. 12
      gulp/res/js/live.js
  13. 10
      gulp/res/js/time.js
  14. 17
      gulp/res/js/titlescroll.js
  15. 2
      gulpfile.js
  16. 8
      helpers/captcha/captchaverify.js
  17. 2
      helpers/paramconverter.js
  18. 1
      migrations/index.js
  19. 36
      migrations/migration-0.0.4.js
  20. 10
      models/forms/changeboardsettings.js
  21. 32
      models/forms/makepost.js
  22. 2
      models/pages/captcha.js
  23. 23
      models/pages/manage/catalog.js
  24. 1
      models/pages/manage/index.js
  25. 2
      package.json
  26. 4
      views/includes/navbar.pug
  27. 3
      views/mixins/catalogtile.pug
  28. 2
      views/mixins/managenav.pug
  29. 2
      views/mixins/post.pug
  30. 4
      views/pages/account.pug
  31. 8
      views/pages/board.pug
  32. 2
      views/pages/boardlist.pug
  33. 38
      views/pages/catalog.pug
  34. 19
      views/pages/managesettings.pug

@ -146,13 +146,14 @@ module.exports = {
//subset of languages to allow.
languageSubset: [
'javascript',
'js',
'typescript',
'perl',
'js',
'c++',
'c',
'java',
'kotlin',
'php',
'c++',
'c',
'h',
'csharp',
'bash',
@ -233,9 +234,9 @@ module.exports = {
theme: 'lain',
codeTheme: 'ir-black',
sfw: false, //safe for work board
locked: false, //board locked, only staff can post
unlisted: false, //board hidden from on-site board list and frontpage
webring: true, //board shown on webring
lockMode: 0, //board lock mode
unlistedLocal: false, //board hidden from on-site board list and frontpage
unlistedWebring: false, //board hidden from webring
captchaMode: 0, //0=disabled, 1=for threads, 2=for all posts
tphTrigger: 0, //numebr of threads in an hour before trigger action is activated
pphTrigger: 0, //number of posts in an hour before ^

@ -27,6 +27,8 @@ module.exports = async (req, res, next) => {
//50 because checked posts is max 10 and 5 reports max per post
errors.push('Cannot check more than 50 reports');
}
} else if (!req.body.checkedreports && req.body.report_ban) {
errors.push('Must select posts+reports to report ban');
}
res.locals.actions = actionChecker(req);

@ -96,13 +96,16 @@ module.exports = async (req, res, next) => {
errors.push(`Max reply message length must be 0-${globalLimits.fieldLength.message} and not less than "Min Reply Message Length" (currently ${res.locals.board.settings.minReplyMessageLength})`);
}
if (typeof req.body.lock_mode === 'number' && (req.body.lock_mode < 0 || req.body.lock_mode > 2)) {
errors.push('Invalid lock mode');
}
if (typeof req.body.captcha_mode === 'number' && (req.body.captcha_mode < 0 || req.body.captcha_mode > 2)) {
errors.push('Invalid captcha mode');
}
if (typeof req.body.tph_trigger === 'number' && (req.body.tph_trigger < 0 || req.body.tph_trigger > 10000)) {
errors.push('Invalid tph trigger threshold');
}
if (typeof req.body.tph_trigger_action === 'number' && (req.body.tph_trigger_action < 0 || req.body.tph_trigger_action > 3)) {
if (typeof req.body.tph_trigger_action === 'number' && (req.body.tph_trigger_action < 0 || req.body.tph_trigger_action > 4)) {
errors.push('Invalid tph trigger action');
}
if (typeof req.body.filter_mode === 'number' && (req.body.filter_mode < 0 || req.body.filter_mode > 2)) {
@ -128,7 +131,7 @@ module.exports = async (req, res, next) => {
if (res.locals.permLevel > 1) { //if not global staff or above
const ratelimitBoard = await Ratelimits.incrmentQuota(req.params.board, 'settings', rateLimitCost.boardSettings); //2 changes a minute
const ratelimitIp = await Ratelimits.incrmentQuota(res.locals.ip.hash, 'settings', rateLimitCost.boardSettings);
const ratelimitIp = await Ratelimits.incrmentQuota(res.locals.ip.single, 'settings', rateLimitCost.boardSettings);
if (ratelimitBoard > 100 || ratelimitIp > 100) {
return dynamicResponse(req, res, 429, 'message', {
'title': 'Ratelimited',

@ -24,7 +24,9 @@ module.exports = async (req, res, next) => {
//50 because checked posts is max 10 and 5 reports max per post
errors.push('Cannot check more than 50 reports');
}
}
} else if (!req.body.checkedreports && req.body.global_report_ban) {
errors.push('Must select posts+reports to report ban');
}
res.locals.actions = actionChecker(req);
@ -74,6 +76,7 @@ module.exports = async (req, res, next) => {
//edit post, only allowing one
return res.render('editpost', {
'post': res.locals.posts[0],
'csrf': req.csrfToken(),
});
}

@ -14,7 +14,7 @@ const express = require('express')
, setMinimal = require(__dirname+'/../helpers/setminimal.js')
//page models
, { manageRecent, manageReports, manageBanners, manageSettings, manageBans,
manageBoard, manageThread, manageLogs } = require(__dirname+'/../models/pages/manage/')
manageBoard, manageThread, manageLogs, manageCatalog } = require(__dirname+'/../models/pages/manage/')
, { globalManageSettings, globalManageReports, globalManageBans,
globalManageRecent, globalManageAccounts, globalManageNews, globalManageLogs } = require(__dirname+'/../models/pages/globalmanage/')
, { changePassword, blockBypass, home, register, login, logout, create,
@ -48,6 +48,7 @@ router.get('/:board/manage/logs.html', sessionRefresh, isLoggedIn, Boards.exists
router.get('/:board/manage/settings.html', sessionRefresh, isLoggedIn, Boards.exists, calcPerms, hasPerms(2), csrf, manageSettings);
router.get('/:board/manage/banners.html', sessionRefresh, isLoggedIn, Boards.exists, calcPerms, hasPerms(2), csrf, manageBanners);
// if (mod view enabled) {
router.get('/:board/manage/catalog.html', sessionRefresh, isLoggedIn, Boards.exists, calcPerms, hasPerms(3), csrf, manageCatalog);
router.get('/:board/manage/:page(1[0-9]{1,}|[2-9][0-9]{0,}|index).html', sessionRefresh, isLoggedIn, Boards.exists, paramConverter, calcPerms, hasPerms(3), csrf, manageBoard);
router.get('/:board/manage/thread/:id([1-9][0-9]{0,}).html', sessionRefresh, isLoggedIn, Boards.exists, paramConverter, calcPerms, hasPerms(3), csrf, Posts.exists, manageThread);

@ -103,7 +103,7 @@ module.exports = {
boardSort: (skip=0, limit=50, sort={ ips:-1, pph:-1, sequence_value:-1 }, filter={}, showUnlisted=false) => {
const addedFilter = {};
if (!showUnlisted) {
addedFilter['settings.unlisted'] = false;
addedFilter['settings.unlistedLocal'] = false;
}
if (filter.search) {
addedFilter['$or'] = [
@ -122,7 +122,7 @@ module.exports = {
'settings.description': 1,
'settings.name': 1,
'settings.tags': 1,
'settings.unlisted': 1,
'settings.unlistedLocal': 1,
}
})
.sort(sort)
@ -133,7 +133,7 @@ module.exports = {
webringBoards: () => {
return db.find({
'settings.webring': true
'settings.unlistedWebring': false
}, {
'projection': {
'_id': 1,
@ -152,7 +152,7 @@ module.exports = {
count: (filter, showUnlisted=false) => {
const addedFilter = {};
if (!showUnlisted) {
addedFilter['settings.unlisted'] = false;
addedFilter['settings.unlistedLocal'] = false;
}
if (filter.search) {
addedFilter['$or'] = [
@ -179,7 +179,7 @@ module.exports = {
},
'unlisted': {
'$sum': {
'$cond': ['$settings.unlisted', 1, 0]
'$cond': ['$settings.unlistedLocal', 1, 0]
}
},
//removed ips because sum is inaccurate

@ -62,6 +62,25 @@ main.minimal {
text-decoration: line-through;
}
@keyframes rainbow-anim {
0% {
background-position: 0 0;
}
100% {
background-position: 400% 0;
}
}
.rainbow {
background: linear-gradient(to right, #6666ff, #0099ff , #00ff00, #ff3399, #6666ff);
-webkit-background-clip: text;
background-clip: text;
color: transparent;
animation: rainbow-anim 10s linear infinite;
background-size: 400% 100%;
text-shadow: #00000050 0px 0px 1px;
}
.underline {
text-decoration: underline;
}
@ -207,8 +226,8 @@ a, a:visited, a.post-name {
}
.invalid-quote {
cursor:pointer;
text-decoration: line-through;
cursor:pointer;
text-decoration: line-through;
}
.post-message a {
@ -277,7 +296,7 @@ p {
}
/*
.upload-list::-webkit-scrollbar {
display: none;
display: none;
}
*/
.upload-item {
@ -384,7 +403,7 @@ p {
}
.edited {
font-style: italic;
font-style: italic;
}
.close {
@ -629,7 +648,7 @@ details.actions div {
.post-check {
position: relative;
top: 2px;
margin: -3px 1px !important;
margin: -3px 1px;
}
.post-files {
@ -773,7 +792,7 @@ input:invalid, textarea:invalid {
box-sizing: border-box;
padding: .5em;
max-width: 100%;
min-width: 25em;
min-width: 30em;
}
.postmenu {
@ -880,6 +899,11 @@ input:invalid, textarea:invalid {
float: left;
padding-left: 10px;
padding-right: 10px;
text-align: center;
}
.nav-item:nth-of-type(3) {
line-height:1.5em;
}
.left {
@ -1000,20 +1024,20 @@ iframe.bypass {
}
.captcharefresh {
position: absolute;
position: absolute;
bottom: 0;
left: 5px;
font-size: 18px;
cursor: pointer;
left: 5px;
font-size: 18px;
cursor: pointer;
color: black;
}
.captcharefresh {
position: absolute;
position: absolute;
bottom: 0;
left: 5px;
font-size: 18px;
cursor: pointer;
left: 5px;
font-size: 18px;
cursor: pointer;
}
.label, .rlabel {
@ -1145,7 +1169,7 @@ table, .boardtable {
width: 100%
}
row.wrap.sb .col {
flex-basis: calc(50% - 5px);
flex-basis: calc(50% - 5px);
}
@media only screen and (max-height: 400px) {
@ -1263,7 +1287,7 @@ row.wrap.sb .col {
.post-check {
top: 1px;
margin-left: 2px!important;
margin-left: 2px;
}
.pages {

@ -126,6 +126,9 @@ width:unset!important;
input[type=submit], input[type=submit], label[for=file] {
background: var(--post-color)!important;
}
.footer {
padding-bottom: 3em;
}
@media only screen and (max-width:600px) {
.nav-item {
padding-left: 5px;

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

@ -31,13 +31,13 @@ window.addEventListener('DOMContentLoaded', (event) => {
};
const loadCaptcha = function(e) {
const captchaDiv = this.previousSibling;
const field = e.target;
const captchaDiv = field.previousSibling;
const captchaImg = document.createElement('img');
const refreshDiv = document.createElement('div');
refreshDiv.classList.add('captcharefresh', 'noselect');
refreshDiv.addEventListener('click', refreshCaptchas, true);
refreshDiv.textContent = '↻';
const field = this;
field.placeholder = 'loading';
captchaImg.src = '/captcha';
captchaImg.onload = function() {
@ -49,8 +49,12 @@ window.addEventListener('DOMContentLoaded', (event) => {
};
for (let i = 0; i < captchaFields.length; i++) {
captchaFields[i].placeholder = 'focus to load captcha';
captchaFields[i].addEventListener('focus', loadCaptcha, { once: true });
const field = captchaFields[i];
if (field.form.action.endsWith('/forms/blockbypass')) {
return loadCaptcha({target: field })
}
field.placeholder = 'focus to load captcha';
field.addEventListener('focus', loadCaptcha, { once: true });
}
});

@ -4,32 +4,39 @@ function removeModal() {
}
function doModal(data, postcallback) {
const modalHtml = modal({ modal: data });
let checkInterval;
document.body.insertAdjacentHTML('afterbegin', modalHtml);
document.getElementById('modalclose').onclick = () => {
removeModal();
clearInterval(checkInterval);
};
document.getElementsByClassName('modal-bg')[0].onclick = () => {
removeModal();
clearInterval(checkInterval);
};
const modalframe = document.getElementById('modalframe');
modalframe.onload = () => {
if (localStorage.getItem('theme') === 'default') {
const currentTheme = document.head.querySelector('#theme').href;
modalframe.contentDocument.styleSheets[1].ownerNode.href = currentTheme;
}
}
if (modalframe && postcallback) {
checkInterval = setInterval(() => {
if (modalframe && modalframe.contentDocument.title == 'Success') {
clearInterval(checkInterval);
removeModal();
postcallback();
try {
const modalHtml = modal({ modal: data });
let checkInterval;
document.body.insertAdjacentHTML('afterbegin', modalHtml);
document.getElementById('modalclose').onclick = () => {
removeModal();
clearInterval(checkInterval);
};
document.getElementsByClassName('modal-bg')[0].onclick = () => {
removeModal();
clearInterval(checkInterval);
};
const modalframe = document.getElementById('modalframe');
if (modalframe) {
//if theres a modal frame and user has default theme, style it
if (localStorage.getItem('theme') === 'default') {
modalframe.onload = () => {
const currentTheme = document.head.querySelector('#theme').href;
modalframe.contentDocument.styleSheets[1].ownerNode.href = currentTheme;
}
}
}, 100);
if (postcallback) {
checkInterval = setInterval(() => {
if (modalframe && modalframe.contentDocument.title == 'Success') {
clearInterval(checkInterval);
removeModal();
postcallback();
}
}, 100);
}
}
} catch(e) {
console.error(e)
}
}
@ -169,6 +176,9 @@ class formHandler {
} else {
if (json.message || json.messages || json.error || json.errors) {
doModal(json);
if (json.message === 'Incorrect captcha answer') {
//todo: create captcha form, add method to captcha frontend code
}
} else if (socket && socket.connected) {
window.myPostId = json.postId;
window.location.hash = json.postId

@ -30,7 +30,7 @@ window.addEventListener('settingsReady', function(event) { //after domcontentloa
lastPostId = data.postId;
const postData = data;
//create a new post
const postHtml = post({ post: postData, modview:isModView });
const postHtml = post({ post: postData, modview:isModView, upLevel:isThread });
//add it to the end of the thread
thread.insertAdjacentHTML('beforeend', postHtml);
for (let j = 0; j < postData.quotes.length; j++) {
@ -42,14 +42,12 @@ window.addEventListener('settingsReady', function(event) { //after domcontentloa
const quotedPostData = quotedPost.querySelector('.post-data');
const newRepliesDiv = document.createElement('div');
newRepliesDiv.textContent = 'Replies: ';
['replies', 'mt-5', 'ml-5'].forEach(c => {
newRepliesDiv.classList.add(c);
});
newRepliesDiv.classList.add('replies', 'mt-5', 'ml-5');
quotedPostData.appendChild(newRepliesDiv);
replies = newRepliesDiv;
}
if (new RegExp(`>>${postData.postId}(\s|$)`).test(replies.innerText)) {
//reply link already exists (probably from a late catch up
//reply link already exists (probably from a late catch up)
continue;
}
const newReply = document.createElement('a');
@ -148,7 +146,9 @@ window.addEventListener('settingsReady', function(event) { //after domcontentloa
const room = `${roomParts[1]}-${roomParts[roomParts.length-1]}`;
socket = io({
transports: ['websocket'],
reconnectionAttempts: 3
reconnectionAttempts: 3,
reconnectionDelay: 3000,
reconnectionDelayMax: 15000,
});
socket.on('connect', async () => {
console.log('socket connected');

@ -120,10 +120,12 @@ window.addEventListener('settingsReady', function(event) {
window.addEventListener('addPost', function(e) {
const date = e.detail.post.querySelector('.reltime');
if (!e.detail.hover) {
dates.push(date);
const dates = e.detail.post.querySelectorAll('.reltime');
for (let date of dates) {
if (!e.detail.hover) {
dates.push(date);
}
changeDateFormat(date);
}
changeDateFormat(date);
});

@ -4,6 +4,16 @@ window.addEventListener('DOMContentLoaded', (event) => {
let unread = [];
const originalTitle = document.title;
const changeFavicon = (href) => {
const currentFav = document.head.querySelector('link[type="image/x-icon"]');
const newFav = document.createElement('link');
newFav.type = 'image/x-icon';
newFav.rel = 'shortcut icon';
newFav.href = href;
currentFav.remove();
document.head.appendChild(newFav);
}
const isVisible = (e) => {
const top = e.getBoundingClientRect().top;
const bottom = e.getBoundingClientRect().bottom;
@ -14,8 +24,10 @@ window.addEventListener('DOMContentLoaded', (event) => {
const updateTitle = () => {
if (unread.length === 0) {
document.title = originalTitle;
changeFavicon('/favicon.ico');
} else {
document.title = `(${unread.length}) ${originalTitle}`;
changeFavicon('/file/favicon2.ico');
}
}
@ -37,10 +49,7 @@ window.addEventListener('DOMContentLoaded', (event) => {
}
}
window.onfocus = () => {
focusChange();
updateVisible();
}
window.onfocus = focusChange;
window.onblur = focusChange;
window.addEventListener('scroll', updateVisible);

@ -208,6 +208,8 @@ function scripts() {
`${paths.scripts.src}/post.js`,
`${paths.scripts.src}/settings.js`,
`${paths.scripts.src}/live.js`,
`${paths.scripts.src}/captcha.js`,
`${paths.scripts.src}/forms.js`,
`${paths.scripts.src}/*.js`,
`!${paths.scripts.src}/dragable.js`,
`!${paths.scripts.src}/hide.js`,

@ -27,12 +27,12 @@ module.exports = async (req, res, next) => {
if (isBypass) {
return res.status(403).render('bypass', {
'minimal': req.body.minimal,
'message': 'Incorrect captcha',
'message': 'Incorrect captcha answer',
});
}
return dynamicResponse(req, res, 403, 'message', {
'title': 'Forbidden',
'message': 'Incorrect captcha',
'message': 'Incorrect captcha answer',
'redirect': req.headers.referer,
});
}
@ -69,12 +69,12 @@ module.exports = async (req, res, next) => {
if (isBypass) {
return res.status(403).render('bypass', {
'minimal': req.body.minimal,
'message': 'Incorrect captcha',
'message': 'Incorrect captcha answer',
});
}
return dynamicResponse(req, res, 403, 'message', {
'title': 'Forbidden',
'message': 'Incorrect captcha',
'message': 'Incorrect captcha answer',
'redirect': req.headers.referer,
});
}

@ -5,7 +5,7 @@ const { ObjectId } = require(__dirname+'/../db/db.js')
'checkedreports', 'checkedbans', 'checkedbanners', 'checkedaccounts']) //only these should be arrays, since express bodyparser can output arrays
, trimFields = ['tags', 'uri', 'moderators', 'filters', 'announcement', 'description', 'message',
'name', 'subject', 'email', 'postpassword', 'password', 'default_name', 'report_reason', 'ban_reason', 'log_message', 'custom_css'] //trim if we dont want filed with whitespace
, numberFields = ['filter_mode', 'captcha_mode', 'tph_trigger', 'pph_trigger', 'trigger_action', 'reply_limit', 'move_to_thread',, 'postId',
, numberFields = ['filter_mode', 'lock_mode', 'captcha_mode', 'tph_trigger', 'pph_trigger', 'trigger_action', 'reply_limit', 'move_to_thread',, 'postId',
'max_files', 'thread_limit', 'thread', 'max_thread_message_length', 'max_reply_message_length', 'min_thread_message_length', 'min_reply_message_length', 'auth_level'] //convert these to numbers before they hit our routes
, banDurationRegex = /^(?<YEAR>[\d]+y)?(?<MONTH>[\d]+m)?(?<WEEK>[\d]+w)?(?<DAY>[\d]+d)?(?<HOUR>[\d]+h)?$/
, timeUtils = require(__dirname+'/timeutils.js')

@ -4,4 +4,5 @@ module.exports = {
'0.0.1': require(__dirname+'/migration-0.0.1.js'), //add bypasses to database
'0.0.2': require(__dirname+'/migration-0.0.2.js'), //rename ip field in posts
'0.0.3': require(__dirname+'/migration-0.0.3.js'), //move files from /img to /file/
'0.0.4': require(__dirname+'/migration-0.0.4.js'), //rename some fields for board lock mode and unlisting
}

@ -0,0 +1,36 @@
'use strict';
module.exports = async(db, redis) => {
console.log('Renaming some settings fields on boards');
await db.collection('boards').updateMany({}, {
'$rename': {
'settings.locked': 'settings.lockMode',
'settings.unlisted': 'settings.unlistedLocal',
'settings.webring': 'settings.unlistedWebring'
}
});
console.log('upadting renamed fields to proper values')
await db.collection('boards').updateMany({
'settings.lockMode': true,
}, {
'$set': {
'settings.lockMode': 2,
}
});
await db.collection('boards').updateMany({
'settings.lockMode': false,
}, {
'$set': {
'settings.lockMode': 0,
}
});
await db.collection('boards').updateMany({
'settings.triggerAction': 3,
}, {
'$set': {
'settings.triggerAction': 4,
}
});
console.log('clearing boards cache');
await redis.deletePattern('board:*')
};

@ -19,7 +19,7 @@ const { Boards, Posts, Accounts } = require(__dirname+'/../../db/')
return setting != null;
}
, arraySetting = (setting, oldSetting, limit) => {
return setting !== null ? setting.split('\r\n').filter(n => n).slice(0,limit) : oldSettings;
return setting !== null ? setting.split(/\r?\n/).filter(n => n).slice(0,limit) : oldSettings;
};
module.exports = async (req, res, next) => {
@ -36,7 +36,7 @@ module.exports = async (req, res, next) => {
markdownAnnouncement = message; //is there a destructure syntax for this?
}
let moderators = req.body.moderators != null ? req.body.moderators.split('\r\n').filter(n => n && !(n == res.locals.board.owner)).slice(0,10) : [];
let moderators = req.body.moderators != null ? req.body.moderators.split(/\r?\n/).filter(n => n && !(n == res.locals.board.owner)).slice(0,10) : [];
if (moderators.length === 0 && oldSettings.moderators.length > 0) {
//remove all mods if mod list being emptied
promises.push(Accounts.removeModBoard(oldSettings.moderators, req.params.board));
@ -69,9 +69,8 @@ module.exports = async (req, res, next) => {
'theme': req.body.theme || oldSettings.theme,
'codeTheme': req.body.code_theme || oldSettings.codeTheme,
'sfw': booleanSetting(req.body.sfw),
'unlisted': booleanSetting(req.body.unlisted),
'webring': booleanSetting(req.body.webring),
'locked': booleanSetting(req.body.locked),
'unlistedLocal': booleanSetting(req.body.unlisted_local),
'unlistedWebring': booleanSetting(req.body.unlisted_webring),
'early404': booleanSetting(req.body.early404),
'ids': booleanSetting(req.body.ids),
'flags': booleanSetting(req.body.flags),
@ -96,6 +95,7 @@ module.exports = async (req, res, next) => {
'minReplyMessageLength': numberSetting(req.body.min_reply_message_length, oldSettings.minReplyMessageLength),
'maxThreadMessageLength': numberSetting(req.body.max_thread_message_length, oldSettings.maxThreadMessageLength),
'maxReplyMessageLength': numberSetting(req.body.max_reply_message_length, oldSettings.maxReplyMessageLength),
'lockMode': numberSetting(req.body.lock_mode, oldSettings.lockMode),
'filterMode': numberSetting(req.body.filter_mode, oldSettings.filterMode),
'filterBanDuration': numberSetting(req.body.ban_duration, oldSettings.filterBanDuration),
'tags': arraySetting(req.body.tags, oldSettings.tags, 10),

@ -47,12 +47,13 @@ module.exports = async (req, res, next) => {
const { filterBanDuration, filterMode, filters,
maxFiles, forceAnon, replyLimit, disableReplySubject,
threadLimit, ids, userPostSpoiler, pphTrigger, tphTrigger, triggerAction,
captchaMode, locked, allowedFileTypes, flags } = res.locals.board.settings;
if (locked === true && res.locals.permLevel >= 4) {
captchaMode, lockMode, allowedFileTypes, flags } = res.locals.board.settings;
if ((lockMode === 2 || (lockMode === 1 && !req.body.thread)) //if board lock, or thread lock and its a new thread
&& res.locals.permLevel >= 4) { //and not staff
await deleteTempFiles(req).catch(e => console.error);
return dynamicResponse(req, res, 400, 'message', {
'title': 'Bad request',
'message': 'Board is locked.',
'message': lockMode === 1 ? 'Thread creation locked' : 'Board locked',
'redirect': redirect
});
}
@ -62,7 +63,7 @@ module.exports = async (req, res, next) => {
await deleteTempFiles(req).catch(e => console.error);
return dynamicResponse(req, res, 400, 'message', {
'title': 'Bad request',
'message': 'Thread does not exist.',
'message': 'Thread does not exist',
'redirect': redirect
});
}
@ -150,7 +151,7 @@ module.exports = async (req, res, next) => {
await deleteTempFiles(req).catch(e => console.error);
return dynamicResponse(req, res, 400, 'message', {
'title': 'Bad request',
'message': `Mime type "${req.files.file[i].mimetype}" for "${req.files.file[i].name}" not allowed.`,
'message': `Mime type "${req.files.file[i].mimetype}" for "${req.files.file[i].name}" not allowed`,
'redirect': redirect
});
}
@ -226,10 +227,11 @@ module.exports = async (req, res, next) => {
const videoData = await ffprobe(req.files.file[i].tempFilePath, null, true);
videoData.streams = videoData.streams.filter(stream => stream.width != null); //filter to only video streams or something with a resolution
if (videoData.streams.length <= 0) {
//corrupt, or audio only?
await deleteTempFiles(req).catch(e => console.error);
return dynamicResponse(req, res, 400, 'message', {
'title': 'Bad request',
'message': 'Audio only video file not supported (yet)',
'message': 'Audio only video file not supported',
'redirect': redirect
});
}
@ -361,10 +363,11 @@ module.exports = async (req, res, next) => {
const postId = await Posts.insertOne(res.locals.board, data, thread);
let enableCaptcha = false;
if (triggerAction > 0 //trigger is enabled and not already been triggered
&& (tphTrigger > 0 || pphTrigger > 0)
&& ((triggerAction < 3 && captchaMode < triggerAction)
|| (triggerAction === 3 && locked !== true))) {
if (triggerAction > 0 //if trigger is enabled
&& (tphTrigger > 0 || pphTrigger > 0) //and has a threshold > 0
&& ((triggerAction < 3 && captchaMode < triggerAction) //and its triggering captcha and captcha isnt on
|| (triggerAction === 3 && lockMode < 1) //or triggering locking and board isnt locked
|| (triggerAction === 4 && lockMode < 2))) {
//read stats to check number threads in past hour
const hourPosts = await Stats.getHourPosts(res.locals.board._id);
if (hourPosts //if stats exist for this hour and its above either trigger
@ -379,8 +382,11 @@ module.exports = async (req, res, next) => {
update['$set']['settings.captchaMode'] = triggerAction;
enableCaptcha = true;
} else if (triggerAction === 3) {
res.locals.board.settings.locked = true;
update['$set']['settings.locked'] = true;
res.locals.board.settings.lockMode = 1;
update['$set']['settings.lockMode'] = 1;
} else if (triggerAction === 4) {
res.locals.board.settings.lockMode = 2;
update['$set']['settings.lockMode'] = 2;
}
//set it in the db
await Boards.updateOne(res.locals.board._id, update);
@ -397,7 +403,7 @@ module.exports = async (req, res, next) => {
}).skip(replyLimit).toArray();
if (cyclicOverflowPosts.length > 0) {
await deletePosts(cyclicOverflowPosts, req.params.board);
const fileCount = cyclicOverflowPosts.reduce((post, acc) => {
const fileCount = cyclicOverflowPosts.reduce((acc, post) => {
return acc + (post.files ? post.files.length : 0);
}, 0);
//reduce amount counted in post by number of posts deleted

@ -13,7 +13,7 @@ module.exports = async (req, res, next) => {
let captchaId;
try {
const ratelimit = await Ratelimits.incrmentQuota(res.locals.ip.hash, 'captcha', rateLimitCost.captcha);
const ratelimit = await Ratelimits.incrmentQuota(res.locals.ip.single, 'captcha', rateLimitCost.captcha);
if (ratelimit > 100) {
return res.status(429).redirect('/file/ratelimit.png');
}

@ -0,0 +1,23 @@
'use strict';
const Posts = require(__dirname+'/../../../db/posts.js');
module.exports = async (req, res, next) => {
let threads;
try {
threads = await Posts.getCatalog(req.params.board);
} catch (err) {
return next(err);
}
res
.set('Cache-Control', 'private, max-age=5')
.render('catalog', {
modview: true,
threads,
board: res.locals.board,
csrf: req.csrfToken(),
});
}

@ -8,5 +8,6 @@ module.exports = {
manageLogs: require(__dirname+'/logs.js'),
manageBanners: require(__dirname+'/banners.js'),
manageBoard: require(__dirname+'/board.js'),
manageCatalog: require(__dirname+'/catalog.js'),
manageThread: require(__dirname+'/thread.js'),
}

@ -1,7 +1,7 @@
{
"name": "jschan",
"version": "0.0.1",
"migrateVersion": "0.0.3",
"migrateVersion": "0.0.4",
"description": "",
"main": "server.js",
"dependencies": {

@ -2,7 +2,9 @@ unless minimal
nav.navbar
a.nav-item(href='/index.html') Home
a.nav-item(href='/news.html') News
a.nav-item(href='/boards.html') Boards
a.nav-item(href='/boards.html')
| Boards
.rainbow +Webring
a.nav-item(href='/account.html') Account
if board
a.nav-item(href=`/${board._id}/manage/reports.html`) Manage

@ -2,6 +2,9 @@ mixin catalogtile(board, post, index)
.catalog-tile(data-board=post.board data-post-id=post.postId data-user-id=post.userId)
- const postURL = `/${board._id}/thread/${post.postId}.html#${post.postId}`
.post-info
input.left.post-check(type='checkbox', name='checkedposts' value=post.postId)
if modview
a.left.ml-5.bold(href=`recent.html?postid=${post.postId}`) [+]
include ../includes/posticons.pug
a.no-decoration.post-subject(href=postURL) #{post.subject || 'No subject'}
br

@ -6,6 +6,8 @@ mixin managenav(selected, upLevel)
else
a(href=`${upLevel ? '../' : ''}index.html` class=(selected === 'index' ? 'bold' : '')) [Mod Index]
|
a(href=`${upLevel ? '../' : ''}catalog.html` class=(selected === 'catalog' ? 'bold' : '')) [Mod Catalog]
|
a(href=`${upLevel ? '../' : ''}recent.html` class=(selected === 'recent' ? 'bold' : '')) [Recent]
|
a(href=`${upLevel ? '../' : ''}reports.html` class=(selected === 'reports' ? 'bold' : '')) [Reports]

@ -4,7 +4,7 @@ mixin post(post, truncate, manage=false, globalmanage=false, ban=false)
div(class=`post-container ${post.thread || ban === true ? '' : 'op'}` data-board=post.board data-post-id=post.postId data-user-id=post.userId)
- const postURL = `/${post.board}/${modview ? 'manage/' : ''}thread/${post.thread || post.postId}.html`;
.post-info
span.noselect
span
label
if globalmanage
input.post-check(type='checkbox', name='globalcheckedposts' value=post._id)

@ -26,6 +26,8 @@ block content
| -
a(href=`/${b}/manage/index.html`) Mod Index
| ,
a(href=`/${b}/manage/catalog.html`) Mod Catalog
| ,
a(href=`/${b}/manage/recent.html`) Recent
| ,
a(href=`/${b}/manage/reports.html`) Reports
@ -49,6 +51,8 @@ block content
| -
a(href=`/${b}/manage/index.html`) Mod Index
| ,
a(href=`/${b}/manage/catalog.html`) Mod Catalog
| ,
a(href=`/${b}/manage/recent.html`) Recent
| ,
a(href=`/${b}/manage/reports.html`) Reports

@ -15,11 +15,11 @@ block content
include ../includes/announcements.pug
include ../includes/stickynav.pug
if modview
+managenav('index')
+managenav('index')
else
.pages
include ../includes/boardpages.pug
+boardnav(null, false, false)
.pages
include ../includes/boardpages.pug
+boardnav(null, false, false)
form(target='_blank' action=`/forms/board/${board._id}/${modview ? 'mod' : ''}actions` method='POST' enctype='application/x-www-form-urlencoded')
if modview
input(type='hidden' name='_csrf' value=csrf)

@ -32,7 +32,7 @@ block content
if board.settings.sfw === true
span(title='SFW') &#x1F4BC;
|
if board.settings.unlisted === true
if board.settings.unlistedLocal === true
span(title='Unlisted') &#x1F441;&#xFE0F;
|
a(href=`/${board._id}/index.html`) /#{board._id}/ - #{board.settings.name}

@ -1,6 +1,7 @@
extends ../layout.pug
include ../mixins/catalogtile.pug
include ../mixins/boardnav.pug
include ../mixins/managenav.pug
include ../mixins/boardheader.pug
block head
@ -13,15 +14,28 @@ block content
br
include ../includes/announcements.pug
include ../includes/stickynav.pug
.pages
+boardnav('catalog', true, false)
hr(size=1)
if threads.length === 0
p No posts.
else
.catalog
for thread, i in threads
+catalogtile(board, thread, i+1)
hr(size=1)
.pages
+boardnav('catalog', true, false)
if modview
+managenav('catalog')
else
.pages
+boardnav('catalog', true, false)
form(target='_blank' action=`/forms/board/${board._id}/${modview ? 'mod' : ''}actions` method='POST' enctype='application/x-www-form-urlencoded')
if modview
input(type='hidden' name='_csrf' value=csrf)
hr(size=1)
if threads.length === 0
p No posts.
else
.catalog
for thread, i in threads
+catalogtile(board, thread, i+1)
hr(size=1)
if modview
+managenav('catalog')
else
.pages
+boardnav('catalog', true, false)
if modview
include ../includes/actionfooter_manage.pug
else
include ../includes/actionfooter.pug

@ -150,17 +150,19 @@ block content
input(type='checkbox', name='flags', value='true' checked=board.settings.flags)
.col
.row
.label Board Locked
label.postform-style.ph-5
input(type='checkbox', name='locked', value='true' checked=board.settings.locked)
.label Lock Mode
select(name='lock_mode')
option(value='0', selected=board.settings.lockMode === 0) Unlocked
option(value='1', selected=board.settings.lockMode === 1) Lock thread creation
option(value='2', selected=board.settings.lockMode === 2) Lock board
.row
.label Unlisted
.label Unlist locally
label.postform-style.ph-5
input(type='checkbox', name='unlisted', value='true' checked=board.settings.unlisted)
input(type='checkbox', name='unlisted_local', value='true' checked=board.settings.unlistedLocal)
.row
.label Webring
.label Unlist from webring
label.postform-style.ph-5
input(type='checkbox', name='webring', value='true' checked=board.settings.webring)
input(type='checkbox', name='unlisted_webring', value='true' checked=board.settings.unlistedWebring)
.row
.label SFW
label.postform-style.ph-5
@ -197,7 +199,8 @@ block content
option(value='0', selected=board.settings.triggerAction === 0) Do nothing
option(value='1', selected=board.settings.triggerAction === 1) Enable captcha for new thread
option(value='2', selected=board.settings.triggerAction === 2) Enable captcha for all posts
option(value='3', selected=board.settings.triggerAction === 3) Lock Board
option(value='3', selected=board.settings.triggerAction === 3) Lock thread creation
option(value='4', selected=board.settings.triggerAction === 4) Lock board
.row
.label Early 404
label.postform-style.ph-5

Loading…
Cancel
Save