diff options
| author | mei23 <m@m544.net> | 2018-08-14 20:13:32 +0900 |
|---|---|---|
| committer | mei23 <m@m544.net> | 2018-08-14 20:13:32 +0900 |
| commit | 0986301788a40663ced0aa9d6d8a97b099b622b1 (patch) | |
| tree | e68319f2e89dcd852ee7cf700dc0c6e033c97897 /src/server/activitypub | |
| parent | 5.23.2 (diff) | |
| download | sharkey-0986301788a40663ced0aa9d6d8a97b099b622b1.tar.gz sharkey-0986301788a40663ced0aa9d6d8a97b099b622b1.tar.bz2 sharkey-0986301788a40663ced0aa9d6d8a97b099b622b1.zip | |
Implement ActivityPub Followers/Following/Outbox
Diffstat (limited to 'src/server/activitypub')
| -rw-r--r-- | src/server/activitypub/followers.ts | 80 | ||||
| -rw-r--r-- | src/server/activitypub/following.ts | 80 | ||||
| -rw-r--r-- | src/server/activitypub/outbox.ts | 96 |
3 files changed, 256 insertions, 0 deletions
diff --git a/src/server/activitypub/followers.ts b/src/server/activitypub/followers.ts new file mode 100644 index 0000000000..d51d45b1c7 --- /dev/null +++ b/src/server/activitypub/followers.ts @@ -0,0 +1,80 @@ +import * as mongo from 'mongodb'; +import * as Koa from 'koa'; +import config from '../../config'; +import $ from 'cafy'; import ID from '../../misc/cafy-id'; +import User from '../../models/user'; +import Following from '../../models/following'; +import pack from '../../remote/activitypub/renderer'; +import renderOrderedCollection from '../../remote/activitypub/renderer/ordered-collection'; +import renderOrderedCollectionPage from '../../remote/activitypub/renderer/ordered-collection-page'; +import renderFollowUser from '../../remote/activitypub/renderer/follow-user'; + +export default async (ctx: Koa.Context) => { + const userId = new mongo.ObjectID(ctx.params.user); + + // Get 'cursor' parameter + const [cursor = null, cursorErr] = $.type(ID).optional.get(ctx.request.query.cursor); + + // Get 'page' parameter + const pageErr = !$.str.optional.or(['true', 'false']).ok(ctx.request.query.page); + const page: boolean = ctx.request.query.page === 'true'; + + // Validate parameters + if (cursorErr || pageErr) { + ctx.status = 400; + return; + } + + // Verify user + const user = await User.findOne({ + _id: userId, + host: null + }); + + if (user === null) { + ctx.status = 404; + return; + } + + const limit = 10; + const partOf = `${config.url}/users/${userId}/followers`; + + if (page) { + // Construct query + const query = { + followeeId: user._id + } as any; + + // カーソルが指定されている場合 + if (cursor) { + query._id = { + $lt: cursor + }; + } + + // Get followers + const followings = await Following + .find(query, { + limit: limit + 1, + sort: { _id: -1 } + }); + + // 「次のページ」があるかどうか + const inStock = followings.length === limit + 1; + if (inStock) followings.pop(); + + const renderedFollowers = await Promise.all(followings.map(following => renderFollowUser(following.followerId))); + const rendered = renderOrderedCollectionPage( + `${partOf}?page=true${cursor ? `&cursor=${cursor}` : ''}`, + user.followersCount, renderedFollowers, partOf, + null, + inStock ? `${partOf}?page=true&cursor=${followings[followings.length - 1]._id}` : null + ); + + ctx.body = pack(rendered); + } else { + // index page + const rendered = renderOrderedCollection(partOf, user.followersCount, `${partOf}?page=true`, null); + ctx.body = pack(rendered); + } +}; diff --git a/src/server/activitypub/following.ts b/src/server/activitypub/following.ts new file mode 100644 index 0000000000..7e496f590d --- /dev/null +++ b/src/server/activitypub/following.ts @@ -0,0 +1,80 @@ +import * as mongo from 'mongodb'; +import * as Koa from 'koa'; +import config from '../../config'; +import $ from 'cafy'; import ID from '../../misc/cafy-id'; +import User from '../../models/user'; +import Following from '../../models/following'; +import pack from '../../remote/activitypub/renderer'; +import renderOrderedCollection from '../../remote/activitypub/renderer/ordered-collection'; +import renderOrderedCollectionPage from '../../remote/activitypub/renderer/ordered-collection-page'; +import renderFollowUser from '../../remote/activitypub/renderer/follow-user'; + +export default async (ctx: Koa.Context) => { + const userId = new mongo.ObjectID(ctx.params.user); + + // Get 'cursor' parameter + const [cursor = null, cursorErr] = $.type(ID).optional.get(ctx.request.query.cursor); + + // Get 'page' parameter + const pageErr = !$.str.optional.or(['true', 'false']).ok(ctx.request.query.page); + const page: boolean = ctx.request.query.page === 'true'; + + // Validate parameters + if (cursorErr || pageErr) { + ctx.status = 400; + return; + } + + // Verify user + const user = await User.findOne({ + _id: userId, + host: null + }); + + if (user === null) { + ctx.status = 404; + return; + } + + const limit = 10; + const partOf = `${config.url}/users/${userId}/following`; + + if (page) { + // Construct query + const query = { + followerId: user._id + } as any; + + // カーソルが指定されている場合 + if (cursor) { + query._id = { + $lt: cursor + }; + } + + // Get followings + const followings = await Following + .find(query, { + limit: limit + 1, + sort: { _id: -1 } + }); + + // 「次のページ」があるかどうか + const inStock = followings.length === limit + 1; + if (inStock) followings.pop(); + + const renderedFollowees = await Promise.all(followings.map(following => renderFollowUser(following.followeeId))); + const rendered = renderOrderedCollectionPage( + `${partOf}?page=true${cursor ? `&cursor=${cursor}` : ''}`, + user.followingCount, renderedFollowees, partOf, + null, + inStock ? `${partOf}?page=true&cursor=${followings[followings.length - 1]._id}` : null + ); + + ctx.body = pack(rendered); + } else { + // index page + const rendered = renderOrderedCollection(partOf, user.followingCount, `${partOf}?page=true`, null); + ctx.body = pack(rendered); + } +}; diff --git a/src/server/activitypub/outbox.ts b/src/server/activitypub/outbox.ts new file mode 100644 index 0000000000..e441e3dc4e --- /dev/null +++ b/src/server/activitypub/outbox.ts @@ -0,0 +1,96 @@ +import * as mongo from 'mongodb'; +import * as Koa from 'koa'; +import config from '../../config'; +import $ from 'cafy'; import ID from '../../misc/cafy-id'; +import User from '../../models/user'; +import pack from '../../remote/activitypub/renderer'; +import renderOrderedCollection from '../../remote/activitypub/renderer/ordered-collection'; +import renderOrderedCollectionPage from '../../remote/activitypub/renderer/ordered-collection-page'; + +import Note from '../../models/note'; +import renderNote from '../../remote/activitypub/renderer/note'; + +export default async (ctx: Koa.Context) => { + const userId = new mongo.ObjectID(ctx.params.user); + + // Get 'sinceId' parameter + const [sinceId, sinceIdErr] = $.type(ID).optional.get(ctx.request.query.since_id); + + // Get 'untilId' parameter + const [untilId, untilIdErr] = $.type(ID).optional.get(ctx.request.query.until_id); + + // Get 'page' parameter + const pageErr = !$.str.optional.or(['true', 'false']).ok(ctx.request.query.page); + const page: boolean = ctx.request.query.page === 'true'; + + // Validate parameters + if (sinceIdErr || untilIdErr || pageErr || [sinceId, untilId].filter(x => x != null).length > 1) { + ctx.status = 400; + return; + } + + // Verify user + const user = await User.findOne({ + _id: userId, + host: null + }); + + if (user === null) { + ctx.status = 404; + return; + } + + const limit = 20; + const partOf = `${config.url}/users/${userId}/outbox`; + + if (page) { + //#region Construct query + const sort = { + _id: -1 + }; + + const query = { + userId: user._id, + $or: [ { visibility: 'public' }, { visibility: 'home' } ], + text: { $ne: null } // exclude renote, but include quote + } as any; + + if (sinceId) { + sort._id = 1; + query._id = { + $gt: sinceId + }; + } else if (untilId) { + query._id = { + $lt: untilId + }; + } + //#endregion + + // Issue query + const notes = await Note + .find(query, { + limit: limit, + sort: sort + }); + + if (sinceId) notes.reverse(); + + const renderedNotes = await Promise.all(notes.map(note => renderNote(note))); + const rendered = renderOrderedCollectionPage( + `${partOf}?page=true${sinceId ? `&since_id=${sinceId}` : ''}${untilId ? `&until_id=${untilId}` : ''}`, + user.notesCount, renderedNotes, partOf, + notes.length > 0 ? `${partOf}?page=true&since_id=${notes[0]._id}` : null, + notes.length > 0 ? `${partOf}?page=true&until_id=${notes[notes.length - 1]._id}` : null + ); + + ctx.body = pack(rendered); + } else { + // index page + const rendered = renderOrderedCollection(partOf, user.notesCount, + `${partOf}?page=true`, + `${partOf}?page=true&since_id=000000000000000000000000` + ); + ctx.body = pack(rendered); + } +}; |