2023-05-17 13:13:30 -04:00
|
|
|
'use strict';
|
|
|
|
|
|
|
|
|
|
const nconf = require('nconf');
|
2024-02-26 11:39:32 -05:00
|
|
|
const winston = require('winston');
|
2023-05-17 13:13:30 -04:00
|
|
|
|
2025-01-20 12:05:17 -05:00
|
|
|
const meta = require('../../meta');
|
2023-06-23 14:59:47 -04:00
|
|
|
const user = require('../../user');
|
2023-06-21 17:16:37 -04:00
|
|
|
const activitypub = require('../../activitypub');
|
2024-02-21 14:58:52 -05:00
|
|
|
const helpers = require('../helpers');
|
2023-05-17 13:13:30 -04:00
|
|
|
|
|
|
|
|
const Controller = module.exports;
|
|
|
|
|
|
2024-01-22 16:18:49 -05:00
|
|
|
Controller.actors = require('./actors');
|
2024-01-12 15:23:30 -05:00
|
|
|
Controller.topics = require('./topics');
|
2023-06-21 17:16:37 -04:00
|
|
|
|
2024-10-23 15:25:16 -04:00
|
|
|
Controller.fetch = async (req, res, next) => {
|
|
|
|
|
// Given a `resource` query parameter, attempts to retrieve and parse it
|
2025-01-20 12:05:17 -05:00
|
|
|
if (!meta.config.activitypubEnabled || !req.query.resource) {
|
2024-10-23 15:25:16 -04:00
|
|
|
return next();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let url;
|
|
|
|
|
try {
|
|
|
|
|
url = new URL(req.query.resource);
|
|
|
|
|
const result = await activitypub.probe({
|
|
|
|
|
uid: req.uid,
|
|
|
|
|
url: url.href,
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
if (typeof result === 'string') {
|
|
|
|
|
return helpers.redirect(res, result);
|
|
|
|
|
} else if (result) {
|
|
|
|
|
const { id, type } = await activitypub.get('uid', req.uid || 0, url.href);
|
|
|
|
|
switch (true) {
|
|
|
|
|
case activitypub._constants.acceptedPostTypes.includes(type): {
|
|
|
|
|
return helpers.redirect(res, `/post/${encodeURIComponent(id)}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
case activitypub._constants.acceptableActorTypes.has(type): {
|
|
|
|
|
await activitypub.actors.assert(id);
|
|
|
|
|
const userslug = await user.getUserField(id, 'userslug');
|
|
|
|
|
return helpers.redirect(res, `/user/${userslug}`);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
default:
|
2024-10-25 12:37:45 -04:00
|
|
|
return helpers.redirect(res, url.href, false);
|
2024-10-23 15:25:16 -04:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
helpers.redirect(res, url.href, false);
|
|
|
|
|
} catch (e) {
|
2024-12-24 20:54:49 -05:00
|
|
|
if (!url || !url.href) {
|
|
|
|
|
return next();
|
|
|
|
|
}
|
2024-10-23 15:25:16 -04:00
|
|
|
activitypub.helpers.log(`[activitypub/fetch] Invalid URL received: ${url}`);
|
2024-10-25 12:49:49 -04:00
|
|
|
helpers.redirect(res, url.href, false);
|
2024-10-23 15:25:16 -04:00
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
2023-05-28 15:42:29 -04:00
|
|
|
Controller.getFollowing = async (req, res) => {
|
2024-02-01 16:53:47 -05:00
|
|
|
const { followingCount, followingRemoteCount } = await user.getUserFields(req.params.uid, ['followingCount', 'followingRemoteCount']);
|
|
|
|
|
const totalItems = parseInt(followingCount || 0, 10) + parseInt(followingRemoteCount || 0, 10);
|
2024-01-30 12:11:33 -05:00
|
|
|
let orderedItems;
|
2024-02-01 16:53:47 -05:00
|
|
|
let next = (totalItems && `${nconf.get('url')}/uid/${req.params.uid}/following?page=`) || null;
|
2023-05-28 15:42:29 -04:00
|
|
|
|
2024-02-01 16:53:47 -05:00
|
|
|
if (totalItems) {
|
|
|
|
|
if (req.query.page) {
|
|
|
|
|
const page = parseInt(req.query.page, 10) || 1;
|
|
|
|
|
const resultsPerPage = 50;
|
|
|
|
|
const start = Math.max(0, page - 1) * resultsPerPage;
|
|
|
|
|
const stop = start + resultsPerPage - 1;
|
2024-01-30 12:11:33 -05:00
|
|
|
|
2024-02-01 16:53:47 -05:00
|
|
|
orderedItems = await user.getFollowing(req.params.uid, start, stop);
|
|
|
|
|
orderedItems = orderedItems.map(({ userslug }) => `${nconf.get('url')}/user/${userslug}`);
|
|
|
|
|
if (stop < totalItems - 1) {
|
|
|
|
|
next = `${next}${page + 1}`;
|
|
|
|
|
} else {
|
|
|
|
|
next = null;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
orderedItems = [];
|
|
|
|
|
next = `${next}1`;
|
|
|
|
|
}
|
2024-01-30 12:11:33 -05:00
|
|
|
}
|
2023-05-28 15:42:29 -04:00
|
|
|
|
|
|
|
|
res.status(200).json({
|
|
|
|
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
|
|
|
|
type: 'OrderedCollection',
|
|
|
|
|
totalItems,
|
|
|
|
|
orderedItems,
|
2024-02-01 16:53:47 -05:00
|
|
|
next,
|
2023-05-28 15:42:29 -04:00
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Controller.getFollowers = async (req, res) => {
|
2024-02-01 16:53:47 -05:00
|
|
|
const { followerCount, followerRemoteCount } = await user.getUserFields(req.params.uid, ['followerCount', 'followerRemoteCount']);
|
|
|
|
|
const totalItems = parseInt(followerCount || 0, 10) + parseInt(followerRemoteCount || 0, 10);
|
|
|
|
|
let orderedItems = [];
|
|
|
|
|
let next = (totalItems && `${nconf.get('url')}/uid/${req.params.uid}/followers?page=`) || null;
|
2024-01-30 12:11:33 -05:00
|
|
|
|
2024-02-01 16:53:47 -05:00
|
|
|
if (totalItems) {
|
|
|
|
|
if (req.query.page) {
|
|
|
|
|
const page = parseInt(req.query.page, 10) || 1;
|
|
|
|
|
const resultsPerPage = 50;
|
|
|
|
|
const start = Math.max(0, page - 1) * resultsPerPage;
|
|
|
|
|
const stop = start + resultsPerPage - 1;
|
2023-05-28 15:42:29 -04:00
|
|
|
|
2024-02-01 16:53:47 -05:00
|
|
|
orderedItems = await user.getFollowers(req.params.uid, start, stop);
|
|
|
|
|
orderedItems = orderedItems.map(({ userslug }) => `${nconf.get('url')}/user/${userslug}`);
|
|
|
|
|
if (stop < totalItems - 1) {
|
|
|
|
|
next = `${next}${page + 1}`;
|
|
|
|
|
} else {
|
|
|
|
|
next = null;
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
orderedItems = [];
|
|
|
|
|
next = `${next}1`;
|
|
|
|
|
}
|
2024-01-30 12:11:33 -05:00
|
|
|
}
|
2023-05-28 15:42:29 -04:00
|
|
|
|
|
|
|
|
res.status(200).json({
|
|
|
|
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
|
|
|
|
type: 'OrderedCollection',
|
|
|
|
|
totalItems,
|
|
|
|
|
orderedItems,
|
2024-02-01 16:53:47 -05:00
|
|
|
next,
|
2023-05-28 15:42:29 -04:00
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2023-05-25 14:29:06 -04:00
|
|
|
Controller.getOutbox = async (req, res) => {
|
|
|
|
|
// stub
|
|
|
|
|
res.status(200).json({
|
|
|
|
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
|
|
|
|
type: 'OrderedCollection',
|
|
|
|
|
totalItems: 0,
|
2024-02-02 17:19:59 -05:00
|
|
|
orderedItems: [],
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Controller.getCategoryOutbox = async (req, res) => {
|
|
|
|
|
// stub
|
|
|
|
|
res.status(200).json({
|
|
|
|
|
'@context': 'https://www.w3.org/ns/activitystreams',
|
|
|
|
|
type: 'OrderedCollection',
|
|
|
|
|
totalItems: 0,
|
2023-05-25 14:29:06 -04:00
|
|
|
orderedItems: [],
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Controller.postOutbox = async (req, res) => {
|
|
|
|
|
// This is a client-to-server feature so it is deliberately not implemented at this time.
|
|
|
|
|
res.sendStatus(405);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Controller.getInbox = async (req, res) => {
|
|
|
|
|
// This is a client-to-server feature so it is deliberately not implemented at this time.
|
|
|
|
|
res.sendStatus(405);
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
Controller.postInbox = async (req, res) => {
|
2023-12-21 15:49:51 -05:00
|
|
|
// Note: underlying methods are internal use only, hence no exposure via src/api
|
2024-02-06 14:57:44 -05:00
|
|
|
const method = String(req.body.type).toLowerCase();
|
2024-02-07 12:35:58 -05:00
|
|
|
if (!activitypub.inbox.hasOwnProperty(method)) {
|
2024-02-26 11:39:32 -05:00
|
|
|
winston.warn(`[activitypub/inbox] Received Activity of type ${method} but unable to handle. Ignoring.`);
|
2024-02-06 14:57:44 -05:00
|
|
|
return res.sendStatus(501);
|
2023-06-28 14:59:39 -04:00
|
|
|
}
|
2024-01-26 21:39:20 -05:00
|
|
|
|
2024-02-21 14:58:52 -05:00
|
|
|
try {
|
|
|
|
|
await activitypub.inbox[method](req);
|
2024-05-14 12:06:59 -04:00
|
|
|
await activitypub.record(req.body);
|
|
|
|
|
helpers.formatApiResponse(202, res);
|
2024-02-21 14:58:52 -05:00
|
|
|
} catch (e) {
|
|
|
|
|
helpers.formatApiResponse(500, res, e);
|
|
|
|
|
}
|
2023-05-25 14:29:06 -04:00
|
|
|
};
|