mirror of
				https://github.com/NodeBB/NodeBB.git
				synced 2025-11-03 20:45: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