summaryrefslogtreecommitdiff
path: root/packages/backend/src
diff options
context:
space:
mode:
authorかっこかり <67428053+kakkokari-gtyih@users.noreply.github.com>2024-02-23 10:47:17 +0900
committerGitHub <noreply@github.com>2024-02-23 10:47:17 +0900
commitd20542c495dd3342b23ef9f1c759c2f4f2dce63e (patch)
treefd8ab7178b16de76a07b84be3c72f666fdbb97d5 /packages/backend/src
parentflash/update で部分的に変更できるようにする (#13396) (diff)
downloadsharkey-d20542c495dd3342b23ef9f1c759c2f4f2dce63e.tar.gz
sharkey-d20542c495dd3342b23ef9f1c759c2f4f2dce63e.tar.bz2
sharkey-d20542c495dd3342b23ef9f1c759c2f4f2dce63e.zip
enhance: `meta`をSSR HTMLに埋め込む (#13436)
* enhance: `meta`をSSR HTMLに埋め込む * HTML Metaの有効時間を指定 * 1時間 * MetaEntityService * JSONをPackするように * :v: --------- Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com>
Diffstat (limited to 'packages/backend/src')
-rw-r--r--packages/backend/src/core/CoreModule.ts6
-rw-r--r--packages/backend/src/core/entities/MetaEntityService.ts154
-rw-r--r--packages/backend/src/misc/json-schema.ts8
-rw-r--r--packages/backend/src/models/json-schema/meta.ts328
-rw-r--r--packages/backend/src/server/api/endpoints/meta.ts417
-rw-r--r--packages/backend/src/server/web/ClientServerService.ts24
-rw-r--r--packages/backend/src/server/web/views/base.pug3
7 files changed, 521 insertions, 419 deletions
diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts
index c31cef36e8..2c27d33c06 100644
--- a/packages/backend/src/core/CoreModule.ts
+++ b/packages/backend/src/core/CoreModule.ts
@@ -116,6 +116,7 @@ import { FlashEntityService } from './entities/FlashEntityService.js';
import { FlashLikeEntityService } from './entities/FlashLikeEntityService.js';
import { RoleEntityService } from './entities/RoleEntityService.js';
import { ReversiGameEntityService } from './entities/ReversiGameEntityService.js';
+import { MetaEntityService } from './entities/MetaEntityService.js';
import { ApAudienceService } from './activitypub/ApAudienceService.js';
import { ApDbResolverService } from './activitypub/ApDbResolverService.js';
@@ -254,6 +255,7 @@ const $FlashEntityService: Provider = { provide: 'FlashEntityService', useExisti
const $FlashLikeEntityService: Provider = { provide: 'FlashLikeEntityService', useExisting: FlashLikeEntityService };
const $RoleEntityService: Provider = { provide: 'RoleEntityService', useExisting: RoleEntityService };
const $ReversiGameEntityService: Provider = { provide: 'ReversiGameEntityService', useExisting: ReversiGameEntityService };
+const $MetaEntityService: Provider = { provide: 'MetaEntityService', useExisting: MetaEntityService };
const $ApAudienceService: Provider = { provide: 'ApAudienceService', useExisting: ApAudienceService };
const $ApDbResolverService: Provider = { provide: 'ApDbResolverService', useExisting: ApDbResolverService };
@@ -393,6 +395,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
FlashLikeEntityService,
RoleEntityService,
ReversiGameEntityService,
+ MetaEntityService,
ApAudienceService,
ApDbResolverService,
@@ -528,6 +531,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$FlashLikeEntityService,
$RoleEntityService,
$ReversiGameEntityService,
+ $MetaEntityService,
$ApAudienceService,
$ApDbResolverService,
@@ -663,6 +667,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
FlashLikeEntityService,
RoleEntityService,
ReversiGameEntityService,
+ MetaEntityService,
ApAudienceService,
ApDbResolverService,
@@ -797,6 +802,7 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$FlashLikeEntityService,
$RoleEntityService,
$ReversiGameEntityService,
+ $MetaEntityService,
$ApAudienceService,
$ApDbResolverService,
diff --git a/packages/backend/src/core/entities/MetaEntityService.ts b/packages/backend/src/core/entities/MetaEntityService.ts
new file mode 100644
index 0000000000..b50d76288f
--- /dev/null
+++ b/packages/backend/src/core/entities/MetaEntityService.ts
@@ -0,0 +1,154 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Brackets } from 'typeorm';
+import { Inject, Injectable } from '@nestjs/common';
+import JSON5 from 'json5';
+import type { Packed } from '@/misc/json-schema.js';
+import type { MiMeta } from '@/models/Meta.js';
+import type { AdsRepository } from '@/models/_.js';
+import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
+import { MetaService } from '@/core/MetaService.js';
+import { bindThis } from '@/decorators.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { InstanceActorService } from '@/core/InstanceActorService.js';
+import type { Config } from '@/config.js';
+import { DI } from '@/di-symbols.js';
+import { DEFAULT_POLICIES } from '@/core/RoleService.js';
+
+@Injectable()
+export class MetaEntityService {
+ constructor(
+ @Inject(DI.config)
+ private config: Config,
+
+ @Inject(DI.adsRepository)
+ private adsRepository: AdsRepository,
+
+ private userEntityService: UserEntityService,
+ private metaService: MetaService,
+ private instanceActorService: InstanceActorService,
+ ) { }
+
+ @bindThis
+ public async pack(meta?: MiMeta): Promise<Packed<'MetaLite'>> {
+ let instance = meta;
+
+ if (!instance) {
+ instance = await this.metaService.fetch();
+ }
+
+ const ads = await this.adsRepository.createQueryBuilder('ads')
+ .where('ads.expiresAt > :now', { now: new Date() })
+ .andWhere('ads.startsAt <= :now', { now: new Date() })
+ .andWhere(new Brackets(qb => {
+ // 曜日のビットフラグを確認する
+ qb.where('ads.dayOfWeek & :dayOfWeek > 0', { dayOfWeek: 1 << new Date().getDay() })
+ .orWhere('ads.dayOfWeek = 0');
+ }))
+ .getMany();
+
+ const packed: Packed<'MetaLite'> = {
+ maintainerName: instance.maintainerName,
+ maintainerEmail: instance.maintainerEmail,
+
+ version: this.config.version,
+ providesTarball: this.config.publishTarballInsteadOfProvideRepositoryUrl,
+
+ name: instance.name,
+ shortName: instance.shortName,
+ uri: this.config.url,
+ description: instance.description,
+ langs: instance.langs,
+ tosUrl: instance.termsOfServiceUrl,
+ repositoryUrl: instance.repositoryUrl,
+ feedbackUrl: instance.feedbackUrl,
+ impressumUrl: instance.impressumUrl,
+ privacyPolicyUrl: instance.privacyPolicyUrl,
+ disableRegistration: instance.disableRegistration,
+ emailRequiredForSignup: instance.emailRequiredForSignup,
+ enableHcaptcha: instance.enableHcaptcha,
+ hcaptchaSiteKey: instance.hcaptchaSiteKey,
+ enableMcaptcha: instance.enableMcaptcha,
+ mcaptchaSiteKey: instance.mcaptchaSitekey,
+ mcaptchaInstanceUrl: instance.mcaptchaInstanceUrl,
+ enableRecaptcha: instance.enableRecaptcha,
+ recaptchaSiteKey: instance.recaptchaSiteKey,
+ enableTurnstile: instance.enableTurnstile,
+ turnstileSiteKey: instance.turnstileSiteKey,
+ swPublickey: instance.swPublicKey,
+ themeColor: instance.themeColor,
+ mascotImageUrl: instance.mascotImageUrl ?? '/assets/ai.png',
+ bannerUrl: instance.bannerUrl,
+ infoImageUrl: instance.infoImageUrl,
+ serverErrorImageUrl: instance.serverErrorImageUrl,
+ notFoundImageUrl: instance.notFoundImageUrl,
+ iconUrl: instance.iconUrl,
+ backgroundImageUrl: instance.backgroundImageUrl,
+ logoImageUrl: instance.logoImageUrl,
+ maxNoteTextLength: MAX_NOTE_TEXT_LENGTH,
+ // クライアントの手間を減らすためあらかじめJSONに変換しておく
+ defaultLightTheme: instance.defaultLightTheme ? JSON.stringify(JSON5.parse(instance.defaultLightTheme)) : null,
+ defaultDarkTheme: instance.defaultDarkTheme ? JSON.stringify(JSON5.parse(instance.defaultDarkTheme)) : null,
+ ads: ads.map(ad => ({
+ id: ad.id,
+ url: ad.url,
+ place: ad.place,
+ ratio: ad.ratio,
+ imageUrl: ad.imageUrl,
+ dayOfWeek: ad.dayOfWeek,
+ })),
+ notesPerOneAd: instance.notesPerOneAd,
+ enableEmail: instance.enableEmail,
+ enableServiceWorker: instance.enableServiceWorker,
+
+ translatorAvailable: instance.deeplAuthKey != null,
+
+ serverRules: instance.serverRules,
+
+ policies: { ...DEFAULT_POLICIES, ...instance.policies },
+
+ mediaProxy: this.config.mediaProxy,
+ };
+
+ return packed;
+ }
+
+ @bindThis
+ public async packDetailed(meta?: MiMeta): Promise<Packed<'MetaDetailed'>> {
+ let instance = meta;
+
+ if (!instance) {
+ instance = await this.metaService.fetch();
+ }
+
+ const packed = await this.pack(instance);
+
+ const proxyAccount = instance.proxyAccountId ? await this.userEntityService.pack(instance.proxyAccountId).catch(() => null) : null;
+
+ const packDetailed: Packed<'MetaDetailed'> = {
+ ...packed,
+ cacheRemoteFiles: instance.cacheRemoteFiles,
+ cacheRemoteSensitiveFiles: instance.cacheRemoteSensitiveFiles,
+ requireSetup: !await this.instanceActorService.realLocalUsersPresent(),
+ proxyAccountName: proxyAccount ? proxyAccount.username : null,
+ features: {
+ localTimeline: instance.policies.ltlAvailable,
+ globalTimeline: instance.policies.gtlAvailable,
+ registration: !instance.disableRegistration,
+ emailRequiredForSignup: instance.emailRequiredForSignup,
+ hcaptcha: instance.enableHcaptcha,
+ recaptcha: instance.enableRecaptcha,
+ turnstile: instance.enableTurnstile,
+ objectStorage: instance.useObjectStorage,
+ serviceWorker: instance.enableServiceWorker,
+ miauth: true,
+ },
+ };
+
+ return packDetailed;
+ }
+}
+
diff --git a/packages/backend/src/misc/json-schema.ts b/packages/backend/src/misc/json-schema.ts
index de38f145b2..8449e5ff07 100644
--- a/packages/backend/src/misc/json-schema.ts
+++ b/packages/backend/src/misc/json-schema.ts
@@ -50,6 +50,11 @@ import {
} from '@/models/json-schema/role.js';
import { packedAdSchema } from '@/models/json-schema/ad.js';
import { packedReversiGameLiteSchema, packedReversiGameDetailedSchema } from '@/models/json-schema/reversi-game.js';
+import {
+ packedMetaLiteSchema,
+ packedMetaDetailedOnlySchema,
+ packedMetaDetailedSchema,
+} from '@/models/json-schema/meta.js';
export const refs = {
UserLite: packedUserLiteSchema,
@@ -99,6 +104,9 @@ export const refs = {
RolePolicies: packedRolePoliciesSchema,
ReversiGameLite: packedReversiGameLiteSchema,
ReversiGameDetailed: packedReversiGameDetailedSchema,
+ MetaLite: packedMetaLiteSchema,
+ MetaDetailedOnly: packedMetaDetailedOnlySchema,
+ MetaDetailed: packedMetaDetailedSchema,
};
export type Packed<x extends keyof typeof refs> = SchemaType<typeof refs[x]>;
diff --git a/packages/backend/src/models/json-schema/meta.ts b/packages/backend/src/models/json-schema/meta.ts
new file mode 100644
index 0000000000..17789f3b46
--- /dev/null
+++ b/packages/backend/src/models/json-schema/meta.ts
@@ -0,0 +1,328 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+export const packedMetaLiteSchema = {
+ type: 'object',
+ optional: false, nullable: false,
+ properties: {
+ maintainerName: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
+ maintainerEmail: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
+ version: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ providesTarball: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ name: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
+ shortName: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
+ uri: {
+ type: 'string',
+ optional: false, nullable: false,
+ format: 'url',
+ example: 'https://misskey.example.com',
+ },
+ description: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
+ langs: {
+ type: 'array',
+ optional: false, nullable: false,
+ items: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ },
+ tosUrl: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
+ repositoryUrl: {
+ type: 'string',
+ optional: false, nullable: true,
+ default: 'https://github.com/misskey-dev/misskey',
+ },
+ feedbackUrl: {
+ type: 'string',
+ optional: false, nullable: true,
+ default: 'https://github.com/misskey-dev/misskey/issues/new',
+ },
+ defaultDarkTheme: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
+ defaultLightTheme: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
+ disableRegistration: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ emailRequiredForSignup: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ enableHcaptcha: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ hcaptchaSiteKey: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
+ enableMcaptcha: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ mcaptchaSiteKey: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
+ mcaptchaInstanceUrl: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
+ enableRecaptcha: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ recaptchaSiteKey: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
+ enableTurnstile: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ turnstileSiteKey: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
+ swPublickey: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
+ mascotImageUrl: {
+ type: 'string',
+ optional: false, nullable: false,
+ default: '/assets/ai.png',
+ },
+ bannerUrl: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
+ serverErrorImageUrl: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
+ infoImageUrl: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
+ notFoundImageUrl: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
+ iconUrl: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
+ maxNoteTextLength: {
+ type: 'number',
+ optional: false, nullable: false,
+ },
+ ads: {
+ type: 'array',
+ optional: false, nullable: false,
+ items: {
+ type: 'object',
+ optional: false, nullable: false,
+ properties: {
+ id: {
+ type: 'string',
+ optional: false, nullable: false,
+ format: 'id',
+ example: 'xxxxxxxxxx',
+ },
+ url: {
+ type: 'string',
+ optional: false, nullable: false,
+ format: 'url',
+ },
+ place: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ ratio: {
+ type: 'number',
+ optional: false, nullable: false,
+ },
+ imageUrl: {
+ type: 'string',
+ optional: false, nullable: false,
+ format: 'url',
+ },
+ dayOfWeek: {
+ type: 'integer',
+ optional: false, nullable: false,
+ },
+ },
+ },
+ },
+ notesPerOneAd: {
+ type: 'number',
+ optional: false, nullable: false,
+ default: 0,
+ },
+ enableEmail: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ enableServiceWorker: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ translatorAvailable: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ mediaProxy: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ backgroundImageUrl: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
+ impressumUrl: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
+ logoImageUrl: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
+ privacyPolicyUrl: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
+ serverRules: {
+ type: 'array',
+ optional: false, nullable: false,
+ items: {
+ type: 'string',
+ },
+ },
+ themeColor: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
+ policies: {
+ type: 'object',
+ optional: false, nullable: false,
+ ref: 'RolePolicies',
+ },
+ },
+} as const;
+
+export const packedMetaDetailedOnlySchema = {
+ type: 'object',
+ optional: false, nullable: false,
+ properties: {
+ features: {
+ type: 'object',
+ optional: true, nullable: false,
+ properties: {
+ registration: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ emailRequiredForSignup: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ localTimeline: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ globalTimeline: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ hcaptcha: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ turnstile: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ recaptcha: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ objectStorage: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ serviceWorker: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ miauth: {
+ type: 'boolean',
+ optional: true, nullable: false,
+ default: true,
+ },
+ },
+ },
+ proxyAccountName: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
+ requireSetup: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ example: false,
+ },
+ cacheRemoteFiles: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ cacheRemoteSensitiveFiles: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ },
+} as const;
+
+export const packedMetaDetailedSchema = {
+ type: 'object',
+ allOf: [
+ {
+ type: 'object',
+ ref: 'MetaLite',
+ },
+ {
+ type: 'object',
+ ref: 'MetaDetailedOnly',
+ },
+ ],
+} as const;
diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts
index 834158baf4..5460635e1d 100644
--- a/packages/backend/src/server/api/endpoints/meta.ts
+++ b/packages/backend/src/server/api/endpoints/meta.ts
@@ -3,18 +3,9 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { IsNull, LessThanOrEqual, MoreThan, Brackets } from 'typeorm';
-import { Inject, Injectable } from '@nestjs/common';
-import JSON5 from 'json5';
-import type { AdsRepository } from '@/models/_.js';
-import { MAX_NOTE_TEXT_LENGTH } from '@/const.js';
+import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import { UserEntityService } from '@/core/entities/UserEntityService.js';
-import { MetaService } from '@/core/MetaService.js';
-import { InstanceActorService } from '@/core/InstanceActorService.js';
-import type { Config } from '@/config.js';
-import { DI } from '@/di-symbols.js';
-import { DEFAULT_POLICIES } from '@/core/RoleService.js';
+import { MetaEntityService } from '@/core/entities/MetaEntityService.js';
export const meta = {
tags: ['meta'],
@@ -23,297 +14,10 @@ export const meta = {
res: {
type: 'object',
- optional: false, nullable: false,
- properties: {
- maintainerName: {
- type: 'string',
- optional: false, nullable: true,
- },
- maintainerEmail: {
- type: 'string',
- optional: false, nullable: true,
- },
- version: {
- type: 'string',
- optional: false, nullable: false,
- },
- providesTarball: {
- type: 'boolean',
- optional: false, nullable: false,
- },
- name: {
- type: 'string',
- optional: false, nullable: false,
- },
- shortName: {
- type: 'string',
- optional: false, nullable: true,
- },
- uri: {
- type: 'string',
- optional: false, nullable: false,
- format: 'url',
- example: 'https://misskey.example.com',
- },
- description: {
- type: 'string',
- optional: false, nullable: true,
- },
- langs: {
- type: 'array',
- optional: false, nullable: false,
- items: {
- type: 'string',
- optional: false, nullable: false,
- },
- },
- tosUrl: {
- type: 'string',
- optional: false, nullable: true,
- },
- repositoryUrl: {
- type: 'string',
- optional: false, nullable: true,
- default: 'https://github.com/misskey-dev/misskey',
- },
- feedbackUrl: {
- type: 'string',
- optional: false, nullable: true,
- default: 'https://github.com/misskey-dev/misskey/issues/new',
- },
- defaultDarkTheme: {
- type: 'string',
- optional: false, nullable: true,
- },
- defaultLightTheme: {
- type: 'string',
- optional: false, nullable: true,
- },
- disableRegistration: {
- type: 'boolean',
- optional: false, nullable: false,
- },
- cacheRemoteFiles: {
- type: 'boolean',
- optional: false, nullable: false,
- },
- cacheRemoteSensitiveFiles: {
- type: 'boolean',
- optional: false, nullable: false,
- },
- emailRequiredForSignup: {
- type: 'boolean',
- optional: false, nullable: false,
- },
- enableHcaptcha: {
- type: 'boolean',
- optional: false, nullable: false,
- },
- hcaptchaSiteKey: {
- type: 'string',
- optional: false, nullable: true,
- },
- enableMcaptcha: {
- type: 'boolean',
- optional: false, nullable: false,
- },
- mcaptchaSiteKey: {
- type: 'string',
- optional: false, nullable: true,
- },
- mcaptchaInstanceUrl: {
- type: 'string',
- optional: false, nullable: true,
- },
- enableRecaptcha: {
- type: 'boolean',
- optional: false, nullable: false,
- },
- recaptchaSiteKey: {
- type: 'string',
- optional: false, nullable: true,
- },
- enableTurnstile: {
- type: 'boolean',
- optional: false, nullable: false,
- },
- turnstileSiteKey: {
- type: 'string',
- optional: false, nullable: true,
- },
- swPublickey: {
- type: 'string',
- optional: false, nullable: true,
- },
- mascotImageUrl: {
- type: 'string',
- optional: false, nullable: false,
- default: '/assets/ai.png',
- },
- bannerUrl: {
- type: 'string',
- optional: false, nullable: false,
- },
- serverErrorImageUrl: {
- type: 'string',
- optional: false, nullable: true,
- },
- infoImageUrl: {
- type: 'string',
- optional: false, nullable: true,
- },
- notFoundImageUrl: {
- type: 'string',
- optional: false, nullable: true,
- },
- iconUrl: {
- type: 'string',
- optional: false, nullable: true,
- },
- maxNoteTextLength: {
- type: 'number',
- optional: false, nullable: false,
- },
- ads: {
- type: 'array',
- optional: false, nullable: false,
- items: {
- type: 'object',
- optional: false, nullable: false,
- properties: {
- id: {
- type: 'string',
- optional: false, nullable: false,
- format: 'id',
- example: 'xxxxxxxxxx',
- },
- url: {
- type: 'string',
- optional: false, nullable: false,
- format: 'url',
- },
- place: {
- type: 'string',
- optional: false, nullable: false,
- },
- ratio: {
- type: 'number',
- optional: false, nullable: false,
- },
- imageUrl: {
- type: 'string',
- optional: false, nullable: false,
- format: 'url',
- },
- dayOfWeek: {
- type: 'integer',
- optional: false, nullable: false,
- },
- },
- },
- },
- notesPerOneAd: {
- type: 'number',
- optional: false, nullable: false,
- default: 0,
- },
- requireSetup: {
- type: 'boolean',
- optional: false, nullable: false,
- example: false,
- },
- enableEmail: {
- type: 'boolean',
- optional: false, nullable: false,
- },
- enableServiceWorker: {
- type: 'boolean',
- optional: false, nullable: false,
- },
- translatorAvailable: {
- type: 'boolean',
- optional: false, nullable: false,
- },
- proxyAccountName: {
- type: 'string',
- optional: false, nullable: true,
- },
- mediaProxy: {
- type: 'string',
- optional: false, nullable: false,
- },
- features: {
- type: 'object',
- optional: true, nullable: false,
- properties: {
- registration: {
- type: 'boolean',
- optional: false, nullable: false,
- },
- localTimeline: {
- type: 'boolean',
- optional: false, nullable: false,
- },
- globalTimeline: {
- type: 'boolean',
- optional: false, nullable: false,
- },
- hcaptcha: {
- type: 'boolean',
- optional: false, nullable: false,
- },
- recaptcha: {
- type: 'boolean',
- optional: false, nullable: false,
- },
- objectStorage: {
- type: 'boolean',
- optional: false, nullable: false,
- },
- serviceWorker: {
- type: 'boolean',
- optional: false, nullable: false,
- },
- miauth: {
- type: 'boolean',
- optional: true, nullable: false,
- default: true,
- },
- },
- },
- backgroundImageUrl: {
- type: 'string',
- optional: false, nullable: true,
- },
- impressumUrl: {
- type: 'string',
- optional: false, nullable: true,
- },
- logoImageUrl: {
- type: 'string',
- optional: false, nullable: true,
- },
- privacyPolicyUrl: {
- type: 'string',
- optional: false, nullable: true,
- },
- serverRules: {
- type: 'array',
- optional: false, nullable: false,
- items: {
- type: 'string',
- },
- },
- themeColor: {
- type: 'string',
- optional: false, nullable: true,
- },
- policies: {
- type: 'object',
- optional: false, nullable: false,
- ref: 'RolePolicies',
- },
- },
+ oneOf: [
+ { type: 'object', ref: 'MetaLite' },
+ { type: 'object', ref: 'MetaDetailed' },
+ ],
},
} as const;
@@ -328,115 +32,10 @@ export const paramDef = {
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
- @Inject(DI.config)
- private config: Config,
-
- @Inject(DI.adsRepository)
- private adsRepository: AdsRepository,
-
- private userEntityService: UserEntityService,
- private metaService: MetaService,
- private instanceActorService: InstanceActorService,
+ private metaEntityService: MetaEntityService,
) {
super(meta, paramDef, async (ps, me) => {
- const instance = await this.metaService.fetch(true);
-
- const ads = await this.adsRepository.createQueryBuilder('ads')
- .where('ads.expiresAt > :now', { now: new Date() })
- .andWhere('ads.startsAt <= :now', { now: new Date() })
- .andWhere(new Brackets(qb => {
- // 曜日のビットフラグを確認する
- qb.where('ads.dayOfWeek & :dayOfWeek > 0', { dayOfWeek: 1 << new Date().getDay() })
- .orWhere('ads.dayOfWeek = 0');
- }))
- .getMany();
-
- const response: any = {
- maintainerName: instance.maintainerName,
- maintainerEmail: instance.maintainerEmail,
-
- version: this.config.version,
- providesTarball: this.config.publishTarballInsteadOfProvideRepositoryUrl,
-
- name: instance.name,
- shortName: instance.shortName,
- uri: this.config.url,
- description: instance.description,
- langs: instance.langs,
- tosUrl: instance.termsOfServiceUrl,
- repositoryUrl: instance.repositoryUrl,
- feedbackUrl: instance.feedbackUrl,
- impressumUrl: instance.impressumUrl,
- privacyPolicyUrl: instance.privacyPolicyUrl,
- disableRegistration: instance.disableRegistration,
- emailRequiredForSignup: instance.emailRequiredForSignup,
- enableHcaptcha: instance.enableHcaptcha,
- hcaptchaSiteKey: instance.hcaptchaSiteKey,
- enableMcaptcha: instance.enableMcaptcha,
- mcaptchaSiteKey: instance.mcaptchaSitekey,
- mcaptchaInstanceUrl: instance.mcaptchaInstanceUrl,
- enableRecaptcha: instance.enableRecaptcha,
- recaptchaSiteKey: instance.recaptchaSiteKey,
- enableTurnstile: instance.enableTurnstile,
- turnstileSiteKey: instance.turnstileSiteKey,
- swPublickey: instance.swPublicKey,
- themeColor: instance.themeColor,
- mascotImageUrl: instance.mascotImageUrl,
- bannerUrl: instance.bannerUrl,
- infoImageUrl: instance.infoImageUrl,
- serverErrorImageUrl: instance.serverErrorImageUrl,
- notFoundImageUrl: instance.notFoundImageUrl,
- iconUrl: instance.iconUrl,
- backgroundImageUrl: instance.backgroundImageUrl,
- logoImageUrl: instance.logoImageUrl,
- maxNoteTextLength: MAX_NOTE_TEXT_LENGTH,
- // クライアントの手間を減らすためあらかじめJSONに変換しておく
- defaultLightTheme: instance.defaultLightTheme ? JSON.stringify(JSON5.parse(instance.defaultLightTheme)) : null,
- defaultDarkTheme: instance.defaultDarkTheme ? JSON.stringify(JSON5.parse(instance.defaultDarkTheme)) : null,
- ads: ads.map(ad => ({
- id: ad.id,
- url: ad.url,
- place: ad.place,
- ratio: ad.ratio,
- imageUrl: ad.imageUrl,
- dayOfWeek: ad.dayOfWeek,
- })),
- notesPerOneAd: instance.notesPerOneAd,
- enableEmail: instance.enableEmail,
- enableServiceWorker: instance.enableServiceWorker,
-
- translatorAvailable: instance.deeplAuthKey != null,
-
- serverRules: instance.serverRules,
-
- policies: { ...DEFAULT_POLICIES, ...instance.policies },
-
- mediaProxy: this.config.mediaProxy,
-
- ...(ps.detail ? {
- cacheRemoteFiles: instance.cacheRemoteFiles,
- cacheRemoteSensitiveFiles: instance.cacheRemoteSensitiveFiles,
- requireSetup: !await this.instanceActorService.realLocalUsersPresent(),
- } : {}),
- };
-
- if (ps.detail) {
- const proxyAccount = instance.proxyAccountId ? await this.userEntityService.pack(instance.proxyAccountId).catch(() => null) : null;
-
- response.proxyAccountName = proxyAccount ? proxyAccount.username : null;
- response.features = {
- registration: !instance.disableRegistration,
- emailRequiredForSignup: instance.emailRequiredForSignup,
- hcaptcha: instance.enableHcaptcha,
- recaptcha: instance.enableRecaptcha,
- turnstile: instance.enableTurnstile,
- objectStorage: instance.useObjectStorage,
- serviceWorker: instance.enableServiceWorker,
- miauth: true,
- };
- }
-
- return response;
+ return ps.detail ? await this.metaEntityService.packDetailed() : await this.metaEntityService.pack();
});
}
}
diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts
index f255e28fc2..e8908f50ec 100644
--- a/packages/backend/src/server/web/ClientServerService.ts
+++ b/packages/backend/src/server/web/ClientServerService.ts
@@ -28,6 +28,7 @@ import type { DbQueue, DeliverQueue, EndedPollNotificationQueue, InboxQueue, Obj
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { PageEntityService } from '@/core/entities/PageEntityService.js';
+import { MetaEntityService } from '@/core/entities/MetaEntityService.js';
import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js';
import { ClipEntityService } from '@/core/entities/ClipEntityService.js';
import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
@@ -93,6 +94,7 @@ export class ClientServerService {
private userEntityService: UserEntityService,
private noteEntityService: NoteEntityService,
private pageEntityService: PageEntityService,
+ private metaEntityService: MetaEntityService,
private galleryPostEntityService: GalleryPostEntityService,
private clipEntityService: ClipEntityService,
private channelEntityService: ChannelEntityService,
@@ -173,7 +175,7 @@ export class ClientServerService {
}
@bindThis
- private generateCommonPugData(meta: MiMeta) {
+ private async generateCommonPugData(meta: MiMeta) {
return {
instanceName: meta.name ?? 'Misskey',
icon: meta.iconUrl,
@@ -183,6 +185,8 @@ export class ClientServerService {
infoImageUrl: meta.infoImageUrl ?? 'https://xn--931a.moe/assets/info.jpg',
notFoundImageUrl: meta.notFoundImageUrl ?? 'https://xn--931a.moe/assets/not-found.jpg',
instanceUrl: this.config.url,
+ metaJson: JSON.stringify(await this.metaEntityService.packDetailed(meta)),
+ now: Date.now(),
};
}
@@ -433,7 +437,7 @@ export class ClientServerService {
url: this.config.url,
title: meta.name ?? 'Misskey',
desc: meta.description,
- ...this.generateCommonPugData(meta),
+ ...await this.generateCommonPugData(meta),
});
};
@@ -520,7 +524,7 @@ export class ClientServerService {
user, profile, me,
avatarUrl: user.avatarUrl ?? this.userEntityService.getIdenticonUrl(user),
sub: request.params.sub,
- ...this.generateCommonPugData(meta),
+ ...await this.generateCommonPugData(meta),
});
} else {
// リモートユーザーなので
@@ -570,7 +574,7 @@ export class ClientServerService {
avatarUrl: _note.user.avatarUrl,
// TODO: Let locale changeable by instance setting
summary: getNoteSummary(_note),
- ...this.generateCommonPugData(meta),
+ ...await this.generateCommonPugData(meta),
});
} else {
return await renderBase(reply);
@@ -609,7 +613,7 @@ export class ClientServerService {
page: _page,
profile,
avatarUrl: _page.user.avatarUrl,
- ...this.generateCommonPugData(meta),
+ ...await this.generateCommonPugData(meta),
});
} else {
return await renderBase(reply);
@@ -635,7 +639,7 @@ export class ClientServerService {
flash: _flash,
profile,
avatarUrl: _flash.user.avatarUrl,
- ...this.generateCommonPugData(meta),
+ ...await this.generateCommonPugData(meta),
});
} else {
return await renderBase(reply);
@@ -661,7 +665,7 @@ export class ClientServerService {
clip: _clip,
profile,
avatarUrl: _clip.user.avatarUrl,
- ...this.generateCommonPugData(meta),
+ ...await this.generateCommonPugData(meta),
});
} else {
return await renderBase(reply);
@@ -685,7 +689,7 @@ export class ClientServerService {
post: _post,
profile,
avatarUrl: _post.user.avatarUrl,
- ...this.generateCommonPugData(meta),
+ ...await this.generateCommonPugData(meta),
});
} else {
return await renderBase(reply);
@@ -704,7 +708,7 @@ export class ClientServerService {
reply.header('Cache-Control', 'public, max-age=15');
return await reply.view('channel', {
channel: _channel,
- ...this.generateCommonPugData(meta),
+ ...await this.generateCommonPugData(meta),
});
} else {
return await renderBase(reply);
@@ -723,7 +727,7 @@ export class ClientServerService {
reply.header('Cache-Control', 'public, max-age=3600');
return await reply.view('reversi-game', {
game: _game,
- ...this.generateCommonPugData(meta),
+ ...await this.generateCommonPugData(meta),
});
} else {
return await renderBase(reply);
diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug
index d167afe1e8..123336809b 100644
--- a/packages/backend/src/server/web/views/base.pug
+++ b/packages/backend/src/server/web/views/base.pug
@@ -68,6 +68,9 @@ html
var VERSION = "#{version}";
var CLIENT_ENTRY = "#{clientEntry.file}";
+ script(type='application/json' id='misskey_meta' data-generated-at=now)
+ != metaJson
+
script
include ../boot.js