From 3178bb20c72380c4379e7b72afa7e468d24e3e97 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 31 Mar 2018 21:41:08 +0900 Subject: Use Vue rendering function and some refactors --- src/client/app/common/views/components/index.ts | 2 +- .../views/components/messaging-room.message.vue | 41 +++--- .../app/common/views/components/post-html.ts | 157 +++++++++++++++++++++ .../app/common/views/components/post-html.vue | 103 -------------- src/client/app/common/views/components/url.vue | 57 ++++++++ .../common/views/components/welcome-timeline.vue | 2 +- .../desktop/views/components/post-detail.sub.vue | 2 +- .../app/desktop/views/components/post-detail.vue | 42 +++--- .../app/desktop/views/components/posts.post.vue | 43 +++--- .../desktop/views/components/sub-post-content.vue | 2 +- .../app/mobile/views/components/post-detail.vue | 37 ++--- src/client/app/mobile/views/components/post.vue | 45 +++--- .../mobile/views/components/sub-post-content.vue | 2 +- src/common/text/parse/index.ts | 2 +- 14 files changed, 324 insertions(+), 213 deletions(-) create mode 100644 src/client/app/common/views/components/post-html.ts delete mode 100644 src/client/app/common/views/components/post-html.vue create mode 100644 src/client/app/common/views/components/url.vue (limited to 'src') diff --git a/src/client/app/common/views/components/index.ts b/src/client/app/common/views/components/index.ts index 8c10bdee28..b58ba37ecb 100644 --- a/src/client/app/common/views/components/index.ts +++ b/src/client/app/common/views/components/index.ts @@ -4,7 +4,7 @@ import signin from './signin.vue'; import signup from './signup.vue'; import forkit from './forkit.vue'; import nav from './nav.vue'; -import postHtml from './post-html.vue'; +import postHtml from './post-html'; import poll from './poll.vue'; import pollEditor from './poll-editor.vue'; import reactionIcon from './reaction-icon.vue'; diff --git a/src/client/app/common/views/components/messaging-room.message.vue b/src/client/app/common/views/components/messaging-room.message.vue index 25ceab85a1..91af26bffe 100644 --- a/src/client/app/common/views/components/messaging-room.message.vue +++ b/src/client/app/common/views/components/messaging-room.message.vue @@ -4,13 +4,13 @@
-
+

%i18n:common.tags.mk-messaging-message.is-read%

- +
@@ -35,35 +35,30 @@ - - diff --git a/src/client/app/common/views/components/url.vue b/src/client/app/common/views/components/url.vue new file mode 100644 index 0000000000..e6ffe4466d --- /dev/null +++ b/src/client/app/common/views/components/url.vue @@ -0,0 +1,57 @@ + + + + + diff --git a/src/client/app/common/views/components/welcome-timeline.vue b/src/client/app/common/views/components/welcome-timeline.vue index f379029f9f..09b090bdc1 100644 --- a/src/client/app/common/views/components/welcome-timeline.vue +++ b/src/client/app/common/views/components/welcome-timeline.vue @@ -15,7 +15,7 @@
- +
diff --git a/src/client/app/desktop/views/components/post-detail.sub.vue b/src/client/app/desktop/views/components/post-detail.sub.vue index b6148d9b28..1d5649cf92 100644 --- a/src/client/app/desktop/views/components/post-detail.sub.vue +++ b/src/client/app/desktop/views/components/post-detail.sub.vue @@ -16,7 +16,7 @@
- +
diff --git a/src/client/app/desktop/views/components/post-detail.vue b/src/client/app/desktop/views/components/post-detail.vue index e75ebe34b4..70bfdbba35 100644 --- a/src/client/app/desktop/views/components/post-detail.vue +++ b/src/client/app/desktop/views/components/post-detail.vue @@ -38,7 +38,7 @@
- +
@@ -79,6 +79,7 @@ import Vue from 'vue'; import dateStringify from '../../../common/scripts/date-stringify'; import getAcct from '../../../../../common/user/get-acct'; +import parse from '../../../../../common/text/parse'; import MkPostFormWindow from './post-form-window.vue'; import MkRepostFormWindow from './repost-form-window.vue'; @@ -90,6 +91,7 @@ export default Vue.extend({ components: { XSub }, + props: { post: { type: Object, @@ -99,19 +101,15 @@ export default Vue.extend({ default: false } }, - computed: { - acct() { - return getAcct(this.post.user); - } - }, + data() { return { context: [], contextFetching: false, - replies: [], - urls: [] + replies: [] }; }, + computed: { isRepost(): boolean { return (this.post.repost && @@ -131,8 +129,22 @@ export default Vue.extend({ }, title(): string { return dateStringify(this.p.createdAt); + }, + acct(): string { + return getAcct(this.p.user); + }, + urls(): string[] { + if (this.p.text) { + const ast = parse(this.p.text); + return ast + .filter(t => (t.type == 'url' || t.type == 'link') && !t.silent) + .map(t => t.url); + } else { + return null; + } } }, + mounted() { // Get replies if (!this.compact) { @@ -162,21 +174,7 @@ export default Vue.extend({ } } }, - watch: { - post: { - handler(newPost, oldPost) { - if (!oldPost || newPost.text !== oldPost.text) { - this.$nextTick(() => { - const elements = this.$refs.text.$el.getElementsByTagName('a'); - this.urls = [].filter.call(elements, ({ origin }) => origin !== location.origin) - .map(({ href }) => href); - }); - } - }, - immediate: true - } - }, methods: { fetchContext() { this.contextFetching = true; diff --git a/src/client/app/desktop/views/components/posts.post.vue b/src/client/app/desktop/views/components/posts.post.vue index f3566c81bf..c31e28d67f 100644 --- a/src/client/app/desktop/views/components/posts.post.vue +++ b/src/client/app/desktop/views/components/posts.post.vue @@ -38,7 +38,7 @@

@@ -86,6 +86,8 @@ import Vue from 'vue'; import dateStringify from '../../../common/scripts/date-stringify'; import getAcct from '../../../../../common/user/get-acct'; +import parse from '../../../../../common/text/parse'; + import MkPostFormWindow from './post-form-window.vue'; import MkRepostFormWindow from './repost-form-window.vue'; import MkPostMenu from '../../../common/views/components/post-menu.vue'; @@ -107,17 +109,19 @@ export default Vue.extend({ components: { XSub }, + props: ['post'], + data() { return { isDetailOpened: false, connection: null, - connectionId: null, - urls: [] + connectionId: null }; }, + computed: { - acct() { + acct(): string { return getAcct(this.p.user); }, isRepost(): boolean { @@ -141,14 +145,26 @@ export default Vue.extend({ }, url(): string { return `/@${this.acct}/${this.p.id}`; + }, + urls(): string[] { + if (this.p.text) { + const ast = parse(this.p.text); + return ast + .filter(t => (t.type == 'url' || t.type == 'link') && !t.silent) + .map(t => t.url); + } else { + return null; + } } }, + created() { if ((this as any).os.isSignedIn) { this.connection = (this as any).os.stream.getConnection(); this.connectionId = (this as any).os.stream.use(); } }, + mounted() { this.capture(true); @@ -174,6 +190,7 @@ export default Vue.extend({ } } }, + beforeDestroy() { this.decapture(true); @@ -182,21 +199,7 @@ export default Vue.extend({ (this as any).os.stream.dispose(this.connectionId); } }, - watch: { - post: { - handler(newPost, oldPost) { - if (!oldPost || newPost.textHtml !== oldPost.textHtml) { - this.$nextTick(() => { - const elements = this.$refs.text.$el.getElementsByTagName('a'); - - this.urls = [].filter.call(elements, ({ origin }) => origin !== location.origin) - .map(({ href }) => href); - }); - } - }, - immediate: true - } - }, + methods: { capture(withHandler = false) { if ((this as any).os.isSignedIn) { @@ -457,7 +460,7 @@ export default Vue.extend({ font-size 1.1em color #717171 - >>> blockquote + >>> .quote margin 8px padding 6px 12px color #aaa diff --git a/src/client/app/desktop/views/components/sub-post-content.vue b/src/client/app/desktop/views/components/sub-post-content.vue index 58c81e7552..17899af280 100644 --- a/src/client/app/desktop/views/components/sub-post-content.vue +++ b/src/client/app/desktop/views/components/sub-post-content.vue @@ -2,7 +2,7 @@
diff --git a/src/client/app/mobile/views/components/post-detail.vue b/src/client/app/mobile/views/components/post-detail.vue index 77a73426f2..0a4e36fc60 100644 --- a/src/client/app/mobile/views/components/post-detail.vue +++ b/src/client/app/mobile/views/components/post-detail.vue @@ -81,6 +81,8 @@ -- cgit v1.2.3-freya From fabf233478ad79488cd95b1fcfb511a0c5d348bb Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Sun, 1 Apr 2018 15:58:49 +0900 Subject: Implement inbox --- package.json | 1 + src/common/remote/activitypub/resolve-person.ts | 4 ++ src/models/user.ts | 4 ++ src/server/activitypub.ts | 62 ------------------------- src/server/activitypub/inbox.ts | 42 +++++++++++++++++ src/server/activitypub/index.ts | 12 +++++ src/server/activitypub/user.ts | 62 +++++++++++++++++++++++++ 7 files changed, 125 insertions(+), 62 deletions(-) delete mode 100644 src/server/activitypub.ts create mode 100644 src/server/activitypub/inbox.ts create mode 100644 src/server/activitypub/index.ts create mode 100644 src/server/activitypub/user.ts (limited to 'src') diff --git a/package.json b/package.json index 4275c1c1c3..14c89927ee 100644 --- a/package.json +++ b/package.json @@ -134,6 +134,7 @@ "hard-source-webpack-plugin": "0.6.4", "highlight.js": "9.12.0", "html-minifier": "3.5.12", + "http-signature": "^1.2.0", "inquirer": "5.2.0", "is-root": "2.0.0", "is-url": "1.2.4", diff --git a/src/common/remote/activitypub/resolve-person.ts b/src/common/remote/activitypub/resolve-person.ts index c7c131b0ea..999a37eea1 100644 --- a/src/common/remote/activitypub/resolve-person.ts +++ b/src/common/remote/activitypub/resolve-person.ts @@ -62,6 +62,10 @@ export default async (value, usernameLower, hostLower, acctLower) => { host: toUnicode(finger.subject.replace(/^.*?@/, '')), hostLower, account: { + publicKey: { + id: object.publicKey.id, + publicKeyPem: object.publicKey.publicKeyPem + }, uri: object.id, }, }); diff --git a/src/models/user.ts b/src/models/user.ts index 02e6a570b9..9588c45153 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -71,6 +71,10 @@ export type ILocalAccount = { export type IRemoteAccount = { uri: string; + publicKey: { + id: string; + publicKeyPem: string; + }; }; export type IUser = { diff --git a/src/server/activitypub.ts b/src/server/activitypub.ts deleted file mode 100644 index a48a8e643b..0000000000 --- a/src/server/activitypub.ts +++ /dev/null @@ -1,62 +0,0 @@ -import * as express from 'express'; - -import config from '../conf'; -import { extractPublic } from '../crypto_key'; -import parseAcct from '../common/user/parse-acct'; -import User, { ILocalAccount } from '../models/user'; - -const app = express(); -app.disable('x-powered-by'); - -app.get('/@:user', async (req, res, next) => { - const accepted = req.accepts(['html', 'application/activity+json', 'application/ld+json']); - if (!['application/activity+json', 'application/ld+json'].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 id = `${config.url}/@${user.username}`; - - if (username !== user.username) { - return res.redirect(id); - } - - res.json({ - '@context': [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1' - ], - type: 'Person', - id, - preferredUsername: user.username, - name: user.name, - summary: user.description, - icon: user.avatarId && { - type: 'Image', - url: `${config.drive_url}/${user.avatarId}` - }, - image: user.bannerId && { - type: 'Image', - url: `${config.drive_url}/${user.bannerId}` - }, - publicKey: { - type: 'Key', - owner: id, - publicKeyPem: extractPublic((user.account as ILocalAccount).keypair) - } - }); -}); - -export default app; diff --git a/src/server/activitypub/inbox.ts b/src/server/activitypub/inbox.ts new file mode 100644 index 0000000000..0d4af7c492 --- /dev/null +++ b/src/server/activitypub/inbox.ts @@ -0,0 +1,42 @@ +import * as bodyParser from 'body-parser'; +import * as express from 'express'; +import { parseRequest, verifySignature } from 'http-signature'; +import User, { IRemoteAccount } from '../../models/user'; +import queue from '../../queue'; + +const app = express(); +app.disable('x-powered-by'); +app.use(bodyParser.json()); + +app.get('/@:user/inbox', async (req, res) => { + let parsed; + + try { + parsed = parseRequest(req); + } catch (exception) { + return res.sendStatus(401); + } + + const user = await User.findOne({ + host: { $ne: null }, + account: { publicKey: { id: parsed.keyId } } + }); + + if (user === null) { + return res.sendStatus(401); + } + + if (!verifySignature(parsed, (user.account as IRemoteAccount).publicKey.publicKeyPem)) { + return res.sendStatus(401); + } + + queue.create('http', { + type: 'performActivityPub', + actor: user._id, + outbox: req.body, + }).save(); + + return res.sendStatus(200); +}); + +export default app; diff --git a/src/server/activitypub/index.ts b/src/server/activitypub/index.ts new file mode 100644 index 0000000000..07ff407a76 --- /dev/null +++ b/src/server/activitypub/index.ts @@ -0,0 +1,12 @@ +import * as express from 'express'; + +import user from './user'; +import inbox from './inbox'; + +const app = express(); +app.disable('x-powered-by'); + +app.use(user); +app.use(inbox); + +export default app; diff --git a/src/server/activitypub/user.ts b/src/server/activitypub/user.ts new file mode 100644 index 0000000000..488de93a92 --- /dev/null +++ b/src/server/activitypub/user.ts @@ -0,0 +1,62 @@ +import * as express from 'express'; +import config from '../../conf'; +import { extractPublic } from '../../crypto_key'; +import parseAcct from '../../common/user/parse-acct'; +import User, { ILocalAccount } from '../../models/user'; + +const app = express(); +app.disable('x-powered-by'); + +app.get('/@:user', 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 id = `${config.url}/@${user.username}`; + + if (username !== user.username) { + return res.redirect(id); + } + + res.json({ + '@context': [ + 'https://www.w3.org/ns/activitystreams', + 'https://w3id.org/security/v1' + ], + type: 'Person', + id, + inbox: `${id}/inbox`, + preferredUsername: user.username, + name: user.name, + summary: user.description, + icon: user.avatarId && { + type: 'Image', + url: `${config.drive_url}/${user.avatarId}` + }, + image: user.bannerId && { + type: 'Image', + url: `${config.drive_url}/${user.bannerId}` + }, + publicKey: { + type: 'Key', + owner: id, + publicKeyPem: extractPublic((user.account as ILocalAccount).keypair) + } + }); +}); + +export default app; -- cgit v1.2.3-freya From 54776545b6bb297b3af19767bed98b2634ba3046 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 1 Apr 2018 16:04:23 +0900 Subject: Use dot notation --- src/server/activitypub/inbox.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/server/activitypub/inbox.ts b/src/server/activitypub/inbox.ts index 0d4af7c492..f76e750d22 100644 --- a/src/server/activitypub/inbox.ts +++ b/src/server/activitypub/inbox.ts @@ -19,7 +19,7 @@ app.get('/@:user/inbox', async (req, res) => { const user = await User.findOne({ host: { $ne: null }, - account: { publicKey: { id: parsed.keyId } } + 'account.publicKey.id': parsed.keyId }); if (user === null) { -- cgit v1.2.3-freya From d36f5376e38f30fe12fd3459aa833def230e5302 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 1 Apr 2018 16:46:33 +0900 Subject: Fix --- src/server/activitypub/inbox.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/server/activitypub/inbox.ts b/src/server/activitypub/inbox.ts index f76e750d22..b4761d9972 100644 --- a/src/server/activitypub/inbox.ts +++ b/src/server/activitypub/inbox.ts @@ -8,7 +8,7 @@ const app = express(); app.disable('x-powered-by'); app.use(bodyParser.json()); -app.get('/@:user/inbox', async (req, res) => { +app.post('/@:user/inbox', async (req, res) => { let parsed; try { -- cgit v1.2.3-freya From 63e8050094c03a61a4a7532c4eadf9586ab7a909 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 1 Apr 2018 18:07:29 +0900 Subject: Add missing property --- src/models/post.ts | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/models/post.ts b/src/models/post.ts index 6c853e4f81..4daad306d6 100644 --- a/src/models/post.ts +++ b/src/models/post.ts @@ -30,6 +30,7 @@ export type IPost = { repostId: mongo.ObjectID; poll: any; // todo text: string; + tags: string[]; textHtml: string; cw: string; userId: mongo.ObjectID; -- cgit v1.2.3-freya From 12a251c7d6538bcaadf6ff00ea82ad994a276725 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Sun, 1 Apr 2018 18:12:51 +0900 Subject: 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 (limited to 'src') diff --git a/src/common/remote/activitypub/context.ts b/src/common/remote/activitypub/context.ts new file mode 100644 index 0000000000..b56f727ae7 --- /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 6c853e4f81..64c46c2635 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 07ff407a76..6618c291f7 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 0000000000..4fb3a1319b --- /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 488de93a92..ef365c2078 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`, -- cgit v1.2.3-freya From c83bb3b8ab59ea8b6a5042b55b859076cdfa127e Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Sun, 1 Apr 2018 18:16:47 +0900 Subject: Respond with 202 for inbox request --- src/server/activitypub/inbox.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/server/activitypub/inbox.ts b/src/server/activitypub/inbox.ts index b4761d9972..9151297487 100644 --- a/src/server/activitypub/inbox.ts +++ b/src/server/activitypub/inbox.ts @@ -36,7 +36,7 @@ app.post('/@:user/inbox', async (req, res) => { outbox: req.body, }).save(); - return res.sendStatus(200); + return res.status(202).end(); }); export default app; -- cgit v1.2.3-freya From fea2e549ba42359a1e32618f489beda50e649504 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 1 Apr 2018 18:17:04 +0900 Subject: Resolve conflict --- src/models/post.ts | 1 - 1 file changed, 1 deletion(-) (limited to 'src') diff --git a/src/models/post.ts b/src/models/post.ts index e7b54180ff..4daad306d6 100644 --- a/src/models/post.ts +++ b/src/models/post.ts @@ -48,7 +48,6 @@ export type IPost = { heading: number; speed: number; }; - tags: string[]; }; /** -- cgit v1.2.3-freya From 9a90d8a7b4f874c8ad2c60c41de6d3669f8799c4 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 1 Apr 2018 18:20:17 +0900 Subject: Fix type annotation --- src/server/activitypub/post.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/server/activitypub/post.ts b/src/server/activitypub/post.ts index 4fb3a1319b..261d7ca4a5 100644 --- a/src/server/activitypub/post.ts +++ b/src/server/activitypub/post.ts @@ -11,7 +11,7 @@ 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)) { + if (!(['application/activity+json', 'application/ld+json'] as any[]).includes(accepted)) { return next(); } -- cgit v1.2.3-freya From 0cb6fbea8c8cf451b33fde510a1008ecd4884e80 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 1 Apr 2018 18:28:10 +0900 Subject: Refactor: Better variable name --- src/server/activitypub/post.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/server/activitypub/post.ts b/src/server/activitypub/post.ts index 261d7ca4a5..bdfce0606d 100644 --- a/src/server/activitypub/post.ts +++ b/src/server/activitypub/post.ts @@ -36,7 +36,7 @@ app.get('/@:user/:post', async (req, res, next) => { return res.sendStatus(404); } - const asyncFiles = DriveFile.find({ _id: { $in: post.mediaIds } }); + const promisedFiles = DriveFile.find({ _id: { $in: post.mediaIds } }); let inReplyTo; if (post.replyId) { @@ -69,7 +69,7 @@ app.get('/@:user/:post', async (req, res, next) => { to: 'https://www.w3.org/ns/activitystreams#Public', cc: `${attributedTo}/followers`, inReplyTo, - attachment: (await asyncFiles).map(({ _id, contentType }) => ({ + attachment: (await promisedFiles).map(({ _id, contentType }) => ({ type: 'Document', mediaType: contentType, url: `${config.drive_url}/${_id}` -- cgit v1.2.3-freya From 1f1417a0f7505276af8d4b5d81edb44c7648b499 Mon Sep 17 00:00:00 2001 From: Akihiko Odaki Date: Sun, 1 Apr 2018 19:18:36 +0900 Subject: Implement outbox --- src/common/remote/activitypub/context.ts | 5 --- src/common/remote/activitypub/renderer/context.ts | 5 +++ src/common/remote/activitypub/renderer/document.ts | 7 +++ src/common/remote/activitypub/renderer/hashtag.ts | 7 +++ src/common/remote/activitypub/renderer/image.ts | 6 +++ src/common/remote/activitypub/renderer/key.ts | 9 ++++ src/common/remote/activitypub/renderer/note.ts | 44 +++++++++++++++++++ .../activitypub/renderer/ordered-collection.ts | 6 +++ src/common/remote/activitypub/renderer/person.ts | 20 +++++++++ src/server/activitypub/index.ts | 6 ++- src/server/activitypub/outbox.ts | 45 +++++++++++++++++++ src/server/activitypub/post.ts | 51 +++------------------- src/server/activitypub/user.ts | 36 ++++----------- 13 files changed, 166 insertions(+), 81 deletions(-) delete mode 100644 src/common/remote/activitypub/context.ts create mode 100644 src/common/remote/activitypub/renderer/context.ts create mode 100644 src/common/remote/activitypub/renderer/document.ts create mode 100644 src/common/remote/activitypub/renderer/hashtag.ts create mode 100644 src/common/remote/activitypub/renderer/image.ts create mode 100644 src/common/remote/activitypub/renderer/key.ts create mode 100644 src/common/remote/activitypub/renderer/note.ts create mode 100644 src/common/remote/activitypub/renderer/ordered-collection.ts create mode 100644 src/common/remote/activitypub/renderer/person.ts create mode 100644 src/server/activitypub/outbox.ts (limited to 'src') diff --git a/src/common/remote/activitypub/context.ts b/src/common/remote/activitypub/context.ts deleted file mode 100644 index b56f727ae7..0000000000 --- a/src/common/remote/activitypub/context.ts +++ /dev/null @@ -1,5 +0,0 @@ -export default [ - 'https://www.w3.org/ns/activitystreams', - 'https://w3id.org/security/v1', - { Hashtag: 'as:Hashtag' } -]; diff --git a/src/common/remote/activitypub/renderer/context.ts b/src/common/remote/activitypub/renderer/context.ts new file mode 100644 index 0000000000..b56f727ae7 --- /dev/null +++ b/src/common/remote/activitypub/renderer/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/common/remote/activitypub/renderer/document.ts b/src/common/remote/activitypub/renderer/document.ts new file mode 100644 index 0000000000..4a456416a9 --- /dev/null +++ b/src/common/remote/activitypub/renderer/document.ts @@ -0,0 +1,7 @@ +import config from '../../../../conf'; + +export default ({ _id, contentType }) => ({ + type: 'Document', + mediaType: contentType, + url: `${config.drive_url}/${_id}` +}); diff --git a/src/common/remote/activitypub/renderer/hashtag.ts b/src/common/remote/activitypub/renderer/hashtag.ts new file mode 100644 index 0000000000..ad42700204 --- /dev/null +++ b/src/common/remote/activitypub/renderer/hashtag.ts @@ -0,0 +1,7 @@ +import config from '../../../../conf'; + +export default tag => ({ + type: 'Hashtag', + href: `${config.url}/search?q=#${encodeURIComponent(tag)}`, + name: '#' + tag +}); diff --git a/src/common/remote/activitypub/renderer/image.ts b/src/common/remote/activitypub/renderer/image.ts new file mode 100644 index 0000000000..345fbbec59 --- /dev/null +++ b/src/common/remote/activitypub/renderer/image.ts @@ -0,0 +1,6 @@ +import config from '../../../../conf'; + +export default ({ _id }) => ({ + type: 'Image', + url: `${config.drive_url}/${_id}` +}); diff --git a/src/common/remote/activitypub/renderer/key.ts b/src/common/remote/activitypub/renderer/key.ts new file mode 100644 index 0000000000..7148c59745 --- /dev/null +++ b/src/common/remote/activitypub/renderer/key.ts @@ -0,0 +1,9 @@ +import config from '../../../../conf'; +import { extractPublic } from '../../../../crypto_key'; +import { ILocalAccount } from '../../../../models/user'; + +export default ({ username, account }) => ({ + type: 'Key', + owner: `${config.url}/@${username}`, + publicKeyPem: extractPublic((account as ILocalAccount).keypair) +}); diff --git a/src/common/remote/activitypub/renderer/note.ts b/src/common/remote/activitypub/renderer/note.ts new file mode 100644 index 0000000000..2fe20b2136 --- /dev/null +++ b/src/common/remote/activitypub/renderer/note.ts @@ -0,0 +1,44 @@ +import renderDocument from './document'; +import renderHashtag from './hashtag'; +import config from '../../../../conf'; +import DriveFile from '../../../../models/drive-file'; +import Post from '../../../../models/post'; +import User from '../../../../models/user'; + +export default async (user, post) => { + const promisedFiles = 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}`; + + return { + 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 promisedFiles).map(renderDocument), + tag: post.tags.map(renderHashtag) + }; +}; diff --git a/src/common/remote/activitypub/renderer/ordered-collection.ts b/src/common/remote/activitypub/renderer/ordered-collection.ts new file mode 100644 index 0000000000..2ca0f77354 --- /dev/null +++ b/src/common/remote/activitypub/renderer/ordered-collection.ts @@ -0,0 +1,6 @@ +export default (id, totalItems, orderedItems) => ({ + id, + type: 'OrderedCollection', + totalItems, + orderedItems +}); diff --git a/src/common/remote/activitypub/renderer/person.ts b/src/common/remote/activitypub/renderer/person.ts new file mode 100644 index 0000000000..7303b30385 --- /dev/null +++ b/src/common/remote/activitypub/renderer/person.ts @@ -0,0 +1,20 @@ +import renderImage from './image'; +import renderKey from './key'; +import config from '../../../../conf'; + +export default user => { + const id = `${config.url}/@${user.username}`; + + return { + type: 'Person', + id, + inbox: `${id}/inbox`, + outbox: `${id}/outbox`, + preferredUsername: user.username, + name: user.name, + summary: user.description, + icon: user.avatarId && renderImage({ _id: user.avatarId }), + image: user.bannerId && renderImage({ _id: user.bannerId }), + publicKey: renderKey(user) + }; +}; diff --git a/src/server/activitypub/index.ts b/src/server/activitypub/index.ts index 6618c291f7..c81024d15f 100644 --- a/src/server/activitypub/index.ts +++ b/src/server/activitypub/index.ts @@ -1,14 +1,16 @@ import * as express from 'express'; -import post from './post'; import user from './user'; import inbox from './inbox'; +import outbox from './outbox'; +import post from './post'; const app = express(); app.disable('x-powered-by'); -app.use(post); app.use(user); app.use(inbox); +app.use(outbox); +app.use(post); export default app; diff --git a/src/server/activitypub/outbox.ts b/src/server/activitypub/outbox.ts new file mode 100644 index 0000000000..c5a42ae0a9 --- /dev/null +++ b/src/server/activitypub/outbox.ts @@ -0,0 +1,45 @@ +import * as express from 'express'; +import context from '../../common/remote/activitypub/renderer/context'; +import renderNote from '../../common/remote/activitypub/renderer/note'; +import renderOrderedCollection from '../../common/remote/activitypub/renderer/ordered-collection'; +import parseAcct from '../../common/user/parse-acct'; +import config from '../../conf'; +import Post from '../../models/post'; +import User from '../../models/user'; + +const app = express(); +app.disable('x-powered-by'); + +app.get('/@:user/outbox', async (req, res) => { + 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 id = `${config.url}/@${user.username}/inbox`; + + if (username !== user.username) { + return res.redirect(id); + } + + const posts = await Post.find({ userId: user._id }, { + limit: 20, + sort: { _id: -1 } + }); + + const renderedPosts = await Promise.all(posts.map(post => renderNote(user, post))); + const rendered = renderOrderedCollection(id, user.postsCount, renderedPosts); + rendered['@context'] = context; + + res.json(rendered); +}); + +export default app; diff --git a/src/server/activitypub/post.ts b/src/server/activitypub/post.ts index bdfce0606d..6644563d8c 100644 --- a/src/server/activitypub/post.ts +++ b/src/server/activitypub/post.ts @@ -1,8 +1,7 @@ import * as express from 'express'; -import context from '../../common/remote/activitypub/context'; +import context from '../../common/remote/activitypub/renderer/context'; +import render from '../../common/remote/activitypub/renderer/note'; 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'; @@ -36,50 +35,10 @@ app.get('/@:user/:post', async (req, res, next) => { return res.sendStatus(404); } - const promisedFiles = DriveFile.find({ _id: { $in: post.mediaIds } }); - let inReplyTo; + const rendered = await render(user, post); + rendered['@context'] = context; - 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 promisedFiles).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 - })) - }); + res.json(rendered); }); export default app; diff --git a/src/server/activitypub/user.ts b/src/server/activitypub/user.ts index ef365c2078..d43a9793d4 100644 --- a/src/server/activitypub/user.ts +++ b/src/server/activitypub/user.ts @@ -1,9 +1,9 @@ import * as express from 'express'; import config from '../../conf'; -import { extractPublic } from '../../crypto_key'; -import context from '../../common/remote/activitypub/context'; +import context from '../../common/remote/activitypub/renderer/context'; +import render from '../../common/remote/activitypub/renderer/person'; import parseAcct from '../../common/user/parse-acct'; -import User, { ILocalAccount } from '../../models/user'; +import User from '../../models/user'; const app = express(); app.disable('x-powered-by'); @@ -27,34 +27,14 @@ app.get('/@:user', async (req, res, next) => { return res.sendStatus(404); } - const id = `${config.url}/@${user.username}`; - if (username !== user.username) { - return res.redirect(id); + return res.redirect(`${config.url}/@${user.username}`); } - res.json({ - '@context': context, - type: 'Person', - id, - inbox: `${id}/inbox`, - preferredUsername: user.username, - name: user.name, - summary: user.description, - icon: user.avatarId && { - type: 'Image', - url: `${config.drive_url}/${user.avatarId}` - }, - image: user.bannerId && { - type: 'Image', - url: `${config.drive_url}/${user.bannerId}` - }, - publicKey: { - type: 'Key', - owner: id, - publicKeyPem: extractPublic((user.account as ILocalAccount).keypair) - } - }); + const rendered = render(user); + rendered['@context'] = context; + + res.json(rendered); }); export default app; -- cgit v1.2.3-freya