summaryrefslogtreecommitdiff
path: root/packages/backend/src/server
diff options
context:
space:
mode:
authormisskey-release-bot[bot] <157398866+misskey-release-bot[bot]@users.noreply.github.com>2025-08-31 08:42:43 +0000
committerGitHub <noreply@github.com>2025-08-31 08:42:43 +0000
commitec21336d45e6e3bb26a0225fc0aa57ac98d5be4b (patch)
tree2c7aef5ba1626009377faf96941a57411dd619e5 /packages/backend/src/server
parentMerge pull request #16244 from misskey-dev/develop (diff)
parentRelease: 2025.8.0 (diff)
downloadmisskey-ec21336d45e6e3bb26a0225fc0aa57ac98d5be4b.tar.gz
misskey-ec21336d45e6e3bb26a0225fc0aa57ac98d5be4b.tar.bz2
misskey-ec21336d45e6e3bb26a0225fc0aa57ac98d5be4b.zip
Merge pull request #16335 from misskey-dev/develop
Release: 2025.8.0
Diffstat (limited to 'packages/backend/src/server')
-rw-r--r--packages/backend/src/server/ServerService.ts24
-rw-r--r--packages/backend/src/server/api/GetterService.ts4
-rw-r--r--packages/backend/src/server/api/endpoint-list.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/admin/announcements/list.ts28
-rw-r--r--packages/backend/src/server/api/endpoints/admin/drive/show-file.ts16
-rw-r--r--packages/backend/src/server/api/endpoints/admin/meta.ts26
-rw-r--r--packages/backend/src/server/api/endpoints/admin/queue/show-job-logs.ts45
-rw-r--r--packages/backend/src/server/api/endpoints/admin/system-webhook/create.ts3
-rw-r--r--packages/backend/src/server/api/endpoints/admin/system-webhook/update.ts3
-rw-r--r--packages/backend/src/server/api/endpoints/admin/update-meta.ts20
-rw-r--r--packages/backend/src/server/api/endpoints/chat/read-all.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/clips/list.ts17
-rw-r--r--packages/backend/src/server/api/endpoints/drive/files/attached-chat-messages.ts10
-rw-r--r--packages/backend/src/server/api/endpoints/flash/update.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/i/apps.ts10
-rw-r--r--packages/backend/src/server/api/endpoints/i/webhooks/list.ts48
-rw-r--r--packages/backend/src/server/api/endpoints/i/webhooks/show.ts24
-rw-r--r--packages/backend/src/server/api/endpoints/notes/create.ts10
-rw-r--r--packages/backend/src/server/api/endpoints/notes/global-timeline.ts1
-rw-r--r--packages/backend/src/server/api/endpoints/notes/mentions.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/notes/show.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/notes/timeline.ts9
-rw-r--r--packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts1
-rw-r--r--packages/backend/src/server/api/endpoints/pages/create.ts41
-rw-r--r--packages/backend/src/server/api/endpoints/pages/delete.ts41
-rw-r--r--packages/backend/src/server/api/endpoints/pages/update.ts69
-rw-r--r--packages/backend/src/server/api/endpoints/users/lists/show.ts10
-rw-r--r--packages/backend/src/server/api/endpoints/users/reactions.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/users/search.ts1
-rw-r--r--packages/backend/src/server/api/endpoints/verify-email.ts66
-rw-r--r--packages/backend/src/server/api/stream/Connection.ts21
-rw-r--r--packages/backend/src/server/api/stream/channels/antenna.ts2
-rw-r--r--packages/backend/src/server/api/stream/channels/channel.ts2
-rw-r--r--packages/backend/src/server/api/stream/channels/global-timeline.ts2
-rw-r--r--packages/backend/src/server/api/stream/channels/hashtag.ts2
-rw-r--r--packages/backend/src/server/api/stream/channels/home-timeline.ts2
-rw-r--r--packages/backend/src/server/api/stream/channels/hybrid-timeline.ts2
-rw-r--r--packages/backend/src/server/api/stream/channels/local-timeline.ts2
-rw-r--r--packages/backend/src/server/api/stream/channels/main.ts2
-rw-r--r--packages/backend/src/server/api/stream/channels/user-list.ts2
-rw-r--r--packages/backend/src/server/web/ClientServerService.ts34
-rw-r--r--packages/backend/src/server/web/boot.embed.js85
-rw-r--r--packages/backend/src/server/web/boot.js147
-rw-r--r--packages/backend/src/server/web/manifest.json8
-rw-r--r--packages/backend/src/server/web/views/base-embed.pug3
-rw-r--r--packages/backend/src/server/web/views/base.pug3
46 files changed, 478 insertions, 384 deletions
diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts
index 23c085ee27..7325c53df0 100644
--- a/packages/backend/src/server/ServerService.ts
+++ b/packages/backend/src/server/ServerService.ts
@@ -238,30 +238,6 @@ export class ServerService implements OnApplicationShutdown {
}
});
- fastify.get<{ Params: { code: string } }>('/verify-email/:code', async (request, reply) => {
- const profile = await this.userProfilesRepository.findOneBy({
- emailVerifyCode: request.params.code,
- });
-
- if (profile != null) {
- await this.userProfilesRepository.update({ userId: profile.userId }, {
- emailVerified: true,
- emailVerifyCode: null,
- });
-
- this.globalEventService.publishMainStream(profile.userId, 'meUpdated', await this.userEntityService.pack(profile.userId, { id: profile.userId }, {
- schema: 'MeDetailed',
- includeSecrets: true,
- }));
-
- reply.code(200).send('Verification succeeded! メールアドレスの認証に成功しました。');
- return;
- } else {
- reply.code(404).send('Verification failed. Please try again. メールアドレスの認証に失敗しました。もう一度お試しください');
- return;
- }
- });
-
fastify.register(this.clientServerService.createServer);
this.streamingApiServerService.attach(fastify.server);
diff --git a/packages/backend/src/server/api/GetterService.ts b/packages/backend/src/server/api/GetterService.ts
index 444e6db744..8f4213dfb6 100644
--- a/packages/backend/src/server/api/GetterService.ts
+++ b/packages/backend/src/server/api/GetterService.ts
@@ -40,8 +40,8 @@ export class GetterService {
}
@bindThis
- public async getNoteWithUser(noteId: MiNote['id']) {
- const note = await this.notesRepository.findOne({ where: { id: noteId }, relations: ['user'] });
+ public async getNoteWithRelations(noteId: MiNote['id']) {
+ const note = await this.notesRepository.findOne({ where: { id: noteId }, relations: ['user', 'reply', 'renote', 'reply.user', 'renote.user'] });
if (note == null) {
throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', 'No such note.');
diff --git a/packages/backend/src/server/api/endpoint-list.ts b/packages/backend/src/server/api/endpoint-list.ts
index 5c4a58a6fc..f4aa07875d 100644
--- a/packages/backend/src/server/api/endpoint-list.ts
+++ b/packages/backend/src/server/api/endpoint-list.ts
@@ -70,6 +70,7 @@ export * as 'admin/queue/inbox-delayed' from './endpoints/admin/queue/inbox-dela
export * as 'admin/queue/retry-job' from './endpoints/admin/queue/retry-job.js';
export * as 'admin/queue/remove-job' from './endpoints/admin/queue/remove-job.js';
export * as 'admin/queue/show-job' from './endpoints/admin/queue/show-job.js';
+export * as 'admin/queue/show-job-logs' from './endpoints/admin/queue/show-job-logs.js';
export * as 'admin/queue/promote-jobs' from './endpoints/admin/queue/promote-jobs.js';
export * as 'admin/queue/jobs' from './endpoints/admin/queue/jobs.js';
export * as 'admin/queue/stats' from './endpoints/admin/queue/stats.js';
@@ -411,6 +412,7 @@ export * as 'users/search' from './endpoints/users/search.js';
export * as 'users/search-by-username-and-host' from './endpoints/users/search-by-username-and-host.js';
export * as 'users/show' from './endpoints/users/show.js';
export * as 'users/update-memo' from './endpoints/users/update-memo.js';
+export * as 'verify-email' from './endpoints/verify-email.js';
export * as 'chat/messages/create-to-user' from './endpoints/chat/messages/create-to-user.js';
export * as 'chat/messages/create-to-room' from './endpoints/chat/messages/create-to-room.js';
export * as 'chat/messages/delete' from './endpoints/chat/messages/delete.js';
diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts
index 81a788de2b..804bd5d9b9 100644
--- a/packages/backend/src/server/api/endpoints/admin/announcements/list.ts
+++ b/packages/backend/src/server/api/endpoints/admin/announcements/list.ts
@@ -49,6 +49,34 @@ export const meta = {
type: 'string',
optional: false, nullable: false,
},
+ icon: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
+ display: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ isActive: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ forExistingUsers: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ silence: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ needConfirmationToRead: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ userId: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
imageUrl: {
type: 'string',
optional: false, nullable: true,
diff --git a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts
index b84a5c73f9..e7a70d0762 100644
--- a/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts
+++ b/packages/backend/src/server/api/endpoints/admin/drive/show-file.ts
@@ -157,6 +157,22 @@ export const meta = {
type: 'boolean',
optional: false, nullable: false,
},
+ maybeSensitive: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ maybePorn: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ requestIp: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
+ requestHeaders: {
+ type: 'object',
+ optional: false, nullable: true,
+ },
},
},
} as const;
diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts
index 924163afbb..21099c0a8c 100644
--- a/packages/backend/src/server/api/endpoints/admin/meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/meta.ts
@@ -223,10 +223,12 @@ export const meta = {
sensitiveMediaDetection: {
type: 'string',
optional: false, nullable: false,
+ enum: ['none', 'all', 'local', 'remote'],
},
sensitiveMediaDetectionSensitivity: {
type: 'string',
optional: false, nullable: false,
+ enum: ['medium', 'low', 'high', 'veryLow', 'veryHigh'],
},
setSensitiveFlagAutomatically: {
type: 'boolean',
@@ -425,6 +427,10 @@ export const meta = {
type: 'string',
optional: false, nullable: true,
},
+ clientOptions: {
+ type: 'object',
+ optional: false, nullable: false,
+ },
description: {
type: 'string',
optional: false, nullable: true,
@@ -469,6 +475,10 @@ export const meta = {
type: 'string',
optional: false, nullable: true,
},
+ feedbackUrl: {
+ type: 'string',
+ optional: false, nullable: true,
+ },
summalyProxy: {
type: 'string',
optional: false, nullable: true,
@@ -571,6 +581,18 @@ export const meta = {
type: 'boolean',
optional: false, nullable: false,
},
+ enableRemoteNotesCleaning: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
+ remoteNotesCleaningExpiryDaysForEachNotes: {
+ type: 'number',
+ optional: false, nullable: false,
+ },
+ remoteNotesCleaningMaxProcessingDurationInMinutes: {
+ type: 'number',
+ optional: false, nullable: false,
+ },
},
},
} as const;
@@ -638,6 +660,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
logoImageUrl: instance.logoImageUrl,
defaultLightTheme: instance.defaultLightTheme,
defaultDarkTheme: instance.defaultDarkTheme,
+ clientOptions: instance.clientOptions,
enableEmail: instance.enableEmail,
enableServiceWorker: instance.enableServiceWorker,
translatorAvailable: instance.deeplAuthKey != null,
@@ -722,6 +745,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
proxyRemoteFiles: instance.proxyRemoteFiles,
signToActivityPubGet: instance.signToActivityPubGet,
allowExternalApRedirect: instance.allowExternalApRedirect,
+ enableRemoteNotesCleaning: instance.enableRemoteNotesCleaning,
+ remoteNotesCleaningExpiryDaysForEachNotes: instance.remoteNotesCleaningExpiryDaysForEachNotes,
+ remoteNotesCleaningMaxProcessingDurationInMinutes: instance.remoteNotesCleaningMaxProcessingDurationInMinutes,
};
});
}
diff --git a/packages/backend/src/server/api/endpoints/admin/queue/show-job-logs.ts b/packages/backend/src/server/api/endpoints/admin/queue/show-job-logs.ts
new file mode 100644
index 0000000000..b9292ed12a
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/admin/queue/show-job-logs.ts
@@ -0,0 +1,45 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { QUEUE_TYPES, QueueService } from '@/core/QueueService.js';
+
+export const meta = {
+ tags: ['admin'],
+
+ requireCredential: true,
+ requireModerator: true,
+ kind: 'read:admin:queue',
+
+ res: {
+ type: 'array',
+ optional: false, nullable: false,
+ items: {
+ optional: false, nullable: false,
+ type: 'string',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ queue: { type: 'string', enum: QUEUE_TYPES },
+ jobId: { type: 'string' },
+ },
+ required: ['queue', 'jobId'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private queueService: QueueService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ return this.queueService.queueGetJobLogs(ps.queue, ps.jobId);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/admin/system-webhook/create.ts b/packages/backend/src/server/api/endpoints/admin/system-webhook/create.ts
index 28071e7a33..93d293ed41 100644
--- a/packages/backend/src/server/api/endpoints/admin/system-webhook/create.ts
+++ b/packages/backend/src/server/api/endpoints/admin/system-webhook/create.ts
@@ -48,8 +48,8 @@ export const paramDef = {
},
secret: {
type: 'string',
- minLength: 1,
maxLength: 1024,
+ default: '',
},
},
required: [
@@ -57,7 +57,6 @@ export const paramDef = {
'name',
'on',
'url',
- 'secret',
],
} as const;
diff --git a/packages/backend/src/server/api/endpoints/admin/system-webhook/update.ts b/packages/backend/src/server/api/endpoints/admin/system-webhook/update.ts
index 8d68bb8f87..e021806398 100644
--- a/packages/backend/src/server/api/endpoints/admin/system-webhook/update.ts
+++ b/packages/backend/src/server/api/endpoints/admin/system-webhook/update.ts
@@ -52,8 +52,8 @@ export const paramDef = {
},
secret: {
type: 'string',
- minLength: 1,
maxLength: 1024,
+ default: '',
},
},
required: [
@@ -62,7 +62,6 @@ export const paramDef = {
'name',
'on',
'url',
- 'secret',
],
} as const;
diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
index 578aa2b662..a1a2a99d6e 100644
--- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
@@ -67,6 +67,7 @@ export const paramDef = {
description: { type: 'string', nullable: true },
defaultLightTheme: { type: 'string', nullable: true },
defaultDarkTheme: { type: 'string', nullable: true },
+ clientOptions: { type: 'object', nullable: false },
cacheRemoteFiles: { type: 'boolean' },
cacheRemoteSensitiveFiles: { type: 'boolean' },
emailRequiredForSignup: { type: 'boolean' },
@@ -205,6 +206,9 @@ export const paramDef = {
proxyRemoteFiles: { type: 'boolean' },
signToActivityPubGet: { type: 'boolean' },
allowExternalApRedirect: { type: 'boolean' },
+ enableRemoteNotesCleaning: { type: 'boolean' },
+ remoteNotesCleaningExpiryDaysForEachNotes: { type: 'number' },
+ remoteNotesCleaningMaxProcessingDurationInMinutes: { type: 'number' },
},
required: [],
} as const;
@@ -323,6 +327,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
set.defaultDarkTheme = ps.defaultDarkTheme;
}
+ if (ps.clientOptions !== undefined) {
+ set.clientOptions = ps.clientOptions;
+ }
+
if (ps.cacheRemoteFiles !== undefined) {
set.cacheRemoteFiles = ps.cacheRemoteFiles;
}
@@ -723,6 +731,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
set.allowExternalApRedirect = ps.allowExternalApRedirect;
}
+ if (ps.enableRemoteNotesCleaning !== undefined) {
+ set.enableRemoteNotesCleaning = ps.enableRemoteNotesCleaning;
+ }
+
+ if (ps.remoteNotesCleaningExpiryDaysForEachNotes !== undefined) {
+ set.remoteNotesCleaningExpiryDaysForEachNotes = ps.remoteNotesCleaningExpiryDaysForEachNotes;
+ }
+
+ if (ps.remoteNotesCleaningMaxProcessingDurationInMinutes !== undefined) {
+ set.remoteNotesCleaningMaxProcessingDurationInMinutes = ps.remoteNotesCleaningMaxProcessingDurationInMinutes;
+ }
+
const before = await this.metaService.fetch(true);
await this.metaService.update(set);
diff --git a/packages/backend/src/server/api/endpoints/chat/read-all.ts b/packages/backend/src/server/api/endpoints/chat/read-all.ts
index 2ed9497eef..e2d9601aa6 100644
--- a/packages/backend/src/server/api/endpoints/chat/read-all.ts
+++ b/packages/backend/src/server/api/endpoints/chat/read-all.ts
@@ -32,6 +32,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private chatService: ChatService,
) {
super(meta, paramDef, async (ps, me) => {
+ await this.chatService.checkChatAvailability(me.id, 'read');
+
await this.chatService.readAllChatMessages(me.id);
});
}
diff --git a/packages/backend/src/server/api/endpoints/clips/list.ts b/packages/backend/src/server/api/endpoints/clips/list.ts
index 2e4a3ff820..af20ea9f8d 100644
--- a/packages/backend/src/server/api/endpoints/clips/list.ts
+++ b/packages/backend/src/server/api/endpoints/clips/list.ts
@@ -5,6 +5,7 @@
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
+import { QueryService } from '@/core/QueryService.js';
import type { ClipsRepository } from '@/models/_.js';
import { ClipEntityService } from '@/core/entities/ClipEntityService.js';
import { DI } from '@/di-symbols.js';
@@ -29,7 +30,13 @@ export const meta = {
export const paramDef = {
type: 'object',
- properties: {},
+ properties: {
+ limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
+ sinceId: { type: 'string', format: 'misskey:id' },
+ untilId: { type: 'string', format: 'misskey:id' },
+ sinceDate: { type: 'integer' },
+ untilDate: { type: 'integer' },
+ },
required: [],
} as const;
@@ -39,12 +46,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
@Inject(DI.clipsRepository)
private clipsRepository: ClipsRepository,
+ private queryService: QueryService,
private clipEntityService: ClipEntityService,
) {
super(meta, paramDef, async (ps, me) => {
- const clips = await this.clipsRepository.findBy({
- userId: me.id,
- });
+ const query = this.queryService.makePaginationQuery(this.clipsRepository.createQueryBuilder('clip'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
+ .andWhere('clip.userId = :userId', { userId: me.id });
+
+ const clips = await query.limit(ps.limit).getMany();
return await this.clipEntityService.packMany(clips, me);
});
diff --git a/packages/backend/src/server/api/endpoints/drive/files/attached-chat-messages.ts b/packages/backend/src/server/api/endpoints/drive/files/attached-chat-messages.ts
index 5be477f468..b34ac4abd1 100644
--- a/packages/backend/src/server/api/endpoints/drive/files/attached-chat-messages.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files/attached-chat-messages.ts
@@ -10,6 +10,7 @@ import { QueryService } from '@/core/QueryService.js';
import { DI } from '@/di-symbols.js';
import { RoleService } from '@/core/RoleService.js';
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
+import { ChatService } from '@/core/ChatService.js';
import { ApiError } from '../../../error.js';
export const meta = {
@@ -60,14 +61,21 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
@Inject(DI.chatMessagesRepository)
private chatMessagesRepository: ChatMessagesRepository,
+ private chatService: ChatService,
private chatEntityService: ChatEntityService,
private queryService: QueryService,
private roleService: RoleService,
) {
super(meta, paramDef, async (ps, me) => {
+ const isModerator = await this.roleService.isModerator(me);
+
+ if (!isModerator) {
+ await this.chatService.checkChatAvailability(me.id, 'read');
+ }
+
const file = await this.driveFilesRepository.findOneBy({
id: ps.fileId,
- userId: await this.roleService.isModerator(me) ? undefined : me.id,
+ userId: isModerator ? undefined : me.id,
});
if (file == null) {
diff --git a/packages/backend/src/server/api/endpoints/flash/update.ts b/packages/backend/src/server/api/endpoints/flash/update.ts
index e378669f0a..8696c6f6e8 100644
--- a/packages/backend/src/server/api/endpoints/flash/update.ts
+++ b/packages/backend/src/server/api/endpoints/flash/update.ts
@@ -73,8 +73,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
updatedAt: new Date(),
...Object.fromEntries(
Object.entries(ps).filter(
- ([key, val]) => (key !== 'flashId') && Object.hasOwn(paramDef.properties, key)
- )
+ ([key, val]) => (key !== 'flashId') && Object.hasOwn(paramDef.properties, key),
+ ),
),
});
});
diff --git a/packages/backend/src/server/api/endpoints/i/apps.ts b/packages/backend/src/server/api/endpoints/i/apps.ts
index 055b5cc061..523d81ac73 100644
--- a/packages/backend/src/server/api/endpoints/i/apps.ts
+++ b/packages/backend/src/server/api/endpoints/i/apps.ts
@@ -46,6 +46,14 @@ export const meta = {
type: 'string',
},
},
+ iconUrl: {
+ type: 'string',
+ optional: true, nullable: true,
+ },
+ description: {
+ type: 'string',
+ optional: true, nullable: true,
+ },
},
},
},
@@ -88,6 +96,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
createdAt: this.idService.parse(token.id).date.toISOString(),
lastUsedAt: token.lastUsedAt?.toISOString(),
permission: token.app ? token.app.permission : token.permission,
+ iconUrl: token.iconUrl,
+ description: token.description ?? token.app?.description ?? null,
})));
});
}
diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts
index 394c178f2a..8a3ba9e026 100644
--- a/packages/backend/src/server/api/endpoints/i/webhooks/list.ts
+++ b/packages/backend/src/server/api/endpoints/i/webhooks/list.ts
@@ -21,29 +21,7 @@ export const meta = {
type: 'array',
items: {
type: 'object',
- properties: {
- id: {
- type: 'string',
- format: 'misskey:id',
- },
- userId: {
- type: 'string',
- format: 'misskey:id',
- },
- name: { type: 'string' },
- on: {
- type: 'array',
- items: {
- type: 'string',
- enum: webhookEventTypes,
- },
- },
- url: { type: 'string' },
- secret: { type: 'string' },
- active: { type: 'boolean' },
- latestSentAt: { type: 'string', format: 'date-time', nullable: true },
- latestStatus: { type: 'integer', nullable: true },
- },
+ ref: 'UserWebhook',
},
},
} as const;
@@ -65,19 +43,17 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
userId: me.id,
});
- return webhooks.map(webhook => (
- {
- id: webhook.id,
- userId: webhook.userId,
- name: webhook.name,
- on: webhook.on,
- url: webhook.url,
- secret: webhook.secret,
- active: webhook.active,
- latestSentAt: webhook.latestSentAt ? webhook.latestSentAt.toISOString() : null,
- latestStatus: webhook.latestStatus,
- }
- ));
+ return webhooks.map(webhook => ({
+ id: webhook.id,
+ userId: webhook.userId,
+ name: webhook.name,
+ on: webhook.on,
+ url: webhook.url,
+ secret: webhook.secret,
+ active: webhook.active,
+ latestSentAt: webhook.latestSentAt ? webhook.latestSentAt.toISOString() : null,
+ latestStatus: webhook.latestStatus,
+ }));
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts
index 4a0c09ff0c..1c19081c98 100644
--- a/packages/backend/src/server/api/endpoints/i/webhooks/show.ts
+++ b/packages/backend/src/server/api/endpoints/i/webhooks/show.ts
@@ -28,29 +28,7 @@ export const meta = {
res: {
type: 'object',
- properties: {
- id: {
- type: 'string',
- format: 'misskey:id',
- },
- userId: {
- type: 'string',
- format: 'misskey:id',
- },
- name: { type: 'string' },
- on: {
- type: 'array',
- items: {
- type: 'string',
- enum: webhookEventTypes,
- },
- },
- url: { type: 'string' },
- secret: { type: 'string' },
- active: { type: 'boolean' },
- latestSentAt: { type: 'string', format: 'date-time', nullable: true },
- latestStatus: { type: 'integer', nullable: true },
- },
+ ref: 'UserWebhook',
},
} as const;
diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts
index 253a360815..7caea8eedc 100644
--- a/packages/backend/src/server/api/endpoints/notes/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/create.ts
@@ -269,7 +269,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
let renote: MiNote | null = null;
if (ps.renoteId != null) {
// Fetch renote to note
- renote = await this.notesRepository.findOneBy({ id: ps.renoteId });
+ renote = await this.notesRepository.findOne({
+ where: { id: ps.renoteId },
+ relations: ['user', 'renote', 'reply'],
+ });
if (renote == null) {
throw new ApiError(meta.errors.noSuchRenoteTarget);
@@ -315,7 +318,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
let reply: MiNote | null = null;
if (ps.replyId != null) {
// Fetch reply
- reply = await this.notesRepository.findOneBy({ id: ps.replyId });
+ reply = await this.notesRepository.findOne({
+ where: { id: ps.replyId },
+ relations: ['user'],
+ });
if (reply == null) {
throw new ApiError(meta.errors.noSuchReplyTarget);
diff --git a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
index 1c73edf08e..7fa8004209 100644
--- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
@@ -91,6 +91,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
qb.orWhere(new Brackets(qb => {
qb.where('note.text IS NOT NULL');
qb.orWhere('note.fileIds != \'{}\'');
+ qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
}));
}));
}
diff --git a/packages/backend/src/server/api/endpoints/notes/mentions.ts b/packages/backend/src/server/api/endpoints/notes/mentions.ts
index 05ffdc1f97..e775bdb7fd 100644
--- a/packages/backend/src/server/api/endpoints/notes/mentions.ts
+++ b/packages/backend/src/server/api/endpoints/notes/mentions.ts
@@ -66,7 +66,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
.orWhere(':meIdAsList <@ note.visibleUserIds');
}))
// Avoid scanning primary key index
- .orderBy('CONCAT(note.id)', 'DESC')
+ .orderBy('CONCAT(note.id)', (ps.sinceDate || ps.sinceId) ? 'ASC' : 'DESC')
.innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('note.reply', 'reply')
.leftJoinAndSelect('note.renote', 'renote')
diff --git a/packages/backend/src/server/api/endpoints/notes/show.ts b/packages/backend/src/server/api/endpoints/notes/show.ts
index b93c73b0c5..cae0e752da 100644
--- a/packages/backend/src/server/api/endpoints/notes/show.ts
+++ b/packages/backend/src/server/api/endpoints/notes/show.ts
@@ -55,7 +55,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private getterService: GetterService,
) {
super(meta, paramDef, async (ps, me) => {
- const note = await this.getterService.getNoteWithUser(ps.noteId).catch(err => {
+ const note = await this.getterService.getNoteWithRelations(ps.noteId).catch(err => {
if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
throw err;
});
diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts
index c76cca1518..eeeb797efc 100644
--- a/packages/backend/src/server/api/endpoints/notes/timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts
@@ -237,7 +237,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}
if (ps.withRenotes === false) {
- query.andWhere('note.renoteId IS NULL');
+ query.andWhere(new Brackets(qb => {
+ qb.orWhere('note.renoteId IS NULL');
+ qb.orWhere(new Brackets(qb => {
+ qb.orWhere('note.text IS NOT NULL');
+ qb.orWhere('note.fileIds != \'{}\'');
+ qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
+ }));
+ }));
}
//#endregion
diff --git a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts
index 614cd9204d..42e80c6ae1 100644
--- a/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts
@@ -223,6 +223,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
qb.orWhere(new Brackets(qb => {
qb.orWhere('note.text IS NOT NULL');
qb.orWhere('note.fileIds != \'{}\'');
+ qb.orWhere('0 < (SELECT COUNT(*) FROM poll WHERE poll."noteId" = note.id)');
}));
}));
}
diff --git a/packages/backend/src/server/api/endpoints/pages/create.ts b/packages/backend/src/server/api/endpoints/pages/create.ts
index 6de5fe3d44..96bc2a953a 100644
--- a/packages/backend/src/server/api/endpoints/pages/create.ts
+++ b/packages/backend/src/server/api/endpoints/pages/create.ts
@@ -5,12 +5,13 @@
import ms from 'ms';
import { Inject, Injectable } from '@nestjs/common';
-import type { DriveFilesRepository, PagesRepository } from '@/models/_.js';
-import { IdService } from '@/core/IdService.js';
-import { MiPage, pageNameSchema } from '@/models/Page.js';
+import type { DriveFilesRepository, MiDriveFile, PagesRepository } from '@/models/_.js';
+import { pageNameSchema } from '@/models/Page.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { PageEntityService } from '@/core/entities/PageEntityService.js';
import { DI } from '@/di-symbols.js';
+import { PageService } from '@/core/PageService.js';
+import { IdentifiableError } from '@/misc/identifiable-error.js';
import { ApiError } from '../../error.js';
export const meta = {
@@ -77,11 +78,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
+ private pageService: PageService,
private pageEntityService: PageEntityService,
- private idService: IdService,
) {
super(meta, paramDef, async (ps, me) => {
- let eyeCatchingImage = null;
+ let eyeCatchingImage: MiDriveFile | null = null;
if (ps.eyeCatchingImageId != null) {
eyeCatchingImage = await this.driveFilesRepository.findOneBy({
id: ps.eyeCatchingImageId,
@@ -102,24 +103,20 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}
});
- const page = await this.pagesRepository.insertOne(new MiPage({
- id: this.idService.gen(),
- updatedAt: new Date(),
- title: ps.title,
- name: ps.name,
- summary: ps.summary,
- content: ps.content,
- variables: ps.variables,
- script: ps.script,
- eyeCatchingImageId: eyeCatchingImage ? eyeCatchingImage.id : null,
- userId: me.id,
- visibility: 'public',
- alignCenter: ps.alignCenter,
- hideTitleWhenPinned: ps.hideTitleWhenPinned,
- font: ps.font,
- }));
+ try {
+ const page = await this.pageService.create(me, {
+ ...ps,
+ eyeCatchingImage,
+ summary: ps.summary ?? null,
+ });
- return await this.pageEntityService.pack(page);
+ return await this.pageEntityService.pack(page);
+ } catch (err) {
+ if (err instanceof IdentifiableError && err.id === '1a79e38e-3d83-4423-845b-a9d83ff93b61') {
+ throw new ApiError(meta.errors.nameAlreadyExists);
+ }
+ throw err;
+ }
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/pages/delete.ts b/packages/backend/src/server/api/endpoints/pages/delete.ts
index f2bc946788..a33868552d 100644
--- a/packages/backend/src/server/api/endpoints/pages/delete.ts
+++ b/packages/backend/src/server/api/endpoints/pages/delete.ts
@@ -4,12 +4,14 @@
*/
import { Inject, Injectable } from '@nestjs/common';
-import type { PagesRepository, UsersRepository } from '@/models/_.js';
+import type { MiDriveFile, PagesRepository, UsersRepository } from '@/models/_.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
import { RoleService } from '@/core/RoleService.js';
import { ApiError } from '../../error.js';
+import { IdentifiableError } from '@/misc/identifiable-error.js';
+import { PageService } from '@/core/PageService.js';
export const meta = {
tags: ['pages'],
@@ -44,36 +46,17 @@ export const paramDef = {
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
- @Inject(DI.pagesRepository)
- private pagesRepository: PagesRepository,
-
- @Inject(DI.usersRepository)
- private usersRepository: UsersRepository,
-
- private moderationLogService: ModerationLogService,
- private roleService: RoleService,
+ private pageService: PageService,
) {
super(meta, paramDef, async (ps, me) => {
- const page = await this.pagesRepository.findOneBy({ id: ps.pageId });
-
- if (page == null) {
- throw new ApiError(meta.errors.noSuchPage);
- }
-
- if (!await this.roleService.isModerator(me) && page.userId !== me.id) {
- throw new ApiError(meta.errors.accessDenied);
- }
-
- await this.pagesRepository.delete(page.id);
-
- if (page.userId !== me.id) {
- const user = await this.usersRepository.findOneByOrFail({ id: page.userId });
- this.moderationLogService.log(me, 'deletePage', {
- pageId: page.id,
- pageUserId: page.userId,
- pageUserUsername: user.username,
- page,
- });
+ try {
+ await this.pageService.delete(me, ps.pageId);
+ } catch (err) {
+ if (err instanceof IdentifiableError) {
+ if (err.id === '66aefd3c-fdb2-4a71-85ae-cc18bea85d3f') throw new ApiError(meta.errors.noSuchPage);
+ if (err.id === 'd0017699-8256-46f1-aed4-bc03bed73616') throw new ApiError(meta.errors.accessDenied);
+ }
+ throw err;
}
});
}
diff --git a/packages/backend/src/server/api/endpoints/pages/update.ts b/packages/backend/src/server/api/endpoints/pages/update.ts
index a6aeb6002e..6fa5c1d75c 100644
--- a/packages/backend/src/server/api/endpoints/pages/update.ts
+++ b/packages/backend/src/server/api/endpoints/pages/update.ts
@@ -4,13 +4,14 @@
*/
import ms from 'ms';
-import { Not } from 'typeorm';
import { Inject, Injectable } from '@nestjs/common';
-import type { PagesRepository, DriveFilesRepository } from '@/models/_.js';
+import type { DriveFilesRepository, MiDriveFile } from '@/models/_.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import { ApiError } from '../../error.js';
import { pageNameSchema } from '@/models/Page.js';
+import { IdentifiableError } from '@/misc/identifiable-error.js';
+import { PageService } from '@/core/PageService.js';
export const meta = {
tags: ['pages'],
@@ -75,57 +76,37 @@ export const paramDef = {
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
- @Inject(DI.pagesRepository)
- private pagesRepository: PagesRepository,
-
@Inject(DI.driveFilesRepository)
private driveFilesRepository: DriveFilesRepository,
+
+ private pageService: PageService,
) {
super(meta, paramDef, async (ps, me) => {
- const page = await this.pagesRepository.findOneBy({ id: ps.pageId });
- if (page == null) {
- throw new ApiError(meta.errors.noSuchPage);
- }
- if (page.userId !== me.id) {
- throw new ApiError(meta.errors.accessDenied);
- }
-
- if (ps.eyeCatchingImageId != null) {
- const eyeCatchingImage = await this.driveFilesRepository.findOneBy({
- id: ps.eyeCatchingImageId,
- userId: me.id,
- });
+ try {
+ let eyeCatchingImage: MiDriveFile | null | undefined | string = ps.eyeCatchingImageId;
+ if (eyeCatchingImage != null) {
+ eyeCatchingImage = await this.driveFilesRepository.findOneBy({
+ id: eyeCatchingImage,
+ userId: me.id,
+ });
- if (eyeCatchingImage == null) {
- throw new ApiError(meta.errors.noSuchFile);
+ if (eyeCatchingImage == null) {
+ throw new ApiError(meta.errors.noSuchFile);
+ }
}
- }
- if (ps.name != null) {
- await this.pagesRepository.findBy({
- id: Not(ps.pageId),
- userId: me.id,
- name: ps.name,
- }).then(result => {
- if (result.length > 0) {
- throw new ApiError(meta.errors.nameAlreadyExists);
- }
+ await this.pageService.update(me, ps.pageId, {
+ ...ps,
+ eyeCatchingImage,
});
+ } catch (err) {
+ if (err instanceof IdentifiableError) {
+ if (err.id === '66aefd3c-fdb2-4a71-85ae-cc18bea85d3f') throw new ApiError(meta.errors.noSuchPage);
+ if (err.id === 'd0017699-8256-46f1-aed4-bc03bed73616') throw new ApiError(meta.errors.accessDenied);
+ if (err.id === 'd05bfe24-24b6-4ea2-a3ec-87cc9bf4daa4') throw new ApiError(meta.errors.nameAlreadyExists);
+ }
+ throw err;
}
-
- await this.pagesRepository.update(page.id, {
- updatedAt: new Date(),
- title: ps.title,
- name: ps.name,
- summary: ps.summary === undefined ? page.summary : ps.summary,
- content: ps.content,
- variables: ps.variables,
- script: ps.script,
- alignCenter: ps.alignCenter,
- hideTitleWhenPinned: ps.hideTitleWhenPinned,
- font: ps.font,
- eyeCatchingImageId: ps.eyeCatchingImageId,
- });
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/users/lists/show.ts b/packages/backend/src/server/api/endpoints/users/lists/show.ts
index 8756801fe4..ed5952d4c5 100644
--- a/packages/backend/src/server/api/endpoints/users/lists/show.ts
+++ b/packages/backend/src/server/api/endpoints/users/lists/show.ts
@@ -23,6 +23,16 @@ export const meta = {
type: 'object',
optional: false, nullable: false,
ref: 'UserList',
+ properties: {
+ likedCount: {
+ type: 'number',
+ optional: true, nullable: false,
+ },
+ isLiked: {
+ type: 'boolean',
+ optional: true, nullable: false,
+ },
+ },
},
errors: {
diff --git a/packages/backend/src/server/api/endpoints/users/reactions.ts b/packages/backend/src/server/api/endpoints/users/reactions.ts
index d6f1ecd8ed..d84a191f7a 100644
--- a/packages/backend/src/server/api/endpoints/users/reactions.ts
+++ b/packages/backend/src/server/api/endpoints/users/reactions.ts
@@ -28,7 +28,7 @@ export const meta = {
items: {
type: 'object',
optional: false, nullable: false,
- ref: 'NoteReaction',
+ ref: 'NoteReactionWithNote',
},
},
@@ -120,7 +120,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
return true;
});
- return await this.noteReactionEntityService.packMany(reactions, me, { withNote: true });
+ return await this.noteReactionEntityService.packManyWithNote(reactions, me);
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/users/search.ts b/packages/backend/src/server/api/endpoints/users/search.ts
index 5d36847e03..c422286152 100644
--- a/packages/backend/src/server/api/endpoints/users/search.ts
+++ b/packages/backend/src/server/api/endpoints/users/search.ts
@@ -13,6 +13,7 @@ export const meta = {
tags: ['users'],
requireCredential: false,
+ requiredRolePolicy: 'canSearchUsers',
description: 'Search for users.',
diff --git a/packages/backend/src/server/api/endpoints/verify-email.ts b/packages/backend/src/server/api/endpoints/verify-email.ts
new file mode 100644
index 0000000000..e069ed59f2
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/verify-email.ts
@@ -0,0 +1,66 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import type { UserProfilesRepository } from '@/models/_.js';
+import { UserEntityService } from '@/core/entities/UserEntityService.js';
+import { DI } from '@/di-symbols.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { ApiError } from '../error.js';
+
+export const meta = {
+ requireCredential: false,
+
+ tags: ['account'],
+
+ errors: {
+ noSuchCode: {
+ message: 'No such code.',
+ code: 'NO_SUCH_CODE',
+ id: '97c1f576-e4b8-4b8a-a6dc-9cb65e7f6f85',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ code: { type: 'string' },
+ },
+ required: ['code'],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ @Inject(DI.userProfilesRepository)
+ private userProfilesRepository: UserProfilesRepository,
+
+ private userEntityService: UserEntityService,
+ private globalEventService: GlobalEventService,
+ ) {
+ super(meta, paramDef, async (ps) => {
+ const profile = await this.userProfilesRepository.findOneBy({
+ emailVerifyCode: ps.code,
+ });
+
+ if (profile == null) {
+ throw new ApiError(meta.errors.noSuchCode);
+ }
+
+ await this.userProfilesRepository.update({ userId: profile.userId }, {
+ emailVerified: true,
+ emailVerifyCode: null,
+ });
+
+ this.globalEventService.publishMainStream(profile.userId, 'meUpdated', await this.userEntityService.pack(profile.userId, { id: profile.userId }, {
+ schema: 'MeDetailed',
+ includeSecrets: true,
+ }));
+ });
+ }
+}
+
diff --git a/packages/backend/src/server/api/stream/Connection.ts b/packages/backend/src/server/api/stream/Connection.ts
index c9801d8314..8e28ab263b 100644
--- a/packages/backend/src/server/api/stream/Connection.ts
+++ b/packages/backend/src/server/api/stream/Connection.ts
@@ -32,7 +32,6 @@ export default class Connection {
public subscriber: StreamEventEmitter;
private channels: Channel[] = [];
private subscribingNotes: Partial<Record<string, number>> = {};
- private cachedNotes: Packed<'Note'>[] = [];
public userProfile: MiUserProfile | null = null;
public following: Record<string, Pick<MiFollowing, 'withReplies'> | undefined> = {};
public followingChannels: Set<string> = new Set();
@@ -133,26 +132,6 @@ export default class Connection {
}
@bindThis
- public cacheNote(note: Packed<'Note'>) {
- const add = (note: Packed<'Note'>) => {
- const existIndex = this.cachedNotes.findIndex(n => n.id === note.id);
- if (existIndex > -1) {
- this.cachedNotes[existIndex] = note;
- return;
- }
-
- this.cachedNotes.unshift(note);
- if (this.cachedNotes.length > 32) {
- this.cachedNotes.splice(32);
- }
- };
-
- add(note);
- if (note.reply) add(note.reply);
- if (note.renote) add(note.renote);
- }
-
- @bindThis
private onReadNotification(payload: JsonValue | undefined) {
this.notificationService.readAllNotification(this.user!.id);
}
diff --git a/packages/backend/src/server/api/stream/channels/antenna.ts b/packages/backend/src/server/api/stream/channels/antenna.ts
index 53dc7f18b6..e08562fdf9 100644
--- a/packages/backend/src/server/api/stream/channels/antenna.ts
+++ b/packages/backend/src/server/api/stream/channels/antenna.ts
@@ -43,8 +43,6 @@ class AntennaChannel extends Channel {
if (this.isNoteMutedOrBlocked(note)) return;
- this.connection.cacheNote(note);
-
this.send('note', note);
} else {
this.send(data.type, data.body);
diff --git a/packages/backend/src/server/api/stream/channels/channel.ts b/packages/backend/src/server/api/stream/channels/channel.ts
index 7108e0cd6e..ac79c31854 100644
--- a/packages/backend/src/server/api/stream/channels/channel.ts
+++ b/packages/backend/src/server/api/stream/channels/channel.ts
@@ -49,8 +49,6 @@ class ChannelChannel extends Channel {
}
}
- this.connection.cacheNote(note);
-
this.send('note', note);
}
diff --git a/packages/backend/src/server/api/stream/channels/global-timeline.ts b/packages/backend/src/server/api/stream/channels/global-timeline.ts
index 795980821b..d7c781ad12 100644
--- a/packages/backend/src/server/api/stream/channels/global-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts
@@ -65,8 +65,6 @@ class GlobalTimelineChannel extends Channel {
}
}
- this.connection.cacheNote(note);
-
this.send('note', note);
}
diff --git a/packages/backend/src/server/api/stream/channels/hashtag.ts b/packages/backend/src/server/api/stream/channels/hashtag.ts
index 8105f15cb1..c911d63642 100644
--- a/packages/backend/src/server/api/stream/channels/hashtag.ts
+++ b/packages/backend/src/server/api/stream/channels/hashtag.ts
@@ -53,8 +53,6 @@ class HashtagChannel extends Channel {
}
}
- this.connection.cacheNote(note);
-
this.send('note', note);
}
diff --git a/packages/backend/src/server/api/stream/channels/home-timeline.ts b/packages/backend/src/server/api/stream/channels/home-timeline.ts
index 66644ed58c..157d9fc279 100644
--- a/packages/backend/src/server/api/stream/channels/home-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts
@@ -86,8 +86,6 @@ class HomeTimelineChannel extends Channel {
}
}
- this.connection.cacheNote(note);
-
this.send('note', note);
}
diff --git a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
index 5681311493..db5b4576be 100644
--- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
@@ -100,8 +100,6 @@ class HybridTimelineChannel extends Channel {
}
}
- this.connection.cacheNote(note);
-
this.send('note', note);
}
diff --git a/packages/backend/src/server/api/stream/channels/local-timeline.ts b/packages/backend/src/server/api/stream/channels/local-timeline.ts
index 2984e18774..3d7ed6acdb 100644
--- a/packages/backend/src/server/api/stream/channels/local-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts
@@ -75,8 +75,6 @@ class LocalTimelineChannel extends Channel {
}
}
- this.connection.cacheNote(note);
-
this.send('note', note);
}
diff --git a/packages/backend/src/server/api/stream/channels/main.ts b/packages/backend/src/server/api/stream/channels/main.ts
index 863d7f4c4e..525f24c105 100644
--- a/packages/backend/src/server/api/stream/channels/main.ts
+++ b/packages/backend/src/server/api/stream/channels/main.ts
@@ -39,7 +39,6 @@ class MainChannel extends Channel {
const note = await this.noteEntityService.pack(data.body.note.id, this.user, {
detail: true,
});
- this.connection.cacheNote(note);
data.body.note = note;
}
break;
@@ -52,7 +51,6 @@ class MainChannel extends Channel {
const note = await this.noteEntityService.pack(data.body.id, this.user, {
detail: true,
});
- this.connection.cacheNote(note);
data.body = note;
}
break;
diff --git a/packages/backend/src/server/api/stream/channels/user-list.ts b/packages/backend/src/server/api/stream/channels/user-list.ts
index 4f38351e94..5bfd8fa68c 100644
--- a/packages/backend/src/server/api/stream/channels/user-list.ts
+++ b/packages/backend/src/server/api/stream/channels/user-list.ts
@@ -118,8 +118,6 @@ class UserListChannel extends Channel {
}
}
- this.connection.cacheNote(note);
-
this.send('note', note);
}
diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts
index 8ca61a497d..b515a0c0c8 100644
--- a/packages/backend/src/server/web/ClientServerService.ts
+++ b/packages/backend/src/server/web/ClientServerService.ts
@@ -20,17 +20,6 @@ import type { Config } from '@/config.js';
import { getNoteSummary } from '@/misc/get-note-summary.js';
import { DI } from '@/di-symbols.js';
import * as Acct from '@/misc/acct.js';
-import type {
- DbQueue,
- DeliverQueue,
- EndedPollNotificationQueue,
- InboxQueue,
- ObjectStorageQueue,
- RelationshipQueue,
- SystemQueue,
- UserWebhookDeliverQueue,
- SystemWebhookDeliverQueue,
-} from '@/core/QueueModule.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { PageEntityService } from '@/core/entities/PageEntityService.js';
@@ -129,16 +118,6 @@ export class ClientServerService {
private feedService: FeedService,
private roleService: RoleService,
private clientLoggerService: ClientLoggerService,
-
- @Inject('queue:system') public systemQueue: SystemQueue,
- @Inject('queue:endedPollNotification') public endedPollNotificationQueue: EndedPollNotificationQueue,
- @Inject('queue:deliver') public deliverQueue: DeliverQueue,
- @Inject('queue:inbox') public inboxQueue: InboxQueue,
- @Inject('queue:db') public dbQueue: DbQueue,
- @Inject('queue:relationship') public relationshipQueue: RelationshipQueue,
- @Inject('queue:objectStorage') public objectStorageQueue: ObjectStorageQueue,
- @Inject('queue:userWebhookDeliver') public userWebhookDeliverQueue: UserWebhookDeliverQueue,
- @Inject('queue:systemWebhookDeliver') public systemWebhookDeliverQueue: SystemWebhookDeliverQueue,
) {
//this.createServer = this.createServer.bind(this);
}
@@ -188,6 +167,10 @@ export class ClientServerService {
'url': 'url',
},
},
+ 'shortcuts': [{
+ 'name': 'Safemode',
+ 'url': '/?safemode=true',
+ }],
};
manifest = {
@@ -580,7 +563,7 @@ export class ClientServerService {
id: request.params.note,
visibility: In(['public', 'home']),
},
- relations: ['user'],
+ relations: ['user', 'reply', 'renote'],
});
if (
@@ -821,8 +804,11 @@ export class ClientServerService {
fastify.get<{ Params: { note: string; } }>('/embed/notes/:note', async (request, reply) => {
reply.removeHeader('X-Frame-Options');
- const note = await this.notesRepository.findOneBy({
- id: request.params.note,
+ const note = await this.notesRepository.findOne({
+ where: {
+ id: request.params.note,
+ },
+ relations: ['user', 'reply', 'renote'],
});
if (note == null) return;
diff --git a/packages/backend/src/server/web/boot.embed.js b/packages/backend/src/server/web/boot.embed.js
index 9de1275380..022ff064ad 100644
--- a/packages/backend/src/server/web/boot.embed.js
+++ b/packages/backend/src/server/web/boot.embed.js
@@ -32,61 +32,30 @@
}
//#region Detect language & fetch translations
- if (!localStorage.hasOwnProperty('locale')) {
- const supportedLangs = LANGS;
- let lang = localStorage.getItem('lang');
- if (lang == null || !supportedLangs.includes(lang)) {
- if (supportedLangs.includes(navigator.language)) {
- lang = navigator.language;
- } else {
- lang = supportedLangs.find(x => x.split('-')[0] === navigator.language);
-
- // Fallback
- if (lang == null) lang = 'en-US';
- }
- }
-
- const metaRes = await window.fetch('/api/meta', {
- method: 'POST',
- body: JSON.stringify({}),
- credentials: 'omit',
- cache: 'no-cache',
- headers: {
- 'Content-Type': 'application/json',
- },
- });
- if (metaRes.status !== 200) {
- renderError('META_FETCH');
- return;
- }
- const meta = await metaRes.json();
- const v = meta.version;
- if (v == null) {
- renderError('META_FETCH_V');
- return;
- }
+ const supportedLangs = LANGS;
+ /** @type { string } */
+ let lang = localStorage.getItem('lang');
+ if (lang == null || !supportedLangs.includes(lang)) {
+ if (supportedLangs.includes(navigator.language)) {
+ lang = navigator.language;
+ } else {
+ lang = supportedLangs.find(x => x.split('-')[0] === navigator.language);
- // for https://github.com/misskey-dev/misskey/issues/10202
- if (lang == null || lang.toString == null || lang.toString() === 'null') {
- console.error('invalid lang value detected!!!', typeof lang, lang);
- lang = 'en-US';
+ // Fallback
+ if (lang == null) lang = 'en-US';
}
+ }
- const localRes = await window.fetch(`/assets/locales/${lang}.${v}.json`);
- if (localRes.status === 200) {
- localStorage.setItem('lang', lang);
- localStorage.setItem('locale', await localRes.text());
- localStorage.setItem('localeVersion', v);
- } else {
- renderError('LOCALE_FETCH');
- return;
- }
+ // for https://github.com/misskey-dev/misskey/issues/10202
+ if (lang == null || lang.toString == null || lang.toString() === 'null') {
+ console.error('invalid lang value detected!!!', typeof lang, lang);
+ lang = 'en-US';
}
//#endregion
//#region Script
async function importAppScript() {
- await import(`/embed_vite/${CLIENT_ENTRY}`)
+ await import(CLIENT_ENTRY ? `/embed_vite/${CLIENT_ENTRY.replace('scripts', lang)}` : '/embed_vite/src/_boot_.ts')
.catch(async e => {
console.error(e);
renderError('APP_IMPORT');
@@ -115,10 +84,26 @@
await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve));
}
- const locale = JSON.parse(localStorage.getItem('locale') || '{}');
+ let messages = null;
+ const bootloaderLocales = localStorage.getItem('bootloaderLocales');
+ if (bootloaderLocales) {
+ messages = JSON.parse(bootloaderLocales);
+ }
+ if (!messages) {
+ // older version of misskey does not store bootloaderLocales, stores locale as a whole
+ const legacyLocale = localStorage.getItem('locale');
+ if (legacyLocale) {
+ const parsed = JSON.parse(legacyLocale);
+ messages = {
+ ...(parsed._bootErrors ?? {}),
+ reload: parsed.reload,
+ };
+ }
+ }
+ if (!messages) messages = {};
- const title = locale?._bootErrors?.title || 'Failed to initialize Misskey';
- const reload = locale?.reload || 'Reload';
+ const title = messages?.title || 'Failed to initialize Misskey';
+ const reload = messages?.reload || 'Reload';
document.body.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /><path d="M12 9v4" /><path d="M12 16v.01" /></svg>
<div class="message">${title}</div>
diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js
index 24794cbf2a..0c0b46f82b 100644
--- a/packages/backend/src/server/web/boot.js
+++ b/packages/backend/src/server/web/boot.js
@@ -22,62 +22,31 @@
return;
}
- //#region Detect language & fetch translations
- if (!localStorage.hasOwnProperty('locale')) {
- const supportedLangs = LANGS;
- let lang = localStorage.getItem('lang');
- if (lang == null || !supportedLangs.includes(lang)) {
- if (supportedLangs.includes(navigator.language)) {
- lang = navigator.language;
- } else {
- lang = supportedLangs.find(x => x.split('-')[0] === navigator.language);
-
- // Fallback
- if (lang == null) lang = 'en-US';
- }
- }
-
- const metaRes = await window.fetch('/api/meta', {
- method: 'POST',
- body: JSON.stringify({}),
- credentials: 'omit',
- cache: 'no-cache',
- headers: {
- 'Content-Type': 'application/json',
- },
- });
- if (metaRes.status !== 200) {
- renderError('META_FETCH');
- return;
- }
- const meta = await metaRes.json();
- const v = meta.version;
- if (v == null) {
- renderError('META_FETCH_V');
- return;
- }
+ //#region Detect language
+ const supportedLangs = LANGS;
+ /** @type { string } */
+ let lang = localStorage.getItem('lang');
+ if (lang == null || !supportedLangs.includes(lang)) {
+ if (supportedLangs.includes(navigator.language)) {
+ lang = navigator.language;
+ } else {
+ lang = supportedLangs.find(x => x.split('-')[0] === navigator.language);
- // for https://github.com/misskey-dev/misskey/issues/10202
- if (lang == null || lang.toString == null || lang.toString() === 'null') {
- console.error('invalid lang value detected!!!', typeof lang, lang);
- lang = 'en-US';
+ // Fallback
+ if (lang == null) lang = 'en-US';
}
+ }
- const localRes = await window.fetch(`/assets/locales/${lang}.${v}.json`);
- if (localRes.status === 200) {
- localStorage.setItem('lang', lang);
- localStorage.setItem('locale', await localRes.text());
- localStorage.setItem('localeVersion', v);
- } else {
- renderError('LOCALE_FETCH');
- return;
- }
+ // for https://github.com/misskey-dev/misskey/issues/10202
+ if (lang == null || lang.toString == null || lang.toString() === 'null') {
+ console.error('invalid lang value detected!!!', typeof lang, lang);
+ lang = 'en-US';
}
//#endregion
//#region Script
async function importAppScript() {
- await import(`/vite/${CLIENT_ENTRY}`)
+ await import(CLIENT_ENTRY ? `/vite/${CLIENT_ENTRY.replace('scripts', lang)}` : '/vite/src/_boot_.ts')
.catch(async e => {
console.error(e);
renderError('APP_IMPORT', e);
@@ -94,23 +63,37 @@
}
//#endregion
+ let isSafeMode = (localStorage.getItem('isSafeMode') === 'true');
+
+ if (!isSafeMode) {
+ const urlParams = new URLSearchParams(window.location.search);
+
+ if (urlParams.has('safemode') && urlParams.get('safemode') === 'true') {
+ localStorage.setItem('isSafeMode', 'true');
+ isSafeMode = true;
+ }
+ }
+
//#region Theme
- const theme = localStorage.getItem('theme');
- if (theme) {
- for (const [k, v] of Object.entries(JSON.parse(theme))) {
- document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString());
+ if (!isSafeMode) {
+ const theme = localStorage.getItem('theme');
+ if (theme) {
+ for (const [k, v] of Object.entries(JSON.parse(theme))) {
+ document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString());
- // HTMLの theme-color 適用
- if (k === 'htmlThemeColor') {
- for (const tag of document.head.children) {
- if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') {
- tag.setAttribute('content', v);
- break;
+ // HTMLの theme-color 適用
+ if (k === 'htmlThemeColor') {
+ for (const tag of document.head.children) {
+ if (tag.tagName === 'META' && tag.getAttribute('name') === 'theme-color') {
+ tag.setAttribute('content', v);
+ break;
+ }
}
}
}
}
}
+
const colorScheme = localStorage.getItem('colorScheme');
if (colorScheme) {
document.documentElement.style.setProperty('color-scheme', colorScheme);
@@ -127,11 +110,13 @@
document.documentElement.classList.add('useSystemFont');
}
- const customCss = localStorage.getItem('customCss');
- if (customCss && customCss.length > 0) {
- const style = document.createElement('style');
- style.innerHTML = customCss;
- document.head.appendChild(style);
+ if (!isSafeMode) {
+ const customCss = localStorage.getItem('customCss');
+ if (customCss && customCss.length > 0) {
+ const style = document.createElement('style');
+ style.innerHTML = customCss;
+ document.head.appendChild(style);
+ }
}
async function addStyle(styleText) {
@@ -146,9 +131,25 @@
await new Promise(resolve => window.addEventListener('DOMContentLoaded', resolve));
}
- const locale = JSON.parse(localStorage.getItem('locale') || '{}');
+ let messages = null;
+ const bootloaderLocales = localStorage.getItem('bootloaderLocales');
+ if (bootloaderLocales) {
+ messages = JSON.parse(bootloaderLocales);
+ }
+ if (!messages) {
+ // older version of misskey does not store bootloaderLocales, stores locale as a whole
+ const legacyLocale = localStorage.getItem('locale');
+ if (legacyLocale) {
+ const parsed = JSON.parse(legacyLocale);
+ messages = {
+ ...(parsed._bootErrors ?? {}),
+ reload: parsed.reload,
+ };
+ }
+ }
+ if (!messages) messages = {};
- const messages = Object.assign({
+ messages = Object.assign({
title: 'Failed to initialize Misskey',
solution: 'The following actions may solve the problem.',
solution1: 'Update your os and browser',
@@ -159,8 +160,12 @@
otherOption1: 'Clear preferences and cache',
otherOption2: 'Start the simple client',
otherOption3: 'Start the repair tool',
- }, locale?._bootErrors || {});
- const reload = locale?.reload || 'Reload';
+ otherOption4: 'Start Misskey in safe mode',
+ reload: 'Reload',
+ }, messages);
+
+ const safeModeUrl = new URL(window.location.href);
+ safeModeUrl.searchParams.set('safemode', 'true');
let errorsElement = document.getElementById('errors');
@@ -173,7 +178,7 @@
</svg>
<h1>${messages.title}</h1>
<button class="button-big" onclick="location.reload(true);">
- <span class="button-label-big">${reload}</span>
+ <span class="button-label-big">${messages?.reload}</span>
</button>
<p><b>${messages.solution}</b></p>
<p>${messages.solution1}</p>
@@ -182,6 +187,12 @@
<p>${messages.solution4}</p>
<details style="color: #86b300;">
<summary>${messages.otherOption}</summary>
+ <a href="${safeModeUrl}">
+ <button class="button-small">
+ <span class="button-label-small">${messages.otherOption4}</span>
+ </button>
+ </a>
+ <br>
<a href="/flush">
<button class="button-small">
<span class="button-label-small">${messages.otherOption1}</span>
diff --git a/packages/backend/src/server/web/manifest.json b/packages/backend/src/server/web/manifest.json
index 41171d62a1..90d4530857 100644
--- a/packages/backend/src/server/web/manifest.json
+++ b/packages/backend/src/server/web/manifest.json
@@ -34,5 +34,11 @@
"text": "text",
"url": "url"
}
- }
+ },
+ "shortcuts": [
+ {
+ "name": "Safemode",
+ "url": "/?safemode=true"
+ }
+ ]
}
diff --git a/packages/backend/src/server/web/views/base-embed.pug b/packages/backend/src/server/web/views/base-embed.pug
index baa0909676..29de86b8b6 100644
--- a/packages/backend/src/server/web/views/base-embed.pug
+++ b/packages/backend/src/server/web/views/base-embed.pug
@@ -19,7 +19,6 @@ html(class='embed')
meta(name='format-detection' content='telephone=no,date=no,address=no,email=no,url=no')
link(rel='icon' href= icon || '/favicon.ico')
link(rel='apple-touch-icon' href= appleTouchIcon || '/apple-touch-icon.png')
- link(rel='modulepreload' href=`/embed_vite/${entry.file}`)
if !config.frontendEmbedManifestExists
script(type="module" src="/embed_vite/@vite/client")
@@ -40,7 +39,7 @@ html(class='embed')
script.
var VERSION = "#{version}";
- var CLIENT_ENTRY = "#{entry.file}";
+ var CLIENT_ENTRY = !{JSON.stringify(entry.file)};
script(type='application/json' id='misskey_meta' data-generated-at=now)
!= metaJson
diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug
index 3883b5e5ab..a76c75fe5c 100644
--- a/packages/backend/src/server/web/views/base.pug
+++ b/packages/backend/src/server/web/views/base.pug
@@ -37,7 +37,6 @@ html
link(rel='prefetch' href=serverErrorImageUrl)
link(rel='prefetch' href=infoImageUrl)
link(rel='prefetch' href=notFoundImageUrl)
- link(rel='modulepreload' href=`/vite/${entry.file}`)
if !config.frontendManifestExists
script(type="module" src="/vite/@vite/client")
@@ -69,7 +68,7 @@ html
script.
var VERSION = "#{version}";
- var CLIENT_ENTRY = "#{entry.file}";
+ var CLIENT_ENTRY = !{JSON.stringify(entry.file)};
script(type='application/json' id='misskey_meta' data-generated-at=now)
!= metaJson