From 12a251c7d6538bcaadf6ff00ea82ad994a276725 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Sun, 1 Apr 2018 18:12:51 +0900 Subject: [PATCH] Implement post Activity Streams --- src/common/remote/activitypub/context.ts | 5 ++ src/models/post.ts | 1 + src/server/activitypub/index.ts | 2 + src/server/activitypub/post.ts | 85 ++++++++++++++++++++++++ src/server/activitypub/user.ts | 6 +- 5 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 src/common/remote/activitypub/context.ts create mode 100644 src/server/activitypub/post.ts diff --git a/src/common/remote/activitypub/context.ts b/src/common/remote/activitypub/context.ts new file mode 100644 index 000000000..b56f727ae --- /dev/null +++ b/src/common/remote/activitypub/context.ts @@ -0,0 +1,5 @@ +export default [ + 'https://www.w3.org/ns/activitystreams', + 'https://w3id.org/security/v1', + { Hashtag: 'as:Hashtag' } +]; diff --git a/src/models/post.ts b/src/models/post.ts index 6c853e4f8..64c46c263 100644 --- a/src/models/post.ts +++ b/src/models/post.ts @@ -47,6 +47,7 @@ export type IPost = { heading: number; speed: number; }; + tags: string[]; }; /** diff --git a/src/server/activitypub/index.ts b/src/server/activitypub/index.ts index 07ff407a7..6618c291f 100644 --- a/src/server/activitypub/index.ts +++ b/src/server/activitypub/index.ts @@ -1,11 +1,13 @@ import * as express from 'express'; +import post from './post'; import user from './user'; import inbox from './inbox'; const app = express(); app.disable('x-powered-by'); +app.use(post); app.use(user); app.use(inbox); diff --git a/src/server/activitypub/post.ts b/src/server/activitypub/post.ts new file mode 100644 index 000000000..4fb3a1319 --- /dev/null +++ b/src/server/activitypub/post.ts @@ -0,0 +1,85 @@ +import * as express from 'express'; +import context from '../../common/remote/activitypub/context'; +import parseAcct from '../../common/user/parse-acct'; +import config from '../../conf'; +import DriveFile from '../../models/drive-file'; +import Post from '../../models/post'; +import User from '../../models/user'; + +const app = express(); +app.disable('x-powered-by'); + +app.get('/@:user/:post', async (req, res, next) => { + const accepted = req.accepts(['html', 'application/activity+json', 'application/ld+json']); + if (!(['application/activity+json', 'application/ld+json'] as Array).includes(accepted)) { + return next(); + } + + const { username, host } = parseAcct(req.params.user); + if (host !== null) { + return res.sendStatus(422); + } + + const user = await User.findOne({ + usernameLower: username.toLowerCase(), + host: null + }); + if (user === null) { + return res.sendStatus(404); + } + + const post = await Post.findOne({ + _id: req.params.post, + userId: user._id + }); + if (post === null) { + return res.sendStatus(404); + } + + const asyncFiles = DriveFile.find({ _id: { $in: post.mediaIds } }); + let inReplyTo; + + if (post.replyId) { + const inReplyToPost = await Post.findOne({ + _id: post.replyId, + }); + + if (inReplyToPost !== null) { + const inReplyToUser = await User.findOne({ + _id: post.userId, + }); + + if (inReplyToUser !== null) { + inReplyTo = `${config.url}@${inReplyToUser.username}/${inReplyToPost._id}`; + } + } + } else { + inReplyTo = null; + } + + const attributedTo = `${config.url}/@${user.username}`; + + res.json({ + '@context': context, + id: `${attributedTo}/${post._id}`, + type: 'Note', + attributedTo, + content: post.textHtml, + published: post.createdAt.toISOString(), + to: 'https://www.w3.org/ns/activitystreams#Public', + cc: `${attributedTo}/followers`, + inReplyTo, + attachment: (await asyncFiles).map(({ _id, contentType }) => ({ + type: 'Document', + mediaType: contentType, + url: `${config.drive_url}/${_id}` + })), + tag: post.tags.map(tag => ({ + type: 'Hashtag', + href: `${config.url}/search?q=#${encodeURIComponent(tag)}`, + name: '#' + tag + })) + }); +}); + +export default app; diff --git a/src/server/activitypub/user.ts b/src/server/activitypub/user.ts index 488de93a9..ef365c207 100644 --- a/src/server/activitypub/user.ts +++ b/src/server/activitypub/user.ts @@ -1,6 +1,7 @@ import * as express from 'express'; import config from '../../conf'; import { extractPublic } from '../../crypto_key'; +import context from '../../common/remote/activitypub/context'; import parseAcct from '../../common/user/parse-acct'; import User, { ILocalAccount } from '../../models/user'; @@ -33,10 +34,7 @@ app.get('/@:user', async (req, res, next) => { } res.json({ - '@context': [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1' - ], + '@context': context, type: 'Person', id, inbox: `${id}/inbox`,