mirror of https://gitgud.io/fatchan/jschan.git
parent
2649a1b6d3
commit
7ce421de3b
45 changed files with 867 additions and 157 deletions
@ -0,0 +1,43 @@ |
||||
'use strict'; |
||||
|
||||
const addFilter = require(__dirname+'/../../models/forms/addfilter.js') |
||||
, dynamicResponse = require(__dirname+'/../../lib/misc/dynamic.js') |
||||
, paramConverter = require(__dirname+'/../../lib/middleware/input/paramconverter.js') |
||||
, { checkSchema, lengthBody, numberBody } = require(__dirname+'/../../lib/input/schema.js'); |
||||
|
||||
module.exports = { |
||||
|
||||
paramConverter: paramConverter({ |
||||
timeFields: ['filter_ban_duration'], |
||||
trimFields: ['filters', 'filter_message'], |
||||
numberFields: ['filter_mode'], |
||||
objectIdFields: ['filter_id'], |
||||
}), |
||||
|
||||
controller: async (req, res, next) => { |
||||
|
||||
const { __ } = res.locals; |
||||
|
||||
const errors = await checkSchema([ |
||||
{ result: lengthBody(req.body.filters, 0, 50000), expected: false, error: __('Filter text cannot exceed 50000 characters') }, |
||||
{ result: numberBody(req.body.filter_mode, 0, 2), expected: true, error: __('Filter mode must be a number from 0-2') }, |
||||
{ result: numberBody(req.body.filter_ban_duration), expected: true, error: __('Invalid filter auto ban duration') }, |
||||
]); |
||||
|
||||
if (errors.length > 0) { |
||||
return dynamicResponse(req, res, 400, 'message', { |
||||
'title': __('Bad request'), |
||||
'errors': req.params.board, |
||||
'redirect': req.params.board ? `/${req.params.board}/manage/filters.html` : '/globalmanage/filters.html' |
||||
}); |
||||
} |
||||
|
||||
try { |
||||
await addFilter(req, res, next); |
||||
} catch (err) { |
||||
return next(err); |
||||
} |
||||
|
||||
} |
||||
|
||||
}; |
@ -0,0 +1,39 @@ |
||||
'use strict'; |
||||
|
||||
const deleteFilter = require(__dirname+'/../../models/forms/deletefilter.js') |
||||
, dynamicResponse = require(__dirname+'/../../lib/misc/dynamic.js') |
||||
, paramConverter = require(__dirname+'/../../lib/middleware/input/paramconverter.js') |
||||
, { checkSchema, lengthBody } = require(__dirname+'/../../lib/input/schema.js'); |
||||
|
||||
module.exports = { |
||||
|
||||
paramConverter: paramConverter({ |
||||
allowedArrays: ['checkedfilters'], |
||||
objectIdArrays: ['checkedfilters'] |
||||
}), |
||||
|
||||
controller: async (req, res, next) => { |
||||
|
||||
const { __ } = res.locals; |
||||
|
||||
const errors = await checkSchema([ |
||||
{ result: lengthBody(req.body.checkedfilters, 1), expected: false, error: __('Must select at least one filter to delete') }, |
||||
]); |
||||
|
||||
if (errors.length > 0) { |
||||
return dynamicResponse(req, res, 400, 'message', { |
||||
'title': __('Bad request'), |
||||
'errors': errors, |
||||
'redirect': req.params.board ? `/${req.params.board}/manage/filters.html` : '/globalmanage/filters.html' |
||||
}); |
||||
} |
||||
|
||||
try { |
||||
await deleteFilter(req, res, next); |
||||
} catch (err) { |
||||
return next(err); |
||||
} |
||||
|
||||
} |
||||
|
||||
}; |
@ -0,0 +1,44 @@ |
||||
'use strict'; |
||||
|
||||
const editFilter = require(__dirname+'/../../models/forms/editfilter.js') |
||||
, dynamicResponse = require(__dirname+'/../../lib/misc/dynamic.js') |
||||
, paramConverter = require(__dirname+'/../../lib/middleware/input/paramconverter.js') |
||||
, { checkSchema, lengthBody, numberBody, existsBody } = require(__dirname+'/../../lib/input/schema.js'); |
||||
|
||||
module.exports = { |
||||
|
||||
paramConverter: paramConverter({ |
||||
timeFields: ['filter_ban_duration'], |
||||
trimFields: ['filters', 'filter_message'], |
||||
numberFields: ['filter_mode'], |
||||
objectIdFields: ['filter_id'], |
||||
}), |
||||
|
||||
controller: async (req, res, next) => { |
||||
|
||||
const { __ } = res.locals; |
||||
|
||||
const errors = await checkSchema([ |
||||
{ result: existsBody(req.body.filter_id), expected: true, error: __('Missing filter id') }, |
||||
{ result: lengthBody(req.body.filters, 0, 50000), expected: false, error: __('Filter text cannot exceed 50000 characters') }, |
||||
{ result: numberBody(req.body.filter_mode, 0, 2), expected: true, error: __('Filter mode must be a number from 0-2') }, |
||||
{ result: numberBody(req.body.filter_ban_duration), expected: true, error: __('Invalid filter auto ban duration') }, |
||||
]); |
||||
|
||||
if (errors.length > 0) { |
||||
return dynamicResponse(req, res, 400, 'message', { |
||||
'title': __('Bad request'), |
||||
'errors': errors, |
||||
'redirect': req.headers.referer || (req.params.board ? `/${req.params.board}/manage/filters.html` : '/globalmanage/filters.html') |
||||
}); |
||||
} |
||||
|
||||
try { |
||||
await editFilter(req, res, next); |
||||
} catch (err) { |
||||
return next(err); |
||||
} |
||||
|
||||
} |
||||
|
||||
}; |
@ -0,0 +1,64 @@ |
||||
|
||||
'use strict'; |
||||
|
||||
const Mongo = require(__dirname+'/db.js') |
||||
, db = Mongo.db.collection('filters'); |
||||
|
||||
module.exports = { |
||||
|
||||
db, |
||||
|
||||
// null board retrieves global filters only
|
||||
findForBoard: (board=null, limit=0) => { |
||||
return db.find({'board': board}).sort({ |
||||
'_id': -1 |
||||
}) |
||||
.limit(limit) |
||||
.toArray(); |
||||
}, |
||||
|
||||
findOne: (board, id) => { |
||||
return db.findOne({ |
||||
'_id': id, |
||||
'board': board, |
||||
}); |
||||
}, |
||||
|
||||
updateOne: (board, id, filters, strictFiltering, filterMode, filterMessage, filterBanDuration, filterBanAppealable) => { |
||||
return db.updateOne({ |
||||
'_id': id, |
||||
'board': board, |
||||
}, { |
||||
'$set': { |
||||
'filters': filters, |
||||
'strictFiltering': strictFiltering, |
||||
'filterMode': filterMode, |
||||
'filterMessage': filterMessage, |
||||
'filterBanDuration': filterBanDuration, |
||||
'filterBanAppealable': filterBanAppealable, |
||||
} |
||||
}); |
||||
}, |
||||
|
||||
insertOne: (filters) => { |
||||
return db.insertOne(filters); |
||||
}, |
||||
|
||||
deleteMany: (board, ids) => { |
||||
return db.deleteMany({ |
||||
'_id': { |
||||
'$in': ids |
||||
}, |
||||
'board': board |
||||
}); |
||||
}, |
||||
|
||||
deleteBoard: (board) => { |
||||
return db.deleteMany({'board': board}); |
||||
}, |
||||
|
||||
deleteAll: () => { |
||||
return db.deleteMany({}); |
||||
}, |
||||
|
||||
}; |
@ -0,0 +1,13 @@ |
||||
'use strict'; |
||||
|
||||
module.exports = (filters, combinedString, strictCombinedString) => { |
||||
|
||||
for (const filter of filters) { |
||||
const string = filter.strictFiltering ? strictCombinedString : combinedString; |
||||
const hitFilter = filter.filters.find(match => { return string.includes(match.toLowerCase()); }); |
||||
if (hitFilter) { |
||||
return [ hitFilter, filter.filterMode, filter.filterMessage, filter.filterBanDuration, filter.filterBanAppealable ]; |
||||
} |
||||
} |
||||
return false; |
||||
}; |
@ -0,0 +1,28 @@ |
||||
'use strict'; |
||||
|
||||
const { Filters } = require(__dirname+'/../../db/') |
||||
, dynamicResponse = require(__dirname+'/../../lib/misc/dynamic.js'); |
||||
|
||||
module.exports = async (req, res) => { |
||||
|
||||
const { __ } = res.locals; |
||||
|
||||
const filter = { |
||||
'board': req.params.board ? req.params.board : null, |
||||
'filters': req.body.filters.split(/\r?\n/).filter(n => n), |
||||
'strictFiltering': req.body.strict_filtering ? true : false, |
||||
'filterMode': req.body.filter_mode, |
||||
'filterMessage': req.body.filter_message, |
||||
'filterBanDuration': req.body.filter_ban_duration, |
||||
'filterBanAppealable': req.body.filter_ban_appealable ? true : false, |
||||
}; |
||||
|
||||
await Filters.insertOne(filter); |
||||
|
||||
return dynamicResponse(req, res, 200, 'message', { |
||||
'title': __('Success'), |
||||
'message': __('Added filter'), |
||||
'redirect': req.params.board ? `/${req.params.board}/manage/filters.html` : '/globalmanage/filters.html' |
||||
}); |
||||
|
||||
}; |
@ -0,0 +1,26 @@ |
||||
'use strict'; |
||||
|
||||
const { Filters } = require(__dirname+'/../../db/') |
||||
, dynamicResponse = require(__dirname+'/../../lib/misc/dynamic.js'); |
||||
|
||||
module.exports = async (req, res) => { |
||||
|
||||
const { __, __n } = res.locals; |
||||
|
||||
const deletedFilters = await Filters.deleteMany(req.params.board, req.body.checkedfilters).then(result => result.deletedCount); |
||||
|
||||
if (deletedFilters === 0 || deletedFilters < req.body.checkedfilters.length) { |
||||
return dynamicResponse(req, res, 400, 'message', { |
||||
'title': __('Bad request'), |
||||
'error': __n('Deleted %s filters', deletedFilters), |
||||
'redirect': req.headers.referer || (req.params.board ? `/${req.params.board}/manage/filters.html` : '/globalmanage/filters.html') |
||||
}); |
||||
} |
||||
|
||||
return dynamicResponse(req, res, 200, 'message', { |
||||
'title': __('Success'), |
||||
'message': __n('Deleted %s filters', deletedFilters), |
||||
'redirect': req.params.board ? `/${req.params.board}/manage/filters.html` : '/globalmanage/filters.html' |
||||
}); |
||||
|
||||
}; |
@ -0,0 +1,35 @@ |
||||
'use strict'; |
||||
|
||||
const { Filters } = require(__dirname+'/../../db/') |
||||
, dynamicResponse = require(__dirname+'/../../lib/misc/dynamic.js'); |
||||
|
||||
module.exports = async (req, res) => { |
||||
|
||||
const { __ } = res.locals; |
||||
|
||||
const updated = await Filters.updateOne( |
||||
req.params.board, |
||||
req.body.filter_id, |
||||
req.body.filters.split(/\r?\n/).filter(n => n), |
||||
req.body.strict_filtering ? true : false, |
||||
req.body.filter_mode, |
||||
req.body.filter_message, |
||||
req.body.filter_ban_duration, |
||||
req.body.filter_ban_appealable ? true : false |
||||
).then(r => r.matchedCount); |
||||
|
||||
if (updated === 0) { |
||||
return dynamicResponse(req, res, 400, 'message', { |
||||
'title': __('Bad request'), |
||||
'error': __('Filter does not exist'), |
||||
'redirect': req.headers.referer || (req.params.board ? `/${req.params.board}/manage/filters.html` : '/globalmanage/filters.html') |
||||
}); |
||||
} |
||||
|
||||
return dynamicResponse(req, res, 200, 'message', { |
||||
'title': __('Success'), |
||||
'message': __('Updated filter'), |
||||
'redirect': req.params.board ? `/${req.params.board}/manage/filters.html` : '/globalmanage/filters.html' |
||||
}); |
||||
|
||||
}; |
@ -0,0 +1,26 @@ |
||||
'use strict'; |
||||
|
||||
const { Filters } = require(__dirname+'/../../../db/'); |
||||
|
||||
module.exports = async (req, res, next) => { |
||||
|
||||
let filter; |
||||
try { |
||||
filter = await Filters.findOne(null, req.params.filterid); |
||||
} catch (err) { |
||||
return next(err); |
||||
} |
||||
|
||||
if (!filter) { |
||||
return next(); |
||||
} |
||||
|
||||
res |
||||
.set('Cache-Control', 'private, max-age=5') |
||||
.render('globaleditfilter', { |
||||
csrf: req.csrfToken(), |
||||
permissions: res.locals.permissions, |
||||
filter, |
||||
}); |
||||
|
||||
}; |
@ -0,0 +1,22 @@ |
||||
'use strict'; |
||||
|
||||
const { Filters } = require(__dirname+'/../../../db/'); |
||||
|
||||
module.exports = async (req, res, next) => { |
||||
|
||||
let filters; |
||||
try { |
||||
filters = await Filters.findForBoard(null); |
||||
} catch (err) { |
||||
return next(err); |
||||
} |
||||
|
||||
res |
||||
.set('Cache-Control', 'private, max-age=5') |
||||
.render('globalmanagefilters', { |
||||
csrf: req.csrfToken(), |
||||
permissions: res.locals.permissions, |
||||
filters, |
||||
}); |
||||
|
||||
}; |
@ -0,0 +1,26 @@ |
||||
'use strict'; |
||||
|
||||
const { Filters } = require(__dirname+'/../../../db/'); |
||||
|
||||
module.exports = async (req, res, next) => { |
||||
|
||||
let filter; |
||||
try { |
||||
filter = await Filters.findOne(req.params.board, req.params.filterid); |
||||
} catch (err) { |
||||
return next(err); |
||||
} |
||||
|
||||
if (!filter) { |
||||
return next(); |
||||
} |
||||
|
||||
res |
||||
.set('Cache-Control', 'private, max-age=5') |
||||
.render('editfilter', { |
||||
csrf: req.csrfToken(), |
||||
permissions: res.locals.permissions, |
||||
filter, |
||||
}); |
||||
|
||||
}; |
@ -0,0 +1,22 @@ |
||||
'use strict'; |
||||
|
||||
const { Filters } = require(__dirname+'/../../../db/'); |
||||
|
||||
module.exports = async (req, res, next) => { |
||||
|
||||
let filters; |
||||
try { |
||||
filters = await Filters.findForBoard(req.params.board); |
||||
} catch (err) { |
||||
return next(err); |
||||
} |
||||
|
||||
res |
||||
.set('Cache-Control', 'private, max-age=5') |
||||
.render('managefilters', { |
||||
csrf: req.csrfToken(), |
||||
permissions: res.locals.permissions, |
||||
filters, |
||||
}); |
||||
|
||||
}; |
@ -0,0 +1,28 @@ |
||||
input(type='hidden' name='_csrf' value=csrf) |
||||
input(type='hidden' name='filter_id' value=filter._id) |
||||
.table-container.flex-center.mv-5 |
||||
table |
||||
tr |
||||
th #{__('Filters')} |
||||
th #{__('Strict Filtering')} |
||||
th #{__('Filter Mode')} |
||||
th #{__('Block/Ban Message')} |
||||
th #{__('Filter Auto Ban Duration')} |
||||
th #{__('Filter Bans Appealable')} |
||||
tr |
||||
td |
||||
textarea(name='filters' rows='2' placeholder=__('Newline separated') required) #{filter.filters.join('\n')} |
||||
td.text-center |
||||
input(type='checkbox', name='strict_filtering', value='true', checked=filter.strictFiltering) |
||||
td |
||||
select(name='filter_mode') |
||||
option(value='0', selected=filter.filterMode === 0) #{__('Do nothing')} |
||||
option(value='1', selected=filter.filterMode === 1) #{__('Block post')} |
||||
option(value='2', selected=filter.filterMode === 2) #{__('Ban')} |
||||
td |
||||
input(type='text' name='filter_message' placeholder=__('e.g. Rule 1: No ad spam') value=filter.filterMessage) |
||||
td |
||||
input(type='text' name='filter_ban_duration' placeholder=__('e.g. 3d') value=filter.filterBanDuration) |
||||
td.text-center |
||||
input(type='checkbox', name='filter_ban_appealable', value='true', checked=filter.filterBanAppealable) |
||||
input(type='submit', value=__('Save')) |
@ -0,0 +1,29 @@ |
||||
input(type='hidden' name='_csrf' value=csrf) |
||||
.table-container.flex-center.mv-5 |
||||
table |
||||
tr |
||||
th #{__('Filters')} |
||||
th #{__('Strict Filtering')} |
||||
th #{__('Filter Mode')} |
||||
th #{__('Block/Ban Message')} |
||||
th #{__('Filter Auto Ban Duration')} |
||||
th #{__('Filter Bans Appealable')} |
||||
tr |
||||
td |
||||
textarea(name='filters' rows='2' placeholder=__('Newline separated') required) |
||||
td.text-center |
||||
label |
||||
input(type='checkbox', name='strict_filtering', value='true') |
||||
td |
||||
select(name='filter_mode') |
||||
option(value='0', selected=true) #{__('Do nothing')} |
||||
option(value='1') #{__('Block post')} |
||||
option(value='2') #{__('Ban')} |
||||
td |
||||
input(type='text' name='filter_message' placeholder=__('e.g. Rule 1: No ad spam')) |
||||
td |
||||
input(type='text' name='filter_ban_duration' placeholder=__('e.g. 3d')) |
||||
td.text-center |
||||
label |
||||
input(type='checkbox', name='filter_ban_appealable', value='true') |
||||
input(type='submit', value=__('Submit')) |
@ -0,0 +1,32 @@ |
||||
mixin filter(f, globalmanage=false) |
||||
.anchor(id=f._id) |
||||
tr |
||||
td |
||||
input.left.post-check(type='checkbox', name='checkedfilters' value=f._id) |
||||
td |
||||
each line in f.filters |
||||
pre.no-m-p.nowrap !{line} |
||||
td.text-center |
||||
label |
||||
input(type='checkbox', name='strict_filtering', value='true', checked=f.strictFiltering, disabled='true') |
||||
td |
||||
if f.filterMode === 0 |
||||
p.no-m-p.nowrap #{__('Do nothing')} |
||||
else if f.filterMode === 1 |
||||
p.no-m-p.nowrap #{__('Block post')} |
||||
else if f.filterMode === 2 |
||||
p.no-m-p.nowrap #{__('Ban')} |
||||
else |
||||
p.no-m-p.nowrap ? |
||||
td |
||||
p.no-m-p !{f.filterMessage} |
||||
td |
||||
p.no-m-p(style='text-align:right;') !{f.filterBanDuration} |
||||
td.text-center |
||||
label |
||||
input(type='checkbox', name='filter_ban_appealable', value='true', checked=f.filterBanAppealable, disabled='true') |
||||
td |
||||
if globalmanage |
||||
a.ml-5(href=`/globalmanage/editfilter/${f._id}.html`, style='overflow-wrap: break-word;') [#{__('Edit')}] |
||||
else |
||||
a.ml-5(href=`/${board._id}/manage/editfilter/${f._id}.html`, style='overflow-wrap: break-word;') [#{__('Edit')}] |
@ -0,0 +1,18 @@ |
||||
extends ../layout.pug |
||||
include ../mixins/managenav.pug |
||||
include ../mixins/boardheader.pug |
||||
|
||||
block head |
||||
title /#{board._id}/ - #{__('Edit Filter')} |
||||
|
||||
block content |
||||
+boardheader(__('Edit Filter')) |
||||
br |
||||
+managenav('filters') |
||||
hr(size=1) |
||||
h4.mv-5 |
||||
a(href='/faq.html#filters') #{__('Filters FAQ')}: |
||||
h4.mv-5 #{__('Edit Filter')}: |
||||
.form-wrapper.flex-center.mv-10 |
||||
form.form-post(action=`/forms/board/${board._id}/editfilter` method='POST') |
||||
include ../includes/filtereditform.pug |
@ -0,0 +1,18 @@ |
||||
extends ../layout.pug |
||||
include ../mixins/globalmanagenav.pug |
||||
|
||||
block head |
||||
title #{__('Edit Filter')} |
||||
|
||||
block content |
||||
h1.board-title #{__('Global Management')} |
||||
br |
||||
+globalmanagenav('filters', true) |
||||
hr(size=1) |
||||
h4.mv-5 |
||||
a(href='/faq.html#filters') #{__('Filters FAQ')}: |
||||
h4.no-m-p #{__('Edit Filter')}: |
||||
include ../includes/stickynav.pug |
||||
.form-wrapper.flex-center.mv-10 |
||||
form.form-post(action='/forms/global/editfilter' method='POST') |
||||
include ../includes/filtereditform.pug |
@ -0,0 +1,37 @@ |
||||
extends ../layout.pug |
||||
include ../mixins/filter.pug |
||||
include ../mixins/globalmanagenav.pug |
||||
|
||||
block head |
||||
title #{__('Filters')} |
||||
|
||||
block content |
||||
h1.board-title #{__('Global Management')} |
||||
br |
||||
+globalmanagenav('filters') |
||||
hr(size=1) |
||||
h4.mv-5 |
||||
a(href='/faq.html#filters') #{__('Filters FAQ')}: |
||||
h4.no-m-p #{__('Add Filter')}: |
||||
.form-wrapper.flexleft |
||||
form.form-post(action='/forms/global/addfilter', enctype='application/x-www-form-urlencoded', method='POST') |
||||
include ../includes/filternewform.pug |
||||
if filters.length > 0 |
||||
hr(size=1) |
||||
h4.no-m-p #{__('Manage Filters')}: |
||||
.form-wrapper.flexleft |
||||
form.form-post(action='/forms/global/deletefilter', enctype='application/x-www-form-urlencoded', method='POST') |
||||
input(type='hidden' name='_csrf' value=csrf) |
||||
table |
||||
tr |
||||
th |
||||
th #{__('Filters')} |
||||
th #{__('Strict Filtering')} |
||||
th #{__('Filter Mode')} |
||||
th #{__('Block/Ban Message')} |
||||
th #{__('Filter Auto Ban Duration')} |
||||
th #{__('Filter Bans Appealable')} |
||||
th #{__('Edit')} |
||||
each f in filters |
||||
+filter(f, true) |
||||
input(type='submit', value=__('Delete')) |
@ -0,0 +1,38 @@ |
||||
extends ../layout.pug |
||||
include ../mixins/filter.pug |
||||
include ../mixins/managenav.pug |
||||
include ../mixins/boardheader.pug |
||||
|
||||
block head |
||||
title /#{board._id}/ - #{__('Filters')} |
||||
|
||||
block content |
||||
+boardheader(__('Filters')) |
||||
br |
||||
+managenav('filters') |
||||
hr(size=1) |
||||
h4.mv-5 |
||||
a(href='/faq.html#filters') #{__('Filters FAQ')}: |
||||
h4.mv-5 #{__('Add Filter')}: |
||||
.form-wrapper.flexleft |
||||
form.form-post(action=`/forms/board/${board._id}/addfilter`, enctype='application/x-www-form-urlencoded', method='POST') |
||||
include ../includes/filternewform.pug |
||||
if filters.length > 0 |
||||
hr(size=1) |
||||
h4.no-m-p #{__('Manage Filters')}: |
||||
.form-wrapper.flexleft |
||||
form.form-post(action=`/forms/board/${board._id}/deletefilter`, enctype='application/x-www-form-urlencoded', method='POST') |
||||
input(type='hidden' name='_csrf' value=csrf) |
||||
table |
||||
tr |
||||
th |
||||
th #{__('Filters')} |
||||
th #{__('Strict Filtering')} |
||||
th #{__('Filter Mode')} |
||||
th #{__('Block/Ban Message')} |
||||
th #{__('Filter Auto Ban Duration')} |
||||
th #{__('Filter Bans Appealable')} |
||||
th #{__('Edit')} |
||||
each f in filters |
||||
+filter(f) |
||||
input(type='submit', value=__('Delete')) |
Loading…
Reference in new issue