summaryrefslogtreecommitdiff
path: root/packages/backend/src/server/api
diff options
context:
space:
mode:
Diffstat (limited to 'packages/backend/src/server/api')
-rw-r--r--packages/backend/src/server/api/EndpointsModule.ts5
-rw-r--r--packages/backend/src/server/api/endpoints.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/add.ts5
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/update.ts27
-rw-r--r--packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts15
-rw-r--r--packages/backend/src/server/api/endpoints/antennas/notes.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/federation/show-instance.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/flash/update.ts12
-rw-r--r--packages/backend/src/server/api/endpoints/following/create.ts15
-rw-r--r--packages/backend/src/server/api/endpoints/gallery/posts/create.ts3
-rw-r--r--packages/backend/src/server/api/endpoints/gallery/posts/update.ts3
-rw-r--r--packages/backend/src/server/api/endpoints/hashtags/search.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/i/notifications-grouped.ts15
-rw-r--r--packages/backend/src/server/api/endpoints/i/notifications.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/i/update.ts6
-rw-r--r--packages/backend/src/server/api/endpoints/meta.ts417
-rw-r--r--packages/backend/src/server/api/endpoints/notes/create.ts24
-rw-r--r--packages/backend/src/server/api/endpoints/notifications/flush.ts33
-rw-r--r--packages/backend/src/server/api/endpoints/pinned-users.ts3
-rw-r--r--packages/backend/src/server/api/endpoints/sw/register.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/sw/unregister.ts7
-rw-r--r--packages/backend/src/server/api/endpoints/sw/update-registration.ts5
-rw-r--r--packages/backend/src/server/api/endpoints/users/reactions.ts2
-rw-r--r--packages/backend/src/server/api/stream/channels/home-timeline.ts10
24 files changed, 158 insertions, 465 deletions
diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts
index 8a003725cd..88d3999eb0 100644
--- a/packages/backend/src/server/api/EndpointsModule.ts
+++ b/packages/backend/src/server/api/EndpointsModule.ts
@@ -293,6 +293,7 @@ import * as ep___notes_translate from './endpoints/notes/translate.js';
import * as ep___notes_unrenote from './endpoints/notes/unrenote.js';
import * as ep___notes_userListTimeline from './endpoints/notes/user-list-timeline.js';
import * as ep___notifications_create from './endpoints/notifications/create.js';
+import * as ep___notifications_flush from './endpoints/notifications/flush.js';
import * as ep___notifications_markAllAsRead from './endpoints/notifications/mark-all-as-read.js';
import * as ep___notifications_testNotification from './endpoints/notifications/test-notification.js';
import * as ep___pagePush from './endpoints/page-push.js';
@@ -664,6 +665,7 @@ const $notes_translate: Provider = { provide: 'ep:notes/translate', useClass: ep
const $notes_unrenote: Provider = { provide: 'ep:notes/unrenote', useClass: ep___notes_unrenote.default };
const $notes_userListTimeline: Provider = { provide: 'ep:notes/user-list-timeline', useClass: ep___notes_userListTimeline.default };
const $notifications_create: Provider = { provide: 'ep:notifications/create', useClass: ep___notifications_create.default };
+const $notifications_flush: Provider = { provide: 'ep:notifications/flush', useClass: ep___notifications_flush.default };
const $notifications_markAllAsRead: Provider = { provide: 'ep:notifications/mark-all-as-read', useClass: ep___notifications_markAllAsRead.default };
const $notifications_testNotification: Provider = { provide: 'ep:notifications/test-notification', useClass: ep___notifications_testNotification.default };
const $pagePush: Provider = { provide: 'ep:page-push', useClass: ep___pagePush.default };
@@ -1039,6 +1041,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$notes_unrenote,
$notes_userListTimeline,
$notifications_create,
+ $notifications_flush,
$notifications_markAllAsRead,
$notifications_testNotification,
$pagePush,
@@ -1408,7 +1411,9 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$notes_unrenote,
$notes_userListTimeline,
$notifications_create,
+ $notifications_flush,
$notifications_markAllAsRead,
+ $notifications_testNotification,
$pagePush,
$pages_create,
$pages_delete,
diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts
index e1c8be727e..f7e64a7356 100644
--- a/packages/backend/src/server/api/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints.ts
@@ -293,6 +293,7 @@ import * as ep___notes_translate from './endpoints/notes/translate.js';
import * as ep___notes_unrenote from './endpoints/notes/unrenote.js';
import * as ep___notes_userListTimeline from './endpoints/notes/user-list-timeline.js';
import * as ep___notifications_create from './endpoints/notifications/create.js';
+import * as ep___notifications_flush from './endpoints/notifications/flush.js';
import * as ep___notifications_markAllAsRead from './endpoints/notifications/mark-all-as-read.js';
import * as ep___notifications_testNotification from './endpoints/notifications/test-notification.js';
import * as ep___pagePush from './endpoints/page-push.js';
@@ -662,6 +663,7 @@ const eps = [
['notes/unrenote', ep___notes_unrenote],
['notes/user-list-timeline', ep___notes_userListTimeline],
['notifications/create', ep___notifications_create],
+ ['notifications/flush', ep___notifications_flush],
['notifications/mark-all-as-read', ep___notifications_markAllAsRead],
['notifications/test-notification', ep___notifications_testNotification],
['page-push', ep___pagePush],
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts
index e32a19120d..796f273330 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts
@@ -31,7 +31,10 @@ export const meta = {
},
},
- ref: 'EmojiDetailed',
+ res: {
+ type: 'object',
+ ref: 'EmojiDetailed',
+ },
} as const;
export const paramDef = {
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
index a9ff4236d2..22609a16a3 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
@@ -57,7 +57,10 @@ export const paramDef = {
type: 'string',
} },
},
- required: ['id', 'name', 'aliases'],
+ anyOf: [
+ { required: ['id'] },
+ { required: ['name'] },
+ ],
} as const;
@Injectable()
@@ -70,27 +73,33 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
) {
super(meta, paramDef, async (ps, me) => {
let driveFile;
-
if (ps.fileId) {
driveFile = await this.driveFilesRepository.findOneBy({ id: ps.fileId });
if (driveFile == null) throw new ApiError(meta.errors.noSuchFile);
}
- const emoji = await this.customEmojiService.getEmojiById(ps.id);
- if (emoji != null) {
- if (ps.name !== emoji.name) {
+
+ let emojiId;
+ if (ps.id) {
+ emojiId = ps.id;
+ const emoji = await this.customEmojiService.getEmojiById(ps.id);
+ if (!emoji) throw new ApiError(meta.errors.noSuchEmoji);
+ if (ps.name && (ps.name !== emoji.name)) {
const isDuplicate = await this.customEmojiService.checkDuplicate(ps.name);
if (isDuplicate) throw new ApiError(meta.errors.sameNameEmojiExists);
}
} else {
- throw new ApiError(meta.errors.noSuchEmoji);
+ if (!ps.name) throw new Error('Invalid Params unexpectedly passed. This is a BUG. Please report it to the development team.');
+ const emoji = await this.customEmojiService.getEmojiByName(ps.name);
+ if (!emoji) throw new ApiError(meta.errors.noSuchEmoji);
+ emojiId = emoji.id;
}
- await this.customEmojiService.update(ps.id, {
+ await this.customEmojiService.update(emojiId, {
driveFile,
name: ps.name,
- category: ps.category ?? null,
+ category: ps.category,
aliases: ps.aliases,
- license: ps.license ?? null,
+ license: ps.license,
isSensitive: ps.isSensitive,
localOnly: ps.localOnly,
roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction,
diff --git a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts
index b989b99e47..0bcdc2a4b8 100644
--- a/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts
+++ b/packages/backend/src/server/api/endpoints/admin/federation/update-instance.ts
@@ -24,8 +24,9 @@ export const paramDef = {
properties: {
host: { type: 'string' },
isSuspended: { type: 'boolean' },
+ moderationNote: { type: 'string' },
},
- required: ['host', 'isSuspended'],
+ required: ['host'],
} as const;
@Injectable()
@@ -47,9 +48,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
await this.federatedInstanceService.update(instance.id, {
isSuspended: ps.isSuspended,
+ moderationNote: ps.moderationNote,
});
- if (instance.isSuspended !== ps.isSuspended) {
+ if (ps.isSuspended != null && instance.isSuspended !== ps.isSuspended) {
if (ps.isSuspended) {
this.moderationLogService.log(me, 'suspendRemoteInstance', {
id: instance.id,
@@ -62,6 +64,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
});
}
}
+
+ if (ps.moderationNote != null && instance.moderationNote !== ps.moderationNote) {
+ this.moderationLogService.log(me, 'updateRemoteInstanceNote', {
+ id: instance.id,
+ host: instance.host,
+ before: instance.moderationNote,
+ after: ps.moderationNote,
+ });
+ }
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts
index 39f3fab21e..f4dfe1ecc4 100644
--- a/packages/backend/src/server/api/endpoints/antennas/notes.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts
@@ -124,9 +124,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
notes.sort((a, b) => a.id > b.id ? -1 : 1);
}
- if (notes.length > 0) {
- this.noteReadService.read(me.id, notes);
- }
+ this.noteReadService.read(me.id, notes);
return await this.noteEntityService.packMany(notes, me);
});
diff --git a/packages/backend/src/server/api/endpoints/federation/show-instance.ts b/packages/backend/src/server/api/endpoints/federation/show-instance.ts
index e3c598d110..2972861a4b 100644
--- a/packages/backend/src/server/api/endpoints/federation/show-instance.ts
+++ b/packages/backend/src/server/api/endpoints/federation/show-instance.ts
@@ -43,7 +43,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
const instance = await this.instancesRepository
.findOneBy({ host: this.utilityService.toPuny(ps.host) });
- return instance ? await this.instanceEntityService.pack(instance) : null;
+ return instance ? await this.instanceEntityService.pack(instance, me) : null;
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/flash/update.ts b/packages/backend/src/server/api/endpoints/flash/update.ts
index 7d7633daa5..e378669f0a 100644
--- a/packages/backend/src/server/api/endpoints/flash/update.ts
+++ b/packages/backend/src/server/api/endpoints/flash/update.ts
@@ -51,7 +51,7 @@ export const paramDef = {
} },
visibility: { type: 'string', enum: ['public', 'private'] },
},
- required: ['flashId', 'title', 'summary', 'script', 'permissions'],
+ required: ['flashId'],
} as const;
@Injectable()
@@ -71,11 +71,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
await this.flashsRepository.update(flash.id, {
updatedAt: new Date(),
- title: ps.title,
- summary: ps.summary,
- script: ps.script,
- permissions: ps.permissions,
- visibility: ps.visibility,
+ ...Object.fromEntries(
+ Object.entries(ps).filter(
+ ([key, val]) => (key !== 'flashId') && Object.hasOwn(paramDef.properties, key)
+ )
+ ),
});
});
}
diff --git a/packages/backend/src/server/api/endpoints/following/create.ts b/packages/backend/src/server/api/endpoints/following/create.ts
index ceaf32ccb2..db320e7129 100644
--- a/packages/backend/src/server/api/endpoints/following/create.ts
+++ b/packages/backend/src/server/api/endpoints/following/create.ts
@@ -71,7 +71,7 @@ export const paramDef = {
type: 'object',
properties: {
userId: { type: 'string', format: 'misskey:id' },
- withReplies: { type: 'boolean' }
+ withReplies: { type: 'boolean' },
},
required: ['userId'],
} as const;
@@ -100,22 +100,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw err;
});
- // Check if already following
- const exist = await this.followingsRepository.exists({
- where: {
- followerId: follower.id,
- followeeId: followee.id,
- },
- });
-
- if (exist) {
- throw new ApiError(meta.errors.alreadyFollowing);
- }
-
try {
await this.userFollowingService.follow(follower, followee, { withReplies: ps.withReplies });
} catch (e) {
if (e instanceof IdentifiableError) {
+ if (e.id === 'ec3f65c0-a9d1-47d9-8791-b2e7b9dcdced') throw new ApiError(meta.errors.alreadyFollowing);
if (e.id === '710e8fb0-b8c3-4922-be49-d5d93d8e6a6e') throw new ApiError(meta.errors.blocking);
if (e.id === '3338392a-f764-498d-8855-db939dcf8c48') throw new ApiError(meta.errors.blocked);
}
diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts
index 784ae5088f..b07cdf1ed9 100644
--- a/packages/backend/src/server/api/endpoints/gallery/posts/create.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/posts/create.ts
@@ -12,6 +12,7 @@ import type { MiDriveFile } from '@/models/DriveFile.js';
import { IdService } from '@/core/IdService.js';
import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js';
import { DI } from '@/di-symbols.js';
+import { isNotNull } from '@/misc/is-not-null.js';
export const meta = {
tags: ['gallery'],
@@ -69,7 +70,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
id: fileId,
userId: me.id,
}),
- ))).filter((file): file is MiDriveFile => file != null);
+ ))).filter(isNotNull);
if (files.length === 0) {
throw new Error();
diff --git a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts
index 8872b261dd..8bd83ff5ba 100644
--- a/packages/backend/src/server/api/endpoints/gallery/posts/update.ts
+++ b/packages/backend/src/server/api/endpoints/gallery/posts/update.ts
@@ -10,6 +10,7 @@ import type { DriveFilesRepository, GalleryPostsRepository } from '@/models/_.js
import type { MiDriveFile } from '@/models/DriveFile.js';
import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js';
import { DI } from '@/di-symbols.js';
+import { isNotNull } from '@/misc/is-not-null.js';
export const meta = {
tags: ['gallery'],
@@ -67,7 +68,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
id: fileId,
userId: me.id,
}),
- ))).filter((file): file is MiDriveFile => file != null);
+ ))).filter(isNotNull);
if (files.length === 0) {
throw new Error();
diff --git a/packages/backend/src/server/api/endpoints/hashtags/search.ts b/packages/backend/src/server/api/endpoints/hashtags/search.ts
index 12d47fa512..d4eb851054 100644
--- a/packages/backend/src/server/api/endpoints/hashtags/search.ts
+++ b/packages/backend/src/server/api/endpoints/hashtags/search.ts
@@ -43,7 +43,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
super(meta, paramDef, async (ps, me) => {
const hashtags = await this.hashtagsRepository.createQueryBuilder('tag')
.where('tag.name like :q', { q: sqlLikeEscape(ps.query.toLowerCase()) + '%' })
- .orderBy('tag.count', 'DESC')
+ .orderBy('tag.mentionedLocalUsersCount', 'DESC')
.groupBy('tag.id')
.limit(ps.limit)
.offset(ps.offset)
diff --git a/packages/backend/src/server/api/endpoints/i/notifications-grouped.ts b/packages/backend/src/server/api/endpoints/i/notifications-grouped.ts
index 703808d279..dc6ffd3e02 100644
--- a/packages/backend/src/server/api/endpoints/i/notifications-grouped.ts
+++ b/packages/backend/src/server/api/endpoints/i/notifications-grouped.ts
@@ -3,11 +3,11 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Brackets, In } from 'typeorm';
+import { In } from 'typeorm';
import * as Redis from 'ioredis';
import { Inject, Injectable } from '@nestjs/common';
import type { NotesRepository } from '@/models/_.js';
-import { obsoleteNotificationTypes, notificationTypes, FilterUnionByProperty } from '@/types.js';
+import { obsoleteNotificationTypes, groupedNotificationTypes, FilterUnionByProperty } from '@/types.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { NoteReadService } from '@/core/NoteReadService.js';
import { NotificationEntityService } from '@/core/entities/NotificationEntityService.js';
@@ -48,10 +48,10 @@ export const paramDef = {
markAsRead: { type: 'boolean', default: true },
// 後方互換のため、廃止された通知タイプも受け付ける
includeTypes: { type: 'array', items: {
- type: 'string', enum: [...notificationTypes, ...obsoleteNotificationTypes],
+ type: 'string', enum: [...groupedNotificationTypes, ...obsoleteNotificationTypes],
} },
excludeTypes: { type: 'array', items: {
- type: 'string', enum: [...notificationTypes, ...obsoleteNotificationTypes],
+ type: 'string', enum: [...groupedNotificationTypes, ...obsoleteNotificationTypes],
} },
},
required: [],
@@ -79,12 +79,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
return [];
}
// excludeTypes に全指定されている場合はクエリしない
- if (notificationTypes.every(type => ps.excludeTypes?.includes(type))) {
+ if (groupedNotificationTypes.every(type => ps.excludeTypes?.includes(type))) {
return [];
}
- const includeTypes = ps.includeTypes && ps.includeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof notificationTypes[number][];
- const excludeTypes = ps.excludeTypes && ps.excludeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof notificationTypes[number][];
+ const includeTypes = ps.includeTypes && ps.includeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof groupedNotificationTypes[number][];
+ const excludeTypes = ps.excludeTypes && ps.excludeTypes.filter(type => !(obsoleteNotificationTypes).includes(type as any)) as typeof groupedNotificationTypes[number][];
const limit = (ps.limit + EXTRA_LIMIT) + (ps.untilId ? 1 : 0) + (ps.sinceId ? 1 : 0); // untilIdに指定したものも含まれるため+1
const notificationsRes = await this.redisClient.xrevrange(
@@ -162,7 +162,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
}
groupedNotifications = groupedNotifications.slice(0, ps.limit);
-
const noteIds = groupedNotifications
.filter((notification): notification is FilterUnionByProperty<MiNotification, 'type', 'mention' | 'reply' | 'quote'> => ['mention', 'reply', 'quote'].includes(notification.type))
.map(notification => notification.noteId!);
diff --git a/packages/backend/src/server/api/endpoints/i/notifications.ts b/packages/backend/src/server/api/endpoints/i/notifications.ts
index 52b6749e3f..320d9fdb00 100644
--- a/packages/backend/src/server/api/endpoints/i/notifications.ts
+++ b/packages/backend/src/server/api/endpoints/i/notifications.ts
@@ -3,7 +3,7 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
-import { Brackets, In } from 'typeorm';
+import { In } from 'typeorm';
import * as Redis from 'ioredis';
import { Inject, Injectable } from '@nestjs/common';
import type { NotesRepository } from '@/models/_.js';
diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts
index bf6c53d8eb..84a1931a3d 100644
--- a/packages/backend/src/server/api/endpoints/i/update.ts
+++ b/packages/backend/src/server/api/endpoints/i/update.ts
@@ -456,9 +456,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
this.hashtagService.updateUsertags(user, tags);
//#endregion
- if (Object.keys(updates).length > 0) await this.usersRepository.update(user.id, updates);
- if (Object.keys(updates).includes('alsoKnownAs')) {
- this.cacheService.uriPersonCache.set(this.userEntityService.genLocalUserUri(user.id), { ...user, ...updates });
+ if (Object.keys(updates).length > 0) {
+ await this.usersRepository.update(user.id, updates);
+ this.globalEventService.publishInternalEvent('localUserUpdated', { id: user.id });
}
await this.userProfilesRepository.update(user.id, {
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/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts
index e6e4fcc745..bfb9214439 100644
--- a/packages/backend/src/server/api/endpoints/notes/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/create.ts
@@ -19,6 +19,7 @@ import { DI } from '@/di-symbols.js';
import { isPureRenote } from '@/misc/is-pure-renote.js';
import { MetaService } from '@/core/MetaService.js';
import { UtilityService } from '@/core/UtilityService.js';
+import { IdentifiableError } from '@/misc/identifiable-error.js';
import { ApiError } from '../../error.js';
export const meta = {
@@ -84,6 +85,12 @@ export const meta = {
id: '3ac74a84-8fd5-4bb0-870f-01804f82ce15',
},
+ cannotReplyToSpecifiedVisibilityNoteWithExtendedVisibility: {
+ message: 'You cannot reply to a specified visibility note with extended visibility.',
+ code: 'CANNOT_REPLY_TO_SPECIFIED_VISIBILITY_NOTE_WITH_EXTENDED_VISIBILITY',
+ id: 'ed940410-535c-4d5e-bfa3-af798671e93c',
+ },
+
cannotCreateAlreadyExpiredPoll: {
message: 'Poll is already expired.',
code: 'CANNOT_CREATE_ALREADY_EXPIRED_POLL',
@@ -119,6 +126,12 @@ export const meta = {
code: 'CONTAINS_PROHIBITED_WORDS',
id: 'aa6e01d3-a85c-669d-758a-76aab43af334',
},
+
+ containsTooManyMentions: {
+ message: 'Cannot post because it exceeds the allowed number of mentions.',
+ code: 'CONTAINS_TOO_MANY_MENTIONS',
+ id: '4de0363a-3046-481b-9b0f-feff3e211025',
+ },
},
} as const;
@@ -312,6 +325,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.cannotReplyToPureRenote);
} else if (!await this.noteEntityService.isVisibleForMe(reply, me.id)) {
throw new ApiError(meta.errors.cannotReplyToInvisibleNote);
+ } else if (reply.visibility === 'specified' && ps.visibility !== 'specified') {
+ throw new ApiError(meta.errors.cannotReplyToSpecifiedVisibilityNoteWithExtendedVisibility);
}
// Check blocking
@@ -376,10 +391,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
};
} catch (e) {
// TODO: 他のErrorもここでキャッチしてエラーメッセージを当てるようにしたい
- if (e instanceof NoteCreateService.ContainsProhibitedWordsError) {
- throw new ApiError(meta.errors.containsProhibitedWords);
+ if (e instanceof IdentifiableError) {
+ if (e.id === '689ee33f-f97c-479a-ac49-1b9f8140af99') {
+ throw new ApiError(meta.errors.containsProhibitedWords);
+ } else if (e.id === '9f466dab-c856-48cd-9e65-ff90ff750580') {
+ throw new ApiError(meta.errors.containsTooManyMentions);
+ }
}
-
throw e;
}
});
diff --git a/packages/backend/src/server/api/endpoints/notifications/flush.ts b/packages/backend/src/server/api/endpoints/notifications/flush.ts
new file mode 100644
index 0000000000..47c0642fd1
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/notifications/flush.ts
@@ -0,0 +1,33 @@
+/*
+ * 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 { NotificationService } from '@/core/NotificationService.js';
+
+export const meta = {
+ tags: ['notifications', 'account'],
+
+ requireCredential: true,
+
+ kind: 'write:notifications',
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {},
+ required: [],
+} as const;
+
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
+ constructor(
+ private notificationService: NotificationService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ this.notificationService.flushAllNotifications(me.id);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/pinned-users.ts b/packages/backend/src/server/api/endpoints/pinned-users.ts
index 1f4509764f..784766bcb5 100644
--- a/packages/backend/src/server/api/endpoints/pinned-users.ts
+++ b/packages/backend/src/server/api/endpoints/pinned-users.ts
@@ -12,6 +12,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
import { MetaService } from '@/core/MetaService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { DI } from '@/di-symbols.js';
+import { isNotNull } from '@/misc/is-not-null.js';
export const meta = {
tags: ['users'],
@@ -52,7 +53,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
host: acct.host ?? IsNull(),
})));
- return await this.userEntityService.packMany(users.filter(x => x !== null) as MiUser[], me, { schema: 'UserDetailed' });
+ return await this.userEntityService.packMany(users.filter(isNotNull), me, { schema: 'UserDetailed' });
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/sw/register.ts b/packages/backend/src/server/api/endpoints/sw/register.ts
index 06c04b3f9a..a9a33149f9 100644
--- a/packages/backend/src/server/api/endpoints/sw/register.ts
+++ b/packages/backend/src/server/api/endpoints/sw/register.ts
@@ -9,6 +9,7 @@ import type { SwSubscriptionsRepository } from '@/models/_.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { MetaService } from '@/core/MetaService.js';
import { DI } from '@/di-symbols.js';
+import { PushNotificationService } from '@/core/PushNotificationService.js';
export const meta = {
tags: ['account'],
@@ -66,6 +67,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private idService: IdService,
private metaService: MetaService,
+ private pushNotificationService: PushNotificationService,
) {
super(meta, paramDef, async (ps, me) => {
// if already subscribed
@@ -97,6 +99,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
sendReadMessage: ps.sendReadMessage,
});
+ this.pushNotificationService.refreshCache(me.id);
+
return {
state: 'subscribed' as const,
key: instance.swPublicKey,
diff --git a/packages/backend/src/server/api/endpoints/sw/unregister.ts b/packages/backend/src/server/api/endpoints/sw/unregister.ts
index 2bc91c7278..2edf7fab1b 100644
--- a/packages/backend/src/server/api/endpoints/sw/unregister.ts
+++ b/packages/backend/src/server/api/endpoints/sw/unregister.ts
@@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
import type { SwSubscriptionsRepository } from '@/models/_.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
+import { PushNotificationService } from '@/core/PushNotificationService.js';
export const meta = {
tags: ['account'],
@@ -29,12 +30,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
constructor(
@Inject(DI.swSubscriptionsRepository)
private swSubscriptionsRepository: SwSubscriptionsRepository,
+
+ private pushNotificationService: PushNotificationService,
) {
super(meta, paramDef, async (ps, me) => {
await this.swSubscriptionsRepository.delete({
...(me ? { userId: me.id } : {}),
endpoint: ps.endpoint,
});
+
+ if (me) {
+ this.pushNotificationService.refreshCache(me.id);
+ }
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/sw/update-registration.ts b/packages/backend/src/server/api/endpoints/sw/update-registration.ts
index b56b07fd00..839a07c770 100644
--- a/packages/backend/src/server/api/endpoints/sw/update-registration.ts
+++ b/packages/backend/src/server/api/endpoints/sw/update-registration.ts
@@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
import type { SwSubscriptionsRepository } from '@/models/_.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
+import { PushNotificationService } from '@/core/PushNotificationService.js';
import { ApiError } from '../../error.js';
export const meta = {
@@ -58,6 +59,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
constructor(
@Inject(DI.swSubscriptionsRepository)
private swSubscriptionsRepository: SwSubscriptionsRepository,
+
+ private pushNotificationService: PushNotificationService,
) {
super(meta, paramDef, async (ps, me) => {
const swSubscription = await this.swSubscriptionsRepository.findOneBy({
@@ -77,6 +80,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
sendReadMessage: swSubscription.sendReadMessage,
});
+ this.pushNotificationService.refreshCache(me.id);
+
return {
userId: swSubscription.userId,
endpoint: swSubscription.endpoint,
diff --git a/packages/backend/src/server/api/endpoints/users/reactions.ts b/packages/backend/src/server/api/endpoints/users/reactions.ts
index e20d896248..aca883a052 100644
--- a/packages/backend/src/server/api/endpoints/users/reactions.ts
+++ b/packages/backend/src/server/api/endpoints/users/reactions.ts
@@ -98,7 +98,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
.limit(ps.limit)
.getMany();
- return await Promise.all(reactions.map(reaction => this.noteReactionEntityService.pack(reaction, me, { withNote: true })));
+ return await this.noteReactionEntityService.packMany(reactions, me, { withNote: true });
});
}
}
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 ce9d7f5647..f45bf8622e 100644
--- a/packages/backend/src/server/api/stream/channels/home-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts
@@ -71,7 +71,15 @@ class HomeTimelineChannel extends Channel {
}
}
- if (note.renote && note.text == null && (note.fileIds == null || note.fileIds.length === 0) && !this.withRenotes) return;
+ // 純粋なリノート(引用リノートでないリノート)の場合
+ if (note.renote && note.text == null && (note.fileIds == null || note.fileIds.length === 0) && note.poll == null) {
+ if (!this.withRenotes) return;
+ if (note.renote.reply) {
+ const reply = note.renote.reply;
+ // 自分のフォローしていないユーザーの visibility: followers な投稿への返信のリノートは弾く
+ if (reply.visibility === 'followers' && !Object.hasOwn(this.following, reply.userId)) return;
+ }
+ }
// 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
if (isUserRelated(note, this.userIdsWhoMeMuting)) return;