mirror of https://gitgud.io/fatchan/jschan.git
commit
ec64faa1c7
49 changed files with 781 additions and 246 deletions
@ -0,0 +1,50 @@ |
||||
'use strict'; |
||||
|
||||
const { globalLimits, ipHashPermLevel } = require(__dirname+'/../../configs/main.js') |
||||
, addBan = require(__dirname+'/../../models/forms/addban.js') |
||||
, dynamicResponse = require(__dirname+'/../../helpers/dynamic.js') |
||||
, { isIP } = require('net'); |
||||
|
||||
module.exports = async (req, res, next) => { |
||||
|
||||
const errors = []; |
||||
|
||||
if (!req.body.ip || req.body.ip.length === 0) { |
||||
errors.push('Missing IP/hash input'); |
||||
} else if (req.body.ip.length > 50) { |
||||
errors.push('IP/hash input must be less than 50 characters'); |
||||
} else if (res.locals.permLevel > ipHashPermLevel && (isIP(req.body.ip) || req.body.ip.length !== 10)) { |
||||
errors.push('Invalid hash input'); |
||||
} |
||||
if (req.body.ban_reason && req.body.ban_reason.length > globalLimits.fieldLength.ban_reason) { |
||||
errors.push(`Ban reason must be ${globalLimits.fieldLength.ban_reason} characters or less`); |
||||
} |
||||
if (req.body.log_message && req.body.log_message.length > globalLimits.fieldLength.log_message) { |
||||
errors.push(`Modlog message must be ${globalLimits.fieldLength.log_message} characters or less`); |
||||
} |
||||
|
||||
let redirect = req.headers.referer; |
||||
if (!redirect) { |
||||
if (!req.params.board) { |
||||
redirect = '/globalmanage/bans.html'; |
||||
} else { |
||||
redirect = `/${req.params.board}/manage/bans.html`; |
||||
} |
||||
} |
||||
|
||||
if (errors.length > 0) { |
||||
return dynamicResponse(req, res, 400, 'message', { |
||||
'title': 'Bad request', |
||||
'errors': errors, |
||||
redirect, |
||||
}); |
||||
} |
||||
|
||||
try { |
||||
await addBan(req, res, redirect); |
||||
} catch (err) { |
||||
return next(err); |
||||
} |
||||
|
||||
} |
||||
|
@ -0,0 +1,19 @@ |
||||
function pug_attr(t,e,n,r){if(!1===e||null==e||!e&&("class"===t||"style"===t))return"";if(!0===e)return" "+(r?t:t+'="'+t+'"');var f=typeof e;return"object"!==f&&"function"!==f||"function"!=typeof e.toJSON||(e=e.toJSON()),"string"==typeof e||(e=JSON.stringify(e),n||-1===e.indexOf('"'))?(n&&(e=pug_escape(e))," "+t+'="'+e+'"'):" "+t+"='"+e.replace(/'/g,"'")+"'"} |
||||
function pug_escape(e){var a=""+e,t=pug_match_html.exec(a);if(!t)return e;var r,c,n,s="";for(r=t.index,c=0;r<a.length;r++){switch(a.charCodeAt(r)){case 34:n=""";break;case 38:n="&";break;case 60:n="<";break;case 62:n=">";break;default:continue}c!==r&&(s+=a.substring(c,r)),c=r+1,s+=n}return c!==r?s+a.substring(c,r):s} |
||||
var pug_match_html=/["&<>]/;function uploaditem(locals) {var pug_html = "", pug_mixins = {}, pug_interp;; |
||||
var locals_for_with = (locals || {}); |
||||
|
||||
(function (uploaditem) { |
||||
pug_mixins["uploaditem"] = pug_interp = function(item){ |
||||
var block = (this && this.block), attributes = (this && this.attributes) || {}; |
||||
pug_html = pug_html + "\u003Cdiv\u003E\u003Cdiv class=\"upload-item\"\u003E\u003Cimg" + (" class=\"upload-thumb\""+pug_attr("src", item.url, true, false)) + "\u002F\u003E\u003Cp\u003E" + (pug_escape(null == (pug_interp = item.name) ? "" : pug_interp)) + "\u003C\u002Fp\u003E\u003Ca class=\"close\"\u003EX\u003C\u002Fa\u003E\u003C\u002Fdiv\u003E\u003Cdiv class=\"row sb\"\u003E"; |
||||
if (item.spoilers) { |
||||
pug_html = pug_html + "\u003Clabel\u003E\u003Cinput" + (" type=\"checkbox\" name=\"spoiler\""+pug_attr("value", item.name, true, false)) + "\u002F\u003ESpoiler\u003C\u002Flabel\u003E"; |
||||
} |
||||
pug_html = pug_html + "\u003Clabel\u003E\u003Cinput" + (" type=\"checkbox\" name=\"strip_filename\""+pug_attr("value", item.name, true, false)) + "\u002F\u003EStrip Filename\u003C\u002Flabel\u003E\u003C\u002Fdiv\u003E\u003C\u002Fdiv\u003E"; |
||||
}; |
||||
pug_mixins["uploaditem"](uploaditem); |
||||
}.call(this, "uploaditem" in locals_for_with ? |
||||
locals_for_with.uploaditem : |
||||
typeof uploaditem !== 'undefined' ? uploaditem : undefined)); |
||||
;;return pug_html;} |
@ -0,0 +1,133 @@ |
||||
setDefaultLocalStorage('notifications', false); |
||||
let notificationsEnabled = localStorage.getItem('notifications') == 'true'; |
||||
setDefaultLocalStorage('notification-yous-only', false); |
||||
let notificationYousOnly = localStorage.getItem('notification-yous-only') == 'true'; |
||||
setDefaultLocalStorage('yous-setting', true); |
||||
let yousEnabled = localStorage.getItem('yous-setting') == 'true'; |
||||
setDefaultLocalStorage('yous', '[]'); |
||||
let savedYous = new Set(JSON.parse(localStorage.getItem('yous'))); |
||||
let yousList; |
||||
|
||||
const toggleAll = (state) => savedYous.forEach(y => toggleOne(y, state)); |
||||
|
||||
const toggleQuotes = (quotes, state) => { |
||||
quotes.forEach(q => { |
||||
q.classList[state?'add':'remove']('you'); |
||||
}); |
||||
} |
||||
|
||||
const toggleOne = (you, state) => { |
||||
const [board, postId] = you.split('-'); |
||||
const post = document.querySelector(`[data-board="${board}"][data-post-id="${postId}"]`); |
||||
if (post) { |
||||
const postName = post.querySelector('.post-name'); |
||||
if (postName) { |
||||
postName.classList[state?'add':'remove']('you'); |
||||
} |
||||
} |
||||
const quotes = document.querySelectorAll(`.quote[href^="/${board}/"][href$="#${postId}"]`); |
||||
if (quotes) { |
||||
toggleQuotes(quotes, state); |
||||
} |
||||
} |
||||
|
||||
if (yousEnabled) { |
||||
toggleAll(yousEnabled); |
||||
} |
||||
|
||||
window.addEventListener('addPost', (e) => { |
||||
const postYou = `${e.detail.json.board}-${e.detail.postId}`; |
||||
const isYou = window.myPostId == e.detail.postId |
||||
if (isYou) { |
||||
//save you
|
||||
savedYous.add(postYou); |
||||
const arrayYous = [...savedYous]; |
||||
yousList.value = arrayYous.toString(); |
||||
setLocalStorage('yous', JSON.stringify(arrayYous)); |
||||
} |
||||
if (savedYous.has(postYou)) { |
||||
//toggle forn own post for name field
|
||||
toggleOne(postYou, yousEnabled); |
||||
} |
||||
const quotesYou = e.detail.json.quotes |
||||
.map(q => `${e.detail.json.board}-${q.postId}`) |
||||
.filter(y => savedYous.has(y)) |
||||
.length > 0; |
||||
const youHoverQuotes = e.detail.json.quotes |
||||
.concat(e.detail.json.backlinks) |
||||
.map(q => `${e.detail.json.board}-${q.postId}`) |
||||
.filter(y => savedYous.has(y)) |
||||
.map(y => { |
||||
const [board, postId] = y.split('-'); |
||||
return e.detail.post.querySelector(`.quote[href^="/${board}/"][href$="#${postId}"]`) |
||||
}); |
||||
//toggle for any quotes in a new post that quote (you)
|
||||
toggleQuotes(youHoverQuotes, yousEnabled); |
||||
//if not a hover newpost, and enabled/for yous, send notification
|
||||
if (!e.detail.hover && notificationsEnabled && !isYou) { |
||||
if (notificationYousOnly && !quotesYou) { |
||||
return; //only send notif for (you) if setting
|
||||
} |
||||
try { |
||||
console.log('attempting to send notification', postYou); |
||||
new Notification(`${quotesYou ? 'New quote in: ' : ''}${document.title}`, { |
||||
body: postData.nomarkup ? postData.nomarkup.substring(0,100) : '' |
||||
}); |
||||
} catch (e) { /* notification cant send for some reason -- user revoked perms in browser? */ } |
||||
} |
||||
}); |
||||
|
||||
window.addEventListener('settingsReady', () => { |
||||
|
||||
yousList = document.getElementById('youslist-setting'); |
||||
yousList.value = [...savedYous]; |
||||
const yousListClearButton = document.getElementById('youslist-clear'); |
||||
const clearYousList = () => { |
||||
if (yousEnabled) { |
||||
toggleAll(false); |
||||
} |
||||
savedYous = new Set(); |
||||
yousList.value = ''; |
||||
setLocalStorage('yous', '[]'); |
||||
console.log('cleared yous'); |
||||
} |
||||
yousListClearButton.addEventListener('click', clearYousList, false); |
||||
|
||||
const yousSetting = document.getElementById('yous-setting'); |
||||
const toggleYousSetting = () => { |
||||
yousEnabled = !yousEnabled; |
||||
setLocalStorage('yous-setting', yousEnabled); |
||||
toggleAll(yousEnabled); |
||||
console.log('toggling yous', yousEnabled); |
||||
} |
||||
yousSetting.checked = yousEnabled; |
||||
yousSetting.addEventListener('change', toggleYousSetting, false); |
||||
|
||||
const notificationYousOnlySetting = document.getElementById('notification-yous-only'); |
||||
const toggleNotificationYousOnlySetting = () => { |
||||
notificationYousOnly = !notificationYousOnly; |
||||
setLocalStorage('notification-yous-only', notificationYousOnly); |
||||
console.log('toggling notification only for yous', yousEnabled); |
||||
} |
||||
notificationYousOnlySetting.checked = notificationYousOnly; |
||||
notificationYousOnlySetting.addEventListener('change', toggleNotificationYousOnlySetting, false); |
||||
|
||||
const notificationSetting = document.getElementById('notification-setting'); |
||||
const toggleNotifications = async () => { |
||||
notificationsEnabled = !notificationsEnabled; |
||||
if (notificationsEnabled) { |
||||
const result = await Notification.requestPermission() |
||||
if (result != 'granted') { |
||||
//user denied permission popup
|
||||
notificationsEnabled = false; |
||||
notificationSetting.checked = false; |
||||
return; |
||||
} |
||||
} |
||||
console.log('toggling notifications', notificationsEnabled); |
||||
setLocalStorage('notifications', notificationsEnabled); |
||||
} |
||||
notificationSetting.checked = notificationsEnabled; |
||||
notificationSetting.addEventListener('change', toggleNotifications, false); |
||||
|
||||
}); |
@ -0,0 +1,63 @@ |
||||
'use strict'; |
||||
|
||||
//modlog
|
||||
if (modlogActions.length > 0) { |
||||
const modlog = {}; |
||||
const logDate = new Date(); //all events current date
|
||||
const message = req.body.log_message || null; |
||||
let logUser; |
||||
if (res.locals.permLevel < 4) { //if staff
|
||||
logUser = req.session.user.username; |
||||
} else { |
||||
logUser = 'Unregistered User'; |
||||
} |
||||
for (let i = 0; i < res.locals.posts.length; i++) { |
||||
const post = res.locals.posts[i]; |
||||
if (!modlog[post.board]) { |
||||
//per board actions, all actions combined to one event
|
||||
modlog[post.board] = { |
||||
postIds: [], |
||||
actions: modlogActions, |
||||
date: logDate, |
||||
showUser: !req.body.hide_name || logUser === 'Unregistered User' ? true : false, |
||||
message: message, |
||||
user: logUser, |
||||
ip: { |
||||
single: res.locals.ip.single, |
||||
raw: res.locals.ip.raw |
||||
} |
||||
}; |
||||
} |
||||
//push each post id
|
||||
modlog[post.board].postIds.push(post.postId); |
||||
} |
||||
const modlogDocuments = []; |
||||
for (let i = 0; i < threadBoards.length; i++) { |
||||
const boardName = threadBoards[i]; |
||||
const boardLog = modlog[boardName]; |
||||
//make it into documents for the db
|
||||
modlogDocuments.push({ |
||||
...boardLog, |
||||
'board': boardName |
||||
}); |
||||
} |
||||
if (modlogDocuments.length > 0) { |
||||
//insert the modlog docs
|
||||
await Modlogs.insertMany(modlogDocuments); |
||||
for (let i = 0; i < threadBoards.length; i++) { |
||||
const board = buildBoards[threadBoards[i]]; |
||||
buildQueue.push({ |
||||
'task': 'buildModLog', |
||||
'options': { |
||||
'board': board, |
||||
} |
||||
}); |
||||
buildQueue.push({ |
||||
'task': 'buildModLogList', |
||||
'options': { |
||||
'board': board, |
||||
} |
||||
}); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,69 @@ |
||||
'use strict'; |
||||
|
||||
const { Bans, Modlogs } = require(__dirname+'/../../db/') |
||||
, dynamicResponse = require(__dirname+'/../../helpers/dynamic.js') |
||||
, hashIp = require(__dirname+'/../../helpers/dynamic.js') |
||||
, buildQueue = require(__dirname+'/../../queue.js') |
||||
, { isIP } = require('net') |
||||
, { ipHashPermLevel, defaultBanDuration } = require(__dirname+'/../../configs/main.js'); |
||||
|
||||
module.exports = async (req, res, redirect) => { |
||||
|
||||
const actionDate = new Date(); |
||||
|
||||
const banPromise = Bans.insertOne({ |
||||
//note: raw ip and type single because of
|
||||
'type': 'single', |
||||
'ip': { |
||||
'single': isIP(req.body.ip) ? hashIp(req.body.ip) : req.body.ip, |
||||
'raw': req.body.ip, |
||||
}, |
||||
'reason': req.body.ban_reason || req.body.log_message || 'No reason specified', |
||||
'board': req.params.board || null, |
||||
'posts': null, |
||||
'issuer': req.session.user.username, |
||||
'date': actionDate, |
||||
'expireAt': new Date(actionDate.getTime() + (req.body.ban_duration || defaultBanDuration)), |
||||
'allowAppeal': req.body.no_appeal ? false : true, |
||||
'appeal': null, |
||||
'seen': false, |
||||
}); |
||||
|
||||
const modlogPromise = Modlogs.insertOne({ |
||||
'board': req.params.board || null, |
||||
'postIds': [], |
||||
'actions': [(req.params.board ? 'Ban' : 'Global Ban')], |
||||
'date': actionDate, |
||||
'showUser': !req.body.hide_name || res.locals.permLevel >= 4 ? true : false, |
||||
'message': req.body.log_message || null, |
||||
'user': res.locals.permLevel < 4 ? req.session.user.username : 'Unregistered User', |
||||
'ip': { |
||||
'single': res.locals.ip.single, |
||||
'raw': res.locals.ip.raw |
||||
} |
||||
}); |
||||
|
||||
await Promise.all([banPromise, modlogPromise]); |
||||
|
||||
if (req.params.board) { |
||||
buildQueue.push({ |
||||
'task': 'buildModLog', |
||||
'options': { |
||||
'board': res.locals.board, |
||||
} |
||||
}); |
||||
buildQueue.push({ |
||||
'task': 'buildModLogList', |
||||
'options': { |
||||
'board': res.locals.board, |
||||
} |
||||
}); |
||||
} |
||||
|
||||
return dynamicResponse(req, res, 200, 'message', { |
||||
'title': 'Success', |
||||
'message': 'Added ban', |
||||
redirect, |
||||
}); |
||||
|
||||
} |
@ -0,0 +1,23 @@ |
||||
'use strict'; |
||||
|
||||
const { Posts, Boards } = require(__dirname+'/../../db/') |
||||
, cache = require(__dirname+'/../../redis.js') |
||||
, { overboardLimit } = require(__dirname+'/../../configs/main.js'); |
||||
|
||||
module.exports = async (req, res, next) => { |
||||
|
||||
let threads = []; |
||||
try { |
||||
const listedBoards = await Boards.getLocalListed(); |
||||
threads = await Posts.getRecent(listedBoards, 1, overboardLimit, false); |
||||
} catch (err) { |
||||
return next(err); |
||||
} |
||||
|
||||
res |
||||
.set('Cache-Control', 'public, max-age=60') |
||||
.render('overboard', { |
||||
threads, |
||||
}); |
||||
|
||||
} |
@ -0,0 +1,22 @@ |
||||
.row |
||||
.label IP/Hash |
||||
input(type='text' name='ip' required) |
||||
.row |
||||
.label Ban Reason |
||||
input(type='text' name='ban_reason') |
||||
.row |
||||
.label Modlog Message |
||||
input(type='text' name='log_message') |
||||
.row |
||||
.label Ban Duration |
||||
input(type='text' name='ban_duration' placeholder='e.g. 7d') |
||||
.row |
||||
.label Non-appealable Ban |
||||
label.postform-style.ph-5 |
||||
input(type='checkbox', name='no_appeal' value='1') |
||||
.row |
||||
.label Hide Username In Modlog |
||||
label.postform-style.ph-5 |
||||
input(type='checkbox', name='hide_name' value='1') |
||||
input(type='submit', value='submit') |
||||
|
@ -1,4 +1,4 @@ |
||||
noscript.no-m-p |
||||
iframe.captcha(src='/captcha.html' 'width=210' height='80' scrolling='no' loading='lazy') |
||||
.jsonly.captcha(style='display:none;') |
||||
input.captchafield(type='text' name='captcha' autocomplete='off' placeholder='captcha text' pattern=".{6}" required title='6 characters') |
||||
input.captchafield(type='text' name='captcha' autocomplete='off' placeholder='Captcha text' pattern=".{6}" required title='6 characters') |
||||
|
@ -1,10 +1,12 @@ |
||||
unless minimal |
||||
small.footer#bottom |
||||
| - |
||||
a(href='/news.html') news |
||||
| - |
||||
a(href='/rules.html') rules |
||||
| - |
||||
a(href='/faq.html') faq |
||||
| - |
||||
a(href='https://github.com/fatchan/jschan/') source code |
||||
a(href='https://gitgud.io/fatchan/jschan/') source code |
||||
| - |
||||
script(src=`/js/render.js?v=${commit}`) |
||||
|
@ -0,0 +1,2 @@ |
||||
include ../mixins/uploaditem.pug |
||||
+uploaditem(uploaditem) |
@ -0,0 +1,14 @@ |
||||
mixin uploaditem(item) |
||||
div |
||||
.upload-item |
||||
img.upload-thumb(src=item.url) |
||||
p #{item.name} |
||||
a.close X |
||||
.row.sb |
||||
if item.spoilers |
||||
label |
||||
input(type='checkbox', name='spoiler', value=item.name) |
||||
| Spoiler |
||||
label |
||||
input(type='checkbox', name='strip_filename', value=item.name) |
||||
| Strip Filename |
@ -0,0 +1,20 @@ |
||||
extends ../layout.pug |
||||
include ../mixins/post.pug |
||||
|
||||
block head |
||||
title Overboard |
||||
|
||||
block content |
||||
.board-header |
||||
h1.board-title Overboard |
||||
h4.board-description Recently bumped threads from all listed boards |
||||
hr(size=1) |
||||
if threads.length === 0 |
||||
p No posts. |
||||
hr(size=1) |
||||
for thread in threads |
||||
.thread |
||||
+post(thread, true) |
||||
for post in thread.replies |
||||
+post(post, true) |
||||
hr(size=1) |
Loading…
Reference in new issue