Skip to content
Snippets Groups Projects
NodeinfoServerService.ts 4.92 KiB
Newer Older
/*
 * SPDX-FileCopyrightText: syuilo and other misskey contributors
 * SPDX-License-Identifier: AGPL-3.0-only
 */

import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
syuilo's avatar
syuilo committed
import type { Config } from '@/config.js';
import { MetaService } from '@/core/MetaService.js';
import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
import { MemorySingleCache } from '@/misc/cache.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js';
import NotesChart from '@/core/chart/charts/notes.js';
import UsersChart from '@/core/chart/charts/users.js';
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
import type { FastifyInstance, FastifyPluginOptions } from 'fastify';

const nodeinfo2_1path = '/nodeinfo/2.1';
const nodeinfo2_0path = '/nodeinfo/2.0';
const nodeinfo_homepage = 'https://misskey-hub.net';

@Injectable()
export class NodeinfoServerService {
	constructor(
		@Inject(DI.config)
		private config: Config,

		private userEntityService: UserEntityService,
		private metaService: MetaService,
		private notesChart: NotesChart,
		private usersChart: UsersChart,
		//this.createServer = this.createServer.bind(this);
	public getLinks() {
xtex's avatar
xtex committed
		return [{
				rel: 'http://nodeinfo.diaspora.software/ns/schema/2.1',
				href: this.config.url + nodeinfo2_1path
			}, {
				rel: 'http://nodeinfo.diaspora.software/ns/schema/2.0',
				href: this.config.url + nodeinfo2_0path,
			}];
	}

syuilo's avatar
syuilo committed
	public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
xtex's avatar
xtex committed
		const nodeinfo2 = async (version: number) => {
			const now = Date.now();

			const notesChart = await this.notesChart.getChart('hour', 1, null);
			const localPosts = notesChart.local.total[0];

			const usersChart = await this.usersChart.getChart('hour', 1, null);
			const total = usersChart.local.total[0];

			const [
				meta,
				//activeHalfyear,
				//activeMonth,
			] = await Promise.all([
				this.metaService.fetch(true),
				// 重い
				//this.usersRepository.count({ where: { host: IsNull(), lastActiveDate: MoreThan(new Date(now - 15552000000)) } }),
				//this.usersRepository.count({ where: { host: IsNull(), lastActiveDate: MoreThan(new Date(now - 2592000000)) } }),
			const activeHalfyear = null;
			const activeMonth = null;

			const proxyAccount = meta.proxyAccountId ? await this.userEntityService.pack(meta.proxyAccountId).catch(() => null) : null;

			const basePolicies = { ...DEFAULT_POLICIES, ...meta.policies };
syuilo's avatar
syuilo committed

xtex's avatar
xtex committed
			// eslint-disable-next-line @typescript-eslint/no-explicit-any
			const document: any = {
				software: {
					name: 'misskey',
					version: this.config.version,
					homepage: nodeinfo_homepage,
					repository: meta.repositoryUrl,
				},
				protocols: ['activitypub'],
				services: {
					inbound: [] as string[],
					outbound: ['atom1.0', 'rss2.0'],
				},
				openRegistrations: !meta.disableRegistration,
				usage: {
					users: { total, activeHalfyear, activeMonth },
					localPosts,
					localComments: 0,
				},
				metadata: {
					nodeName: meta.name,
					nodeDescription: meta.description,
					maintainer: {
						name: meta.maintainerName,
						email: meta.maintainerEmail,
					},
					langs: meta.langs,
syuilo's avatar
syuilo committed
					tosUrl: meta.termsOfServiceUrl,
					privacyPolicyUrl: meta.privacyPolicyUrl,
					impressumUrl: meta.impressumUrl,
					repositoryUrl: meta.repositoryUrl,
					feedbackUrl: meta.feedbackUrl,
					disableRegistration: meta.disableRegistration,
					disableLocalTimeline: !basePolicies.ltlAvailable,
					disableGlobalTimeline: !basePolicies.gtlAvailable,
					emailRequiredForSignup: meta.emailRequiredForSignup,
					enableHcaptcha: meta.enableHcaptcha,
					enableRecaptcha: meta.enableRecaptcha,
					maxNoteTextLength: MAX_NOTE_TEXT_LENGTH,
					enableEmail: meta.enableEmail,
					enableServiceWorker: meta.enableServiceWorker,
					proxyAccountName: proxyAccount ? proxyAccount.username : null,
					themeColor: meta.themeColor ?? '#86b300',
				},
			};
xtex's avatar
xtex committed
			if (version >= 21) {
				document.software.repository = meta.repositoryUrl;
				document.software.homepage = meta.repositoryUrl;
			}
			return document;
		const cache = new MemorySingleCache<Awaited<ReturnType<typeof nodeinfo2>>>(1000 * 60 * 10);
syuilo's avatar
syuilo committed
		fastify.get(nodeinfo2_1path, async (request, reply) => {
xtex's avatar
xtex committed
			const base = await cache.fetch(() => nodeinfo2(21));
xtex's avatar
xtex committed
			reply
				.type(
					'application/json; profile="http://nodeinfo.diaspora.software/ns/schema/2.1#"',
				)
				.header('Cache-Control', 'public, max-age=600');
syuilo's avatar
syuilo committed
			return { version: '2.1', ...base };
syuilo's avatar
syuilo committed
		fastify.get(nodeinfo2_0path, async (request, reply) => {
xtex's avatar
xtex committed
			const base = await cache.fetch(() => nodeinfo2(20));
syuilo's avatar
syuilo committed
			delete (base as any).software.repository;
xtex's avatar
xtex committed
			reply
				.type(
					'application/json; profile="http://nodeinfo.diaspora.software/ns/schema/2.0#"',
				)
				.header('Cache-Control', 'public, max-age=600');
syuilo's avatar
syuilo committed
			return { version: '2.0', ...base };
syuilo's avatar
syuilo committed
		done();