mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-10-31 19:15:58 +01:00 
			
		
		
		
	feat: federate flag creation
This commit is contained in:
		| @@ -8,6 +8,8 @@ define('flags', ['hooks', 'components', 'api', 'alerts'], function (hooks, compo | |||||||
| 	let flagReason; | 	let flagReason; | ||||||
|  |  | ||||||
| 	Flag.showFlagModal = function (data) { | 	Flag.showFlagModal = function (data) { | ||||||
|  | 		data.remote = URL.canParse(data.id) ? new URL(data.id).hostname : false; | ||||||
|  |  | ||||||
| 		app.parseAndTranslate('modals/flag', data, function (html) { | 		app.parseAndTranslate('modals/flag', data, function (html) { | ||||||
| 			flagModal = html; | 			flagModal = html; | ||||||
| 			flagModal.on('hidden.bs.modal', function () { | 			flagModal.on('hidden.bs.modal', function () { | ||||||
| @@ -35,18 +37,21 @@ define('flags', ['hooks', 'components', 'api', 'alerts'], function (hooks, compo | |||||||
| 				if (selected.attr('id') === 'flag-reason-other') { | 				if (selected.attr('id') === 'flag-reason-other') { | ||||||
| 					reason = flagReason.val(); | 					reason = flagReason.val(); | ||||||
| 				} | 				} | ||||||
| 				createFlag(data.type, data.id, reason); | 				const notifyRemote = $('input[name="flag-notify-remote"]').is(':checked'); | ||||||
|  | 				createFlag(data.type, data.id, reason, notifyRemote); | ||||||
| 			}); | 			}); | ||||||
|  |  | ||||||
| 			flagModal.on('click', '#flag-reason-other', function () { | 			flagModal.on('click', '#flag-reason-other', function () { | ||||||
| 				flagReason.focus(); | 				flagReason.focus(); | ||||||
| 			}); | 			}); | ||||||
|  |  | ||||||
|  |  | ||||||
| 			flagModal.modal('show'); | 			flagModal.modal('show'); | ||||||
| 			hooks.fire('action:flag.showModal', { | 			hooks.fire('action:flag.showModal', { | ||||||
| 				modalEl: flagModal, | 				modalEl: flagModal, | ||||||
| 				type: data.type, | 				type: data.type, | ||||||
| 				id: data.id, | 				id: data.id, | ||||||
|  | 				remote: data.remote, | ||||||
| 			}); | 			}); | ||||||
|  |  | ||||||
| 			flagModal.find('#flag-reason-custom').on('keyup blur change', checkFlagButtonEnable); | 			flagModal.find('#flag-reason-custom').on('keyup blur change', checkFlagButtonEnable); | ||||||
| @@ -63,7 +68,7 @@ define('flags', ['hooks', 'components', 'api', 'alerts'], function (hooks, compo | |||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	Flag.rescindByType = function (type, id) { | 	Flag.rescindByType = function (type, id) { | ||||||
| 		api.del(`/flags/${type}/${id}/report`).then(() => { | 		api.del(`/flags/${type}/${encodeURIComponent(id)}/report`).then(() => { | ||||||
| 			alerts.success('[[flags:rescinded]]'); | 			alerts.success('[[flags:rescinded]]'); | ||||||
| 			hooks.fire('action:flag.rescinded', { type: type, id: id }); | 			hooks.fire('action:flag.rescinded', { type: type, id: id }); | ||||||
| 			if (type === 'post') { | 			if (type === 'post') { | ||||||
| @@ -88,11 +93,11 @@ define('flags', ['hooks', 'components', 'api', 'alerts'], function (hooks, compo | |||||||
| 		}).catch(alerts.error); | 		}).catch(alerts.error); | ||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	function createFlag(type, id, reason) { | 	function createFlag(type, id, reason, notifyRemote = false) { | ||||||
| 		if (!type || !id || !reason) { | 		if (!type || !id || !reason) { | ||||||
| 			return; | 			return; | ||||||
| 		} | 		} | ||||||
| 		const data = { type: type, id: id, reason: reason }; | 		const data = { type: type, id: id, reason: reason, notifyRemote: notifyRemote }; | ||||||
| 		api.post('/flags', data, function (err, flagId) { | 		api.post('/flags', data, function (err, flagId) { | ||||||
| 			if (err) { | 			if (err) { | ||||||
| 				return alerts.error(err); | 				return alerts.error(err); | ||||||
|   | |||||||
| @@ -214,3 +214,19 @@ activitypubApi.undo.like = enabledCheck(async (caller, { pid }) => { | |||||||
| 		}, | 		}, | ||||||
| 	}); | 	}); | ||||||
| }); | }); | ||||||
|  |  | ||||||
|  | activitypubApi.flag = enabledCheck(async (caller, flag) => { | ||||||
|  | 	if (!activitypub.helpers.isUri(flag.targetId)) { | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 	const reportedIds = [flag.targetId]; | ||||||
|  | 	if (flag.type === 'post' && activitypub.helpers.isUri(flag.targetUid)) { | ||||||
|  | 		reportedIds.push(flag.targetUid); | ||||||
|  | 	} | ||||||
|  | 	const reason = flag.reports.filter(report => report.reporter.uid === caller.uid).at(-1); | ||||||
|  | 	await activitypub.send('uid', caller.uid, reportedIds, { | ||||||
|  | 		type: 'Flag', | ||||||
|  | 		object: reportedIds, | ||||||
|  | 		content: reason ? reason.value : undefined, | ||||||
|  | 	}); | ||||||
|  | }); | ||||||
|   | |||||||
| @@ -11,7 +11,7 @@ flagsApi.create = async (caller, data) => { | |||||||
| 		throw new Error('[[error:invalid-data]]'); | 		throw new Error('[[error:invalid-data]]'); | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	const { type, id, reason } = data; | 	const { type, id, reason, notifyRemote } = data; | ||||||
|  |  | ||||||
| 	await flags.validate({ | 	await flags.validate({ | ||||||
| 		uid: caller.uid, | 		uid: caller.uid, | ||||||
| @@ -19,7 +19,7 @@ flagsApi.create = async (caller, data) => { | |||||||
| 		id: id, | 		id: id, | ||||||
| 	}); | 	}); | ||||||
|  |  | ||||||
| 	const flagObj = await flags.create(type, id, caller.uid, reason); | 	const flagObj = await flags.create(type, id, caller.uid, reason, undefined, undefined, notifyRemote); | ||||||
| 	flags.notify(flagObj, caller.uid); | 	flags.notify(flagObj, caller.uid); | ||||||
|  |  | ||||||
| 	return flagObj; | 	return flagObj; | ||||||
|   | |||||||
| @@ -7,8 +7,8 @@ const helpers = require('../helpers'); | |||||||
| const Flags = module.exports; | const Flags = module.exports; | ||||||
|  |  | ||||||
| Flags.create = async (req, res) => { | Flags.create = async (req, res) => { | ||||||
| 	const { type, id, reason } = req.body; | 	const { type, id, reason, notifyRemote } = req.body; | ||||||
| 	const flagObj = await api.flags.create(req, { type, id, reason }); | 	const flagObj = await api.flags.create(req, { type, id, reason, notifyRemote }); | ||||||
| 	helpers.formatApiResponse(200, res, await user.isPrivileged(req.uid) ? flagObj : undefined); | 	helpers.formatApiResponse(200, res, await user.isPrivileged(req.uid) ? flagObj : undefined); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| @@ -36,12 +36,10 @@ Flags.rescind = async (req, res) => { | |||||||
| 	await api.flags.rescind(req, { flagId: req.params.flagId }); | 	await api.flags.rescind(req, { flagId: req.params.flagId }); | ||||||
| 	helpers.formatApiResponse(200, res); | 	helpers.formatApiResponse(200, res); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| Flags.rescindPost = async (req, res) => { | Flags.rescindPost = async (req, res) => { | ||||||
| 	await api.flags.rescindPost(req, { pid: req.params.pid }); | 	await api.flags.rescindPost(req, { pid: req.params.pid }); | ||||||
| 	helpers.formatApiResponse(200, res); | 	helpers.formatApiResponse(200, res); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| Flags.rescindUser = async (req, res) => { | Flags.rescindUser = async (req, res) => { | ||||||
| 	await api.flags.rescindUser(req, { uid: req.params.uid }); | 	await api.flags.rescindUser(req, { uid: req.params.uid }); | ||||||
| 	helpers.formatApiResponse(200, res); | 	helpers.formatApiResponse(200, res); | ||||||
|   | |||||||
							
								
								
									
										17
									
								
								src/flags.js
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								src/flags.js
									
									
									
									
									
								
							| @@ -4,6 +4,8 @@ const _ = require('lodash'); | |||||||
| const winston = require('winston'); | const winston = require('winston'); | ||||||
| const validator = require('validator'); | const validator = require('validator'); | ||||||
|  |  | ||||||
|  | const activitypub = require('./activitypub'); | ||||||
|  | const activitypubApi = require('./api/activitypub'); | ||||||
| const db = require('./database'); | const db = require('./database'); | ||||||
| const user = require('./user'); | const user = require('./user'); | ||||||
| const groups = require('./groups'); | const groups = require('./groups'); | ||||||
| @@ -389,7 +391,7 @@ Flags.deleteNote = async function (flagId, datetime) { | |||||||
| 	await db.sortedSetRemove(`flag:${flagId}:notes`, note[0]); | 	await db.sortedSetRemove(`flag:${flagId}:notes`, note[0]); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| Flags.create = async function (type, id, uid, reason, timestamp, forceFlag = false) { | Flags.create = async function (type, id, uid, reason, timestamp, forceFlag = false, notifyRemote = false) { | ||||||
| 	let doHistoryAppend = false; | 	let doHistoryAppend = false; | ||||||
| 	if (!timestamp) { | 	if (!timestamp) { | ||||||
| 		timestamp = Date.now(); | 		timestamp = Date.now(); | ||||||
| @@ -474,6 +476,11 @@ Flags.create = async function (type, id, uid, reason, timestamp, forceFlag = fal | |||||||
|  |  | ||||||
| 	const flagObj = await Flags.get(flagId); | 	const flagObj = await Flags.get(flagId); | ||||||
|  |  | ||||||
|  | 	if (notifyRemote && activitypub.helpers.isUri(id)) { | ||||||
|  | 		const caller = await user.getUserData(uid); | ||||||
|  | 		activitypubApi.flag(caller, flagObj); | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	plugins.hooks.fire('action:flags.create', { flag: flagObj }); | 	plugins.hooks.fire('action:flags.create', { flag: flagObj }); | ||||||
| 	return flagObj; | 	return flagObj; | ||||||
| }; | }; | ||||||
| @@ -681,6 +688,14 @@ Flags.targetExists = async function (type, id) { | |||||||
| 	if (type === 'post') { | 	if (type === 'post') { | ||||||
| 		return await posts.exists(id); | 		return await posts.exists(id); | ||||||
| 	} else if (type === 'user') { | 	} else if (type === 'user') { | ||||||
|  | 		if (activitypub.helpers.isUri(id)) { | ||||||
|  | 			try { | ||||||
|  | 				const actor = await activitypub.get('uid', 0, id); | ||||||
|  | 				return !!actor; | ||||||
|  | 			} catch (_) { | ||||||
|  | 				return false; | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
| 		return await user.exists(id); | 		return await user.exists(id); | ||||||
| 	} | 	} | ||||||
| 	throw new Error('[[error:invalid-data]]'); | 	throw new Error('[[error:invalid-data]]'); | ||||||
|   | |||||||
| @@ -353,7 +353,8 @@ module.exports = function (User) { | |||||||
| 	}; | 	}; | ||||||
|  |  | ||||||
| 	User.setUserFields = async function (uid, data) { | 	User.setUserFields = async function (uid, data) { | ||||||
| 		await db.setObject(`user:${uid}`, data); | 		const userKey = isFinite(uid) ? `user:${uid}` : `userRemote:${uid}`; | ||||||
|  | 		await db.setObject(userKey, data); | ||||||
| 		for (const [field, value] of Object.entries(data)) { | 		for (const [field, value] of Object.entries(data)) { | ||||||
| 			plugins.hooks.fire('action:user.set', { uid, field, value, type: 'set' }); | 			plugins.hooks.fire('action:user.set', { uid, field, value, type: 'set' }); | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -31,9 +31,17 @@ | |||||||
| 						</label> | 						</label> | ||||||
| 					</div> | 					</div> | ||||||
| 				</div> | 				</div> | ||||||
| 				<div class="mb-3"> | 				<div class="mb-2"> | ||||||
| 					<textarea class="form-control" id="flag-reason-custom" placeholder="[[flags:modal-reason-custom]]" disabled="disabled"></textarea> | 					<textarea class="form-control" id="flag-reason-custom" placeholder="[[flags:modal-reason-custom]]" disabled="disabled"></textarea> | ||||||
| 				</div> | 				</div> | ||||||
|  | 				{{{ if remote }}} | ||||||
|  | 				<div class="form-check form-switch mb-3"> | ||||||
|  | 					<input class="form-check-input" type="checkbox" name="flag-notify-remote" checked="checked"> | ||||||
|  | 					<label class="form-check-label text-sm" for="flag-notify-remote"> | ||||||
|  | 						[[flags:modal-notify-remote, {remote}]] | ||||||
|  | 					</label> | ||||||
|  | 				</div> | ||||||
|  | 				{{{ end }}} | ||||||
|  |  | ||||||
| 				<button type="button" class="btn btn-primary" id="flag-post-commit" disabled>[[flags:modal-submit]]</button> | 				<button type="button" class="btn btn-primary" id="flag-post-commit" disabled>[[flags:modal-submit]]</button> | ||||||
| 			</div> | 			</div> | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user