diff options
| author | syuilo <4439005+syuilo@users.noreply.github.com> | 2024-03-01 21:17:01 +0900 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-03-01 21:17:01 +0900 |
| commit | 7e706ea6693781fb8973800bb3d0c91b5ab91cdf (patch) | |
| tree | 8abd91edd288284cf3cc47949e749f881cfaff8e /packages/backend/src/server/api | |
| parent | Merge pull request #13045 from misskey-dev/develop (diff) | |
| parent | New translations ja-jp.yml (Chinese Traditional) (#13480) (diff) | |
| download | sharkey-7e706ea6693781fb8973800bb3d0c91b5ab91cdf.tar.gz sharkey-7e706ea6693781fb8973800bb3d0c91b5ab91cdf.tar.bz2 sharkey-7e706ea6693781fb8973800bb3d0c91b5ab91cdf.zip | |
Merge pull request #13447 from misskey-dev/develop
Release: 2024.3.0
Diffstat (limited to 'packages/backend/src/server/api')
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; |