diff --git a/public/src/modules/flags.js b/public/src/modules/flags.js index 845a605f01..a105e22181 100644 --- a/public/src/modules/flags.js +++ b/public/src/modules/flags.js @@ -8,6 +8,8 @@ define('flags', ['hooks', 'components', 'api', 'alerts'], function (hooks, compo let flagReason; Flag.showFlagModal = function (data) { + data.remote = URL.canParse(data.id) ? new URL(data.id).hostname : false; + app.parseAndTranslate('modals/flag', data, function (html) { flagModal = html; 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') { 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 () { flagReason.focus(); }); + flagModal.modal('show'); hooks.fire('action:flag.showModal', { modalEl: flagModal, type: data.type, id: data.id, + remote: data.remote, }); 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) { - api.del(`/flags/${type}/${id}/report`).then(() => { + api.del(`/flags/${type}/${encodeURIComponent(id)}/report`).then(() => { alerts.success('[[flags:rescinded]]'); hooks.fire('action:flag.rescinded', { type: type, id: id }); if (type === 'post') { @@ -88,11 +93,11 @@ define('flags', ['hooks', 'components', 'api', 'alerts'], function (hooks, compo }).catch(alerts.error); }; - function createFlag(type, id, reason) { + function createFlag(type, id, reason, notifyRemote = false) { if (!type || !id || !reason) { 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) { if (err) { return alerts.error(err); diff --git a/src/api/activitypub.js b/src/api/activitypub.js index 6e92763c45..dd0cd4a19f 100644 --- a/src/api/activitypub.js +++ b/src/api/activitypub.js @@ -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, + }); +}); diff --git a/src/api/flags.js b/src/api/flags.js index 78fe35bb51..ff30978982 100644 --- a/src/api/flags.js +++ b/src/api/flags.js @@ -11,7 +11,7 @@ flagsApi.create = async (caller, data) => { throw new Error('[[error:invalid-data]]'); } - const { type, id, reason } = data; + const { type, id, reason, notifyRemote } = data; await flags.validate({ uid: caller.uid, @@ -19,7 +19,7 @@ flagsApi.create = async (caller, data) => { 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); return flagObj; diff --git a/src/controllers/write/flags.js b/src/controllers/write/flags.js index cb81ee10ac..2af5d7e9b4 100644 --- a/src/controllers/write/flags.js +++ b/src/controllers/write/flags.js @@ -7,8 +7,8 @@ const helpers = require('../helpers'); const Flags = module.exports; Flags.create = async (req, res) => { - const { type, id, reason } = req.body; - const flagObj = await api.flags.create(req, { type, id, reason }); + const { type, id, reason, notifyRemote } = req.body; + const flagObj = await api.flags.create(req, { type, id, reason, notifyRemote }); 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 }); helpers.formatApiResponse(200, res); }; - Flags.rescindPost = async (req, res) => { await api.flags.rescindPost(req, { pid: req.params.pid }); helpers.formatApiResponse(200, res); }; - Flags.rescindUser = async (req, res) => { await api.flags.rescindUser(req, { uid: req.params.uid }); helpers.formatApiResponse(200, res); diff --git a/src/flags.js b/src/flags.js index 833bb7edd8..d62f9e00ba 100644 --- a/src/flags.js +++ b/src/flags.js @@ -4,6 +4,8 @@ const _ = require('lodash'); const winston = require('winston'); const validator = require('validator'); +const activitypub = require('./activitypub'); +const activitypubApi = require('./api/activitypub'); const db = require('./database'); const user = require('./user'); const groups = require('./groups'); @@ -389,7 +391,7 @@ Flags.deleteNote = async function (flagId, datetime) { 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; if (!timestamp) { timestamp = Date.now(); @@ -474,6 +476,11 @@ Flags.create = async function (type, id, uid, reason, timestamp, forceFlag = fal 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 }); return flagObj; }; @@ -681,6 +688,14 @@ Flags.targetExists = async function (type, id) { if (type === 'post') { return await posts.exists(id); } 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); } throw new Error('[[error:invalid-data]]'); diff --git a/src/user/data.js b/src/user/data.js index 86469b0f05..91b6448c60 100644 --- a/src/user/data.js +++ b/src/user/data.js @@ -353,7 +353,8 @@ module.exports = function (User) { }; 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)) { plugins.hooks.fire('action:user.set', { uid, field, value, type: 'set' }); } diff --git a/src/views/modals/flag.tpl b/src/views/modals/flag.tpl index 3b101138e4..c49c5e76b7 100644 --- a/src/views/modals/flag.tpl +++ b/src/views/modals/flag.tpl @@ -31,9 +31,17 @@ -
+
+ {{{ if remote }}} +
+ + +
+ {{{ end }}}