feat(backend): リプライ付き/ファイル付きノートのフィードの追加
This commit is contained in:
@@ -40,7 +40,7 @@ import { RoleService } from '@/core/RoleService.js';
|
|||||||
import { FeedService } from './FeedService.js';
|
import { FeedService } from './FeedService.js';
|
||||||
import { UrlPreviewService } from './UrlPreviewService.js';
|
import { UrlPreviewService } from './UrlPreviewService.js';
|
||||||
import { ClientLoggerService } from './ClientLoggerService.js';
|
import { ClientLoggerService } from './ClientLoggerService.js';
|
||||||
import type { FastifyInstance, FastifyPluginOptions, FastifyReply } from 'fastify';
|
import type { FastifyInstance, FastifyPluginOptions, FastifyReply, FastifyRequest } from 'fastify';
|
||||||
|
|
||||||
const _filename = fileURLToPath(import.meta.url);
|
const _filename = fileURLToPath(import.meta.url);
|
||||||
const _dirname = dirname(_filename);
|
const _dirname = dirname(_filename);
|
||||||
@@ -416,55 +416,51 @@ export class ClientServerService {
|
|||||||
// URL preview endpoint
|
// URL preview endpoint
|
||||||
fastify.get<{ Querystring: { url: string; lang: string; } }>('/url', (request, reply) => this.urlPreviewService.handle(request, reply));
|
fastify.get<{ Querystring: { url: string; lang: string; } }>('/url', (request, reply) => this.urlPreviewService.handle(request, reply));
|
||||||
|
|
||||||
const getFeed = async (acct: string) => {
|
const feedHandler = (
|
||||||
const { username, host } = Acct.parse(acct);
|
feedType: 'atom' | 'rss' | 'json',
|
||||||
|
options?: {
|
||||||
|
withReplies?: boolean;
|
||||||
|
withFiles?: boolean;
|
||||||
|
},
|
||||||
|
) => async (
|
||||||
|
request: FastifyRequest<{ Params: { user: string; } }>,
|
||||||
|
reply: FastifyReply,
|
||||||
|
) => {
|
||||||
|
const { username, host } = Acct.parse(request.params.user);
|
||||||
const user = await this.usersRepository.findOneBy({
|
const user = await this.usersRepository.findOneBy({
|
||||||
usernameLower: username.toLowerCase(),
|
usernameLower: username.toLowerCase(),
|
||||||
host: host ?? IsNull(),
|
host: host ?? IsNull(),
|
||||||
isSuspended: false,
|
isSuspended: false,
|
||||||
});
|
});
|
||||||
|
|
||||||
return user && await this.feedService.packFeed(user);
|
const feed = user && await this.feedService.packFeed(user, options);
|
||||||
|
|
||||||
|
if (feed) {
|
||||||
|
reply.header('Content-Type', `application/${feedType}+xml; charset=utf-8`);
|
||||||
|
|
||||||
|
if (feedType === 'atom') return feed.atom1();
|
||||||
|
else if (feedType === 'rss') return feed.rss2();
|
||||||
|
else return feed.json1();
|
||||||
|
} else {
|
||||||
|
reply.code(404);
|
||||||
|
return;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Atom
|
// Atom
|
||||||
fastify.get<{ Params: { user: string; } }>('/@:user.atom', async (request, reply) => {
|
fastify.get<{ Params: { user: string; } }>('/@:user.atom', feedHandler('atom'));
|
||||||
const feed = await getFeed(request.params.user);
|
fastify.get<{ Params: { user: string; } }>('/@:user.with_replies.atom', feedHandler('atom', { withReplies: true }));
|
||||||
|
fastify.get<{ Params: { user: string; } }>('/@:user.with_files.atom', feedHandler('atom', { withFiles: true }));
|
||||||
if (feed) {
|
|
||||||
reply.header('Content-Type', 'application/atom+xml; charset=utf-8');
|
|
||||||
return feed.atom1();
|
|
||||||
} else {
|
|
||||||
reply.code(404);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// RSS
|
// RSS
|
||||||
fastify.get<{ Params: { user: string; } }>('/@:user.rss', async (request, reply) => {
|
fastify.get<{ Params: { user: string; } }>('/@:user.rss', feedHandler('rss'));
|
||||||
const feed = await getFeed(request.params.user);
|
fastify.get<{ Params: { user: string; } }>('/@:user.with_replies.rss', feedHandler('rss', { withReplies: true }));
|
||||||
|
fastify.get<{ Params: { user: string; } }>('/@:user.with_files.rss', feedHandler('rss', { withFiles: true }));
|
||||||
if (feed) {
|
|
||||||
reply.header('Content-Type', 'application/rss+xml; charset=utf-8');
|
|
||||||
return feed.rss2();
|
|
||||||
} else {
|
|
||||||
reply.code(404);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// JSON
|
// JSON
|
||||||
fastify.get<{ Params: { user: string; } }>('/@:user.json', async (request, reply) => {
|
fastify.get<{ Params: { user: string; } }>('/@:user.json', feedHandler('json'));
|
||||||
const feed = await getFeed(request.params.user);
|
fastify.get<{ Params: { user: string; } }>('/@:user.with_replies.json', feedHandler('json', { withReplies: true }));
|
||||||
|
fastify.get<{ Params: { user: string; } }>('/@:user.with_files.json', feedHandler('json', { withFiles: true }));
|
||||||
if (feed) {
|
|
||||||
reply.header('Content-Type', 'application/json; charset=utf-8');
|
|
||||||
return feed.json1();
|
|
||||||
} else {
|
|
||||||
reply.code(404);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
//#region SSR (for crawlers)
|
//#region SSR (for crawlers)
|
||||||
// User
|
// User
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { Inject, Injectable } from '@nestjs/common';
|
import { Inject, Injectable } from '@nestjs/common';
|
||||||
import { In, IsNull } from 'typeorm';
|
import { Equal, In, IsNull, Not } from 'typeorm';
|
||||||
import { Feed } from 'feed';
|
import { Feed } from 'feed';
|
||||||
import { DI } from '@/di-symbols.js';
|
import { DI } from '@/di-symbols.js';
|
||||||
import type { DriveFilesRepository, NotesRepository, UserProfilesRepository } from '@/models/_.js';
|
import type { DriveFilesRepository, NotesRepository, UserProfilesRepository } from '@/models/_.js';
|
||||||
@@ -37,7 +37,18 @@ export class FeedService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@bindThis
|
@bindThis
|
||||||
public async packFeed(user: MiUser) {
|
public async packFeed(
|
||||||
|
user: MiUser,
|
||||||
|
options?: {
|
||||||
|
withReplies?: boolean;
|
||||||
|
withFiles?: boolean;
|
||||||
|
},
|
||||||
|
) {
|
||||||
|
const opts = Object.assign({
|
||||||
|
withReplies: false,
|
||||||
|
withFiles: false,
|
||||||
|
}, options);
|
||||||
|
|
||||||
const author = {
|
const author = {
|
||||||
link: `${this.config.url}/@${user.username}`,
|
link: `${this.config.url}/@${user.username}`,
|
||||||
name: user.name ?? user.username,
|
name: user.name ?? user.username,
|
||||||
@@ -50,6 +61,12 @@ export class FeedService {
|
|||||||
userId: user.id,
|
userId: user.id,
|
||||||
renoteId: IsNull(),
|
renoteId: IsNull(),
|
||||||
visibility: In(['public', 'home']),
|
visibility: In(['public', 'home']),
|
||||||
|
...(opts.withReplies ? {} : {
|
||||||
|
replyId: IsNull(),
|
||||||
|
}),
|
||||||
|
...(opts.withFiles ? {
|
||||||
|
fileIds: Not(Equal('{}')),
|
||||||
|
} : {}),
|
||||||
},
|
},
|
||||||
order: { id: -1 },
|
order: { id: -1 },
|
||||||
take: 20,
|
take: 20,
|
||||||
|
|||||||
Reference in New Issue
Block a user