summaryrefslogtreecommitdiff
path: root/packages/backend/src/server/api
diff options
context:
space:
mode:
authorsyuilo <Syuilotan@yahoo.co.jp>2023-03-22 09:55:38 +0900
committerGitHub <noreply@github.com>2023-03-22 09:55:38 +0900
commit1e67e9c6616c6e87ae85ece71e5401006df2dd34 (patch)
treea0d6df03a3d0ac2edf1fda7ed4bfb789b5a29720 /packages/backend/src/server/api
parentMerge pull request #10218 from misskey-dev/develop (diff)
parentfix drive-cleaner (diff)
downloadmisskey-1e67e9c6616c6e87ae85ece71e5401006df2dd34.tar.gz
misskey-1e67e9c6616c6e87ae85ece71e5401006df2dd34.tar.bz2
misskey-1e67e9c6616c6e87ae85ece71e5401006df2dd34.zip
Merge pull request #10342 from misskey-dev/develop
Release: 13.10.0
Diffstat (limited to 'packages/backend/src/server/api')
-rw-r--r--packages/backend/src/server/api/ApiCallService.ts6
-rw-r--r--packages/backend/src/server/api/ApiServerService.ts2
-rw-r--r--packages/backend/src/server/api/EndpointsModule.ts32
-rw-r--r--packages/backend/src/server/api/SignupApiService.ts25
-rw-r--r--packages/backend/src/server/api/StreamingApiServerService.ts8
-rw-r--r--packages/backend/src/server/api/endpoint-base.ts2
-rw-r--r--packages/backend/src/server/api/endpoints.ts18
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/add.ts1
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/copy.ts3
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/delete.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/admin/emoji/update.ts15
-rw-r--r--packages/backend/src/server/api/endpoints/admin/meta.ts13
-rw-r--r--packages/backend/src/server/api/endpoints/admin/queue/promote.ts52
-rw-r--r--packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/admin/roles/create.ts3
-rw-r--r--packages/backend/src/server/api/endpoints/admin/roles/update.ts3
-rw-r--r--packages/backend/src/server/api/endpoints/admin/update-meta.ts21
-rw-r--r--packages/backend/src/server/api/endpoints/antennas/create.ts9
-rw-r--r--packages/backend/src/server/api/endpoints/antennas/notes.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/ap/show.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/channels/update.ts8
-rw-r--r--packages/backend/src/server/api/endpoints/clips/add-note.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/clips/create.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/clips/favorite.ts76
-rw-r--r--packages/backend/src/server/api/endpoints/clips/list.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/clips/my-favorites.ts52
-rw-r--r--packages/backend/src/server/api/endpoints/clips/show.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/clips/unfavorite.ts65
-rw-r--r--packages/backend/src/server/api/endpoints/clips/update.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/drive/files.ts10
-rw-r--r--packages/backend/src/server/api/endpoints/emoji.ts56
-rw-r--r--packages/backend/src/server/api/endpoints/emojis.ts19
-rw-r--r--packages/backend/src/server/api/endpoints/federation/instances.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/i.ts16
-rw-r--r--packages/backend/src/server/api/endpoints/meta.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/notes/clips.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/notes/create.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/notes/featured.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/notes/global-timeline.ts1
-rw-r--r--packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts5
-rw-r--r--packages/backend/src/server/api/endpoints/notes/local-timeline.ts5
-rw-r--r--packages/backend/src/server/api/endpoints/notes/polls/vote.ts4
-rw-r--r--packages/backend/src/server/api/endpoints/notes/search.ts13
-rw-r--r--packages/backend/src/server/api/endpoints/notes/timeline.ts5
-rw-r--r--packages/backend/src/server/api/endpoints/notifications/create.ts6
-rw-r--r--packages/backend/src/server/api/endpoints/renote-mute/create.ts99
-rw-r--r--packages/backend/src/server/api/endpoints/renote-mute/delete.ts87
-rw-r--r--packages/backend/src/server/api/endpoints/renote-mute/list.ts57
-rw-r--r--packages/backend/src/server/api/endpoints/users/clips.ts2
-rw-r--r--packages/backend/src/server/api/endpoints/users/relation.ts8
-rw-r--r--packages/backend/src/server/api/endpoints/users/show.ts1
-rw-r--r--packages/backend/src/server/api/error.ts2
-rw-r--r--packages/backend/src/server/api/openapi/OpenApiServerService.ts31
-rw-r--r--packages/backend/src/server/api/openapi/gen-spec.ts193
-rw-r--r--packages/backend/src/server/api/openapi/schemas.ts4
-rw-r--r--packages/backend/src/server/api/stream/channel.ts4
-rw-r--r--packages/backend/src/server/api/stream/channels/antenna.ts2
-rw-r--r--packages/backend/src/server/api/stream/channels/channel.ts4
-rw-r--r--packages/backend/src/server/api/stream/channels/global-timeline.ts4
-rw-r--r--packages/backend/src/server/api/stream/channels/hashtag.ts4
-rw-r--r--packages/backend/src/server/api/stream/channels/home-timeline.ts4
-rw-r--r--packages/backend/src/server/api/stream/channels/hybrid-timeline.ts4
-rw-r--r--packages/backend/src/server/api/stream/channels/local-timeline.ts4
-rw-r--r--packages/backend/src/server/api/stream/channels/user-list.ts4
-rw-r--r--packages/backend/src/server/api/stream/index.ts20
-rw-r--r--packages/backend/src/server/api/stream/types.ts2
71 files changed, 1043 insertions, 101 deletions
diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts
index f84a3aa59b..bf5cb20918 100644
--- a/packages/backend/src/server/api/ApiCallService.ts
+++ b/packages/backend/src/server/api/ApiCallService.ts
@@ -75,7 +75,7 @@ export class ApiCallService implements OnApplicationShutdown {
}
this.send(reply, res);
}).catch((err: ApiError) => {
- this.send(reply, err.httpStatusCode ? err.httpStatusCode : err.kind === 'client' ? 400 : 500, err);
+ this.send(reply, err.httpStatusCode ? err.httpStatusCode : err.kind === 'client' ? 400 : err.kind === 'permission' ? 403 : 500, err);
});
if (user) {
@@ -129,7 +129,7 @@ export class ApiCallService implements OnApplicationShutdown {
}, request).then((res) => {
this.send(reply, res);
}).catch((err: ApiError) => {
- this.send(reply, err.httpStatusCode ? err.httpStatusCode : err.kind === 'client' ? 400 : 500, err);
+ this.send(reply, err.httpStatusCode ? err.httpStatusCode : err.kind === 'client' ? 400 : err.kind === 'permission' ? 403 : 500, err);
});
if (user) {
@@ -321,7 +321,7 @@ export class ApiCallService implements OnApplicationShutdown {
// API invoking
return await ep.exec(data, user, token, file, request.ip, request.headers).catch((err: Error) => {
- if (err instanceof ApiError) {
+ if (err instanceof ApiError || err instanceof AuthenticationError) {
throw err;
} else {
const errId = uuid();
diff --git a/packages/backend/src/server/api/ApiServerService.ts b/packages/backend/src/server/api/ApiServerService.ts
index 115d60986c..b806ad5ca3 100644
--- a/packages/backend/src/server/api/ApiServerService.ts
+++ b/packages/backend/src/server/api/ApiServerService.ts
@@ -167,7 +167,7 @@ export class ApiServerService {
// Make sure any unknown path under /api returns HTTP 404 Not Found,
// because otherwise ClientServerService will return the base client HTML
// page with HTTP 200.
- fastify.get('*', (request, reply) => {
+ fastify.get('/*', (request, reply) => {
reply.code(404);
// Mock ApiCallService.send's error handling
reply.send({
diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts
index d3e2219bd5..835e884193 100644
--- a/packages/backend/src/server/api/EndpointsModule.ts
+++ b/packages/backend/src/server/api/EndpointsModule.ts
@@ -42,6 +42,7 @@ import * as ep___admin_promo_create from './endpoints/admin/promo/create.js';
import * as ep___admin_queue_clear from './endpoints/admin/queue/clear.js';
import * as ep___admin_queue_deliverDelayed from './endpoints/admin/queue/deliver-delayed.js';
import * as ep___admin_queue_inboxDelayed from './endpoints/admin/queue/inbox-delayed.js';
+import * as ep___admin_queue_promote from './endpoints/admin/queue/promote.js';
import * as ep___admin_queue_stats from './endpoints/admin/queue/stats.js';
import * as ep___admin_relays_add from './endpoints/admin/relays/add.js';
import * as ep___admin_relays_list from './endpoints/admin/relays/list.js';
@@ -114,6 +115,9 @@ import * as ep___clips_list from './endpoints/clips/list.js';
import * as ep___clips_notes from './endpoints/clips/notes.js';
import * as ep___clips_show from './endpoints/clips/show.js';
import * as ep___clips_update from './endpoints/clips/update.js';
+import * as ep___clips_favorite from './endpoints/clips/favorite.js';
+import * as ep___clips_unfavorite from './endpoints/clips/unfavorite.js';
+import * as ep___clips_myFavorites from './endpoints/clips/my-favorites.js';
import * as ep___drive from './endpoints/drive.js';
import * as ep___drive_files from './endpoints/drive/files.js';
import * as ep___drive_files_attachedNotes from './endpoints/drive/files/attached-notes.js';
@@ -220,10 +224,14 @@ import * as ep___i_webhooks_update from './endpoints/i/webhooks/update.js';
import * as ep___i_webhooks_delete from './endpoints/i/webhooks/delete.js';
import * as ep___meta from './endpoints/meta.js';
import * as ep___emojis from './endpoints/emojis.js';
+import * as ep___emoji from './endpoints/emoji.js';
import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js';
import * as ep___mute_create from './endpoints/mute/create.js';
import * as ep___mute_delete from './endpoints/mute/delete.js';
import * as ep___mute_list from './endpoints/mute/list.js';
+import * as ep___renoteMute_create from './endpoints/renote-mute/create.js';
+import * as ep___renoteMute_delete from './endpoints/renote-mute/delete.js';
+import * as ep___renoteMute_list from './endpoints/renote-mute/list.js';
import * as ep___my_apps from './endpoints/my/apps.js';
import * as ep___notes from './endpoints/notes.js';
import * as ep___notes_children from './endpoints/notes/children.js';
@@ -363,6 +371,7 @@ const $admin_promo_create: Provider = { provide: 'ep:admin/promo/create', useCla
const $admin_queue_clear: Provider = { provide: 'ep:admin/queue/clear', useClass: ep___admin_queue_clear.default };
const $admin_queue_deliverDelayed: Provider = { provide: 'ep:admin/queue/deliver-delayed', useClass: ep___admin_queue_deliverDelayed.default };
const $admin_queue_inboxDelayed: Provider = { provide: 'ep:admin/queue/inbox-delayed', useClass: ep___admin_queue_inboxDelayed.default };
+const $admin_queue_promote: Provider = { provide: 'ep:admin/queue/promote', useClass: ep___admin_queue_promote.default };
const $admin_queue_stats: Provider = { provide: 'ep:admin/queue/stats', useClass: ep___admin_queue_stats.default };
const $admin_relays_add: Provider = { provide: 'ep:admin/relays/add', useClass: ep___admin_relays_add.default };
const $admin_relays_list: Provider = { provide: 'ep:admin/relays/list', useClass: ep___admin_relays_list.default };
@@ -435,6 +444,9 @@ const $clips_list: Provider = { provide: 'ep:clips/list', useClass: ep___clips_l
const $clips_notes: Provider = { provide: 'ep:clips/notes', useClass: ep___clips_notes.default };
const $clips_show: Provider = { provide: 'ep:clips/show', useClass: ep___clips_show.default };
const $clips_update: Provider = { provide: 'ep:clips/update', useClass: ep___clips_update.default };
+const $clips_favorite: Provider = { provide: 'ep:clips/favorite', useClass: ep___clips_favorite.default };
+const $clips_unfavorite: Provider = { provide: 'ep:clips/unfavorite', useClass: ep___clips_unfavorite.default };
+const $clips_myFavorites: Provider = { provide: 'ep:clips/my-favorites', useClass: ep___clips_myFavorites.default };
const $drive: Provider = { provide: 'ep:drive', useClass: ep___drive.default };
const $drive_files: Provider = { provide: 'ep:drive/files', useClass: ep___drive_files.default };
const $drive_files_attachedNotes: Provider = { provide: 'ep:drive/files/attached-notes', useClass: ep___drive_files_attachedNotes.default };
@@ -541,10 +553,14 @@ const $i_webhooks_update: Provider = { provide: 'ep:i/webhooks/update', useClass
const $i_webhooks_delete: Provider = { provide: 'ep:i/webhooks/delete', useClass: ep___i_webhooks_delete.default };
const $meta: Provider = { provide: 'ep:meta', useClass: ep___meta.default };
const $emojis: Provider = { provide: 'ep:emojis', useClass: ep___emojis.default };
+const $emoji: Provider = { provide: 'ep:emoji', useClass: ep___emoji.default };
const $miauth_genToken: Provider = { provide: 'ep:miauth/gen-token', useClass: ep___miauth_genToken.default };
const $mute_create: Provider = { provide: 'ep:mute/create', useClass: ep___mute_create.default };
const $mute_delete: Provider = { provide: 'ep:mute/delete', useClass: ep___mute_delete.default };
const $mute_list: Provider = { provide: 'ep:mute/list', useClass: ep___mute_list.default };
+const $renoteMute_create: Provider = { provide: 'ep:renote-mute/create', useClass: ep___renoteMute_create.default };
+const $renoteMute_delete: Provider = { provide: 'ep:renote-mute/delete', useClass: ep___renoteMute_delete.default };
+const $renoteMute_list: Provider = { provide: 'ep:renote-mute/list', useClass: ep___renoteMute_list.default };
const $my_apps: Provider = { provide: 'ep:my/apps', useClass: ep___my_apps.default };
const $notes: Provider = { provide: 'ep:notes', useClass: ep___notes.default };
const $notes_children: Provider = { provide: 'ep:notes/children', useClass: ep___notes_children.default };
@@ -688,6 +704,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$admin_queue_clear,
$admin_queue_deliverDelayed,
$admin_queue_inboxDelayed,
+ $admin_queue_promote,
$admin_queue_stats,
$admin_relays_add,
$admin_relays_list,
@@ -760,6 +777,9 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$clips_notes,
$clips_show,
$clips_update,
+ $clips_favorite,
+ $clips_unfavorite,
+ $clips_myFavorites,
$drive,
$drive_files,
$drive_files_attachedNotes,
@@ -866,10 +886,14 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$i_webhooks_delete,
$meta,
$emojis,
+ $emoji,
$miauth_genToken,
$mute_create,
$mute_delete,
$mute_list,
+ $renoteMute_create,
+ $renoteMute_delete,
+ $renoteMute_list,
$my_apps,
$notes,
$notes_children,
@@ -1007,6 +1031,7 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$admin_queue_clear,
$admin_queue_deliverDelayed,
$admin_queue_inboxDelayed,
+ $admin_queue_promote,
$admin_queue_stats,
$admin_relays_add,
$admin_relays_list,
@@ -1079,6 +1104,9 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$clips_notes,
$clips_show,
$clips_update,
+ $clips_favorite,
+ $clips_unfavorite,
+ $clips_myFavorites,
$drive,
$drive_files,
$drive_files_attachedNotes,
@@ -1185,10 +1213,14 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$i_webhooks_delete,
$meta,
$emojis,
+ $emoji,
$miauth_genToken,
$mute_create,
$mute_delete,
$mute_list,
+ $renoteMute_create,
+ $renoteMute_delete,
+ $renoteMute_list,
$my_apps,
$notes,
$notes_children,
diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts
index 41e8365d08..fbabf47aff 100644
--- a/packages/backend/src/server/api/SignupApiService.ts
+++ b/packages/backend/src/server/api/SignupApiService.ts
@@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
import rndstr from 'rndstr';
import bcrypt from 'bcryptjs';
import { DI } from '@/di-symbols.js';
-import type { RegistrationTicketsRepository, UserPendingsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js';
+import type { RegistrationTicketsRepository, UsedUsernamesRepository, UserPendingsRepository, UserProfilesRepository, UsersRepository } from '@/models/index.js';
import type { Config } from '@/config.js';
import { MetaService } from '@/core/MetaService.js';
import { CaptchaService } from '@/core/CaptchaService.js';
@@ -15,6 +15,7 @@ import { FastifyReplyError } from '@/misc/fastify-reply-error.js';
import { bindThis } from '@/decorators.js';
import { SigninService } from './SigninService.js';
import type { FastifyRequest, FastifyReply } from 'fastify';
+import { IsNull } from 'typeorm';
@Injectable()
export class SignupApiService {
@@ -31,6 +32,9 @@ export class SignupApiService {
@Inject(DI.userPendingsRepository)
private userPendingsRepository: UserPendingsRepository,
+ @Inject(DI.usedUsernamesRepository)
+ private usedUsernamesRepository: UsedUsernamesRepository,
+
@Inject(DI.registrationTicketsRepository)
private registrationTicketsRepository: RegistrationTicketsRepository,
@@ -124,12 +128,21 @@ export class SignupApiService {
}
if (instance.emailRequiredForSignup) {
+ if (await this.usersRepository.findOneBy({ usernameLower: username.toLowerCase(), host: IsNull() })) {
+ throw new FastifyReplyError(400, 'DUPLICATED_USERNAME');
+ }
+
+ // Check deleted username duplication
+ if (await this.usedUsernamesRepository.findOneBy({ username: username.toLowerCase() })) {
+ throw new FastifyReplyError(400, 'USED_USERNAME');
+ }
+
const code = rndstr('a-z0-9', 16);
-
+
// Generate hash of password
const salt = await bcrypt.genSalt(8);
const hash = await bcrypt.hash(password, salt);
-
+
await this.userPendingsRepository.insert({
id: this.idService.genId(),
createdAt: new Date(),
@@ -138,13 +151,13 @@ export class SignupApiService {
username: username,
password: hash,
});
-
+
const link = `${this.config.url}/signup-complete/${code}`;
-
+
this.emailService.sendEmail(emailAddress!, 'Signup',
`To complete signup, please click this link:<br><a href="${link}">${link}</a>`,
`To complete signup, please click this link: ${link}`);
-
+
reply.code(204);
return;
} else {
diff --git a/packages/backend/src/server/api/StreamingApiServerService.ts b/packages/backend/src/server/api/StreamingApiServerService.ts
index 487eef2d50..13526f277d 100644
--- a/packages/backend/src/server/api/StreamingApiServerService.ts
+++ b/packages/backend/src/server/api/StreamingApiServerService.ts
@@ -3,17 +3,17 @@ import { Inject, Injectable } from '@nestjs/common';
import Redis from 'ioredis';
import * as websocket from 'websocket';
import { DI } from '@/di-symbols.js';
-import type { UsersRepository, BlockingsRepository, ChannelFollowingsRepository, FollowingsRepository, MutingsRepository, UserProfilesRepository } from '@/models/index.js';
+import type { UsersRepository, BlockingsRepository, ChannelFollowingsRepository, FollowingsRepository, MutingsRepository, UserProfilesRepository, RenoteMutingsRepository } from '@/models/index.js';
import type { Config } from '@/config.js';
import { NoteReadService } from '@/core/NoteReadService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { NotificationService } from '@/core/NotificationService.js';
+import { bindThis } from '@/decorators.js';
import { AuthenticateService } from './AuthenticateService.js';
import MainStreamConnection from './stream/index.js';
import { ChannelsService } from './stream/ChannelsService.js';
import type { ParsedUrlQuery } from 'querystring';
import type * as http from 'node:http';
-import { bindThis } from '@/decorators.js';
@Injectable()
export class StreamingApiServerService {
@@ -33,6 +33,9 @@ export class StreamingApiServerService {
@Inject(DI.mutingsRepository)
private mutingsRepository: MutingsRepository,
+ @Inject(DI.renoteMutingsRepository)
+ private renoteMutingsRepository: RenoteMutingsRepository,
+
@Inject(DI.blockingsRepository)
private blockingsRepository: BlockingsRepository,
@@ -84,6 +87,7 @@ export class StreamingApiServerService {
const main = new MainStreamConnection(
this.followingsRepository,
this.mutingsRepository,
+ this.renoteMutingsRepository,
this.blockingsRepository,
this.channelFollowingsRepository,
this.userProfilesRepository,
diff --git a/packages/backend/src/server/api/endpoint-base.ts b/packages/backend/src/server/api/endpoint-base.ts
index ed283eb834..1555a3ca46 100644
--- a/packages/backend/src/server/api/endpoint-base.ts
+++ b/packages/backend/src/server/api/endpoint-base.ts
@@ -1,6 +1,6 @@
import * as fs from 'node:fs';
import Ajv from 'ajv';
-import type { Schema, SchemaType } from '@/misc/schema.js';
+import type { Schema, SchemaType } from '@/misc/json-schema.js';
import type { LocalUser } from '@/models/entities/User.js';
import type { AccessToken } from '@/models/entities/AccessToken.js';
import { ApiError } from './error.js';
diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts
index 4f521148e0..f6fc79fc70 100644
--- a/packages/backend/src/server/api/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints.ts
@@ -1,4 +1,4 @@
-import type { Schema } from '@/misc/schema.js';
+import type { Schema } from '@/misc/json-schema.js';
import { RolePolicies } from '@/core/RoleService.js';
import * as ep___admin_meta from './endpoints/admin/meta.js';
@@ -42,6 +42,7 @@ import * as ep___admin_promo_create from './endpoints/admin/promo/create.js';
import * as ep___admin_queue_clear from './endpoints/admin/queue/clear.js';
import * as ep___admin_queue_deliverDelayed from './endpoints/admin/queue/deliver-delayed.js';
import * as ep___admin_queue_inboxDelayed from './endpoints/admin/queue/inbox-delayed.js';
+import * as ep___admin_queue_promote from './endpoints/admin/queue/promote.js';
import * as ep___admin_queue_stats from './endpoints/admin/queue/stats.js';
import * as ep___admin_relays_add from './endpoints/admin/relays/add.js';
import * as ep___admin_relays_list from './endpoints/admin/relays/list.js';
@@ -114,6 +115,9 @@ import * as ep___clips_list from './endpoints/clips/list.js';
import * as ep___clips_notes from './endpoints/clips/notes.js';
import * as ep___clips_show from './endpoints/clips/show.js';
import * as ep___clips_update from './endpoints/clips/update.js';
+import * as ep___clips_favorite from './endpoints/clips/favorite.js';
+import * as ep___clips_unfavorite from './endpoints/clips/unfavorite.js';
+import * as ep___clips_myFavorites from './endpoints/clips/my-favorites.js';
import * as ep___drive from './endpoints/drive.js';
import * as ep___drive_files from './endpoints/drive/files.js';
import * as ep___drive_files_attachedNotes from './endpoints/drive/files/attached-notes.js';
@@ -220,10 +224,14 @@ import * as ep___i_webhooks_update from './endpoints/i/webhooks/update.js';
import * as ep___i_webhooks_delete from './endpoints/i/webhooks/delete.js';
import * as ep___meta from './endpoints/meta.js';
import * as ep___emojis from './endpoints/emojis.js';
+import * as ep___emoji from './endpoints/emoji.js';
import * as ep___miauth_genToken from './endpoints/miauth/gen-token.js';
import * as ep___mute_create from './endpoints/mute/create.js';
import * as ep___mute_delete from './endpoints/mute/delete.js';
import * as ep___mute_list from './endpoints/mute/list.js';
+import * as ep___renoteMute_create from './endpoints/renote-mute/create.js';
+import * as ep___renoteMute_delete from './endpoints/renote-mute/delete.js';
+import * as ep___renoteMute_list from './endpoints/renote-mute/list.js';
import * as ep___my_apps from './endpoints/my/apps.js';
import * as ep___notes from './endpoints/notes.js';
import * as ep___notes_children from './endpoints/notes/children.js';
@@ -361,6 +369,7 @@ const eps = [
['admin/queue/clear', ep___admin_queue_clear],
['admin/queue/deliver-delayed', ep___admin_queue_deliverDelayed],
['admin/queue/inbox-delayed', ep___admin_queue_inboxDelayed],
+ ['admin/queue/promote', ep___admin_queue_promote],
['admin/queue/stats', ep___admin_queue_stats],
['admin/relays/add', ep___admin_relays_add],
['admin/relays/list', ep___admin_relays_list],
@@ -433,6 +442,9 @@ const eps = [
['clips/notes', ep___clips_notes],
['clips/show', ep___clips_show],
['clips/update', ep___clips_update],
+ ['clips/favorite', ep___clips_favorite],
+ ['clips/unfavorite', ep___clips_unfavorite],
+ ['clips/my-favorites', ep___clips_myFavorites],
['drive', ep___drive],
['drive/files', ep___drive_files],
['drive/files/attached-notes', ep___drive_files_attachedNotes],
@@ -539,10 +551,14 @@ const eps = [
['i/webhooks/delete', ep___i_webhooks_delete],
['meta', ep___meta],
['emojis', ep___emojis],
+ ['emoji', ep___emoji],
['miauth/gen-token', ep___miauth_genToken],
['mute/create', ep___mute_create],
['mute/delete', ep___mute_delete],
['mute/list', ep___mute_list],
+ ['renote-mute/create', ep___renoteMute_create],
+ ['renote-mute/delete', ep___renoteMute_delete],
+ ['renote-mute/list', ep___renoteMute_list],
['my/apps', ep___my_apps],
['notes', ep___notes],
['notes/children', ep___notes_children],
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts
index 0cc60e9191..4e4f845b0b 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts
@@ -53,7 +53,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
});
}
- await this.db.queryResultCache!.remove(['meta_emojis']);
+ await this.db.queryResultCache?.remove(['meta_emojis']);
this.globalEventService.publishBroadcastStream('emojiUpdated', {
emojis: await this.emojiEntityService.packDetailedMany(ps.ids),
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 04c58050ff..2fb3e489e7 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/add.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/add.ts
@@ -56,6 +56,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
category: null,
aliases: [],
host: null,
+ license: null,
});
this.moderationLogService.insertModerationLog(me, 'addEmoji', {
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts
index 8885a40fd9..fea11a67d6 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts
@@ -87,9 +87,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
originalUrl: driveFile.url,
publicUrl: driveFile.webpublicUrl ?? driveFile.url,
type: driveFile.webpublicType ?? driveFile.type,
+ license: emoji.license,
}).then(x => this.emojisRepository.findOneByOrFail(x.identifiers[0]));
- await this.db.queryResultCache!.remove(['meta_emojis']);
+ await this.db.queryResultCache?.remove(['meta_emojis']);
this.globalEventService.publishBroadcastStream('emojiAdded', {
emoji: await this.emojiEntityService.packDetailed(copied.id),
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts
index f298baaedf..84aad020af 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts
@@ -47,7 +47,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
for (const emoji of emojis) {
await this.emojisRepository.delete(emoji.id);
- await this.db.queryResultCache!.remove(['meta_emojis']);
+ await this.db.queryResultCache?.remove(['meta_emojis']);
this.moderationLogService.insertModerationLog(me, 'deleteEmoji', {
emoji: emoji,
});
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts
index a5fbe3f4ea..90a5856a1b 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts
@@ -54,7 +54,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
await this.emojisRepository.delete(emoji.id);
- await this.db.queryResultCache!.remove(['meta_emojis']);
+ await this.db.queryResultCache?.remove(['meta_emojis']);
this.globalEventService.publishBroadcastStream('emojiDeleted', {
emojis: [await this.emojiEntityService.packDetailed(emoji)],
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts
index 66547024f7..3935183502 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts
@@ -53,7 +53,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
});
}
- await this.db.queryResultCache!.remove(['meta_emojis']);
+ await this.db.queryResultCache?.remove(['meta_emojis']);
this.globalEventService.publishBroadcastStream('emojiUpdated', {
emojis: await this.emojiEntityService.packDetailedMany(ps.ids),
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts
index c8992eeb04..6a875f9c83 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts
@@ -49,7 +49,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
aliases: ps.aliases,
});
- await this.db.queryResultCache!.remove(['meta_emojis']);
+ await this.db.queryResultCache?.remove(['meta_emojis']);
this.globalEventService.publishBroadcastStream('emojiUpdated', {
emojis: await this.emojiEntityService.packDetailedMany(ps.ids),
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts
index 8a538c1003..d3b999c0ed 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts
@@ -51,7 +51,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
category: ps.category,
});
- await this.db.queryResultCache!.remove(['meta_emojis']);
+ await this.db.queryResultCache?.remove(['meta_emojis']);
this.globalEventService.publishBroadcastStream('emojiUpdated', {
emojis: await this.emojiEntityService.packDetailedMany(ps.ids),
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 809bf77d6b..1c649db93e 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
@@ -19,6 +19,11 @@ export const meta = {
code: 'NO_SUCH_EMOJI',
id: '684dec9d-a8c2-4364-9aa8-456c49cb1dc8',
},
+ alreadyexistsemoji: {
+ message: 'Emoji already exists',
+ code: 'EMOJI_ALREADY_EXISTS',
+ id: '7180fe9d-1ee3-bff9-647d-fe9896d2ffb8',
+ },
},
} as const;
@@ -26,7 +31,7 @@ export const paramDef = {
type: 'object',
properties: {
id: { type: 'string', format: 'misskey:id' },
- name: { type: 'string' },
+ name: { type: 'string', pattern: '^[a-zA-Z0-9_]+$' },
category: {
type: 'string',
nullable: true,
@@ -35,6 +40,7 @@ export const paramDef = {
aliases: { type: 'array', items: {
type: 'string',
} },
+ license: { type: 'string', nullable: true },
},
required: ['id', 'name', 'aliases'],
} as const;
@@ -56,17 +62,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
) {
super(meta, paramDef, async (ps, me) => {
const emoji = await this.emojisRepository.findOneBy({ id: ps.id });
-
+ const emojiname = await this.emojisRepository.findOneBy({ name: ps.name });
if (emoji == null) throw new ApiError(meta.errors.noSuchEmoji);
-
+ if (emojiname != null && emojiname.id !== ps.id) throw new ApiError(meta.errors.alreadyexistsemoji);
await this.emojisRepository.update(emoji.id, {
updatedAt: new Date(),
name: ps.name,
category: ps.category,
aliases: ps.aliases,
+ license: ps.license,
});
- await this.db.queryResultCache!.remove(['meta_emojis']);
+ await this.db.queryResultCache?.remove(['meta_emojis']);
const updated = await this.emojiEntityService.packDetailed(emoji.id);
diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts
index 9eef1b29c5..ce7e0d569d 100644
--- a/packages/backend/src/server/api/endpoints/admin/meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/meta.ts
@@ -110,6 +110,14 @@ export const meta = {
optional: false, nullable: false,
},
},
+ sensitiveWords: {
+ type: 'array',
+ optional: true, nullable: false,
+ items: {
+ type: 'string',
+ optional: false, nullable: false,
+ },
+ },
hcaptchaSecretKey: {
type: 'string',
optional: true, nullable: true,
@@ -266,7 +274,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
uri: this.config.url,
description: instance.description,
langs: instance.langs,
- tosUrl: instance.ToSUrl,
+ tosUrl: instance.termsOfServiceUrl,
repositoryUrl: instance.repositoryUrl,
feedbackUrl: instance.feedbackUrl,
disableRegistration: instance.disableRegistration,
@@ -290,13 +298,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
enableEmail: instance.enableEmail,
enableServiceWorker: instance.enableServiceWorker,
translatorAvailable: instance.deeplAuthKey != null,
- pinnedPages: instance.pinnedPages,
- pinnedClipId: instance.pinnedClipId,
cacheRemoteFiles: instance.cacheRemoteFiles,
useStarForReactionFallback: instance.useStarForReactionFallback,
pinnedUsers: instance.pinnedUsers,
hiddenTags: instance.hiddenTags,
blockedHosts: instance.blockedHosts,
+ sensitiveWords: instance.sensitiveWords,
hcaptchaSecretKey: instance.hcaptchaSecretKey,
recaptchaSecretKey: instance.recaptchaSecretKey,
turnstileSecretKey: instance.turnstileSecretKey,
diff --git a/packages/backend/src/server/api/endpoints/admin/queue/promote.ts b/packages/backend/src/server/api/endpoints/admin/queue/promote.ts
new file mode 100644
index 0000000000..4e57e6613e
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/admin/queue/promote.ts
@@ -0,0 +1,52 @@
+import { Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { ModerationLogService } from '@/core/ModerationLogService.js';
+import { QueueService } from '@/core/QueueService.js';
+
+export const meta = {
+ tags: ['admin'],
+
+ requireCredential: true,
+ requireModerator: true,
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ type: { type: 'string', enum: ['deliver', 'inbox'] },
+ },
+ required: ['type'],
+} as const;
+
+// eslint-disable-next-line import/no-default-export
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ private moderationLogService: ModerationLogService,
+ private queueService: QueueService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ let delayedQueues;
+
+ switch (ps.type) {
+ case 'deliver':
+ delayedQueues = await this.queueService.deliverQueue.getDelayed();
+ for (let queueIndex = 0; queueIndex < delayedQueues.length; queueIndex++) {
+ const queue = delayedQueues[queueIndex];
+ await queue.promote();
+ }
+ break;
+
+ case 'inbox':
+ delayedQueues = await this.queueService.inboxQueue.getDelayed();
+ for (let queueIndex = 0; queueIndex < delayedQueues.length; queueIndex++) {
+ const queue = delayedQueues[queueIndex];
+ await queue.promote();
+ }
+ break;
+ }
+
+ this.moderationLogService.insertModerationLog(me, 'promoteQueue');
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts
index d0d52089e6..aead894611 100644
--- a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts
+++ b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts
@@ -49,7 +49,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
const actor = await this.instanceActorService.getInstanceActor();
const targetUser = await this.usersRepository.findOneByOrFail({ id: report.targetUserId });
- this.queueService.deliver(actor, this.apRendererService.addContext(this.apRendererService.renderFlag(actor, targetUser.uri!, report.comment)), targetUser.inbox);
+ this.queueService.deliver(actor, this.apRendererService.addContext(this.apRendererService.renderFlag(actor, targetUser.uri!, report.comment)), targetUser.inbox, false);
}
await this.abuseUserReportsRepository.update(report.id, {
diff --git a/packages/backend/src/server/api/endpoints/admin/roles/create.ts b/packages/backend/src/server/api/endpoints/admin/roles/create.ts
index df60c6be94..1359894634 100644
--- a/packages/backend/src/server/api/endpoints/admin/roles/create.ts
+++ b/packages/backend/src/server/api/endpoints/admin/roles/create.ts
@@ -27,6 +27,7 @@ export const paramDef = {
isAdministrator: { type: 'boolean' },
asBadge: { type: 'boolean' },
canEditMembersByModerator: { type: 'boolean' },
+ displayOrder: { type: 'number' },
policies: {
type: 'object',
},
@@ -43,6 +44,7 @@ export const paramDef = {
'isAdministrator',
'asBadge',
'canEditMembersByModerator',
+ 'displayOrder',
'policies',
],
} as const;
@@ -76,6 +78,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
isModerator: ps.isModerator,
asBadge: ps.asBadge,
canEditMembersByModerator: ps.canEditMembersByModerator,
+ displayOrder: ps.displayOrder,
policies: ps.policies,
}).then(x => this.rolesRepository.findOneByOrFail(x.identifiers[0]));
diff --git a/packages/backend/src/server/api/endpoints/admin/roles/update.ts b/packages/backend/src/server/api/endpoints/admin/roles/update.ts
index b939ccdbf9..37b68c4c41 100644
--- a/packages/backend/src/server/api/endpoints/admin/roles/update.ts
+++ b/packages/backend/src/server/api/endpoints/admin/roles/update.ts
@@ -35,6 +35,7 @@ export const paramDef = {
isAdministrator: { type: 'boolean' },
asBadge: { type: 'boolean' },
canEditMembersByModerator: { type: 'boolean' },
+ displayOrder: { type: 'number' },
policies: {
type: 'object',
},
@@ -52,6 +53,7 @@ export const paramDef = {
'isAdministrator',
'asBadge',
'canEditMembersByModerator',
+ 'displayOrder',
'policies',
],
} as const;
@@ -85,6 +87,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
isAdministrator: ps.isAdministrator,
asBadge: ps.asBadge,
canEditMembersByModerator: ps.canEditMembersByModerator,
+ displayOrder: ps.displayOrder,
policies: ps.policies,
});
const updated = await this.rolesRepository.findOneByOrFail({ id: ps.roleId });
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 a7531aae89..2f23aca243 100644
--- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts
@@ -27,6 +27,9 @@ export const paramDef = {
blockedHosts: { type: 'array', nullable: true, items: {
type: 'string',
} },
+ sensitiveWords: { type: 'array', nullable: true, items: {
+ type: 'string',
+ } },
themeColor: { type: 'string', nullable: true, pattern: '^#[0-9a-fA-F]{6}$' },
mascotImageUrl: { type: 'string', nullable: true },
bannerUrl: { type: 'string', nullable: true },
@@ -56,10 +59,6 @@ export const paramDef = {
proxyAccountId: { type: 'string', format: 'misskey:id', nullable: true },
maintainerName: { type: 'string', nullable: true },
maintainerEmail: { type: 'string', nullable: true },
- pinnedPages: { type: 'array', items: {
- type: 'string',
- } },
- pinnedClipId: { type: 'string', format: 'misskey:id', nullable: true },
langs: { type: 'array', items: {
type: 'string',
} },
@@ -131,6 +130,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
set.blockedHosts = ps.blockedHosts.filter(Boolean).map(x => x.toLowerCase());
}
+ if (Array.isArray(ps.sensitiveWords)) {
+ set.sensitiveWords = ps.sensitiveWords.filter(Boolean);
+ }
+
if (ps.themeColor !== undefined) {
set.themeColor = ps.themeColor;
}
@@ -247,14 +250,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
set.langs = ps.langs.filter(Boolean);
}
- if (Array.isArray(ps.pinnedPages)) {
- set.pinnedPages = ps.pinnedPages.filter(Boolean);
- }
-
- if (ps.pinnedClipId !== undefined) {
- set.pinnedClipId = ps.pinnedClipId;
- }
-
if (ps.summalyProxy !== undefined) {
set.summalyProxy = ps.summalyProxy;
}
@@ -304,7 +299,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
}
if (ps.tosUrl !== undefined) {
- set.ToSUrl = ps.tosUrl;
+ set.termsOfServiceUrl = ps.tosUrl;
}
if (ps.repositoryUrl !== undefined) {
diff --git a/packages/backend/src/server/api/endpoints/antennas/create.ts b/packages/backend/src/server/api/endpoints/antennas/create.ts
index bc5d249ae5..d147ddb7f1 100644
--- a/packages/backend/src/server/api/endpoints/antennas/create.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/create.ts
@@ -79,6 +79,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private globalEventService: GlobalEventService,
) {
super(meta, paramDef, async (ps, me) => {
+ if (ps.keywords.length === 0) {
+ throw new Error('invalid param');
+ }
+
const currentAntennasCount = await this.antennasRepository.countBy({
userId: me.id,
});
@@ -99,9 +103,12 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
}
}
+ const now = new Date();
+
const antenna = await this.antennasRepository.insert({
id: this.idService.genId(),
- createdAt: new Date(),
+ createdAt: now,
+ lastUsedAt: now,
userId: me.id,
name: ps.name,
src: ps.src,
diff --git a/packages/backend/src/server/api/endpoints/antennas/notes.ts b/packages/backend/src/server/api/endpoints/antennas/notes.ts
index fbb5acf617..039ba1115a 100644
--- a/packages/backend/src/server/api/endpoints/antennas/notes.ts
+++ b/packages/backend/src/server/api/endpoints/antennas/notes.ts
@@ -101,6 +101,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
this.noteReadService.read(me.id, notes);
}
+ this.antennasRepository.update(antenna.id, {
+ lastUsedAt: new Date(),
+ });
+
return await this.noteEntityService.packMany(notes, me);
});
}
diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts
index 61e05531e6..a103d4196a 100644
--- a/packages/backend/src/server/api/endpoints/ap/show.ts
+++ b/packages/backend/src/server/api/endpoints/ap/show.ts
@@ -5,7 +5,7 @@ import type { UsersRepository, NotesRepository } from '@/models/index.js';
import type { Note } from '@/models/entities/Note.js';
import type { LocalUser, User } from '@/models/entities/User.js';
import { isActor, isPost, getApId } from '@/core/activitypub/type.js';
-import type { SchemaType } from '@/misc/schema.js';
+import type { SchemaType } from '@/misc/json-schema.js';
import { ApResolverService } from '@/core/activitypub/ApResolverService.js';
import { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js';
import { MetaService } from '@/core/MetaService.js';
diff --git a/packages/backend/src/server/api/endpoints/channels/update.ts b/packages/backend/src/server/api/endpoints/channels/update.ts
index d006e89bd2..a86cc2565a 100644
--- a/packages/backend/src/server/api/endpoints/channels/update.ts
+++ b/packages/backend/src/server/api/endpoints/channels/update.ts
@@ -4,6 +4,7 @@ import type { DriveFilesRepository, ChannelsRepository } from '@/models/index.js
import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
import { DI } from '@/di-symbols.js';
import { ApiError } from '../../error.js';
+import { RoleService } from '@/core/RoleService.js';
export const meta = {
tags: ['channels'],
@@ -61,7 +62,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private driveFilesRepository: DriveFilesRepository,
private channelEntityService: ChannelEntityService,
- ) {
+
+ private roleService: RoleService,
+ ) {
super(meta, paramDef, async (ps, me) => {
const channel = await this.channelsRepository.findOneBy({
id: ps.channelId,
@@ -71,7 +74,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
throw new ApiError(meta.errors.noSuchChannel);
}
- if (channel.userId !== me.id) {
+ const iAmModerator = await this.roleService.isModerator(me);
+ if (channel.userId !== me.id && !iAmModerator) {
throw new ApiError(meta.errors.accessDenied);
}
diff --git a/packages/backend/src/server/api/endpoints/clips/add-note.ts b/packages/backend/src/server/api/endpoints/clips/add-note.ts
index f3f9c3477f..b9d8dce47a 100644
--- a/packages/backend/src/server/api/endpoints/clips/add-note.ts
+++ b/packages/backend/src/server/api/endpoints/clips/add-note.ts
@@ -106,6 +106,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
noteId: note.id,
clipId: clip.id,
});
+
+ await this.clipsRepository.update(clip.id, {
+ lastClippedAt: new Date(),
+ });
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/clips/create.ts b/packages/backend/src/server/api/endpoints/clips/create.ts
index c095de702c..a770dc986d 100644
--- a/packages/backend/src/server/api/endpoints/clips/create.ts
+++ b/packages/backend/src/server/api/endpoints/clips/create.ts
@@ -67,7 +67,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
description: ps.description,
}).then(x => this.clipsRepository.findOneByOrFail(x.identifiers[0]));
- return await this.clipEntityService.pack(clip);
+ return await this.clipEntityService.pack(clip, me);
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/clips/favorite.ts b/packages/backend/src/server/api/endpoints/clips/favorite.ts
new file mode 100644
index 0000000000..6addf743a2
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/clips/favorite.ts
@@ -0,0 +1,76 @@
+import { Inject, Injectable } from '@nestjs/common';
+import type { ClipsRepository, ClipFavoritesRepository } from '@/models/index.js';
+import { IdService } from '@/core/IdService.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { DI } from '@/di-symbols.js';
+import { ApiError } from '../../error.js';
+
+export const meta = {
+ tags: ['clip'],
+
+ requireCredential: true,
+
+ kind: 'write:clip-favorite',
+
+ errors: {
+ noSuchClip: {
+ message: 'No such clip.',
+ code: 'NO_SUCH_CLIP',
+ id: '4c2aaeae-80d8-4250-9606-26cb1fdb77a5',
+ },
+
+ alreadyFavorited: {
+ message: 'The clip has already been favorited.',
+ code: 'ALREADY_FAVORITED',
+ id: '92658936-c625-4273-8326-2d790129256e',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ clipId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['clipId'],
+} as const;
+
+// eslint-disable-next-line import/no-default-export
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.clipsRepository)
+ private clipsRepository: ClipsRepository,
+
+ @Inject(DI.clipFavoritesRepository)
+ private clipFavoritesRepository: ClipFavoritesRepository,
+
+ private idService: IdService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const clip = await this.clipsRepository.findOneBy({ id: ps.clipId });
+ if (clip == null) {
+ throw new ApiError(meta.errors.noSuchClip);
+ }
+ if ((clip.userId !== me.id) && !clip.isPublic) {
+ throw new ApiError(meta.errors.noSuchClip);
+ }
+
+ const exist = await this.clipFavoritesRepository.findOneBy({
+ clipId: clip.id,
+ userId: me.id,
+ });
+
+ if (exist != null) {
+ throw new ApiError(meta.errors.alreadyFavorited);
+ }
+
+ await this.clipFavoritesRepository.insert({
+ id: this.idService.genId(),
+ createdAt: new Date(),
+ clipId: clip.id,
+ userId: 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 63ca069364..3b8deab709 100644
--- a/packages/backend/src/server/api/endpoints/clips/list.ts
+++ b/packages/backend/src/server/api/endpoints/clips/list.ts
@@ -42,7 +42,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
userId: me.id,
});
- return await Promise.all(clips.map(x => this.clipEntityService.pack(x)));
+ return await this.clipEntityService.packMany(clips, me);
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/clips/my-favorites.ts b/packages/backend/src/server/api/endpoints/clips/my-favorites.ts
new file mode 100644
index 0000000000..fc727e93bd
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/clips/my-favorites.ts
@@ -0,0 +1,52 @@
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import type { ClipFavoritesRepository } from '@/models/index.js';
+import { DI } from '@/di-symbols.js';
+import { ClipEntityService } from '@/core/entities/ClipEntityService.js';
+
+export const meta = {
+ tags: ['account', 'clip'],
+
+ requireCredential: true,
+
+ kind: 'read:clip-favorite',
+
+ res: {
+ type: 'array',
+ optional: false, nullable: false,
+ items: {
+ type: 'object',
+ optional: false, nullable: false,
+ ref: 'Clip',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ },
+ required: [],
+} as const;
+
+// eslint-disable-next-line import/no-default-export
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.clipFavoritesRepository)
+ private clipFavoritesRepository: ClipFavoritesRepository,
+
+ private clipEntityService: ClipEntityService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const query = this.clipFavoritesRepository.createQueryBuilder('favorite')
+ .andWhere('favorite.userId = :meId', { meId: me.id })
+ .leftJoinAndSelect('favorite.clip', 'clip');
+
+ const favorites = await query
+ .getMany();
+
+ return this.clipEntityService.packMany(favorites.map(x => x.clip!), me);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/clips/show.ts b/packages/backend/src/server/api/endpoints/clips/show.ts
index e6d3f4f1f8..99d630a9b5 100644
--- a/packages/backend/src/server/api/endpoints/clips/show.ts
+++ b/packages/backend/src/server/api/endpoints/clips/show.ts
@@ -58,7 +58,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
throw new ApiError(meta.errors.noSuchClip);
}
- return await this.clipEntityService.pack(clip);
+ return await this.clipEntityService.pack(clip, me);
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/clips/unfavorite.ts b/packages/backend/src/server/api/endpoints/clips/unfavorite.ts
new file mode 100644
index 0000000000..244843d50f
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/clips/unfavorite.ts
@@ -0,0 +1,65 @@
+import { Inject, Injectable } from '@nestjs/common';
+import type { ClipsRepository, ClipFavoritesRepository } from '@/models/index.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { DI } from '@/di-symbols.js';
+import { ApiError } from '../../error.js';
+
+export const meta = {
+ tags: ['clip'],
+
+ requireCredential: true,
+
+ kind: 'write:clip-favorite',
+
+ errors: {
+ noSuchClip: {
+ message: 'No such clip.',
+ code: 'NO_SUCH_CLIP',
+ id: '2603966e-b865-426c-94a7-af4a01241dc1',
+ },
+
+ notFavorited: {
+ message: 'You have not favorited the clip.',
+ code: 'NOT_FAVORITED',
+ id: '90c3a9e8-b321-4dae-bf57-2bf79bbcc187',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ clipId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['clipId'],
+} as const;
+
+// eslint-disable-next-line import/no-default-export
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.clipsRepository)
+ private clipsRepository: ClipsRepository,
+
+ @Inject(DI.clipFavoritesRepository)
+ private clipFavoritesRepository: ClipFavoritesRepository,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const clip = await this.clipsRepository.findOneBy({ id: ps.clipId });
+ if (clip == null) {
+ throw new ApiError(meta.errors.noSuchClip);
+ }
+
+ const exist = await this.clipFavoritesRepository.findOneBy({
+ clipId: clip.id,
+ userId: me.id,
+ });
+
+ if (exist == null) {
+ throw new ApiError(meta.errors.notFavorited);
+ }
+
+ await this.clipFavoritesRepository.delete(exist.id);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/clips/update.ts b/packages/backend/src/server/api/endpoints/clips/update.ts
index 597b67c442..a103c3f7d3 100644
--- a/packages/backend/src/server/api/endpoints/clips/update.ts
+++ b/packages/backend/src/server/api/endpoints/clips/update.ts
@@ -64,7 +64,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
isPublic: ps.isPublic,
});
- return await this.clipEntityService.pack(clip.id);
+ return await this.clipEntityService.pack(clip.id, me);
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/drive/files.ts b/packages/backend/src/server/api/endpoints/drive/files.ts
index f6fad50fd9..4609307774 100644
--- a/packages/backend/src/server/api/endpoints/drive/files.ts
+++ b/packages/backend/src/server/api/endpoints/drive/files.ts
@@ -31,6 +31,7 @@ export const paramDef = {
untilId: { type: 'string', format: 'misskey:id' },
folderId: { type: 'string', format: 'misskey:id', nullable: true, default: null },
type: { type: 'string', nullable: true, pattern: /^[a-zA-Z\/\-*]+$/.toString().slice(1, -1) },
+ sort: { type: 'string', nullable: true, enum: ['+createdAt', '-createdAt', '+name', '-name', '+size', '-size'] },
},
required: [],
} as const;
@@ -63,6 +64,15 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
}
}
+ switch (ps.sort) {
+ case '+createdAt': query.orderBy('file.createdAt', 'DESC'); break;
+ case '-createdAt': query.orderBy('file.createdAt', 'ASC'); break;
+ case '+name': query.orderBy('file.name', 'DESC'); break;
+ case '-name': query.orderBy('file.name', 'ASC'); break;
+ case '+size': query.orderBy('file.size', 'DESC'); break;
+ case '-size': query.orderBy('file.size', 'ASC'); break;
+ }
+
const files = await query.take(ps.limit).getMany();
return await this.driveFileEntityService.packMany(files, { detail: false, self: true });
diff --git a/packages/backend/src/server/api/endpoints/emoji.ts b/packages/backend/src/server/api/endpoints/emoji.ts
new file mode 100644
index 0000000000..681d3e649e
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/emoji.ts
@@ -0,0 +1,56 @@
+import { IsNull } from 'typeorm';
+import { Inject, Injectable } from '@nestjs/common';
+import type { EmojisRepository } from '@/models/index.js';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
+import type { Config } from '@/config.js';
+import { DI } from '@/di-symbols.js';
+
+export const meta = {
+ tags: ['meta'],
+
+ requireCredential: false,
+ allowGet: true,
+ cacheSec: 3600,
+
+ res: {
+ type: 'object',
+ optional: false, nullable: false,
+ ref: 'EmojiDetailed',
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ name: {
+ type: 'string',
+ },
+ },
+ required: ['name'],
+} as const;
+
+// eslint-disable-next-line import/no-default-export
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.config)
+ private config: Config,
+
+ @Inject(DI.emojisRepository)
+ private emojisRepository: EmojisRepository,
+
+ private emojiEntityService: EmojiEntityService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const emoji = await this.emojisRepository.findOneOrFail({
+ where: {
+ name: ps.name,
+ host: IsNull(),
+ },
+ });
+
+ return this.emojiEntityService.packDetailed(emoji);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/emojis.ts b/packages/backend/src/server/api/endpoints/emojis.ts
index 325b758358..0711fe4a57 100644
--- a/packages/backend/src/server/api/endpoints/emojis.ts
+++ b/packages/backend/src/server/api/endpoints/emojis.ts
@@ -23,24 +23,7 @@ export const meta = {
items: {
type: 'object',
optional: false, nullable: false,
- properties: {
- name: {
- type: 'string',
- optional: false, nullable: false,
- },
- aliases: {
- type: 'array',
- optional: false, nullable: false,
- items: {
- type: 'string',
- optional: false, nullable: false,
- },
- },
- category: {
- type: 'string',
- optional: false, nullable: true,
- },
- },
+ ref: 'EmojiSimple',
},
},
},
diff --git a/packages/backend/src/server/api/endpoints/federation/instances.ts b/packages/backend/src/server/api/endpoints/federation/instances.ts
index 60b24e9585..061c6eb5be 100644
--- a/packages/backend/src/server/api/endpoints/federation/instances.ts
+++ b/packages/backend/src/server/api/endpoints/federation/instances.ts
@@ -76,9 +76,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
if (typeof ps.blocked === 'boolean') {
const meta = await this.metaService.fetch(true);
if (ps.blocked) {
- query.andWhere('instance.host IN (:...blocks)', { blocks: meta.blockedHosts });
+ query.andWhere(meta.blockedHosts.length === 0 ? '1=0' : 'instance.host IN (:...blocks)', { blocks: meta.blockedHosts });
} else {
- query.andWhere('instance.host NOT IN (:...blocks)', { blocks: meta.blockedHosts });
+ query.andWhere(meta.blockedHosts.length === 0 ? '1=1' : 'instance.host NOT IN (:...blocks)', { blocks: meta.blockedHosts });
}
}
diff --git a/packages/backend/src/server/api/endpoints/i.ts b/packages/backend/src/server/api/endpoints/i.ts
index 6beef5ab85..a3e3e02a12 100644
--- a/packages/backend/src/server/api/endpoints/i.ts
+++ b/packages/backend/src/server/api/endpoints/i.ts
@@ -3,6 +3,7 @@ import type { UserProfilesRepository, UsersRepository } from '@/models/index.js'
import { Endpoint } from '@/server/api/endpoint-base.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { DI } from '@/di-symbols.js';
+import { ApiError } from '../error.js';
export const meta = {
tags: ['account'],
@@ -14,6 +15,15 @@ export const meta = {
optional: false, nullable: false,
ref: 'MeDetailed',
},
+
+ errors: {
+ userIsDeleted: {
+ message: 'User is deleted.',
+ code: 'USER_IS_DELETED',
+ id: 'e5b3b9f0-2b8f-4b9f-9c1f-8c5c1b2e1b1a',
+ kind: 'permission',
+ },
+ }
} as const;
export const paramDef = {
@@ -41,13 +51,17 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
const today = `${now.getFullYear()}/${now.getMonth() + 1}/${now.getDate()}`;
// 渡ってきている user はキャッシュされていて古い可能性があるので改めて取得
- const userProfile = await this.userProfilesRepository.findOneOrFail({
+ const userProfile = await this.userProfilesRepository.findOne({
where: {
userId: user.id,
},
relations: ['user'],
});
+ if (userProfile == null) {
+ throw new ApiError(meta.errors.userIsDeleted);
+ }
+
if (!userProfile.loggedInDates.includes(today)) {
this.userProfilesRepository.update({ userId: user.id }, {
loggedInDates: [...userProfile.loggedInDates, today],
diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts
index cdb314a873..37974ce2a3 100644
--- a/packages/backend/src/server/api/endpoints/meta.ts
+++ b/packages/backend/src/server/api/endpoints/meta.ts
@@ -276,7 +276,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
uri: this.config.url,
description: instance.description,
langs: instance.langs,
- tosUrl: instance.ToSUrl,
+ tosUrl: instance.termsOfServiceUrl,
repositoryUrl: instance.repositoryUrl,
feedbackUrl: instance.feedbackUrl,
disableRegistration: instance.disableRegistration,
@@ -315,8 +315,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
mediaProxy: this.config.mediaProxy,
...(ps.detail ? {
- pinnedPages: instance.pinnedPages,
- pinnedClipId: instance.pinnedClipId,
cacheRemoteFiles: instance.cacheRemoteFiles,
requireSetup: (await this.usersRepository.countBy({
host: IsNull(),
diff --git a/packages/backend/src/server/api/endpoints/notes/clips.ts b/packages/backend/src/server/api/endpoints/notes/clips.ts
index d5caec6e1d..0a5542f497 100644
--- a/packages/backend/src/server/api/endpoints/notes/clips.ts
+++ b/packages/backend/src/server/api/endpoints/notes/clips.ts
@@ -4,8 +4,8 @@ import type { ClipNotesRepository, ClipsRepository } from '@/models/index.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { ClipEntityService } from '@/core/entities/ClipEntityService.js';
import { DI } from '@/di-symbols.js';
-import { ApiError } from '../../error.js';
import { GetterService } from '@/server/api/GetterService.js';
+import { ApiError } from '../../error.js';
export const meta = {
tags: ['clips', 'notes'],
@@ -67,7 +67,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
isPublic: true,
});
- return await Promise.all(clips.map(x => this.clipEntityService.pack(x)));
+ return await this.clipEntityService.packMany(clips, me);
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts
index 786ad103b0..69fafcb9c7 100644
--- a/packages/backend/src/server/api/endpoints/notes/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/create.ts
@@ -97,6 +97,7 @@ export const paramDef = {
} },
cw: { type: 'string', nullable: true, maxLength: 100 },
localOnly: { type: 'boolean', default: false },
+ reactionAcceptance: { type: 'string', nullable: true, enum: [null, 'likeOnly', 'likeOnlyForRemote'], default: null },
noExtractMentions: { type: 'boolean', default: false },
noExtractHashtags: { type: 'boolean', default: false },
noExtractEmojis: { type: 'boolean', default: false },
@@ -110,7 +111,7 @@ export const paramDef = {
type: 'string',
minLength: 1,
maxLength: MAX_NOTE_TEXT_LENGTH,
- nullable: false
+ nullable: false,
},
fileIds: {
type: 'array',
@@ -280,6 +281,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
renote,
cw: ps.cw,
localOnly: ps.localOnly,
+ reactionAcceptance: ps.reactionAcceptance,
visibility: ps.visibility,
visibleUsers,
channel,
diff --git a/packages/backend/src/server/api/endpoints/notes/featured.ts b/packages/backend/src/server/api/endpoints/notes/featured.ts
index cf939f6631..6bf17b222a 100644
--- a/packages/backend/src/server/api/endpoints/notes/featured.ts
+++ b/packages/backend/src/server/api/endpoints/notes/featured.ts
@@ -71,7 +71,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
let notes = await query
.orderBy('note.score', 'DESC')
- .take(50)
+ .take(100)
.getMany();
notes.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
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 5d0cdc3fca..9118d33936 100644
--- a/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/global-timeline.ts
@@ -89,6 +89,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
this.queryService.generateMutedUserQuery(query, me);
this.queryService.generateMutedNoteQuery(query, me);
this.queryService.generateBlockedUserQuery(query, me);
+ this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
}
if (ps.withFiles) {
diff --git a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
index 2819abb125..8a7ec65ab4 100644
--- a/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
@@ -8,6 +8,7 @@ import { MetaService } from '@/core/MetaService.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { DI } from '@/di-symbols.js';
import { RoleService } from '@/core/RoleService.js';
+import { IdService } from '@/core/IdService.js';
import { ApiError } from '../../error.js';
export const meta = {
@@ -69,6 +70,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private metaService: MetaService,
private roleService: RoleService,
private activeUsersChart: ActiveUsersChart,
+ private idService: IdService,
) {
super(meta, paramDef, async (ps, me) => {
const policies = await this.roleService.getUserPolicies(me.id);
@@ -83,7 +85,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'),
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
- .andWhere('note.createdAt > :minDate', { minDate: new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)) }) // 30日前まで
+ .andWhere('note.id > :minId', { minId: this.idService.genId(new Date(Date.now() - (1000 * 60 * 60 * 24 * 10))) }) // 10日前まで
.andWhere(new Brackets(qb => {
qb.where(`((note.userId IN (${ followingQuery.getQuery() })) OR (note.userId = :meId))`, { meId: me.id })
.orWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)');
@@ -107,6 +109,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
this.queryService.generateMutedUserQuery(query, me);
this.queryService.generateMutedNoteQuery(query, me);
this.queryService.generateBlockedUserQuery(query, me);
+ this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
if (ps.includeMyRenotes === false) {
query.andWhere(new Brackets(qb => {
diff --git a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
index 18ed6d4e21..8c1c07a9f4 100644
--- a/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/local-timeline.ts
@@ -8,6 +8,7 @@ import { MetaService } from '@/core/MetaService.js';
import ActiveUsersChart from '@/core/chart/charts/active-users.js';
import { DI } from '@/di-symbols.js';
import { RoleService } from '@/core/RoleService.js';
+import { IdService } from '@/core/IdService.js';
import { ApiError } from '../../error.js';
export const meta = {
@@ -65,6 +66,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private metaService: MetaService,
private roleService: RoleService,
private activeUsersChart: ActiveUsersChart,
+ private idService: IdService,
) {
super(meta, paramDef, async (ps, me) => {
const policies = await this.roleService.getUserPolicies(me ? me.id : null);
@@ -75,7 +77,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
//#region Construct query
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'),
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
- .andWhere('note.createdAt > :minDate', { minDate: new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)) }) // 30日前まで
+ .andWhere('note.id > :minId', { minId: this.idService.genId(new Date(Date.now() - (1000 * 60 * 60 * 24 * 10))) }) // 10日前まで
.andWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)')
.innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('user.avatar', 'avatar')
@@ -95,6 +97,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
if (me) this.queryService.generateMutedUserQuery(query, me);
if (me) this.queryService.generateMutedNoteQuery(query, me);
if (me) this.queryService.generateBlockedUserQuery(query, me);
+ if (me) this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
if (ps.withFiles) {
query.andWhere('note.fileIds != \'{}\'');
diff --git a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts
index b9e06a7834..2a44dc537e 100644
--- a/packages/backend/src/server/api/endpoints/notes/polls/vote.ts
+++ b/packages/backend/src/server/api/endpoints/notes/polls/vote.ts
@@ -8,7 +8,6 @@ import { QueueService } from '@/core/QueueService.js';
import { PollService } from '@/core/PollService.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
-import { CreateNotificationService } from '@/core/CreateNotificationService.js';
import { DI } from '@/di-symbols.js';
import { UserBlockingService } from '@/core/UserBlockingService.js';
import { ApiError } from '../../../error.js';
@@ -89,7 +88,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private pollService: PollService,
private apRendererService: ApRendererService,
private globalEventService: GlobalEventService,
- private createNotificationService: CreateNotificationService,
private userBlockingService: UserBlockingService,
) {
super(meta, paramDef, async (ps, me) => {
@@ -161,7 +159,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
if (note.userHost != null) {
const pollOwner = await this.usersRepository.findOneByOrFail({ id: note.userId }) as RemoteUser;
- this.queueService.deliver(me, this.apRendererService.addContext(await this.apRendererService.renderVote(me, vote, note, poll, pollOwner)), pollOwner.inbox);
+ this.queueService.deliver(me, this.apRendererService.addContext(await this.apRendererService.renderVote(me, vote, note, poll, pollOwner)), pollOwner.inbox, false);
}
// リモートフォロワーにUpdate配信
diff --git a/packages/backend/src/server/api/endpoints/notes/search.ts b/packages/backend/src/server/api/endpoints/notes/search.ts
index ef47a3004d..5db5b6267f 100644
--- a/packages/backend/src/server/api/endpoints/notes/search.ts
+++ b/packages/backend/src/server/api/endpoints/notes/search.ts
@@ -6,6 +6,8 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import type { Config } from '@/config.js';
import { DI } from '@/di-symbols.js';
import { sqlLikeEscape } from '@/misc/sql-like-escape.js';
+import { RoleService } from '@/core/RoleService.js';
+import { ApiError } from '../../error.js';
export const meta = {
tags: ['notes'],
@@ -23,6 +25,11 @@ export const meta = {
},
errors: {
+ unavailable: {
+ message: 'Search of notes unavailable.',
+ code: 'UNAVAILABLE',
+ id: '0b44998d-77aa-4427-80d0-d2c9b8523011',
+ },
},
} as const;
@@ -59,8 +66,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private noteEntityService: NoteEntityService,
private queryService: QueryService,
+ private roleService: RoleService,
) {
super(meta, paramDef, async (ps, me) => {
+ const policies = await this.roleService.getUserPolicies(me ? me.id : null);
+ if (!policies.canSearchNotes) {
+ throw new ApiError(meta.errors.unavailable);
+ }
+
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'), ps.sinceId, ps.untilId);
if (ps.userId) {
diff --git a/packages/backend/src/server/api/endpoints/notes/timeline.ts b/packages/backend/src/server/api/endpoints/notes/timeline.ts
index e6de087c4a..d9e72d2603 100644
--- a/packages/backend/src/server/api/endpoints/notes/timeline.ts
+++ b/packages/backend/src/server/api/endpoints/notes/timeline.ts
@@ -6,6 +6,7 @@ import { QueryService } from '@/core/QueryService.js';
import ActiveUsersChart from '@/core/chart/charts/active-users.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { DI } from '@/di-symbols.js';
+import { IdService } from '@/core/IdService.js';
export const meta = {
tags: ['notes'],
@@ -56,6 +57,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
private noteEntityService: NoteEntityService,
private queryService: QueryService,
private activeUsersChart: ActiveUsersChart,
+ private idService: IdService,
) {
super(meta, paramDef, async (ps, me) => {
const followees = await this.followingsRepository.createQueryBuilder('following')
@@ -66,7 +68,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
//#region Construct query
const query = this.queryService.makePaginationQuery(this.notesRepository.createQueryBuilder('note'),
ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate)
- .andWhere('note.createdAt > :minDate', { minDate: new Date(Date.now() - (1000 * 60 * 60 * 24 * 30)) }) // 30日前まで
+ .andWhere('note.id > :minId', { minId: this.idService.genId(new Date(Date.now() - (1000 * 60 * 60 * 24 * 10))) }) // 10日前まで
.innerJoinAndSelect('note.user', 'user')
.leftJoinAndSelect('user.avatar', 'avatar')
.leftJoinAndSelect('user.banner', 'banner')
@@ -93,6 +95,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
this.queryService.generateMutedUserQuery(query, me);
this.queryService.generateMutedNoteQuery(query, me);
this.queryService.generateBlockedUserQuery(query, me);
+ this.queryService.generateMutedUserRenotesQueryForNotes(query, me);
if (ps.includeMyRenotes === false) {
query.andWhere(new Brackets(qb => {
diff --git a/packages/backend/src/server/api/endpoints/notifications/create.ts b/packages/backend/src/server/api/endpoints/notifications/create.ts
index 2e63eee263..4102a924ad 100644
--- a/packages/backend/src/server/api/endpoints/notifications/create.ts
+++ b/packages/backend/src/server/api/endpoints/notifications/create.ts
@@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
-import { CreateNotificationService } from '@/core/CreateNotificationService.js';
+import { NotificationService } from '@/core/NotificationService.js';
export const meta = {
tags: ['notifications'],
@@ -27,10 +27,10 @@ export const paramDef = {
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
- private createNotificationService: CreateNotificationService,
+ private notificationService: NotificationService,
) {
super(meta, paramDef, async (ps, user, token) => {
- this.createNotificationService.createNotification(user.id, 'app', {
+ this.notificationService.createNotification(user.id, 'app', {
appAccessTokenId: token ? token.id : null,
customBody: ps.body,
customHeader: ps.header,
diff --git a/packages/backend/src/server/api/endpoints/renote-mute/create.ts b/packages/backend/src/server/api/endpoints/renote-mute/create.ts
new file mode 100644
index 0000000000..051a005b67
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/renote-mute/create.ts
@@ -0,0 +1,99 @@
+import { Inject, Injectable } from '@nestjs/common';
+import ms from 'ms';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import { IdService } from '@/core/IdService.js';
+import type { RenoteMutingsRepository } from '@/models/index.js';
+import type { RenoteMuting } from '@/models/entities/RenoteMuting.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { DI } from '@/di-symbols.js';
+import { GetterService } from '@/server/api/GetterService.js';
+import { ApiError } from '../../error.js';
+
+export const meta = {
+ tags: ['account'],
+
+ requireCredential: true,
+
+ kind: 'write:mutes',
+
+ limit: {
+ duration: ms('1hour'),
+ max: 20,
+ },
+
+ errors: {
+ noSuchUser: {
+ message: 'No such user.',
+ code: 'NO_SUCH_USER',
+ id: '5e0a5dff-1e94-4202-87ae-4d9c89eb2271',
+ },
+
+ muteeIsYourself: {
+ message: 'Mutee is yourself.',
+ code: 'MUTEE_IS_YOURSELF',
+ id: '37285718-52f7-4aef-b7de-c38b8e8a8420',
+ },
+
+ alreadyMuting: {
+ message: 'You are already muting that user.',
+ code: 'ALREADY_MUTING',
+ id: 'ccfecbe4-1f1c-4fc2-8a3d-c3ffee61cb7b',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ userId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['userId'],
+} as const;
+
+// eslint-disable-next-line import/no-default-export
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.renoteMutingsRepository)
+ private renoteMutingsRepository: RenoteMutingsRepository,
+
+ private globalEventService: GlobalEventService,
+ private getterService: GetterService,
+ private idService: IdService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const muter = me;
+
+ // 自分自身
+ if (me.id === ps.userId) {
+ throw new ApiError(meta.errors.muteeIsYourself);
+ }
+
+ // Get mutee
+ const mutee = await getterService.getUser(ps.userId).catch(err => {
+ if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+ throw err;
+ });
+
+ // Check if already muting
+ const exist = await this.renoteMutingsRepository.findOneBy({
+ muterId: muter.id,
+ muteeId: mutee.id,
+ });
+
+ if (exist != null) {
+ throw new ApiError(meta.errors.alreadyMuting);
+ }
+
+ // Create mute
+ await this.renoteMutingsRepository.insert({
+ id: this.idService.genId(),
+ createdAt: new Date(),
+ muterId: muter.id,
+ muteeId: mutee.id,
+ } as RenoteMuting);
+
+ // publishUserEvent(user.id, 'mute', mutee);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/renote-mute/delete.ts b/packages/backend/src/server/api/endpoints/renote-mute/delete.ts
new file mode 100644
index 0000000000..51a895fb7e
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/renote-mute/delete.ts
@@ -0,0 +1,87 @@
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import type { RenoteMutingsRepository } from '@/models/index.js';
+import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { DI } from '@/di-symbols.js';
+import { GetterService } from '@/server/api/GetterService.js';
+import { ApiError } from '../../error.js';
+
+export const meta = {
+ tags: ['account'],
+
+ requireCredential: true,
+
+ kind: 'write:mutes',
+
+ errors: {
+ noSuchUser: {
+ message: 'No such user.',
+ code: 'NO_SUCH_USER',
+ id: '9b6728cf-638c-4aa1-bedb-e07d8101474d',
+ },
+
+ muteeIsYourself: {
+ message: 'Mutee is yourself.',
+ code: 'MUTEE_IS_YOURSELF',
+ id: '619b1314-0850-4597-a242-e245f3da42af',
+ },
+
+ notMuting: {
+ message: 'You are not muting that user.',
+ code: 'NOT_MUTING',
+ id: '2e4ef874-8bf0-4b4b-b069-4598f6d05817',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ userId: { type: 'string', format: 'misskey:id' },
+ },
+ required: ['userId'],
+} as const;
+
+// eslint-disable-next-line import/no-default-export
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.renoteMutingsRepository)
+ private renoteMutingsRepository: RenoteMutingsRepository,
+
+ private globalEventService: GlobalEventService,
+ private getterService: GetterService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const muter = me;
+
+ // Check if the mutee is yourself
+ if (me.id === ps.userId) {
+ throw new ApiError(meta.errors.muteeIsYourself);
+ }
+
+ // Get mutee
+ const mutee = await this.getterService.getUser(ps.userId).catch(err => {
+ if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
+ throw err;
+ });
+
+ // Check not muting
+ const exist = await this.renoteMutingsRepository.findOneBy({
+ muterId: muter.id,
+ muteeId: mutee.id,
+ });
+
+ if (exist == null) {
+ throw new ApiError(meta.errors.notMuting);
+ }
+
+ // Delete mute
+ await this.renoteMutingsRepository.delete({
+ id: exist.id,
+ });
+
+ // publishUserEvent(user.id, 'unmute', mutee);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/renote-mute/list.ts b/packages/backend/src/server/api/endpoints/renote-mute/list.ts
new file mode 100644
index 0000000000..b2d7addb64
--- /dev/null
+++ b/packages/backend/src/server/api/endpoints/renote-mute/list.ts
@@ -0,0 +1,57 @@
+import { Inject, Injectable } from '@nestjs/common';
+import { Endpoint } from '@/server/api/endpoint-base.js';
+import type { RenoteMutingsRepository } from '@/models/index.js';
+import { QueryService } from '@/core/QueryService.js';
+import { RenoteMutingEntityService } from '@/core/entities/RenoteMutingEntityService.js';
+import { DI } from '@/di-symbols.js';
+
+export const meta = {
+ tags: ['account'],
+
+ requireCredential: true,
+
+ kind: 'read:mutes',
+
+ res: {
+ type: 'array',
+ optional: false, nullable: false,
+ items: {
+ type: 'object',
+ optional: false, nullable: false,
+ ref: 'RenoteMuting',
+ },
+ },
+} as const;
+
+export const paramDef = {
+ type: 'object',
+ properties: {
+ limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 },
+ sinceId: { type: 'string', format: 'misskey:id' },
+ untilId: { type: 'string', format: 'misskey:id' },
+ },
+ required: [],
+} as const;
+
+// eslint-disable-next-line import/no-default-export
+@Injectable()
+export default class extends Endpoint<typeof meta, typeof paramDef> {
+ constructor(
+ @Inject(DI.renoteMutingsRepository)
+ private renoteMutingsRepository: RenoteMutingsRepository,
+
+ private renoteMutingEntityService: RenoteMutingEntityService,
+ private queryService: QueryService,
+ ) {
+ super(meta, paramDef, async (ps, me) => {
+ const query = this.queryService.makePaginationQuery(this.renoteMutingsRepository.createQueryBuilder('muting'), ps.sinceId, ps.untilId)
+ .andWhere('muting.muterId = :meId', { meId: me.id });
+
+ const mutings = await query
+ .take(ps.limit)
+ .getMany();
+
+ return await this.renoteMutingEntityService.packMany(mutings, me);
+ });
+ }
+}
diff --git a/packages/backend/src/server/api/endpoints/users/clips.ts b/packages/backend/src/server/api/endpoints/users/clips.ts
index e3fd0920c9..c5aa93baaf 100644
--- a/packages/backend/src/server/api/endpoints/users/clips.ts
+++ b/packages/backend/src/server/api/endpoints/users/clips.ts
@@ -51,7 +51,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
.take(ps.limit)
.getMany();
- return await this.clipEntityService.packMany(clips);
+ return await this.clipEntityService.packMany(clips, me);
});
}
}
diff --git a/packages/backend/src/server/api/endpoints/users/relation.ts b/packages/backend/src/server/api/endpoints/users/relation.ts
index ac9104bf92..3267c18846 100644
--- a/packages/backend/src/server/api/endpoints/users/relation.ts
+++ b/packages/backend/src/server/api/endpoints/users/relation.ts
@@ -50,6 +50,10 @@ export const meta = {
type: 'boolean',
optional: false, nullable: false,
},
+ isRenoteMuted: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
},
},
{
@@ -91,6 +95,10 @@ export const meta = {
type: 'boolean',
optional: false, nullable: false,
},
+ isRenoteMuted: {
+ type: 'boolean',
+ optional: false, nullable: false,
+ },
},
},
},
diff --git a/packages/backend/src/server/api/endpoints/users/show.ts b/packages/backend/src/server/api/endpoints/users/show.ts
index 29f24b045a..ba432c273b 100644
--- a/packages/backend/src/server/api/endpoints/users/show.ts
+++ b/packages/backend/src/server/api/endpoints/users/show.ts
@@ -48,6 +48,7 @@ export const meta = {
message: 'No such user.',
code: 'NO_SUCH_USER',
id: '4362f8dc-731f-4ad8-a694-be5a88922a24',
+ httpStatusCode: 404,
},
},
} as const;
diff --git a/packages/backend/src/server/api/error.ts b/packages/backend/src/server/api/error.ts
index 347d5650ad..34f4521606 100644
--- a/packages/backend/src/server/api/error.ts
+++ b/packages/backend/src/server/api/error.ts
@@ -1,4 +1,4 @@
-type E = { message: string, code: string, id: string, kind?: 'client' | 'server', httpStatusCode?: number };
+type E = { message: string, code: string, id: string, kind?: 'client' | 'server' | 'permission', httpStatusCode?: number };
export class ApiError extends Error {
public message: string;
diff --git a/packages/backend/src/server/api/openapi/OpenApiServerService.ts b/packages/backend/src/server/api/openapi/OpenApiServerService.ts
new file mode 100644
index 0000000000..e804ba276c
--- /dev/null
+++ b/packages/backend/src/server/api/openapi/OpenApiServerService.ts
@@ -0,0 +1,31 @@
+import { fileURLToPath } from 'node:url';
+import { Inject, Injectable } from '@nestjs/common';
+import type { Config } from '@/config.js';
+import { DI } from '@/di-symbols.js';
+import { bindThis } from '@/decorators.js';
+import { genOpenapiSpec } from './gen-spec.js';
+import type { FastifyInstance, FastifyPluginOptions } from 'fastify';
+
+const staticAssets = fileURLToPath(new URL('../../../../assets/', import.meta.url));
+
+@Injectable()
+export class OpenApiServerService {
+ constructor(
+ @Inject(DI.config)
+ private config: Config,
+ ) {
+ }
+
+ @bindThis
+ public createServer(fastify: FastifyInstance, _options: FastifyPluginOptions, done: (err?: Error) => void) {
+ fastify.get('/api-doc', async (_request, reply) => {
+ reply.header('Cache-Control', 'public, max-age=86400');
+ return await reply.sendFile('/redoc.html', staticAssets);
+ });
+ fastify.get('/api.json', (_request, reply) => {
+ reply.header('Cache-Control', 'public, max-age=600');
+ reply.send(genOpenapiSpec(this.config));
+ });
+ done();
+ }
+}
diff --git a/packages/backend/src/server/api/openapi/gen-spec.ts b/packages/backend/src/server/api/openapi/gen-spec.ts
new file mode 100644
index 0000000000..fa62480c02
--- /dev/null
+++ b/packages/backend/src/server/api/openapi/gen-spec.ts
@@ -0,0 +1,193 @@
+import type { Config } from '@/config.js';
+import endpoints from '../endpoints.js';
+import { errors as basicErrors } from './errors.js';
+import { schemas, convertSchemaToOpenApiSchema } from './schemas.js';
+
+export function genOpenapiSpec(config: Config) {
+ const spec = {
+ openapi: '3.0.0',
+
+ info: {
+ version: config.version,
+ title: 'Misskey API',
+ 'x-logo': { url: '/static-assets/api-doc.png' },
+ },
+
+ externalDocs: {
+ description: 'Repository',
+ url: 'https://github.com/misskey-dev/misskey',
+ },
+
+ servers: [{
+ url: config.apiUrl,
+ }],
+
+ paths: {} as any,
+
+ components: {
+ schemas: schemas,
+
+ securitySchemes: {
+ ApiKeyAuth: {
+ type: 'apiKey',
+ in: 'body',
+ name: 'i',
+ },
+ },
+ },
+ };
+
+ for (const endpoint of endpoints.filter(ep => !ep.meta.secure)) {
+ const errors = {} as any;
+
+ if (endpoint.meta.errors) {
+ for (const e of Object.values(endpoint.meta.errors)) {
+ errors[e.code] = {
+ value: {
+ error: e,
+ },
+ };
+ }
+ }
+
+ const resSchema = endpoint.meta.res ? convertSchemaToOpenApiSchema(endpoint.meta.res) : {};
+
+ let desc = (endpoint.meta.description ? endpoint.meta.description : 'No description provided.') + '\n\n';
+ desc += `**Credential required**: *${endpoint.meta.requireCredential ? 'Yes' : 'No'}*`;
+ if (endpoint.meta.kind) {
+ const kind = endpoint.meta.kind;
+ desc += ` / **Permission**: *${kind}*`;
+ }
+
+ const requestType = endpoint.meta.requireFile ? 'multipart/form-data' : 'application/json';
+ const schema = { ...endpoint.params };
+
+ if (endpoint.meta.requireFile) {
+ schema.properties = {
+ ...schema.properties,
+ file: {
+ type: 'string',
+ format: 'binary',
+ description: 'The file contents.',
+ },
+ };
+ schema.required = [...schema.required ?? [], 'file'];
+ }
+
+ const info = {
+ operationId: endpoint.name,
+ summary: endpoint.name,
+ description: desc,
+ externalDocs: {
+ description: 'Source code',
+ url: `https://github.com/misskey-dev/misskey/blob/develop/packages/backend/src/server/api/endpoints/${endpoint.name}.ts`,
+ },
+ ...(endpoint.meta.tags ? {
+ tags: [endpoint.meta.tags[0]],
+ } : {}),
+ ...(endpoint.meta.requireCredential ? {
+ security: [{
+ ApiKeyAuth: [],
+ }],
+ } : {}),
+ requestBody: {
+ required: true,
+ content: {
+ [requestType]: {
+ schema,
+ },
+ },
+ },
+ responses: {
+ ...(endpoint.meta.res ? {
+ '200': {
+ description: 'OK (with results)',
+ content: {
+ 'application/json': {
+ schema: resSchema,
+ },
+ },
+ },
+ } : {
+ '204': {
+ description: 'OK (without any results)',
+ },
+ }),
+ '400': {
+ description: 'Client error',
+ content: {
+ 'application/json': {
+ schema: {
+ $ref: '#/components/schemas/Error',
+ },
+ examples: { ...errors, ...basicErrors['400'] },
+ },
+ },
+ },
+ '401': {
+ description: 'Authentication error',
+ content: {
+ 'application/json': {
+ schema: {
+ $ref: '#/components/schemas/Error',
+ },
+ examples: basicErrors['401'],
+ },
+ },
+ },
+ '403': {
+ description: 'Forbidden error',
+ content: {
+ 'application/json': {
+ schema: {
+ $ref: '#/components/schemas/Error',
+ },
+ examples: basicErrors['403'],
+ },
+ },
+ },
+ '418': {
+ description: 'I\'m Ai',
+ content: {
+ 'application/json': {
+ schema: {
+ $ref: '#/components/schemas/Error',
+ },
+ examples: basicErrors['418'],
+ },
+ },
+ },
+ ...(endpoint.meta.limit ? {
+ '429': {
+ description: 'To many requests',
+ content: {
+ 'application/json': {
+ schema: {
+ $ref: '#/components/schemas/Error',
+ },
+ examples: basicErrors['429'],
+ },
+ },
+ },
+ } : {}),
+ '500': {
+ description: 'Internal server error',
+ content: {
+ 'application/json': {
+ schema: {
+ $ref: '#/components/schemas/Error',
+ },
+ examples: basicErrors['500'],
+ },
+ },
+ },
+ },
+ };
+
+ spec.paths['/' + endpoint.name] = {
+ post: info,
+ };
+ }
+
+ return spec;
+}
diff --git a/packages/backend/src/server/api/openapi/schemas.ts b/packages/backend/src/server/api/openapi/schemas.ts
index 796383f5e3..0cef361caf 100644
--- a/packages/backend/src/server/api/openapi/schemas.ts
+++ b/packages/backend/src/server/api/openapi/schemas.ts
@@ -1,5 +1,5 @@
-import type { Schema } from '@/misc/schema.js';
-import { refs } from '@/misc/schema.js';
+import type { Schema } from '@/misc/json-schema.js';
+import { refs } from '@/misc/json-schema.js';
export function convertSchemaToOpenApiSchema(schema: Schema) {
const res: any = schema;
diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts
index 3e67880b45..32935325aa 100644
--- a/packages/backend/src/server/api/stream/channel.ts
+++ b/packages/backend/src/server/api/stream/channel.ts
@@ -27,6 +27,10 @@ export default abstract class Channel {
return this.connection.muting;
}
+ protected get renoteMuting() {
+ return this.connection.renoteMuting;
+ }
+
protected get blocking() {
return this.connection.blocking;
}
diff --git a/packages/backend/src/server/api/stream/channels/antenna.ts b/packages/backend/src/server/api/stream/channels/antenna.ts
index 18604d94f0..e2a42fbfe9 100644
--- a/packages/backend/src/server/api/stream/channels/antenna.ts
+++ b/packages/backend/src/server/api/stream/channels/antenna.ts
@@ -39,6 +39,8 @@ class AntennaChannel extends Channel {
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
if (isUserRelated(note, this.blocking)) return;
+ if (note.renote && !note.text && isUserRelated(note, this.renoteMuting)) return;
+
this.connection.cacheNote(note);
this.send('note', note);
diff --git a/packages/backend/src/server/api/stream/channels/channel.ts b/packages/backend/src/server/api/stream/channels/channel.ts
index f5ef1d1102..12caa7f233 100644
--- a/packages/backend/src/server/api/stream/channels/channel.ts
+++ b/packages/backend/src/server/api/stream/channels/channel.ts
@@ -1,6 +1,6 @@
import { Injectable } from '@nestjs/common';
import { isUserRelated } from '@/misc/is-user-related.js';
-import type { Packed } from '@/misc/schema.js';
+import type { Packed } from '@/misc/json-schema.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { bindThis } from '@/decorators.js';
import Channel from '../channel.js';
@@ -51,6 +51,8 @@ class ChannelChannel extends Channel {
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
if (isUserRelated(note, this.blocking)) return;
+ if (note.renote && !note.text && isUserRelated(note, this.renoteMuting)) return;
+
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 b8c0076ed9..d79247cd6e 100644
--- a/packages/backend/src/server/api/stream/channels/global-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts
@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
import { checkWordMute } from '@/misc/check-word-mute.js';
import { isInstanceMuted } from '@/misc/is-instance-muted.js';
import { isUserRelated } from '@/misc/is-user-related.js';
-import type { Packed } from '@/misc/schema.js';
+import type { Packed } from '@/misc/json-schema.js';
import { MetaService } from '@/core/MetaService.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { bindThis } from '@/decorators.js';
@@ -68,6 +68,8 @@ class GlobalTimelineChannel extends Channel {
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
if (isUserRelated(note, this.blocking)) return;
+ if (note.renote && !note.text && isUserRelated(note, this.renoteMuting)) return;
+
// 流れてきたNoteがミュートすべきNoteだったら無視する
// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
diff --git a/packages/backend/src/server/api/stream/channels/hashtag.ts b/packages/backend/src/server/api/stream/channels/hashtag.ts
index 00f8d8ecd2..98dc858ded 100644
--- a/packages/backend/src/server/api/stream/channels/hashtag.ts
+++ b/packages/backend/src/server/api/stream/channels/hashtag.ts
@@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common';
import { normalizeForSearch } from '@/misc/normalize-for-search.js';
import { isUserRelated } from '@/misc/is-user-related.js';
-import type { Packed } from '@/misc/schema.js';
+import type { Packed } from '@/misc/json-schema.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { bindThis } from '@/decorators.js';
import Channel from '../channel.js';
@@ -49,6 +49,8 @@ class HashtagChannel extends Channel {
if (isUserRelated(note, this.muting)) return;
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
if (isUserRelated(note, this.blocking)) return;
+
+ if (note.renote && !note.text && isUserRelated(note, this.renoteMuting)) return;
this.connection.cacheNote(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 04a9f29686..c623fef64a 100644
--- a/packages/backend/src/server/api/stream/channels/home-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts
@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
import { checkWordMute } from '@/misc/check-word-mute.js';
import { isUserRelated } from '@/misc/is-user-related.js';
import { isInstanceMuted } from '@/misc/is-instance-muted.js';
-import type { Packed } from '@/misc/schema.js';
+import type { Packed } from '@/misc/json-schema.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { bindThis } from '@/decorators.js';
import Channel from '../channel.js';
@@ -75,6 +75,8 @@ class HomeTimelineChannel extends Channel {
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
if (isUserRelated(note, this.blocking)) return;
+ if (note.renote && !note.text && isUserRelated(note, this.renoteMuting)) return;
+
// 流れてきたNoteがミュートすべきNoteだったら無視する
// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
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 ab52aabb30..f54767bc9d 100644
--- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
@@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common';
import { checkWordMute } from '@/misc/check-word-mute.js';
import { isUserRelated } from '@/misc/is-user-related.js';
import { isInstanceMuted } from '@/misc/is-instance-muted.js';
-import type { Packed } from '@/misc/schema.js';
+import type { Packed } from '@/misc/json-schema.js';
import { MetaService } from '@/core/MetaService.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { bindThis } from '@/decorators.js';
@@ -86,6 +86,8 @@ class HybridTimelineChannel extends Channel {
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
if (isUserRelated(note, this.blocking)) return;
+ if (note.renote && !note.text && isUserRelated(note, this.renoteMuting)) return;
+
// 流れてきたNoteがミュートすべきNoteだったら無視する
// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
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 d8532c477b..eb0642900d 100644
--- a/packages/backend/src/server/api/stream/channels/local-timeline.ts
+++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts
@@ -1,7 +1,7 @@
import { Injectable } from '@nestjs/common';
import { checkWordMute } from '@/misc/check-word-mute.js';
import { isUserRelated } from '@/misc/is-user-related.js';
-import type { Packed } from '@/misc/schema.js';
+import type { Packed } from '@/misc/json-schema.js';
import { MetaService } from '@/core/MetaService.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { bindThis } from '@/decorators.js';
@@ -65,6 +65,8 @@ class LocalTimelineChannel extends Channel {
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
if (isUserRelated(note, this.blocking)) return;
+ if (note.renote && !note.text && isUserRelated(note, this.renoteMuting)) return;
+
// 流れてきたNoteがミュートすべきNoteだったら無視する
// TODO: 将来的には、単にMutedNoteテーブルにレコードがあるかどうかで判定したい(以下の理由により難しそうではある)
// 現状では、ワードミュートにおけるMutedNoteレコードの追加処理はストリーミングに流す処理と並列で行われるため、
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 7254d0a6d4..8a42e99a54 100644
--- a/packages/backend/src/server/api/stream/channels/user-list.ts
+++ b/packages/backend/src/server/api/stream/channels/user-list.ts
@@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
import type { UserListJoiningsRepository, UserListsRepository } from '@/models/index.js';
import type { User } from '@/models/entities/User.js';
import { isUserRelated } from '@/misc/is-user-related.js';
-import type { Packed } from '@/misc/schema.js';
+import type { Packed } from '@/misc/json-schema.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js';
@@ -93,6 +93,8 @@ class UserListChannel extends Channel {
// 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
if (isUserRelated(note, this.blocking)) return;
+ if (note.renote && !note.text && isUserRelated(note, this.renoteMuting)) return;
+
this.send('note', note);
}
diff --git a/packages/backend/src/server/api/stream/index.ts b/packages/backend/src/server/api/stream/index.ts
index d3056aca57..7c6eb9a20a 100644
--- a/packages/backend/src/server/api/stream/index.ts
+++ b/packages/backend/src/server/api/stream/index.ts
@@ -1,9 +1,9 @@
import type { User } from '@/models/entities/User.js';
import type { Channel as ChannelModel } from '@/models/entities/Channel.js';
-import type { FollowingsRepository, MutingsRepository, UserProfilesRepository, ChannelFollowingsRepository, BlockingsRepository } from '@/models/index.js';
+import type { FollowingsRepository, MutingsRepository, RenoteMutingsRepository, UserProfilesRepository, ChannelFollowingsRepository, BlockingsRepository } from '@/models/index.js';
import type { AccessToken } from '@/models/entities/AccessToken.js';
import type { UserProfile } from '@/models/entities/UserProfile.js';
-import type { Packed } from '@/misc/schema.js';
+import type { Packed } from '@/misc/json-schema.js';
import type { GlobalEventService } from '@/core/GlobalEventService.js';
import type { NoteReadService } from '@/core/NoteReadService.js';
import type { NotificationService } from '@/core/NotificationService.js';
@@ -22,6 +22,7 @@ export default class Connection {
public userProfile?: UserProfile | null;
public following: Set<User['id']> = new Set();
public muting: Set<User['id']> = new Set();
+ public renoteMuting: Set<User['id']> = new Set();
public blocking: Set<User['id']> = new Set(); // "被"blocking
public followingChannels: Set<ChannelModel['id']> = new Set();
public token?: AccessToken;
@@ -34,6 +35,7 @@ export default class Connection {
constructor(
private followingsRepository: FollowingsRepository,
private mutingsRepository: MutingsRepository,
+ private renoteMutingsRepository: RenoteMutingsRepository,
private blockingsRepository: BlockingsRepository,
private channelFollowingsRepository: ChannelFollowingsRepository,
private userProfilesRepository: UserProfilesRepository,
@@ -66,6 +68,7 @@ export default class Connection {
if (this.user) {
this.updateFollowing();
this.updateMuting();
+ this.updateRenoteMuting();
this.updateBlocking();
this.updateFollowingChannels();
this.updateUserProfile();
@@ -93,6 +96,7 @@ export default class Connection {
this.muting.delete(data.body.id);
break;
+ // TODO: renote mute events
// TODO: block events
case 'followChannel':
@@ -343,6 +347,18 @@ export default class Connection {
}
@bindThis
+ private async updateRenoteMuting() {
+ const renoteMutings = await this.renoteMutingsRepository.find({
+ where: {
+ muterId: this.user!.id,
+ },
+ select: ['muteeId'],
+ });
+
+ this.renoteMuting = new Set<string>(renoteMutings.map(x => x.muteeId));
+ }
+
+ @bindThis
private async updateBlocking() { // ここでいうBlockingは被Blockingの意
const blockings = await this.blockingsRepository.find({
where: {
diff --git a/packages/backend/src/server/api/stream/types.ts b/packages/backend/src/server/api/stream/types.ts
index c450773055..b8f50e0546 100644
--- a/packages/backend/src/server/api/stream/types.ts
+++ b/packages/backend/src/server/api/stream/types.ts
@@ -9,7 +9,7 @@ import type { UserList } from '@/models/entities/UserList.js';
import type { AbuseUserReport } from '@/models/entities/AbuseUserReport.js';
import type { Signin } from '@/models/entities/Signin.js';
import type { Page } from '@/models/entities/Page.js';
-import type { Packed } from '@/misc/schema.js';
+import type { Packed } from '@/misc/json-schema.js';
import type { Webhook } from '@/models/entities/Webhook.js';
import type { Meta } from '@/models/entities/Meta.js';
import { Role, RoleAssignment } from '@/models';