merge upstream
This commit is contained in:
commit
d8908ef2d8
1065 changed files with 32953 additions and 20092 deletions
|
|
@ -369,7 +369,7 @@ export class ApiCallService implements OnApplicationShutdown {
|
|||
}
|
||||
}
|
||||
|
||||
if ((ep.meta.requireModerator || ep.meta.requireAdmin) && !user!.isRoot) {
|
||||
if ((ep.meta.requireModerator || ep.meta.requireAdmin) && (this.meta.rootUserId !== user!.id)) {
|
||||
const myRoles = await this.roleService.getUserRoles(user!.id);
|
||||
if (ep.meta.requireModerator && !myRoles.some(r => r.isModerator || r.isAdministrator)) {
|
||||
throw new ApiError({
|
||||
|
|
@ -389,10 +389,10 @@ export class ApiCallService implements OnApplicationShutdown {
|
|||
}
|
||||
}
|
||||
|
||||
if (ep.meta.requireRolePolicy != null && !user!.isRoot) {
|
||||
if (ep.meta.requiredRolePolicy != null && (this.meta.rootUserId !== user!.id)) {
|
||||
const myRoles = await this.roleService.getUserRoles(user!.id);
|
||||
const policies = await this.roleService.getUserPolicies(user!.id);
|
||||
if (!policies[ep.meta.requireRolePolicy] && !myRoles.some(r => r.isAdministrator)) {
|
||||
if (!policies[ep.meta.requiredRolePolicy] && !myRoles.some(r => r.isAdministrator)) {
|
||||
throw new ApiError({
|
||||
message: 'You are not assigned to a required role.',
|
||||
code: 'ROLE_PERMISSION_DENIED',
|
||||
|
|
|
|||
|
|
@ -6,7 +6,6 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import cors from '@fastify/cors';
|
||||
import multipart from '@fastify/multipart';
|
||||
import fastifyCookie from '@fastify/cookie';
|
||||
import { ModuleRef } from '@nestjs/core';
|
||||
import { AuthenticationResponseJSON } from '@simplewebauthn/types';
|
||||
import type { Config } from '@/config.js';
|
||||
|
|
@ -57,8 +56,6 @@ export class ApiServerService {
|
|||
},
|
||||
});
|
||||
|
||||
fastify.register(fastifyCookie, {});
|
||||
|
||||
// Prevent cache
|
||||
fastify.addHook('onRequest', (request, reply, done) => {
|
||||
reply.header('Cache-Control', 'private, max-age=0, must-revalidate');
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import type { MiAccessToken } from '@/models/AccessToken.js';
|
|||
import { MemoryKVCache } from '@/misc/cache.js';
|
||||
import type { MiApp } from '@/models/App.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
import isNativeToken from '@/misc/is-native-token.js';
|
||||
import { isNativeUserToken } from '@/misc/token.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
||||
export class AuthenticationError extends Error {
|
||||
|
|
@ -46,7 +46,7 @@ export class AuthenticateService implements OnApplicationShutdown {
|
|||
return [null, null];
|
||||
}
|
||||
|
||||
if (isNativeToken(token)) {
|
||||
if (isNativeUserToken(token)) {
|
||||
const user = await this.cacheService.localUserByNativeTokenCache.fetch(token,
|
||||
() => this.usersRepository.findOneBy({ token }) as Promise<MiLocalUser | null>);
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import proxyAddr from 'proxy-addr';
|
|||
import ms from 'ms';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { UsersRepository, MiAccessToken } from '@/models/_.js';
|
||||
import { NoteReadService } from '@/core/NoteReadService.js';
|
||||
import { NotificationService } from '@/core/NotificationService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
|
|
@ -42,7 +41,6 @@ export class StreamingApiServerService {
|
|||
private usersRepository: UsersRepository,
|
||||
|
||||
private cacheService: CacheService,
|
||||
private noteReadService: NoteReadService,
|
||||
private authenticateService: AuthenticateService,
|
||||
private channelsService: ChannelsService,
|
||||
private notificationService: NotificationService,
|
||||
|
|
@ -154,7 +152,6 @@ export class StreamingApiServerService {
|
|||
|
||||
const stream = new MainStreamConnection(
|
||||
this.channelsService,
|
||||
this.noteReadService,
|
||||
this.notificationService,
|
||||
this.cacheService,
|
||||
this.channelFollowingService,
|
||||
|
|
|
|||
|
|
@ -109,6 +109,7 @@ export * as 'admin/unsilence-user' from './endpoints/admin/unsilence-user.js';
|
|||
export * as 'admin/unsuspend-user' from './endpoints/admin/unsuspend-user.js';
|
||||
export * as 'admin/update-abuse-user-report' from './endpoints/admin/update-abuse-user-report.js';
|
||||
export * as 'admin/update-meta' from './endpoints/admin/update-meta.js';
|
||||
export * as 'admin/update-proxy-account' from './endpoints/admin/update-proxy-account.js';
|
||||
export * as 'admin/update-user-note' from './endpoints/admin/update-user-note.js';
|
||||
export * as 'announcements' from './endpoints/announcements.js';
|
||||
export * as 'announcements/show' from './endpoints/announcements/show.js';
|
||||
|
|
@ -273,7 +274,6 @@ export * as 'i/notifications-grouped' from './endpoints/i/notifications-grouped.
|
|||
export * as 'i/page-likes' from './endpoints/i/page-likes.js';
|
||||
export * as 'i/pages' from './endpoints/i/pages.js';
|
||||
export * as 'i/pin' from './endpoints/i/pin.js';
|
||||
export * as 'i/read-all-unread-notes' from './endpoints/i/read-all-unread-notes.js';
|
||||
export * as 'i/read-announcement' from './endpoints/i/read-announcement.js';
|
||||
export * as 'i/regenerate-token' from './endpoints/i/regenerate-token.js';
|
||||
export * as 'i/registry/get' from './endpoints/i/registry/get.js';
|
||||
|
|
@ -418,4 +418,26 @@ export * as 'users/search' from './endpoints/users/search.js';
|
|||
export * as 'users/search-by-username-and-host' from './endpoints/users/search-by-username-and-host.js';
|
||||
export * as 'users/show' from './endpoints/users/show.js';
|
||||
export * as 'users/update-memo' from './endpoints/users/update-memo.js';
|
||||
export * as 'chat/messages/create-to-user' from './endpoints/chat/messages/create-to-user.js';
|
||||
export * as 'chat/messages/create-to-room' from './endpoints/chat/messages/create-to-room.js';
|
||||
export * as 'chat/messages/delete' from './endpoints/chat/messages/delete.js';
|
||||
export * as 'chat/messages/show' from './endpoints/chat/messages/show.js';
|
||||
export * as 'chat/messages/react' from './endpoints/chat/messages/react.js';
|
||||
export * as 'chat/messages/user-timeline' from './endpoints/chat/messages/user-timeline.js';
|
||||
export * as 'chat/messages/room-timeline' from './endpoints/chat/messages/room-timeline.js';
|
||||
export * as 'chat/messages/search' from './endpoints/chat/messages/search.js';
|
||||
export * as 'chat/rooms/create' from './endpoints/chat/rooms/create.js';
|
||||
export * as 'chat/rooms/delete' from './endpoints/chat/rooms/delete.js';
|
||||
export * as 'chat/rooms/join' from './endpoints/chat/rooms/join.js';
|
||||
export * as 'chat/rooms/leave' from './endpoints/chat/rooms/leave.js';
|
||||
export * as 'chat/rooms/mute' from './endpoints/chat/rooms/mute.js';
|
||||
export * as 'chat/rooms/show' from './endpoints/chat/rooms/show.js';
|
||||
export * as 'chat/rooms/owned' from './endpoints/chat/rooms/owned.js';
|
||||
export * as 'chat/rooms/joining' from './endpoints/chat/rooms/joining.js';
|
||||
export * as 'chat/rooms/update' from './endpoints/chat/rooms/update.js';
|
||||
export * as 'chat/rooms/members' from './endpoints/chat/rooms/members.js';
|
||||
export * as 'chat/rooms/invitations/create' from './endpoints/chat/rooms/invitations/create.js';
|
||||
export * as 'chat/rooms/invitations/ignore' from './endpoints/chat/rooms/invitations/ignore.js';
|
||||
export * as 'chat/rooms/invitations/inbox' from './endpoints/chat/rooms/invitations/inbox.js';
|
||||
export * as 'chat/history' from './endpoints/chat/history.js';
|
||||
export * as 'v2/admin/emoji/list' from './endpoints/v2/admin/emoji/list.js';
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ interface IEndpointMetaBase {
|
|||
*/
|
||||
readonly requireAdmin?: boolean;
|
||||
|
||||
readonly requireRolePolicy?: KeyOf<'RolePolicies'>;
|
||||
readonly requiredRolePolicy?: KeyOf<'RolePolicies'>;
|
||||
|
||||
/**
|
||||
* 引っ越し済みのユーザーによるリクエストを禁止するか
|
||||
|
|
@ -100,7 +100,7 @@ export type IEndpointMeta = (Omit<IEndpointMetaBase, 'requireCrential' | 'requir
|
|||
}) | (Omit<IEndpointMetaBase, 'requireAdmin' | 'kind'> & {
|
||||
requireAdmin: true,
|
||||
kind: (typeof permissions)[number],
|
||||
})
|
||||
});
|
||||
|
||||
export interface IEndpoint {
|
||||
name: string;
|
||||
|
|
|
|||
|
|
@ -5,10 +5,9 @@
|
|||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { UsersRepository } from '@/models/_.js';
|
||||
import type { MiMeta, UsersRepository } from '@/models/_.js';
|
||||
import { SignupService } from '@/core/SignupService.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { InstanceActorService } from '@/core/InstanceActorService.js';
|
||||
import { localUsernameSchema, passwordSchema } from '@/models/User.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { Config } from '@/config.js';
|
||||
|
|
@ -90,20 +89,21 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
@Inject(DI.config)
|
||||
private config: Config,
|
||||
|
||||
@Inject(DI.meta)
|
||||
private serverSettings: MiMeta,
|
||||
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
private roleService: RoleService,
|
||||
private userEntityService: UserEntityService,
|
||||
private signupService: SignupService,
|
||||
private instanceActorService: InstanceActorService,
|
||||
private readonly moderationLogService: ModerationLogService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, _me, token) => {
|
||||
const me = _me ? await this.usersRepository.findOneByOrFail({ id: _me.id }) : null;
|
||||
const realUsers = await this.instanceActorService.realLocalUsersPresent();
|
||||
|
||||
if (!realUsers && me == null && token == null) {
|
||||
if (this.serverSettings.rootUserId == null && me == null && token == null) {
|
||||
// 初回セットアップの場合
|
||||
if (this.config.setupPassword != null) {
|
||||
// 初期パスワードが設定されている場合
|
||||
|
|
@ -127,7 +127,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
}
|
||||
|
||||
// Anonymous access is only allowed for initial instance setup (this check may be redundant)
|
||||
if (!me && realUsers) {
|
||||
if (!me && this.serverSettings.rootUserId != null) {
|
||||
throw new ApiError(meta.errors.noCredential);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,10 +42,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
throw new Error('user not found');
|
||||
}
|
||||
|
||||
if (user.isRoot) {
|
||||
throw new Error('cannot delete a root account');
|
||||
}
|
||||
|
||||
await this.deleteAccoountService.deleteAccount(user, me);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export const meta = {
|
|||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageAvatarDecorations',
|
||||
requiredRolePolicy: 'canManageAvatarDecorations',
|
||||
kind: 'write:admin:avatar-decorations',
|
||||
|
||||
res: {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export const meta = {
|
|||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageAvatarDecorations',
|
||||
requiredRolePolicy: 'canManageAvatarDecorations',
|
||||
kind: 'write:admin:avatar-decorations',
|
||||
errors: {
|
||||
},
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export const meta = {
|
|||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageAvatarDecorations',
|
||||
requiredRolePolicy: 'canManageAvatarDecorations',
|
||||
kind: 'read:admin:avatar-decorations',
|
||||
|
||||
res: {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ export const meta = {
|
|||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageAvatarDecorations',
|
||||
requiredRolePolicy: 'canManageAvatarDecorations',
|
||||
kind: 'write:admin:avatar-decorations',
|
||||
|
||||
errors: {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export const meta = {
|
|||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageCustomEmojis',
|
||||
requiredRolePolicy: 'canManageCustomEmojis',
|
||||
kind: 'write:admin:emoji',
|
||||
} as const;
|
||||
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export const meta = {
|
|||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageCustomEmojis',
|
||||
requiredRolePolicy: 'canManageCustomEmojis',
|
||||
kind: 'write:admin:emoji',
|
||||
|
||||
errors: {
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ export const meta = {
|
|||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageCustomEmojis',
|
||||
requiredRolePolicy: 'canManageCustomEmojis',
|
||||
kind: 'write:admin:emoji',
|
||||
|
||||
errors: {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export const meta = {
|
|||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageCustomEmojis',
|
||||
requiredRolePolicy: 'canManageCustomEmojis',
|
||||
kind: 'write:admin:emoji',
|
||||
} as const;
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export const meta = {
|
|||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageCustomEmojis',
|
||||
requiredRolePolicy: 'canManageCustomEmojis',
|
||||
kind: 'write:admin:emoji',
|
||||
|
||||
errors: {
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ import { DI } from '@/di-symbols.js';
|
|||
export const meta = {
|
||||
secure: true,
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageCustomEmojis',
|
||||
requiredRolePolicy: 'canManageCustomEmojis',
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export const meta = {
|
|||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageCustomEmojis',
|
||||
requiredRolePolicy: 'canManageCustomEmojis',
|
||||
kind: 'read:admin:emoji',
|
||||
|
||||
res: {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ export const meta = {
|
|||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageCustomEmojis',
|
||||
requiredRolePolicy: 'canManageCustomEmojis',
|
||||
kind: 'read:admin:emoji',
|
||||
|
||||
res: {
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export const meta = {
|
|||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageCustomEmojis',
|
||||
requiredRolePolicy: 'canManageCustomEmojis',
|
||||
kind: 'write:admin:emoji',
|
||||
} as const;
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export const meta = {
|
|||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageCustomEmojis',
|
||||
requiredRolePolicy: 'canManageCustomEmojis',
|
||||
kind: 'write:admin:emoji',
|
||||
} as const;
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export const meta = {
|
|||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageCustomEmojis',
|
||||
requiredRolePolicy: 'canManageCustomEmojis',
|
||||
kind: 'write:admin:emoji',
|
||||
} as const;
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export const meta = {
|
|||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageCustomEmojis',
|
||||
requiredRolePolicy: 'canManageCustomEmojis',
|
||||
kind: 'write:admin:emoji',
|
||||
} as const;
|
||||
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export const meta = {
|
|||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageCustomEmojis',
|
||||
requiredRolePolicy: 'canManageCustomEmojis',
|
||||
kind: 'write:admin:emoji',
|
||||
|
||||
errors: {
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import type { Config } from '@/config.js';
|
|||
import { DI } from '@/di-symbols.js';
|
||||
import { DEFAULT_POLICIES } from '@/core/RoleService.js';
|
||||
import { instanceUnsignedFetchOptions } from '@/const.js';
|
||||
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['meta'],
|
||||
|
|
@ -86,6 +87,10 @@ export const meta = {
|
|||
type: 'boolean',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
googleAnalyticsMeasurementId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
},
|
||||
swPublickey: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
|
|
@ -265,7 +270,7 @@ export const meta = {
|
|||
},
|
||||
proxyAccountId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
optional: false, nullable: false,
|
||||
format: 'id',
|
||||
},
|
||||
email: {
|
||||
|
|
@ -580,6 +585,7 @@ export const meta = {
|
|||
},
|
||||
federation: {
|
||||
type: 'string',
|
||||
enum: ['all', 'specified', 'none'],
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
federationHosts: {
|
||||
|
|
@ -617,10 +623,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private config: Config,
|
||||
|
||||
private metaService: MetaService,
|
||||
private systemAccountService: SystemAccountService,
|
||||
) {
|
||||
super(meta, paramDef, async () => {
|
||||
const instance = await this.metaService.fetch(true);
|
||||
|
||||
const proxy = await this.systemAccountService.fetch('proxy');
|
||||
|
||||
return {
|
||||
maintainerName: instance.maintainerName,
|
||||
maintainerEmail: instance.maintainerEmail,
|
||||
|
|
@ -652,6 +661,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
enableFC: instance.enableFC,
|
||||
fcSiteKey: instance.fcSiteKey,
|
||||
enableTestcaptcha: instance.enableTestcaptcha,
|
||||
googleAnalyticsMeasurementId: instance.googleAnalyticsMeasurementId,
|
||||
swPublickey: instance.swPublicKey,
|
||||
themeColor: instance.themeColor,
|
||||
mascotImageUrl: instance.mascotImageUrl,
|
||||
|
|
@ -693,7 +703,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
setSensitiveFlagAutomatically: instance.setSensitiveFlagAutomatically,
|
||||
enableSensitiveMediaDetectionForVideos: instance.enableSensitiveMediaDetectionForVideos,
|
||||
enableBotTrending: instance.enableBotTrending,
|
||||
proxyAccountId: instance.proxyAccountId,
|
||||
proxyAccountId: proxy.id,
|
||||
email: instance.email,
|
||||
smtpSecure: instance.smtpSecure,
|
||||
smtpHost: instance.smtpHost,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
|||
//import bcrypt from 'bcryptjs';
|
||||
import * as argon2 from 'argon2';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { UsersRepository, UserProfilesRepository } from '@/models/_.js';
|
||||
import type { UsersRepository, UserProfilesRepository, MiMeta } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
|
|
@ -45,6 +45,9 @@ export const paramDef = {
|
|||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
@Inject(DI.meta)
|
||||
private serverSettings: MiMeta,
|
||||
|
||||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
|
|
@ -60,7 +63,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
throw new Error('user not found');
|
||||
}
|
||||
|
||||
if (user.isRoot) {
|
||||
if (this.serverSettings.rootUserId === user.id) {
|
||||
throw new Error('cannot reset password of root');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -91,12 +91,12 @@ export const paramDef = {
|
|||
fcSiteKey: { type: 'string', nullable: true },
|
||||
fcSecretKey: { type: 'string', nullable: true },
|
||||
enableTestcaptcha: { type: 'boolean' },
|
||||
googleAnalyticsMeasurementId: { type: 'string', nullable: true },
|
||||
sensitiveMediaDetection: { type: 'string', enum: ['none', 'all', 'local', 'remote'] },
|
||||
sensitiveMediaDetectionSensitivity: { type: 'string', enum: ['medium', 'low', 'high', 'veryLow', 'veryHigh'] },
|
||||
setSensitiveFlagAutomatically: { type: 'boolean' },
|
||||
enableSensitiveMediaDetectionForVideos: { type: 'boolean' },
|
||||
enableBotTrending: { type: 'boolean' },
|
||||
proxyAccountId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||
maintainerName: { type: 'string', nullable: true },
|
||||
maintainerEmail: { type: 'string', nullable: true },
|
||||
langs: {
|
||||
|
|
@ -130,7 +130,7 @@ export const paramDef = {
|
|||
useObjectStorage: { type: 'boolean' },
|
||||
objectStorageBaseUrl: { type: 'string', nullable: true },
|
||||
objectStorageBucket: { type: 'string', nullable: true },
|
||||
objectStoragePrefix: { type: 'string', nullable: true },
|
||||
objectStoragePrefix: { type: 'string', pattern: /^[a-zA-Z0-9-._]*$/.source, nullable: true },
|
||||
objectStorageEndpoint: { type: 'string', nullable: true },
|
||||
objectStorageRegion: { type: 'string', nullable: true },
|
||||
objectStoragePort: { type: 'integer', nullable: true },
|
||||
|
|
@ -413,6 +413,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
set.enableTestcaptcha = ps.enableTestcaptcha;
|
||||
}
|
||||
|
||||
if (ps.googleAnalyticsMeasurementId !== undefined) {
|
||||
// 空文字列をnullにしたいので??は使わない
|
||||
set.googleAnalyticsMeasurementId = ps.googleAnalyticsMeasurementId || null;
|
||||
}
|
||||
|
||||
if (ps.fcSiteKey !== undefined) {
|
||||
set.fcSiteKey = ps.fcSiteKey;
|
||||
}
|
||||
|
|
@ -425,10 +430,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
set.enableBotTrending = ps.enableBotTrending;
|
||||
}
|
||||
|
||||
if (ps.proxyAccountId !== undefined) {
|
||||
set.proxyAccountId = ps.proxyAccountId;
|
||||
}
|
||||
|
||||
if (ps.maintainerName !== undefined) {
|
||||
set.maintainerName = ps.maintainerName;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import {
|
||||
descriptionSchema,
|
||||
} from '@/models/User.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import { SystemAccountService } from '@/core/SystemAccountService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'write:admin:account',
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
nullable: false, optional: false,
|
||||
ref: 'UserDetailed',
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
description: { ...descriptionSchema, nullable: true },
|
||||
},
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private userEntityService: UserEntityService,
|
||||
private moderationLogService: ModerationLogService,
|
||||
private systemAccountService: SystemAccountService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const proxy = await this.systemAccountService.updateCorrespondingUserProfile('proxy', {
|
||||
description: ps.description,
|
||||
});
|
||||
|
||||
const updated = await this.userEntityService.pack(proxy.id, proxy, {
|
||||
schema: 'MeDetailed',
|
||||
});
|
||||
|
||||
if (ps.description !== undefined) {
|
||||
this.moderationLogService.log(me, 'updateProxyAccountDescription', {
|
||||
before: null, //TODO
|
||||
after: ps.description,
|
||||
});
|
||||
}
|
||||
|
||||
return updated;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -8,7 +8,6 @@ import * as Redis from 'ioredis';
|
|||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { NotesRepository, AntennasRepository } from '@/models/_.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import { NoteReadService } from '@/core/NoteReadService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
|
|
@ -65,9 +64,6 @@ export const paramDef = {
|
|||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
@Inject(DI.redisForTimelines)
|
||||
private redisForTimelines: Redis.Redis,
|
||||
|
||||
@Inject(DI.notesRepository)
|
||||
private notesRepository: NotesRepository,
|
||||
|
||||
|
|
@ -77,7 +73,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private idService: IdService,
|
||||
private noteEntityService: NoteEntityService,
|
||||
private queryService: QueryService,
|
||||
private noteReadService: NoteReadService,
|
||||
private fanoutTimelineService: FanoutTimelineService,
|
||||
private globalEventService: GlobalEventService,
|
||||
) {
|
||||
|
|
@ -130,8 +125,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
notes.sort((a, b) => a.id > b.id ? -1 : 1);
|
||||
}
|
||||
|
||||
this.noteReadService.read(me.id, notes);
|
||||
|
||||
return await this.noteEntityService.packMany(notes, me);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import { ApRequestService } from '@/core/activitypub/ApRequestService.js';
|
|||
import { InstanceActorService } from '@/core/InstanceActorService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
import { FetchAllowSoftFailMask } from '@/core/activitypub/misc/check-against-url.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['federation'],
|
||||
|
|
@ -55,11 +56,6 @@ export const meta = {
|
|||
code: 'RESPONSE_INVALID',
|
||||
id: '70193c39-54f3-4813-82f0-70a680f7495b',
|
||||
},
|
||||
responseInvalidIdHostNotMatch: {
|
||||
message: 'Requested URI and response URI host does not match.',
|
||||
code: 'RESPONSE_INVALID_ID_HOST_NOT_MATCH',
|
||||
id: 'a2c9c61a-cb72-43ab-a964-3ca5fddb410a',
|
||||
},
|
||||
noSuchObject: {
|
||||
message: 'No such object.',
|
||||
code: 'NO_SUCH_OBJECT',
|
||||
|
|
@ -165,7 +161,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
|
||||
// リモートから一旦オブジェクトフェッチ
|
||||
const resolver = this.apResolverService.createResolver();
|
||||
const object = await resolver.resolve(uri).catch((err) => {
|
||||
// allow ap/show exclusively to lookup URLs that are cross-origin or non-canonical (like https://alice.example.com/@bob@bob.example.com -> https://bob.example.com/@bob)
|
||||
const object = await resolver.resolve(uri, FetchAllowSoftFailMask.CrossOrigin | FetchAllowSoftFailMask.NonCanonicalId).catch((err) => {
|
||||
if (err instanceof IdentifiableError) {
|
||||
switch (err.id) {
|
||||
// resolve
|
||||
|
|
@ -177,10 +174,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
case '09d79f9e-64f1-4316-9cfa-e75c4d091574':
|
||||
throw new ApiError(meta.errors.federationNotAllowed);
|
||||
case '72180409-793c-4973-868e-5a118eb5519b':
|
||||
case 'ad2dc287-75c1-44c4-839d-3d2e64576675':
|
||||
throw new ApiError(meta.errors.responseInvalid);
|
||||
case 'fd93c2fa-69a8-440f-880b-bf178e0ec877':
|
||||
throw new ApiError(meta.errors.responseInvalidIdHostNotMatch);
|
||||
|
||||
// resolveLocal
|
||||
case '02b40cd0-fa92-4b0c-acc9-fb2ada952ab8':
|
||||
|
|
|
|||
73
packages/backend/src/server/api/endpoints/chat/history.ts
Normal file
73
packages/backend/src/server/api/endpoints/chat/history.ts
Normal file
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['chat'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'read:chat',
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'ChatMessage',
|
||||
},
|
||||
},
|
||||
|
||||
errors: {
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
room: { type: 'boolean', default: false },
|
||||
},
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private chatEntityService: ChatEntityService,
|
||||
private chatService: ChatService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const history = ps.room ? await this.chatService.roomHistory(me.id, ps.limit) : await this.chatService.userHistory(me.id, ps.limit);
|
||||
|
||||
const packedMessages = await this.chatEntityService.packMessagesDetailed(history, me);
|
||||
|
||||
if (ps.room) {
|
||||
const roomIds = history.map(m => m.toRoomId!);
|
||||
const readStateMap = await this.chatService.getRoomReadStateMap(me.id, roomIds);
|
||||
|
||||
for (const message of packedMessages) {
|
||||
message.isRead = readStateMap[message.toRoomId!] ?? false;
|
||||
}
|
||||
} else {
|
||||
const otherIds = history.map(m => m.fromUserId === me.id ? m.toUserId! : m.fromUserId!);
|
||||
const readStateMap = await this.chatService.getUserReadStateMap(me.id, otherIds);
|
||||
|
||||
for (const message of packedMessages) {
|
||||
const otherId = message.fromUserId === me.id ? message.toUserId! : message.fromUserId!;
|
||||
message.isRead = readStateMap[otherId] ?? false;
|
||||
}
|
||||
}
|
||||
|
||||
return packedMessages;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,105 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { GetterService } from '@/server/api/GetterService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import type { DriveFilesRepository, MiUser } from '@/models/_.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['chat'],
|
||||
|
||||
requireCredential: true,
|
||||
requiredRolePolicy: 'canChat',
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:chat',
|
||||
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
max: 500,
|
||||
},
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'ChatMessageLite',
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchRoom: {
|
||||
message: 'No such room.',
|
||||
code: 'NO_SUCH_ROOM',
|
||||
id: '8098520d-2da5-4e8f-8ee1-df78b55a4ec6',
|
||||
},
|
||||
|
||||
noSuchFile: {
|
||||
message: 'No such file.',
|
||||
code: 'NO_SUCH_FILE',
|
||||
id: 'b6accbd3-1d7b-4d9f-bdb7-eb185bac06db',
|
||||
},
|
||||
|
||||
contentRequired: {
|
||||
message: 'Content required. You need to set text or fileId.',
|
||||
code: 'CONTENT_REQUIRED',
|
||||
id: '340517b7-6d04-42c0-bac1-37ee804e3594',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
text: { type: 'string', nullable: true, maxLength: 2000 },
|
||||
fileId: { type: 'string', format: 'misskey:id' },
|
||||
toRoomId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['toRoomId'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
@Inject(DI.driveFilesRepository)
|
||||
private driveFilesRepository: DriveFilesRepository,
|
||||
|
||||
private getterService: GetterService,
|
||||
private chatService: ChatService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const room = await this.chatService.findRoomById(ps.toRoomId);
|
||||
if (room == null) {
|
||||
throw new ApiError(meta.errors.noSuchRoom);
|
||||
}
|
||||
|
||||
let file = null;
|
||||
if (ps.fileId != null) {
|
||||
file = await this.driveFilesRepository.findOneBy({
|
||||
id: ps.fileId,
|
||||
userId: me.id,
|
||||
});
|
||||
|
||||
if (file == null) {
|
||||
throw new ApiError(meta.errors.noSuchFile);
|
||||
}
|
||||
}
|
||||
|
||||
// テキストが無いかつ添付ファイルも無かったらエラー
|
||||
if (ps.text == null && file == null) {
|
||||
throw new ApiError(meta.errors.contentRequired);
|
||||
}
|
||||
|
||||
return await this.chatService.createMessageToRoom(me, room, {
|
||||
text: ps.text,
|
||||
file: file,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { GetterService } from '@/server/api/GetterService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import type { DriveFilesRepository, MiUser } from '@/models/_.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['chat'],
|
||||
|
||||
requireCredential: true,
|
||||
requiredRolePolicy: 'canChat',
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:chat',
|
||||
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
max: 500,
|
||||
},
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'ChatMessageLite',
|
||||
},
|
||||
|
||||
errors: {
|
||||
recipientIsYourself: {
|
||||
message: 'You can not send a message to yourself.',
|
||||
code: 'RECIPIENT_IS_YOURSELF',
|
||||
id: '17e2ba79-e22a-4cbc-bf91-d327643f4a7e',
|
||||
},
|
||||
|
||||
noSuchUser: {
|
||||
message: 'No such user.',
|
||||
code: 'NO_SUCH_USER',
|
||||
id: '11795c64-40ea-4198-b06e-3c873ed9039d',
|
||||
},
|
||||
|
||||
noSuchFile: {
|
||||
message: 'No such file.',
|
||||
code: 'NO_SUCH_FILE',
|
||||
id: '4372b8e2-185d-4146-8749-2f68864a3e5f',
|
||||
},
|
||||
|
||||
contentRequired: {
|
||||
message: 'Content required. You need to set text or fileId.',
|
||||
code: 'CONTENT_REQUIRED',
|
||||
id: '25587321-b0e6-449c-9239-f8925092942c',
|
||||
},
|
||||
|
||||
youHaveBeenBlocked: {
|
||||
message: 'You cannot send a message because you have been blocked by this user.',
|
||||
code: 'YOU_HAVE_BEEN_BLOCKED',
|
||||
id: 'c15a5199-7422-4968-941a-2a462c478f7d',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
text: { type: 'string', nullable: true, maxLength: 2000 },
|
||||
fileId: { type: 'string', format: 'misskey:id' },
|
||||
toUserId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['toUserId'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
@Inject(DI.driveFilesRepository)
|
||||
private driveFilesRepository: DriveFilesRepository,
|
||||
|
||||
private getterService: GetterService,
|
||||
private chatService: ChatService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
let file = null;
|
||||
if (ps.fileId != null) {
|
||||
file = await this.driveFilesRepository.findOneBy({
|
||||
id: ps.fileId,
|
||||
userId: me.id,
|
||||
});
|
||||
|
||||
if (file == null) {
|
||||
throw new ApiError(meta.errors.noSuchFile);
|
||||
}
|
||||
}
|
||||
|
||||
// テキストが無いかつ添付ファイルも無かったらエラー
|
||||
if (ps.text == null && file == null) {
|
||||
throw new ApiError(meta.errors.contentRequired);
|
||||
}
|
||||
|
||||
// Myself
|
||||
if (ps.toUserId === me.id) {
|
||||
throw new ApiError(meta.errors.recipientIsYourself);
|
||||
}
|
||||
|
||||
const toUser = await this.getterService.getUser(ps.toUserId).catch(err => {
|
||||
if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
|
||||
throw err;
|
||||
});
|
||||
|
||||
return await this.chatService.createMessageToUser(me, toUser, {
|
||||
text: ps.text,
|
||||
file: file,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['chat'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:chat',
|
||||
|
||||
res: {
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchMessage: {
|
||||
message: 'No such message.',
|
||||
code: 'NO_SUCH_MESSAGE',
|
||||
id: '36b67f0e-66a6-414b-83df-992a55294f17',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
messageId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['messageId'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private chatService: ChatService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const message = await this.chatService.findMyMessageById(me.id, ps.messageId);
|
||||
if (message == null) {
|
||||
throw new ApiError(meta.errors.noSuchMessage);
|
||||
}
|
||||
await this.chatService.deleteMessage(message);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['chat'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:chat',
|
||||
|
||||
res: {
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchMessage: {
|
||||
message: 'No such message.',
|
||||
code: 'NO_SUCH_MESSAGE',
|
||||
id: '9b5839b9-0ba0-4351-8c35-37082093d200',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
messageId: { type: 'string', format: 'misskey:id' },
|
||||
reaction: { type: 'string' },
|
||||
},
|
||||
required: ['messageId', 'reaction'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private chatService: ChatService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
await this.chatService.react(ps.messageId, me.id, ps.reaction);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['chat'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'read:chat',
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'ChatMessageLite',
|
||||
},
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchRoom: {
|
||||
message: 'No such room.',
|
||||
code: 'NO_SUCH_ROOM',
|
||||
id: 'c4d9f88c-9270-4632-b032-6ed8cee36f7f',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
roomId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['roomId'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private chatEntityService: ChatEntityService,
|
||||
private chatService: ChatService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const room = await this.chatService.findRoomById(ps.roomId);
|
||||
if (room == null) {
|
||||
throw new ApiError(meta.errors.noSuchRoom);
|
||||
}
|
||||
|
||||
if (!(await this.chatService.isRoomMember(room, me.id))) {
|
||||
throw new ApiError(meta.errors.noSuchRoom);
|
||||
}
|
||||
|
||||
const messages = await this.chatService.roomTimeline(room.id, ps.limit, ps.sinceId, ps.untilId);
|
||||
|
||||
this.chatService.readRoomChatMessage(me.id, room.id);
|
||||
|
||||
return await this.chatEntityService.packMessagesLiteForRoom(messages);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,76 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['chat'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'read:chat',
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'ChatMessage',
|
||||
},
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchRoom: {
|
||||
message: 'No such room.',
|
||||
code: 'NO_SUCH_ROOM',
|
||||
id: '460b3669-81b0-4dc9-a997-44442141bf83',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
query: { type: 'string', minLength: 1, maxLength: 256 },
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
userId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||
roomId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||
},
|
||||
required: ['query'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private chatEntityService: ChatEntityService,
|
||||
private chatService: ChatService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
if (ps.roomId != null) {
|
||||
const room = await this.chatService.findRoomById(ps.roomId);
|
||||
if (room == null) {
|
||||
throw new ApiError(meta.errors.noSuchRoom);
|
||||
}
|
||||
|
||||
if (!(await this.chatService.isRoomMember(room, me.id))) {
|
||||
throw new ApiError(meta.errors.noSuchRoom);
|
||||
}
|
||||
}
|
||||
|
||||
const messages = await this.chatService.searchMessages(me.id, ps.query, ps.limit, {
|
||||
userId: ps.userId,
|
||||
roomId: ps.roomId,
|
||||
});
|
||||
|
||||
return await this.chatEntityService.packMessagesDetailed(messages, me);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { GetterService } from '@/server/api/GetterService.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['chat'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'read:chat',
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'ChatMessage',
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchMessage: {
|
||||
message: 'No such message.',
|
||||
code: 'NO_SUCH_MESSAGE',
|
||||
id: '3710865b-1848-4da9-8d61-cfed15510b93',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
messageId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['messageId'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private chatService: ChatService,
|
||||
private roleService: RoleService,
|
||||
private chatEntityService: ChatEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const message = await this.chatService.findMessageById(ps.messageId);
|
||||
if (message == null) {
|
||||
throw new ApiError(meta.errors.noSuchMessage);
|
||||
}
|
||||
if (message.fromUserId !== me.id && message.toUserId !== me.id && !(await this.roleService.isModerator(me))) {
|
||||
throw new ApiError(meta.errors.noSuchMessage);
|
||||
}
|
||||
return this.chatEntityService.packMessageDetailed(message, me);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { GetterService } from '@/server/api/GetterService.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['chat'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'read:chat',
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'ChatMessageLite',
|
||||
},
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchUser: {
|
||||
message: 'No such user.',
|
||||
code: 'NO_SUCH_USER',
|
||||
id: '11795c64-40ea-4198-b06e-3c873ed9039d',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['userId'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private chatEntityService: ChatEntityService,
|
||||
private chatService: ChatService,
|
||||
private getterService: GetterService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const other = await this.getterService.getUser(ps.userId).catch(err => {
|
||||
if (err.id === '15348ddd-432d-49c2-8a5a-8069753becff') throw new ApiError(meta.errors.noSuchUser);
|
||||
throw err;
|
||||
});
|
||||
|
||||
const messages = await this.chatService.userTimeline(me.id, other.id, ps.limit, ps.sinceId, ps.untilId);
|
||||
|
||||
this.chatService.readUserChatMessage(me.id, other.id);
|
||||
|
||||
return await this.chatEntityService.packMessagesLiteFor1on1(messages);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['chat'],
|
||||
|
||||
requireCredential: true,
|
||||
requiredRolePolicy: 'canChat',
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:chat',
|
||||
|
||||
limit: {
|
||||
duration: ms('1day'),
|
||||
max: 10,
|
||||
},
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'ChatRoom',
|
||||
},
|
||||
|
||||
errors: {
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
name: { type: 'string', maxLength: 256 },
|
||||
description: { type: 'string', maxLength: 1024 },
|
||||
},
|
||||
required: ['name'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private chatService: ChatService,
|
||||
private chatEntityService: ChatEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const room = await this.chatService.createRoom(me, {
|
||||
name: ps.name,
|
||||
description: ps.description ?? '',
|
||||
});
|
||||
return await this.chatEntityService.packRoom(room);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['chat'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:chat',
|
||||
|
||||
res: {
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchRoom: {
|
||||
message: 'No such room.',
|
||||
code: 'NO_SUCH_ROOM',
|
||||
id: 'd4e3753d-97bf-4a19-ab8e-21080fbc0f4b',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
roomId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['roomId'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private chatService: ChatService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const room = await this.chatService.findMyRoomById(me.id, ps.roomId);
|
||||
if (room == null) {
|
||||
throw new ApiError(meta.errors.noSuchRoom);
|
||||
}
|
||||
await this.chatService.deleteRoom(room);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['chat'],
|
||||
|
||||
requireCredential: true,
|
||||
requiredRolePolicy: 'canChat',
|
||||
|
||||
prohibitMoved: true,
|
||||
|
||||
kind: 'write:chat',
|
||||
|
||||
limit: {
|
||||
duration: ms('1day'),
|
||||
max: 50,
|
||||
},
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'ChatRoomInvitation',
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchRoom: {
|
||||
message: 'No such room.',
|
||||
code: 'NO_SUCH_ROOM',
|
||||
id: '916f9507-49ba-4e90-b57f-1fd4deaa47a5',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
roomId: { type: 'string', format: 'misskey:id' },
|
||||
userId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['roomId', 'userId'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private chatService: ChatService,
|
||||
private chatEntityService: ChatEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const room = await this.chatService.findMyRoomById(me.id, ps.roomId);
|
||||
if (room == null) {
|
||||
throw new ApiError(meta.errors.noSuchRoom);
|
||||
}
|
||||
const invitation = await this.chatService.createRoomInvitation(me.id, room.id, ps.userId);
|
||||
return await this.chatEntityService.packRoomInvitation(invitation, me);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['chat'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:chat',
|
||||
|
||||
res: {
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchRoom: {
|
||||
message: 'No such room.',
|
||||
code: 'NO_SUCH_ROOM',
|
||||
id: '5130557e-5a11-4cfb-9cc5-fe60cda5de0d',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
roomId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['roomId'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private chatService: ChatService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
await this.chatService.ignoreRoomInvitation(me.id, ps.roomId);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['chat'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'read:chat',
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'ChatRoomInvitation',
|
||||
},
|
||||
},
|
||||
|
||||
errors: {
|
||||
},
|
||||
} 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' },
|
||||
},
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private chatEntityService: ChatEntityService,
|
||||
private chatService: ChatService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const invitations = await this.chatService.getReceivedRoomInvitationsWithPagination(me.id, ps.limit, ps.sinceId, ps.untilId);
|
||||
return this.chatEntityService.packRoomInvitations(invitations, me);
|
||||
});
|
||||
}
|
||||
}
|
||||
48
packages/backend/src/server/api/endpoints/chat/rooms/join.ts
Normal file
48
packages/backend/src/server/api/endpoints/chat/rooms/join.ts
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['chat'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:chat',
|
||||
|
||||
res: {
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchRoom: {
|
||||
message: 'No such room.',
|
||||
code: 'NO_SUCH_ROOM',
|
||||
id: '84416476-5ce8-4a2c-b568-9569f1b10733',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
roomId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['roomId'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private chatService: ChatService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
await this.chatService.joinToRoom(me.id, ps.roomId);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['chat'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'read:chat',
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'ChatRoomMembership',
|
||||
},
|
||||
},
|
||||
|
||||
errors: {
|
||||
},
|
||||
} 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' },
|
||||
},
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private chatService: ChatService,
|
||||
private chatEntityService: ChatEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const memberships = await this.chatService.getMyMemberships(me.id, ps.limit, ps.sinceId, ps.untilId);
|
||||
|
||||
return this.chatEntityService.packRoomMemberships(memberships, me, {
|
||||
populateUser: false,
|
||||
populateRoom: true,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['chat'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:chat',
|
||||
|
||||
res: {
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchRoom: {
|
||||
message: 'No such room.',
|
||||
code: 'NO_SUCH_ROOM',
|
||||
id: 'cb7f3179-50e8-4389-8c30-dbe2650a67c9',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
roomId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['roomId'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private chatService: ChatService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
await this.chatService.leaveRoom(me.id, ps.roomId);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['chat'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:chat',
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'ChatRoomMembership',
|
||||
},
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchRoom: {
|
||||
message: 'No such room.',
|
||||
code: 'NO_SUCH_ROOM',
|
||||
id: '7b9fe84c-eafc-4d21-bf89-485458ed2c18',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
roomId: { type: 'string', format: 'misskey:id' },
|
||||
limit: { type: 'integer', minimum: 1, maximum: 100, default: 30 },
|
||||
sinceId: { type: 'string', format: 'misskey:id' },
|
||||
untilId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['roomId'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private chatService: ChatService,
|
||||
private chatEntityService: ChatEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const room = await this.chatService.findRoomById(ps.roomId);
|
||||
if (room == null) {
|
||||
throw new ApiError(meta.errors.noSuchRoom);
|
||||
}
|
||||
|
||||
if (!(await this.chatService.isRoomMember(room, me.id))) {
|
||||
throw new ApiError(meta.errors.noSuchRoom);
|
||||
}
|
||||
|
||||
const memberships = await this.chatService.getRoomMembershipsWithPagination(room.id, ps.limit, ps.sinceId, ps.untilId);
|
||||
|
||||
return this.chatEntityService.packRoomMemberships(memberships, me, {
|
||||
populateUser: true,
|
||||
populateRoom: false,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
49
packages/backend/src/server/api/endpoints/chat/rooms/mute.ts
Normal file
49
packages/backend/src/server/api/endpoints/chat/rooms/mute.ts
Normal file
|
|
@ -0,0 +1,49 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['chat'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:chat',
|
||||
|
||||
res: {
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchRoom: {
|
||||
message: 'No such room.',
|
||||
code: 'NO_SUCH_ROOM',
|
||||
id: 'c2cde4eb-8d0f-42f1-8f2f-c4d6bfc8e5df',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
roomId: { type: 'string', format: 'misskey:id' },
|
||||
mute: { type: 'boolean' },
|
||||
},
|
||||
required: ['roomId', 'mute'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private chatService: ChatService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
await this.chatService.muteRoom(me.id, ps.roomId, ps.mute);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['chat'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'read:chat',
|
||||
|
||||
res: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'ChatRoom',
|
||||
},
|
||||
},
|
||||
|
||||
errors: {
|
||||
},
|
||||
} 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' },
|
||||
},
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private chatEntityService: ChatEntityService,
|
||||
private chatService: ChatService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const rooms = await this.chatService.getOwnedRoomsWithPagination(me.id, ps.limit, ps.sinceId, ps.untilId);
|
||||
return this.chatEntityService.packRooms(rooms, me);
|
||||
});
|
||||
}
|
||||
}
|
||||
58
packages/backend/src/server/api/endpoints/chat/rooms/show.ts
Normal file
58
packages/backend/src/server/api/endpoints/chat/rooms/show.ts
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['chat'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'read:chat',
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'ChatRoom',
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchRoom: {
|
||||
message: 'No such room.',
|
||||
code: 'NO_SUCH_ROOM',
|
||||
id: '857ae02f-8759-4d20-9adb-6e95fffe4fd7',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
roomId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['roomId'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private chatService: ChatService,
|
||||
private chatEntityService: ChatEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const room = await this.chatService.findRoomById(ps.roomId);
|
||||
if (room == null) {
|
||||
throw new ApiError(meta.errors.noSuchRoom);
|
||||
}
|
||||
|
||||
return this.chatEntityService.packRoom(room, me);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import { ApiError } from '@/server/api/error.js';
|
||||
import { ChatEntityService } from '@/core/entities/ChatEntityService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['chat'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:chat',
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
ref: 'ChatRoom',
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchRoom: {
|
||||
message: 'No such room.',
|
||||
code: 'NO_SUCH_ROOM',
|
||||
id: 'fcdb0f92-bda6-47f9-bd05-343e0e020932',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
roomId: { type: 'string', format: 'misskey:id' },
|
||||
name: { type: 'string', maxLength: 256 },
|
||||
description: { type: 'string', maxLength: 1024 },
|
||||
},
|
||||
required: ['roomId'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private chatService: ChatService,
|
||||
private chatEntityService: ChatEntityService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const room = await this.chatService.findMyRoomById(me.id, ps.roomId);
|
||||
if (room == null) {
|
||||
throw new ApiError(meta.errors.noSuchRoom);
|
||||
}
|
||||
|
||||
const updated = await this.chatService.updateRoom(room, {
|
||||
name: ps.name,
|
||||
description: ps.description,
|
||||
});
|
||||
|
||||
return this.chatEntityService.packRoom(updated, me);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -45,7 +45,7 @@ export const paramDef = {
|
|||
properties: {
|
||||
name: { type: 'string', minLength: 1, maxLength: 100 },
|
||||
isPublic: { type: 'boolean', default: false },
|
||||
description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 },
|
||||
description: { type: 'string', nullable: true, maxLength: 2048 },
|
||||
},
|
||||
required: ['name'],
|
||||
} as const;
|
||||
|
|
@ -59,7 +59,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
super(meta, paramDef, async (ps, me) => {
|
||||
let clip: MiClip;
|
||||
try {
|
||||
clip = await this.clipService.create(me, ps.name, ps.isPublic, ps.description ?? null);
|
||||
// 空文字列をnullにしたいので??は使わない
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
clip = await this.clipService.create(me, ps.name, ps.isPublic, ps.description || null);
|
||||
} catch (e) {
|
||||
if (e instanceof ClipService.TooManyClipsError) {
|
||||
throw new ApiError(meta.errors.tooManyClips);
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ export const paramDef = {
|
|||
clipId: { type: 'string', format: 'misskey:id' },
|
||||
name: { type: 'string', minLength: 1, maxLength: 100 },
|
||||
isPublic: { type: 'boolean' },
|
||||
description: { type: 'string', nullable: true, minLength: 1, maxLength: 2048 },
|
||||
description: { type: 'string', nullable: true, maxLength: 2048 },
|
||||
},
|
||||
required: ['clipId'],
|
||||
} as const;
|
||||
|
|
@ -59,7 +59,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
try {
|
||||
await this.clipService.update(me, ps.clipId, ps.name, ps.isPublic, ps.description);
|
||||
// 空文字列をnullにしたいので??は使わない
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
await this.clipService.update(me, ps.clipId, ps.name, ps.isPublic, ps.description || null);
|
||||
} catch (e) {
|
||||
if (e instanceof ClipService.NoSuchClipError) {
|
||||
throw new ApiError(meta.errors.noSuchClip);
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
|
||||
await this.userFollowingService.unfollow(follower, followee);
|
||||
|
||||
return await this.userEntityService.pack(followee.id, me);
|
||||
return await this.userEntityService.pack(follower.id, me);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import { ApiError } from '../../error.js';
|
|||
export const meta = {
|
||||
secure: true,
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canImportAntennas',
|
||||
requiredRolePolicy: 'canImportAntennas',
|
||||
prohibitMoved: true,
|
||||
|
||||
limit: {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import { ApiError } from '../../error.js';
|
|||
export const meta = {
|
||||
secure: true,
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canImportBlocking',
|
||||
requiredRolePolicy: 'canImportBlocking',
|
||||
prohibitMoved: true,
|
||||
|
||||
limit: {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import { ApiError } from '../../error.js';
|
|||
export const meta = {
|
||||
secure: true,
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canImportFollowing',
|
||||
requiredRolePolicy: 'canImportFollowing',
|
||||
prohibitMoved: true,
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import { ApiError } from '../../error.js';
|
|||
export const meta = {
|
||||
secure: true,
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canImportMuting',
|
||||
requiredRolePolicy: 'canImportMuting',
|
||||
prohibitMoved: true,
|
||||
|
||||
limit: {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import { ApiError } from '../../error.js';
|
|||
export const meta = {
|
||||
secure: true,
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canImportUserLists',
|
||||
requiredRolePolicy: 'canImportUserLists',
|
||||
prohibitMoved: true,
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import ms from 'ms';
|
||||
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
|
|
@ -19,6 +19,8 @@ import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
|
|||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
|
||||
import * as Acct from '@/misc/acct.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { MiMeta } from '@/models/_.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['users'],
|
||||
|
|
@ -81,6 +83,9 @@ export const paramDef = {
|
|||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
@Inject(DI.meta)
|
||||
private serverSettings: MiMeta,
|
||||
|
||||
private remoteUserResolveService: RemoteUserResolveService,
|
||||
private apiLoggerService: ApiLoggerService,
|
||||
private accountMoveService: AccountMoveService,
|
||||
|
|
@ -92,7 +97,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
// check parameter
|
||||
if (!ps.moveToAccount) throw new ApiError(meta.errors.noSuchUser);
|
||||
// abort if user is the root
|
||||
if (me.isRoot) throw new ApiError(meta.errors.rootForbidden);
|
||||
if (this.serverSettings.rootUserId === me.id) throw new ApiError(meta.errors.rootForbidden);
|
||||
// abort if user has already moved
|
||||
if (me.movedToUri) throw new ApiError(meta.errors.alreadyMoved);
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import { Inject, Injectable } from '@nestjs/common';
|
|||
import type { NotesRepository } from '@/models/_.js';
|
||||
import { obsoleteNotificationTypes, groupedNotificationTypes, FilterUnionByProperty } from '@/types.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { NoteReadService } from '@/core/NoteReadService.js';
|
||||
import { NotificationEntityService } from '@/core/entities/NotificationEntityService.js';
|
||||
import { NotificationService } from '@/core/NotificationService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
|
@ -63,13 +62,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
@Inject(DI.redis)
|
||||
private redisClient: Redis.Redis,
|
||||
|
||||
@Inject(DI.notesRepository)
|
||||
private notesRepository: NotesRepository,
|
||||
|
||||
private idService: IdService,
|
||||
private notificationEntityService: NotificationEntityService,
|
||||
private notificationService: NotificationService,
|
||||
private noteReadService: NoteReadService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const EXTRA_LIMIT = 100;
|
||||
|
|
@ -162,14 +157,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
}
|
||||
|
||||
groupedNotifications = groupedNotifications.slice(0, ps.limit);
|
||||
const noteIds = groupedNotifications
|
||||
.filter((notification): notification is FilterUnionByProperty<MiNotification, 'type', 'mention' | 'reply' | 'quote' | 'edited'> => ['mention', 'reply', 'quote', 'edited'].includes(notification.type))
|
||||
.map(notification => notification.noteId!);
|
||||
|
||||
if (noteIds.length > 0) {
|
||||
const notes = await this.notesRepository.findBy({ id: In(noteIds) });
|
||||
this.noteReadService.read(me.id, notes);
|
||||
}
|
||||
|
||||
return await this.notificationEntityService.packGroupedMany(groupedNotifications, me.id);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import { Inject, Injectable } from '@nestjs/common';
|
|||
import type { NotesRepository } from '@/models/_.js';
|
||||
import { FilterUnionByProperty, notificationTypes, obsoleteNotificationTypes } from '@/types.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { NoteReadService } from '@/core/NoteReadService.js';
|
||||
import { NotificationEntityService } from '@/core/entities/NotificationEntityService.js';
|
||||
import { NotificationService } from '@/core/NotificationService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
|
@ -69,7 +68,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private idService: IdService,
|
||||
private notificationEntityService: NotificationEntityService,
|
||||
private notificationService: NotificationService,
|
||||
private noteReadService: NoteReadService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
// includeTypes が空の場合はクエリしない
|
||||
|
|
@ -136,15 +134,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
this.notificationService.readAllNotification(me.id);
|
||||
}
|
||||
|
||||
const noteIds = notifications
|
||||
.filter((notification): notification is FilterUnionByProperty<MiNotification, 'type', 'mention' | 'reply' | 'quote' | 'edited'> => ['mention', 'reply', 'quote', 'edited'].includes(notification.type))
|
||||
.map(notification => notification.noteId);
|
||||
|
||||
if (noteIds.length > 0) {
|
||||
const notes = await this.notesRepository.findBy({ id: In(noteIds) });
|
||||
this.noteReadService.read(me.id, notes);
|
||||
}
|
||||
|
||||
return await this.notificationEntityService.packMany(notifications, me.id);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,51 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { NoteUnreadsRepository } from '@/models/_.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['account'],
|
||||
|
||||
requireCredential: true,
|
||||
|
||||
kind: 'write:account',
|
||||
|
||||
// 2 calls per second
|
||||
limit: {
|
||||
duration: 1000,
|
||||
max: 2,
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {},
|
||||
required: [],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
@Inject(DI.noteUnreadsRepository)
|
||||
private noteUnreadsRepository: NoteUnreadsRepository,
|
||||
|
||||
private globalEventService: GlobalEventService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
// Remove documents
|
||||
await this.noteUnreadsRepository.delete({
|
||||
userId: me.id,
|
||||
});
|
||||
|
||||
// 全て既読になったイベントを発行
|
||||
this.globalEventService.publishMainStream(me.id, 'readAllUnreadMentions');
|
||||
this.globalEventService.publishMainStream(me.id, 'readAllUnreadSpecifiedNotes');
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
|||
import ms from 'ms';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { UsersRepository, UserProfilesRepository } from '@/models/_.js';
|
||||
import generateUserToken from '@/misc/generate-native-user-token.js';
|
||||
import { generateNativeUserToken } from '@/misc/token.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
||||
|
|
@ -57,7 +57,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
throw new Error('incorrect password');
|
||||
}
|
||||
|
||||
const newToken = generateUserToken();
|
||||
const newToken = generateNativeUserToken();
|
||||
|
||||
await this.usersRepository.update(me.id, {
|
||||
token: newToken,
|
||||
|
|
|
|||
|
|
@ -215,6 +215,7 @@ export const paramDef = {
|
|||
autoSensitive: { type: 'boolean' },
|
||||
followingVisibility: { type: 'string', enum: ['public', 'followers', 'private'] },
|
||||
followersVisibility: { type: 'string', enum: ['public', 'followers', 'private'] },
|
||||
chatScope: { type: 'string', enum: ['everyone', 'followers', 'following', 'mutual', 'none'] },
|
||||
pinnedPageId: { type: 'string', format: 'misskey:id', nullable: true },
|
||||
mutedWords: muteWords,
|
||||
hardMutedWords: muteWords,
|
||||
|
|
@ -325,6 +326,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
if (ps.listenbrainz !== undefined) profileUpdates.listenbrainz = ps.listenbrainz;
|
||||
if (ps.followingVisibility !== undefined) profileUpdates.followingVisibility = ps.followingVisibility;
|
||||
if (ps.followersVisibility !== undefined) profileUpdates.followersVisibility = ps.followersVisibility;
|
||||
if (ps.chatScope !== undefined) updates.chatScope = ps.chatScope;
|
||||
|
||||
function checkMuteWordCount(mutedWords: (string[] | string)[], limit: number) {
|
||||
// TODO: ちゃんと数える
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ export const meta = {
|
|||
tags: ['meta'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canInvite',
|
||||
requiredRolePolicy: 'canInvite',
|
||||
kind: 'write:invite-codes',
|
||||
|
||||
errors: {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export const meta = {
|
|||
tags: ['meta'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canInvite',
|
||||
requiredRolePolicy: 'canInvite',
|
||||
kind: 'write:invite-codes',
|
||||
|
||||
errors: {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export const meta = {
|
|||
tags: ['meta'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canInvite',
|
||||
requiredRolePolicy: 'canInvite',
|
||||
kind: 'read:invite-codes',
|
||||
|
||||
res: {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ export const meta = {
|
|||
tags: ['meta'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canInvite',
|
||||
requiredRolePolicy: 'canInvite',
|
||||
kind: 'read:invite-codes',
|
||||
|
||||
res: {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
|||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { AccessTokensRepository } from '@/models/_.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { NotificationService } from '@/core/NotificationService.js';
|
||||
import { secureRndstr } from '@/misc/secure-rndstr.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
||||
|
|
@ -56,6 +57,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private accessTokensRepository: AccessTokensRepository,
|
||||
|
||||
private idService: IdService,
|
||||
private notificationService: NotificationService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
// Generate access token
|
||||
|
|
@ -77,6 +79,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
permission: ps.permission,
|
||||
});
|
||||
|
||||
// アクセストークンが生成されたことを通知
|
||||
this.notificationService.createNotification(me.id, 'createToken', {});
|
||||
|
||||
return {
|
||||
token: accessToken,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import type { NotesRepository, FollowingsRepository } from '@/models/_.js';
|
|||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { NoteReadService } from '@/core/NoteReadService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
||||
export const meta = {
|
||||
|
|
@ -58,7 +57,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
|
||||
private noteEntityService: NoteEntityService,
|
||||
private queryService: QueryService,
|
||||
private noteReadService: NoteReadService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const followingQuery = this.followingsRepository.createQueryBuilder('following')
|
||||
|
|
@ -95,8 +93,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
|
||||
const mentions = await query.limit(ps.limit).getMany();
|
||||
|
||||
this.noteReadService.read(me.id, mentions);
|
||||
|
||||
return await this.noteEntityService.packMany(mentions, me);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ import type { NotesRepository, NoteThreadMutingsRepository } from '@/models/_.js
|
|||
import { IdService } from '@/core/IdService.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { GetterService } from '@/server/api/GetterService.js';
|
||||
import { NoteReadService } from '@/core/NoteReadService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ApiError } from '../../../error.js';
|
||||
|
||||
|
|
@ -52,7 +51,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
private noteThreadMutingsRepository: NoteThreadMutingsRepository,
|
||||
|
||||
private getterService: GetterService,
|
||||
private noteReadService: NoteReadService,
|
||||
private idService: IdService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
|
|
@ -69,8 +67,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
}],
|
||||
});
|
||||
|
||||
await this.noteReadService.read(me.id, mutedNotes);
|
||||
|
||||
await this.noteThreadMutingsRepository.insert({
|
||||
id: this.idService.gen(),
|
||||
threadId: note.threadId ?? note.id,
|
||||
|
|
|
|||
|
|
@ -6,9 +6,12 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DataSource } from 'typeorm';
|
||||
import * as Redis from 'ioredis';
|
||||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { resetDb } from '@/misc/reset-db.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['non-productive'],
|
||||
|
|
@ -42,13 +45,27 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
|
||||
@Inject(DI.redis)
|
||||
private redisClient: Redis.Redis,
|
||||
|
||||
private loggerService: LoggerService,
|
||||
private metaService: MetaService,
|
||||
private globalEventService: GlobalEventService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
if (process.env.NODE_ENV !== 'test') throw new Error('NODE_ENV is not a test');
|
||||
|
||||
await redisClient.flushdb();
|
||||
const logger = this.loggerService.getLogger('reset-db');
|
||||
logger.info('---- Resetting database...');
|
||||
|
||||
await this.redisClient.flushdb();
|
||||
await resetDb(this.db);
|
||||
|
||||
// DIコンテナで管理しているmetaのインスタンスには上記のリセット処理が届かないため、
|
||||
// 初期値を流して明示的にリフレッシュする
|
||||
const meta = await this.metaService.fetch(true);
|
||||
this.globalEventService.publishInternalEvent('metaUpdated', { after: meta });
|
||||
|
||||
logger.info('---- Database reset complete.');
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,7 +12,7 @@ export const meta = {
|
|||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageCustomEmojis',
|
||||
requiredRolePolicy: 'canManageCustomEmojis',
|
||||
kind: 'read:admin:emoji',
|
||||
|
||||
res: {
|
||||
|
|
|
|||
|
|
@ -210,9 +210,15 @@ export function genOpenapiSpec(config: Config, includeSelfRef = false) {
|
|||
|
||||
spec.paths['/' + endpoint.name] = {
|
||||
...(endpoint.meta.allowGet ? {
|
||||
get: info,
|
||||
get: {
|
||||
...info,
|
||||
operationId: 'get___' + info.operationId,
|
||||
},
|
||||
} : {}),
|
||||
post: info,
|
||||
post: {
|
||||
...info,
|
||||
operationId: 'post___' + info.operationId,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,6 +20,8 @@ import { AntennaChannelService } from './channels/antenna.js';
|
|||
import { DriveChannelService } from './channels/drive.js';
|
||||
import { HashtagChannelService } from './channels/hashtag.js';
|
||||
import { RoleTimelineChannelService } from './channels/role-timeline.js';
|
||||
import { ChatUserChannelService } from './channels/chat-user.js';
|
||||
import { ChatRoomChannelService } from './channels/chat-room.js';
|
||||
import { ReversiChannelService } from './channels/reversi.js';
|
||||
import { ReversiGameChannelService } from './channels/reversi-game.js';
|
||||
import { type MiChannelService } from './channel.js';
|
||||
|
|
@ -42,6 +44,8 @@ export class ChannelsService {
|
|||
private serverStatsChannelService: ServerStatsChannelService,
|
||||
private queueStatsChannelService: QueueStatsChannelService,
|
||||
private adminChannelService: AdminChannelService,
|
||||
private chatUserChannelService: ChatUserChannelService,
|
||||
private chatRoomChannelService: ChatRoomChannelService,
|
||||
private reversiChannelService: ReversiChannelService,
|
||||
private reversiGameChannelService: ReversiGameChannelService,
|
||||
) {
|
||||
|
|
@ -65,6 +69,8 @@ export class ChannelsService {
|
|||
case 'serverStats': return this.serverStatsChannelService;
|
||||
case 'queueStats': return this.queueStatsChannelService;
|
||||
case 'admin': return this.adminChannelService;
|
||||
case 'chatUser': return this.chatUserChannelService;
|
||||
case 'chatRoom': return this.chatRoomChannelService;
|
||||
case 'reversi': return this.reversiChannelService;
|
||||
case 'reversiGame': return this.reversiGameChannelService;
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import * as WebSocket from 'ws';
|
|||
import type { MiUser } from '@/models/User.js';
|
||||
import type { MiAccessToken } from '@/models/AccessToken.js';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
import type { NoteReadService } from '@/core/NoteReadService.js';
|
||||
import type { NotificationService } from '@/core/NotificationService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
|
|
@ -51,7 +50,6 @@ export default class Connection {
|
|||
|
||||
constructor(
|
||||
private channelsService: ChannelsService,
|
||||
private noteReadService: NoteReadService,
|
||||
private notificationService: NotificationService,
|
||||
private cacheService: CacheService,
|
||||
private channelFollowingService: ChannelFollowingService,
|
||||
|
|
@ -154,7 +152,7 @@ export default class Connection {
|
|||
case 'readNotification': this.onReadNotification(body); break;
|
||||
case 'subNote': this.onSubscribeNote(body); break;
|
||||
case 's': this.onSubscribeNote(body); break; // alias
|
||||
case 'sr': this.onSubscribeNote(body); this.readNote(body); break;
|
||||
case 'sr': this.onSubscribeNote(body); break;
|
||||
case 'unsubNote': this.onUnsubscribeNote(body); break;
|
||||
case 'un': this.onUnsubscribeNote(body); break; // alias
|
||||
case 'connect': this.onChannelConnectRequested(body); break;
|
||||
|
|
@ -189,19 +187,6 @@ export default class Connection {
|
|||
if (note.renote) add(note.renote);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private readNote(body: JsonValue | undefined) {
|
||||
if (!isJsonObject(body)) return;
|
||||
const id = body.id;
|
||||
|
||||
const note = this.cachedNotes.find(n => n.id === id);
|
||||
if (note == null) return;
|
||||
|
||||
if (this.user && (note.userId !== this.user.id)) {
|
||||
this.noteReadService.read(this.user.id, [note]);
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private onReadNotification(payload: JsonValue | undefined) {
|
||||
this.notificationService.readAllNotification(this.user!.id);
|
||||
|
|
|
|||
|
|
@ -106,8 +106,8 @@ export default abstract class Channel {
|
|||
this.noteEntityService = noteEntityService;
|
||||
}
|
||||
|
||||
public send(payload: { type: string, body: JsonValue }): void
|
||||
public send(type: string, payload: JsonValue): void
|
||||
public send(payload: { type: string, body: JsonValue }): void;
|
||||
public send(type: string, payload: JsonValue): void;
|
||||
@bindThis
|
||||
public send(typeOrPayload: { type: string, body: JsonValue } | string, payload?: JsonValue) {
|
||||
const type = payload === undefined ? (typeOrPayload as { type: string, body: JsonValue }).type : (typeOrPayload as string);
|
||||
|
|
@ -166,4 +166,4 @@ export type MiChannelService<T extends boolean> = {
|
|||
requireCredential: T;
|
||||
kind: T extends true ? string : string | null | undefined;
|
||||
create: (id: string, connection: Connection) => Channel;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
78
packages/backend/src/server/api/stream/channels/chat-room.ts
Normal file
78
packages/backend/src/server/api/stream/channels/chat-room.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
||||
import type { JsonObject } from '@/misc/json-value.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import Channel, { type MiChannelService } from '../channel.js';
|
||||
|
||||
class ChatRoomChannel extends Channel {
|
||||
public readonly chName = 'chatRoom';
|
||||
public static shouldShare = false;
|
||||
public static requireCredential = true as const;
|
||||
public static kind = 'read:chat';
|
||||
private roomId: string;
|
||||
|
||||
constructor(
|
||||
private chatService: ChatService,
|
||||
|
||||
id: string,
|
||||
connection: Channel['connection'],
|
||||
) {
|
||||
super(id, connection);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async init(params: JsonObject) {
|
||||
if (typeof params.roomId !== 'string') return;
|
||||
this.roomId = params.roomId;
|
||||
|
||||
this.subscriber.on(`chatRoomStream:${this.roomId}`, this.onEvent);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async onEvent(data: GlobalEvents['chat']['payload']) {
|
||||
this.send(data.type, data.body);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public onMessage(type: string, body: any) {
|
||||
switch (type) {
|
||||
case 'read':
|
||||
if (this.roomId) {
|
||||
this.chatService.readRoomChatMessage(this.user!.id, this.roomId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public dispose() {
|
||||
this.subscriber.off(`chatRoomStream:${this.roomId}`, this.onEvent);
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ChatRoomChannelService implements MiChannelService<true> {
|
||||
public readonly shouldShare = ChatRoomChannel.shouldShare;
|
||||
public readonly requireCredential = ChatRoomChannel.requireCredential;
|
||||
public readonly kind = ChatRoomChannel.kind;
|
||||
|
||||
constructor(
|
||||
private chatService: ChatService,
|
||||
) {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public create(id: string, connection: Channel['connection']): ChatRoomChannel {
|
||||
return new ChatRoomChannel(
|
||||
this.chatService,
|
||||
id,
|
||||
connection,
|
||||
);
|
||||
}
|
||||
}
|
||||
78
packages/backend/src/server/api/stream/channels/chat-user.ts
Normal file
78
packages/backend/src/server/api/stream/channels/chat-user.ts
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import type { GlobalEvents } from '@/core/GlobalEventService.js';
|
||||
import type { JsonObject } from '@/misc/json-value.js';
|
||||
import { ChatService } from '@/core/ChatService.js';
|
||||
import Channel, { type MiChannelService } from '../channel.js';
|
||||
|
||||
class ChatUserChannel extends Channel {
|
||||
public readonly chName = 'chatUser';
|
||||
public static shouldShare = false;
|
||||
public static requireCredential = true as const;
|
||||
public static kind = 'read:chat';
|
||||
private otherId: string;
|
||||
|
||||
constructor(
|
||||
private chatService: ChatService,
|
||||
|
||||
id: string,
|
||||
connection: Channel['connection'],
|
||||
) {
|
||||
super(id, connection);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async init(params: JsonObject) {
|
||||
if (typeof params.otherId !== 'string') return;
|
||||
this.otherId = params.otherId;
|
||||
|
||||
this.subscriber.on(`chatUserStream:${this.user!.id}-${this.otherId}`, this.onEvent);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
private async onEvent(data: GlobalEvents['chat']['payload']) {
|
||||
this.send(data.type, data.body);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public onMessage(type: string, body: any) {
|
||||
switch (type) {
|
||||
case 'read':
|
||||
if (this.otherId) {
|
||||
this.chatService.readUserChatMessage(this.user!.id, this.otherId);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public dispose() {
|
||||
this.subscriber.off(`chatUserStream:${this.user!.id}-${this.otherId}`, this.onEvent);
|
||||
}
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class ChatUserChannelService implements MiChannelService<true> {
|
||||
public readonly shouldShare = ChatUserChannel.shouldShare;
|
||||
public readonly requireCredential = ChatUserChannel.requireCredential;
|
||||
public readonly kind = ChatUserChannel.kind;
|
||||
|
||||
constructor(
|
||||
private chatService: ChatService,
|
||||
) {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public create(id: string, connection: Channel['connection']): ChatUserChannel {
|
||||
return new ChatUserChannel(
|
||||
this.chatService,
|
||||
id,
|
||||
connection,
|
||||
);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue