diff --git a/package.json b/package.json index 2eafdd583..71c0c592a 100644 --- a/package.json +++ b/package.json @@ -114,6 +114,7 @@ "eslint": "5.8.0", "eslint-plugin-vue": "4.7.1", "eventemitter3": "3.1.0", + "feed": "2.0.2", "file-loader": "2.0.0", "file-type": "10.6.0", "fuckadblock": "3.2.1", diff --git a/src/server/web/feed.ts b/src/server/web/feed.ts new file mode 100644 index 000000000..09ac10c57 --- /dev/null +++ b/src/server/web/feed.ts @@ -0,0 +1,54 @@ +import { Feed } from 'feed'; +import config from '../../config'; +import Note from '../../models/note'; +import { IUser } from '../../models/user'; +import { getOriginalUrl } from '../../misc/get-drive-file-url'; + +export default async function(user: IUser) { + const author: Author = { + link: `${config.url}/@${user.username}`, + name: user.name || user.username + }; + + const notes = await Note.find({ + userId: user._id, + renoteId: null, + $or: [ + { visibility: 'public' }, + { visibility: 'home' } + ] + }, { + sort: { createdAt: -1 }, + limit: 20 + }); + + const feed = new Feed({ + id: author.link, + title: `${author.name} (@${user.username}@${config.host})`, + updated: notes[0].createdAt, + generator: 'Misskey', + description: `${user.notesCount} Notes, ${user.followingCount} Following, ${user.followersCount} Followers${user.description ? ` ยท ${user.description}` : ''}`, + link: author.link, + image: user.avatarUrl, + feedLinks: { + json: `${author.link}.json`, + atom: `${author.link}.atom`, + }, + author + } as FeedOptions); + + for (const note of notes) { + const file = note._files && note._files.find(file => file.contentType.startsWith('image/')); + + feed.addItem({ + title: `New note by ${author.name}`, + link: `${config.url}/notes/${note._id}`, + date: note.createdAt, + description: note.cw, + content: note.text, + image: file && getOriginalUrl(file) + }); + } + + return feed; +} diff --git a/src/server/web/index.ts b/src/server/web/index.ts index 5a5029c7f..f2a40c01f 100644 --- a/src/server/web/index.ts +++ b/src/server/web/index.ts @@ -10,6 +10,7 @@ import * as favicon from 'koa-favicon'; import * as views from 'koa-views'; import docs from './docs'; +import packFeed from './feed'; import User from '../../models/user'; import parseAcct from '../../misc/acct/parse'; import config from '../../config'; @@ -82,6 +83,52 @@ router.use('/docs', docs.routes()); // URL preview endpoint router.get('/url', require('./url-preview')); +const getFeed = async (acct: string) => { + const { username, host } = parseAcct(acct); + const user = await User.findOne({ + usernameLower: username.toLowerCase(), + host + }); + + return user && await packFeed(user); +}; + +// Atom +router.get('/@:user.atom', async ctx => { + const feed = await getFeed(ctx.params.user); + + if (feed) { + ctx.set('Content-Type', 'application/atom+xml; charset=utf-8'); + ctx.body = feed.atom1(); + } else { + ctx.status = 404; + } +}); + +// RSS +router.get('/@:user.rss', async ctx => { + const feed = await getFeed(ctx.params.user); + + if (feed) { + ctx.set('Content-Type', 'application/rss+xml; charset=utf-8'); + ctx.body = feed.rss2(); + } else { + ctx.status = 404; + } +}); + +// JSON +router.get('/@:user.json', async ctx => { + const feed = await getFeed(ctx.params.user); + + if (feed) { + ctx.set('Content-Type', 'application/json; charset=utf-8'); + ctx.body = feed.json1(); + } else { + ctx.status = 404; + } +}); + //#region for crawlers // User router.get('/@:user', async (ctx, next) => {