feat: federate flag creation

This commit is contained in:
Opliko
2024-04-14 00:51:53 +02:00
parent 7bacbf76f0
commit 2a2b855fe2
7 changed files with 56 additions and 13 deletions

View File

@@ -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);

View File

@@ -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,
});
});

View File

@@ -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;

View File

@@ -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);

View File

@@ -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]]');

View File

@@ -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' });
} }

View File

@@ -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>