Merge tag '2024.11.0' into feature/2024.10
This commit is contained in:
commit
bc816cb166
234 changed files with 6612 additions and 4634 deletions
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class SigninRequiredForShowContents1729333924409 {
|
||||
name = 'SigninRequiredForShowContents1729333924409'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "user" ADD "requireSigninToViewContents" boolean NOT NULL DEFAULT false`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "requireSigninToViewContents"`);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class MakeNotesHiddenBefore1729486255072 {
|
||||
name = 'MakeNotesHiddenBefore1729486255072'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "user" ADD "makeNotesFollowersOnlyBefore" integer`);
|
||||
await queryRunner.query(`ALTER TABLE "user" ADD "makeNotesHiddenBefore" integer`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "makeNotesHiddenBefore"`);
|
||||
await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "makeNotesFollowersOnlyBefore"`);
|
||||
}
|
||||
}
|
||||
|
|
@ -67,32 +67,32 @@
|
|||
"dependencies": {
|
||||
"@aws-sdk/client-s3": "3.620.0",
|
||||
"@aws-sdk/lib-storage": "3.620.0",
|
||||
"@bull-board/api": "6.0.0",
|
||||
"@bull-board/fastify": "6.0.0",
|
||||
"@bull-board/ui": "6.0.0",
|
||||
"@bull-board/api": "6.5.0",
|
||||
"@bull-board/fastify": "6.5.0",
|
||||
"@bull-board/ui": "6.5.0",
|
||||
"@discordapp/twemoji": "15.1.0",
|
||||
"@fastify/accepts": "5.0.1",
|
||||
"@fastify/cookie": "10.0.1",
|
||||
"@fastify/cookie": "11.0.1",
|
||||
"@fastify/cors": "10.0.1",
|
||||
"@fastify/express": "4.0.1",
|
||||
"@fastify/http-proxy": "10.0.0",
|
||||
"@fastify/http-proxy": "10.0.1",
|
||||
"@fastify/multipart": "9.0.1",
|
||||
"@fastify/static": "8.0.1",
|
||||
"@fastify/static": "8.0.2",
|
||||
"@fastify/view": "10.0.1",
|
||||
"@misskey-dev/sharp-read-bmp": "1.2.0",
|
||||
"@misskey-dev/summaly": "5.1.0",
|
||||
"@napi-rs/canvas": "0.1.56",
|
||||
"@nestjs/common": "10.4.4",
|
||||
"@nestjs/core": "10.4.4",
|
||||
"@nestjs/testing": "10.4.4",
|
||||
"@nestjs/common": "10.4.7",
|
||||
"@nestjs/core": "10.4.7",
|
||||
"@nestjs/testing": "10.4.7",
|
||||
"@peertube/http-signature": "1.7.0",
|
||||
"@sentry/node": "8.20.0",
|
||||
"@sentry/profiling-node": "8.20.0",
|
||||
"@sentry/node": "8.38.0",
|
||||
"@sentry/profiling-node": "8.38.0",
|
||||
"@simplewebauthn/server": "10.0.1",
|
||||
"@sinonjs/fake-timers": "11.2.2",
|
||||
"@smithy/node-http-handler": "2.5.0",
|
||||
"@swc/cli": "0.3.12",
|
||||
"@swc/core": "1.6.6",
|
||||
"@swc/core": "1.9.2",
|
||||
"@transfem-org/sfm-js": "0.24.5",
|
||||
"@twemoji/parser": "15.1.1",
|
||||
"accepts": "1.3.8",
|
||||
|
|
@ -103,7 +103,7 @@
|
|||
"bcryptjs": "2.4.3",
|
||||
"blurhash": "2.0.5",
|
||||
"body-parser": "1.20.3",
|
||||
"bullmq": "5.15.0",
|
||||
"bullmq": "5.26.1",
|
||||
"cacheable-lookup": "7.0.0",
|
||||
"cbor": "9.0.2",
|
||||
"chalk": "5.3.0",
|
||||
|
|
@ -119,12 +119,12 @@
|
|||
"fastify-multer": "^2.0.3",
|
||||
"fastify-raw-body": "5.0.0",
|
||||
"feed": "4.2.2",
|
||||
"file-type": "19.5.0",
|
||||
"file-type": "19.6.0",
|
||||
"fluent-ffmpeg": "2.1.3",
|
||||
"form-data": "4.0.0",
|
||||
"form-data": "4.0.1",
|
||||
"glob": "11.0.0",
|
||||
"got": "14.4.2",
|
||||
"happy-dom": "15.7.4",
|
||||
"got": "14.4.4",
|
||||
"happy-dom": "15.11.4",
|
||||
"hpagent": "1.2.0",
|
||||
"htmlescape": "1.1.1",
|
||||
"http-link-header": "1.1.3",
|
||||
|
|
@ -138,24 +138,24 @@
|
|||
"jsonld": "8.3.2",
|
||||
"jsrsasign": "11.1.0",
|
||||
"megalodon": "workspace:*",
|
||||
"meilisearch": "0.42.0",
|
||||
"meilisearch": "0.45.0",
|
||||
"juice": "11.0.0",
|
||||
"microformats-parser": "2.0.2",
|
||||
"mime-types": "2.1.35",
|
||||
"misskey-js": "workspace:*",
|
||||
"misskey-reversi": "workspace:*",
|
||||
"ms": "3.0.0-canary.1",
|
||||
"nanoid": "5.0.7",
|
||||
"nanoid": "5.0.8",
|
||||
"nested-property": "4.0.0",
|
||||
"node-fetch": "3.3.2",
|
||||
"nodemailer": "6.9.15",
|
||||
"nodemailer": "6.9.16",
|
||||
"oauth": "0.10.0",
|
||||
"oauth2orize": "1.12.0",
|
||||
"oauth2orize-pkce": "0.1.2",
|
||||
"os-utils": "0.0.14",
|
||||
"otpauth": "9.3.4",
|
||||
"parse5": "7.1.2",
|
||||
"pg": "8.13.0",
|
||||
"parse5": "7.2.1",
|
||||
"pg": "8.13.1",
|
||||
"pkce-challenge": "4.1.0",
|
||||
"probe-image-size": "7.2.3",
|
||||
"promise-limit": "2.7.0",
|
||||
|
|
@ -183,7 +183,7 @@
|
|||
"tsc-alias": "1.8.10",
|
||||
"tsconfig-paths": "4.2.0",
|
||||
"typeorm": "0.3.20",
|
||||
"typescript": "5.6.2",
|
||||
"typescript": "5.6.3",
|
||||
"ulid": "2.3.0",
|
||||
"uuid": "^9.0.1",
|
||||
"vary": "1.1.2",
|
||||
|
|
@ -193,28 +193,28 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@jest/globals": "29.7.0",
|
||||
"@nestjs/platform-express": "10.4.4",
|
||||
"@nestjs/platform-express": "10.4.7",
|
||||
"@simplewebauthn/types": "10.0.0",
|
||||
"@swc/jest": "0.2.36",
|
||||
"@swc/jest": "0.2.37",
|
||||
"@types/accepts": "1.3.7",
|
||||
"@types/archiver": "6.0.2",
|
||||
"@types/archiver": "6.0.3",
|
||||
"@types/bcryptjs": "2.4.6",
|
||||
"@types/body-parser": "1.19.5",
|
||||
"@types/color-convert": "2.0.4",
|
||||
"@types/content-disposition": "0.5.8",
|
||||
"@types/fluent-ffmpeg": "2.1.26",
|
||||
"@types/fluent-ffmpeg": "2.1.27",
|
||||
"@types/htmlescape": "1.1.3",
|
||||
"@types/http-link-header": "1.0.7",
|
||||
"@types/jest": "29.5.13",
|
||||
"@types/jest": "29.5.14",
|
||||
"@types/js-yaml": "4.0.9",
|
||||
"@types/jsdom": "21.1.7",
|
||||
"@types/jsonld": "1.5.15",
|
||||
"@types/jsrsasign": "10.5.14",
|
||||
"@types/mime-types": "2.1.4",
|
||||
"@types/ms": "0.7.34",
|
||||
"@types/node": "20.14.12",
|
||||
"@types/node": "22.9.0",
|
||||
"@types/nodemailer": "6.4.16",
|
||||
"@types/oauth": "0.9.5",
|
||||
"@types/oauth": "0.9.6",
|
||||
"@types/oauth2orize": "1.11.5",
|
||||
"@types/oauth2orize-pkce": "0.1.2",
|
||||
"@types/pg": "8.11.10",
|
||||
|
|
@ -233,14 +233,14 @@
|
|||
"@types/tmp": "0.2.6",
|
||||
"@types/uuid": "^9.0.4",
|
||||
"@types/vary": "1.1.3",
|
||||
"@types/web-push": "3.6.3",
|
||||
"@types/ws": "8.5.12",
|
||||
"@types/web-push": "3.6.4",
|
||||
"@types/ws": "8.5.13",
|
||||
"@typescript-eslint/eslint-plugin": "7.17.0",
|
||||
"@typescript-eslint/parser": "7.17.0",
|
||||
"aws-sdk-client-mock": "4.0.1",
|
||||
"cross-env": "7.0.3",
|
||||
"eslint-plugin-import": "2.30.0",
|
||||
"execa": "9.4.0",
|
||||
"execa": "8.0.1",
|
||||
"fkill": "9.0.0",
|
||||
"jest": "29.7.0",
|
||||
"jest-mock": "29.7.0",
|
||||
|
|
|
|||
|
|
@ -154,9 +154,9 @@ export class AbuseReportNotificationService implements OnApplicationShutdown {
|
|||
const convertedReports = abuseReports.map(it => {
|
||||
return {
|
||||
...it,
|
||||
reporter: usersMap.get(it.reporterId),
|
||||
targetUser: usersMap.get(it.targetUserId),
|
||||
assignee: it.assigneeId ? usersMap.get(it.assigneeId) : null,
|
||||
reporter: usersMap.get(it.reporterId) ?? null,
|
||||
targetUser: usersMap.get(it.targetUserId) ?? null,
|
||||
assignee: it.assigneeId ? (usersMap.get(it.assigneeId) ?? null) : null,
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ export class AnnouncementService {
|
|||
updatedAt: null,
|
||||
title: values.title,
|
||||
text: values.text,
|
||||
imageUrl: values.imageUrl,
|
||||
imageUrl: values.imageUrl || null,
|
||||
icon: values.icon,
|
||||
display: values.display,
|
||||
forExistingUsers: values.forExistingUsers,
|
||||
|
|
|
|||
|
|
@ -58,6 +58,7 @@ import { isUserRelated } from '@/misc/is-user-related.js';
|
|||
import { IdentifiableError } from '@/misc/identifiable-error.js';
|
||||
import { LatestNoteService } from '@/core/LatestNoteService.js';
|
||||
import { CollapsedQueue } from '@/misc/collapsed-queue.js';
|
||||
import { CacheService } from '@/core/CacheService.js';
|
||||
|
||||
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
|
||||
|
||||
|
|
@ -608,13 +609,21 @@ export class NoteCreateService implements OnApplicationShutdown {
|
|||
this.followingsRepository.findBy({
|
||||
followeeId: user.id,
|
||||
notify: 'normal',
|
||||
}).then(followings => {
|
||||
}).then(async followings => {
|
||||
if (note.visibility !== 'specified') {
|
||||
const isPureRenote = this.isRenote(data) && !this.isQuote(data) ? true : false;
|
||||
for (const following of followings) {
|
||||
// TODO: ワードミュート考慮
|
||||
this.notificationService.createNotification(following.followerId, 'note', {
|
||||
noteId: note.id,
|
||||
}, user.id);
|
||||
let isRenoteMuted = false;
|
||||
if (isPureRenote) {
|
||||
const userIdsWhoMeMutingRenotes = await this.cacheService.renoteMutingsCache.fetch(following.followerId);
|
||||
isRenoteMuted = userIdsWhoMeMutingRenotes.has(user.id);
|
||||
}
|
||||
if (!isRenoteMuted) {
|
||||
this.notificationService.createNotification(following.followerId, 'note', {
|
||||
noteId: note.id,
|
||||
}, user.id);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -7,13 +7,15 @@ import { randomUUID } from 'node:crypto';
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { IActivity } from '@/core/activitypub/type.js';
|
||||
import type { MiDriveFile } from '@/models/DriveFile.js';
|
||||
import type { MiWebhook, webhookEventTypes } from '@/models/Webhook.js';
|
||||
import type { MiWebhook, WebhookEventTypes } from '@/models/Webhook.js';
|
||||
import type { MiSystemWebhook, SystemWebhookEventType } from '@/models/SystemWebhook.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import type { Antenna } from '@/server/api/endpoints/i/import-antennas.js';
|
||||
import { ApRequestCreator } from '@/core/activitypub/ApRequestService.js';
|
||||
import { type SystemWebhookPayload } from '@/core/SystemWebhookService.js';
|
||||
import { type UserWebhookPayload } from './UserWebhookService.js';
|
||||
import type {
|
||||
DbJobData,
|
||||
DeliverJobData,
|
||||
|
|
@ -30,8 +32,8 @@ import type {
|
|||
ObjectStorageQueue,
|
||||
RelationshipQueue,
|
||||
SystemQueue,
|
||||
UserWebhookDeliverQueue,
|
||||
SystemWebhookDeliverQueue,
|
||||
UserWebhookDeliverQueue,
|
||||
} from './QueueModule.js';
|
||||
import type httpSignature from '@peertube/http-signature';
|
||||
import type * as Bull from 'bullmq';
|
||||
|
|
@ -527,10 +529,10 @@ export class QueueService {
|
|||
* @see UserWebhookDeliverProcessorService
|
||||
*/
|
||||
@bindThis
|
||||
public userWebhookDeliver(
|
||||
public userWebhookDeliver<T extends WebhookEventTypes>(
|
||||
webhook: MiWebhook,
|
||||
type: typeof webhookEventTypes[number],
|
||||
content: unknown,
|
||||
type: T,
|
||||
content: UserWebhookPayload<T>,
|
||||
opts?: { attempts?: number },
|
||||
) {
|
||||
const data: UserWebhookDeliverJobData = {
|
||||
|
|
@ -559,10 +561,10 @@ export class QueueService {
|
|||
* @see SystemWebhookDeliverProcessorService
|
||||
*/
|
||||
@bindThis
|
||||
public systemWebhookDeliver(
|
||||
public systemWebhookDeliver<T extends SystemWebhookEventType>(
|
||||
webhook: MiSystemWebhook,
|
||||
type: SystemWebhookEventType,
|
||||
content: unknown,
|
||||
type: T,
|
||||
content: SystemWebhookPayload<T>,
|
||||
opts?: { attempts?: number },
|
||||
) {
|
||||
const data: SystemWebhookDeliverJobData = {
|
||||
|
|
|
|||
|
|
@ -15,8 +15,39 @@ import { QueueService } from '@/core/QueueService.js';
|
|||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
import Logger from '@/logger.js';
|
||||
import { Packed } from '@/misc/json-schema.js';
|
||||
import { AbuseReportResolveType } from '@/models/AbuseUserReport.js';
|
||||
import { ModeratorInactivityRemainingTime } from '@/queue/processors/CheckModeratorsActivityProcessorService.js';
|
||||
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||
|
||||
export type AbuseReportPayload = {
|
||||
id: string;
|
||||
targetUserId: string;
|
||||
targetUser: Packed<'UserLite'> | null;
|
||||
targetUserHost: string | null;
|
||||
reporterId: string;
|
||||
reporter: Packed<'UserLite'> | null;
|
||||
reporterHost: string | null;
|
||||
assigneeId: string | null;
|
||||
assignee: Packed<'UserLite'> | null;
|
||||
resolved: boolean;
|
||||
forwarded: boolean;
|
||||
comment: string;
|
||||
moderationNote: string;
|
||||
resolvedAs: AbuseReportResolveType | null;
|
||||
};
|
||||
|
||||
export type InactiveModeratorsWarningPayload = {
|
||||
remainingTime: ModeratorInactivityRemainingTime;
|
||||
};
|
||||
|
||||
export type SystemWebhookPayload<T extends SystemWebhookEventType> =
|
||||
T extends 'abuseReport' | 'abuseReportResolved' ? AbuseReportPayload :
|
||||
T extends 'userCreated' ? Packed<'UserLite'> :
|
||||
T extends 'inactiveModeratorsWarning' ? InactiveModeratorsWarningPayload :
|
||||
T extends 'inactiveModeratorsInvitationOnlyChanged' ? Record<string, never> :
|
||||
never;
|
||||
|
||||
@Injectable()
|
||||
export class SystemWebhookService implements OnApplicationShutdown {
|
||||
private logger: Logger;
|
||||
|
|
@ -168,7 +199,7 @@ export class SystemWebhookService implements OnApplicationShutdown {
|
|||
public async enqueueSystemWebhook<T extends SystemWebhookEventType>(
|
||||
webhook: MiSystemWebhook | MiSystemWebhook['id'],
|
||||
type: T,
|
||||
content: unknown,
|
||||
content: SystemWebhookPayload<T>,
|
||||
) {
|
||||
const webhookEntity = typeof webhook === 'string'
|
||||
? (await this.fetchActiveSystemWebhooks()).find(a => a.id === webhook)
|
||||
|
|
|
|||
|
|
@ -6,11 +6,23 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as Redis from 'ioredis';
|
||||
import { type WebhooksRepository } from '@/models/_.js';
|
||||
import { MiWebhook } from '@/models/Webhook.js';
|
||||
import { MiWebhook, WebhookEventTypes } from '@/models/Webhook.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { GlobalEvents } from '@/core/GlobalEventService.js';
|
||||
import type { OnApplicationShutdown } from '@nestjs/common';
|
||||
import type { Packed } from '@/misc/json-schema.js';
|
||||
|
||||
export type UserWebhookPayload<T extends WebhookEventTypes> =
|
||||
T extends 'note' | 'reply' | 'renote' |'mention' ? {
|
||||
note: Packed<'Note'>,
|
||||
} :
|
||||
T extends 'follow' | 'unfollow' ? {
|
||||
user: Packed<'UserDetailedNotMe'>,
|
||||
} :
|
||||
T extends 'followed' ? {
|
||||
user: Packed<'UserLite'>,
|
||||
} : never;
|
||||
|
||||
@Injectable()
|
||||
export class UserWebhookService implements OnApplicationShutdown {
|
||||
|
|
|
|||
|
|
@ -122,6 +122,7 @@ export class UtilityService {
|
|||
return host;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public isFederationAllowedHost(host: string): boolean {
|
||||
if (this.meta.federation === 'none') return false;
|
||||
if (this.meta.federation === 'specified' && !this.meta.federationHosts.some(x => `.${host.toLowerCase()}`.endsWith(`.${x}`))) return false;
|
||||
|
|
|
|||
|
|
@ -246,14 +246,12 @@ export class WebAuthnService {
|
|||
|
||||
@bindThis
|
||||
public async verifyAuthentication(userId: MiUser['id'], response: AuthenticationResponseJSON): Promise<boolean> {
|
||||
const challenge = await this.redisClient.get(`webauthn:challenge:${userId}`);
|
||||
const challenge = await this.redisClient.getdel(`webauthn:challenge:${userId}`);
|
||||
|
||||
if (!challenge) {
|
||||
throw new IdentifiableError('2d16e51c-007b-4edd-afd2-f7dd02c947f6', 'challenge not found');
|
||||
}
|
||||
|
||||
await this.redisClient.del(`webauthn:challenge:${userId}`);
|
||||
|
||||
const key = await this.userSecurityKeysRepository.findOneBy({
|
||||
id: response.id,
|
||||
userId: userId,
|
||||
|
|
|
|||
|
|
@ -7,22 +7,16 @@ import { Injectable } from '@nestjs/common';
|
|||
import { MiAbuseUserReport, MiNote, MiUser, MiWebhook } from '@/models/_.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { MiSystemWebhook, type SystemWebhookEventType } from '@/models/SystemWebhook.js';
|
||||
import { SystemWebhookService } from '@/core/SystemWebhookService.js';
|
||||
import { AbuseReportPayload, SystemWebhookPayload, SystemWebhookService } from '@/core/SystemWebhookService.js';
|
||||
import { Packed } from '@/misc/json-schema.js';
|
||||
import { type WebhookEventTypes } from '@/models/Webhook.js';
|
||||
import { UserWebhookService } from '@/core/UserWebhookService.js';
|
||||
import { type UserWebhookPayload, UserWebhookService } from '@/core/UserWebhookService.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
import { ModeratorInactivityRemainingTime } from '@/queue/processors/CheckModeratorsActivityProcessorService.js';
|
||||
|
||||
const oneDayMillis = 24 * 60 * 60 * 1000;
|
||||
|
||||
type AbuseUserReportDto = Omit<MiAbuseUserReport, 'targetUser' | 'reporter' | 'assignee'> & {
|
||||
targetUser: Packed<'UserLite'> | null,
|
||||
reporter: Packed<'UserLite'> | null,
|
||||
assignee: Packed<'UserLite'> | null,
|
||||
};
|
||||
|
||||
function generateAbuseReport(override?: Partial<MiAbuseUserReport>): AbuseUserReportDto {
|
||||
function generateAbuseReport(override?: Partial<MiAbuseUserReport>): AbuseReportPayload {
|
||||
const result: MiAbuseUserReport = {
|
||||
id: 'dummy-abuse-report1',
|
||||
targetUserId: 'dummy-target-user',
|
||||
|
|
@ -89,6 +83,9 @@ function generateDummyUser(override?: Partial<MiUser>): MiUser {
|
|||
isExplorable: true,
|
||||
isHibernated: false,
|
||||
isDeleted: false,
|
||||
requireSigninToViewContents: false,
|
||||
makeNotesFollowersOnlyBefore: null,
|
||||
makeNotesHiddenBefore: null,
|
||||
emojis: [],
|
||||
score: 0,
|
||||
host: null,
|
||||
|
|
@ -322,10 +319,10 @@ export class WebhookTestService {
|
|||
* - 送信対象イベント(on)に関する設定
|
||||
*/
|
||||
@bindThis
|
||||
public async testUserWebhook(
|
||||
public async testUserWebhook<T extends WebhookEventTypes>(
|
||||
params: {
|
||||
webhookId: MiWebhook['id'],
|
||||
type: WebhookEventTypes,
|
||||
type: T,
|
||||
override?: Partial<Omit<MiWebhook, 'id'>>,
|
||||
},
|
||||
sender: MiUser | null,
|
||||
|
|
@ -337,7 +334,7 @@ export class WebhookTestService {
|
|||
}
|
||||
|
||||
const webhook = webhooks[0];
|
||||
const send = (contents: unknown) => {
|
||||
const send = <U extends WebhookEventTypes>(type: U, contents: UserWebhookPayload<U>) => {
|
||||
const merged = {
|
||||
...webhook,
|
||||
...params.override,
|
||||
|
|
@ -345,7 +342,7 @@ export class WebhookTestService {
|
|||
|
||||
// テスト目的なのでUserWebhookServiceの機能を経由せず直接キューに追加する(チェック処理などをスキップする意図).
|
||||
// また、Jobの試行回数も1回だけ.
|
||||
this.queueService.userWebhookDeliver(merged, params.type, contents, { attempts: 1 });
|
||||
this.queueService.userWebhookDeliver(merged, type, contents, { attempts: 1 });
|
||||
};
|
||||
|
||||
const dummyNote1 = generateDummyNote({
|
||||
|
|
@ -377,33 +374,41 @@ export class WebhookTestService {
|
|||
|
||||
switch (params.type) {
|
||||
case 'note': {
|
||||
send(toPackedNote(dummyNote1));
|
||||
send('note', { note: toPackedNote(dummyNote1) });
|
||||
break;
|
||||
}
|
||||
case 'reply': {
|
||||
send(toPackedNote(dummyReply1));
|
||||
send('reply', { note: toPackedNote(dummyReply1) });
|
||||
break;
|
||||
}
|
||||
case 'renote': {
|
||||
send(toPackedNote(dummyRenote1));
|
||||
send('renote', { note: toPackedNote(dummyRenote1) });
|
||||
break;
|
||||
}
|
||||
case 'mention': {
|
||||
send(toPackedNote(dummyMention1));
|
||||
send('mention', { note: toPackedNote(dummyMention1) });
|
||||
break;
|
||||
}
|
||||
case 'follow': {
|
||||
send(toPackedUserDetailedNotMe(dummyUser1));
|
||||
send('follow', { user: toPackedUserDetailedNotMe(dummyUser1) });
|
||||
break;
|
||||
}
|
||||
case 'followed': {
|
||||
send(toPackedUserLite(dummyUser2));
|
||||
send('followed', { user: toPackedUserLite(dummyUser2) });
|
||||
break;
|
||||
}
|
||||
case 'unfollow': {
|
||||
send(toPackedUserDetailedNotMe(dummyUser3));
|
||||
send('unfollow', { user: toPackedUserDetailedNotMe(dummyUser3) });
|
||||
break;
|
||||
}
|
||||
// まだ実装されていない (#9485)
|
||||
case 'reaction':
|
||||
return;
|
||||
default: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const _exhaustiveAssertion: never = params.type;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -416,10 +421,10 @@ export class WebhookTestService {
|
|||
* - 送信対象イベント(on)に関する設定
|
||||
*/
|
||||
@bindThis
|
||||
public async testSystemWebhook(
|
||||
public async testSystemWebhook<T extends SystemWebhookEventType>(
|
||||
params: {
|
||||
webhookId: MiSystemWebhook['id'],
|
||||
type: SystemWebhookEventType,
|
||||
type: T,
|
||||
override?: Partial<Omit<MiSystemWebhook, 'id'>>,
|
||||
},
|
||||
) {
|
||||
|
|
@ -429,7 +434,7 @@ export class WebhookTestService {
|
|||
}
|
||||
|
||||
const webhook = webhooks[0];
|
||||
const send = (contents: unknown) => {
|
||||
const send = <U extends SystemWebhookEventType>(type: U, contents: SystemWebhookPayload<U>) => {
|
||||
const merged = {
|
||||
...webhook,
|
||||
...params.override,
|
||||
|
|
@ -437,12 +442,12 @@ export class WebhookTestService {
|
|||
|
||||
// テスト目的なのでSystemWebhookServiceの機能を経由せず直接キューに追加する(チェック処理などをスキップする意図).
|
||||
// また、Jobの試行回数も1回だけ.
|
||||
this.queueService.systemWebhookDeliver(merged, params.type, contents, { attempts: 1 });
|
||||
this.queueService.systemWebhookDeliver(merged, type, contents, { attempts: 1 });
|
||||
};
|
||||
|
||||
switch (params.type) {
|
||||
case 'abuseReport': {
|
||||
send(generateAbuseReport({
|
||||
send('abuseReport', generateAbuseReport({
|
||||
targetUserId: dummyUser1.id,
|
||||
targetUser: dummyUser1,
|
||||
reporterId: dummyUser2.id,
|
||||
|
|
@ -451,7 +456,7 @@ export class WebhookTestService {
|
|||
break;
|
||||
}
|
||||
case 'abuseReportResolved': {
|
||||
send(generateAbuseReport({
|
||||
send('abuseReportResolved', generateAbuseReport({
|
||||
targetUserId: dummyUser1.id,
|
||||
targetUser: dummyUser1,
|
||||
reporterId: dummyUser2.id,
|
||||
|
|
@ -463,7 +468,7 @@ export class WebhookTestService {
|
|||
break;
|
||||
}
|
||||
case 'userCreated': {
|
||||
send(toPackedUserLite(dummyUser1));
|
||||
send('userCreated', toPackedUserLite(dummyUser1));
|
||||
break;
|
||||
}
|
||||
case 'inactiveModeratorsWarning': {
|
||||
|
|
@ -473,15 +478,20 @@ export class WebhookTestService {
|
|||
asHours: 24,
|
||||
};
|
||||
|
||||
send({
|
||||
send('inactiveModeratorsWarning', {
|
||||
remainingTime: dummyTime,
|
||||
});
|
||||
break;
|
||||
}
|
||||
case 'inactiveModeratorsInvitationOnlyChanged': {
|
||||
send({});
|
||||
send('inactiveModeratorsInvitationOnlyChanged', {});
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const _exhaustiveAssertion: never = params.type;
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -135,6 +135,7 @@ export class ApInboxService {
|
|||
if (actor.uri) {
|
||||
if (actor.lastFetchedAt == null || Date.now() - actor.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) {
|
||||
setImmediate(() => {
|
||||
// 同一ユーザーの情報を再度処理するので、使用済みのresolverを再利用してはいけない
|
||||
this.apPersonService.updatePerson(actor.uri);
|
||||
});
|
||||
}
|
||||
|
|
|
|||
|
|
@ -517,6 +517,9 @@ export class ApRendererService {
|
|||
summary: profile.description ? this.mfmService.toHtml(mfm.parse(profile.description)) : null,
|
||||
_misskey_summary: profile.description,
|
||||
_misskey_followedMessage: profile.followedMessage,
|
||||
_misskey_requireSigninToViewContents: user.requireSigninToViewContents,
|
||||
_misskey_makeNotesFollowersOnlyBefore: user.makeNotesFollowersOnlyBefore,
|
||||
_misskey_makeNotesHiddenBefore: user.makeNotesHiddenBefore,
|
||||
icon: avatar ? this.renderImage(avatar) : null,
|
||||
image: banner ? this.renderImage(banner) : null,
|
||||
backgroundUrl: background ? this.renderImage(background) : null,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import { DI } from '@/di-symbols.js';
|
|||
import type { Config } from '@/config.js';
|
||||
import type { MiUser } from '@/models/User.js';
|
||||
import { UserKeypairService } from '@/core/UserKeypairService.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
||||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
|
@ -19,6 +20,7 @@ import type { IObject } from './type.js';
|
|||
import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js';
|
||||
import { assertActivityMatchesUrls } from '@/core/activitypub/misc/check-against-url.js';
|
||||
import { UtilityService } from "@/core/UtilityService.js";
|
||||
import type { IObject } from './type.js';
|
||||
|
||||
type Request = {
|
||||
url: string;
|
||||
|
|
@ -242,10 +244,8 @@ export class ApRequestService {
|
|||
const alternate = document.querySelector('head > link[rel="alternate"][type="application/activity+json"]');
|
||||
if (alternate) {
|
||||
const href = alternate.getAttribute('href');
|
||||
if (href) {
|
||||
if (this.utilityService.punyHost(url) === this.utilityService.punyHost(href)) {
|
||||
return await this.signedGet(href, user, false);
|
||||
}
|
||||
if (href && this.utilityService.punyHost(url) === this.utilityService.punyHost(href)) {
|
||||
return await this.signedGet(href, user, false);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
|
|
|
|||
|
|
@ -558,6 +558,9 @@ const extension_context_definition = {
|
|||
'_misskey_votes': 'misskey:_misskey_votes',
|
||||
'_misskey_summary': 'misskey:_misskey_summary',
|
||||
'_misskey_followedMessage': 'misskey:_misskey_followedMessage',
|
||||
'_misskey_requireSigninToViewContents': 'misskey:_misskey_requireSigninToViewContents',
|
||||
'_misskey_makeNotesFollowersOnlyBefore': 'misskey:_misskey_makeNotesFollowersOnlyBefore',
|
||||
'_misskey_makeNotesHiddenBefore': 'misskey:_misskey_makeNotesHiddenBefore',
|
||||
'isCat': 'misskey:isCat',
|
||||
// Firefish
|
||||
firefish: 'https://joinfirefish.org/ns#',
|
||||
|
|
|
|||
|
|
@ -108,6 +108,10 @@ export class ApNoteService {
|
|||
return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', `invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${actualHost}`);
|
||||
}
|
||||
|
||||
if (object.published && !this.idService.isSafeT(new Date(object.published).valueOf())) {
|
||||
return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', 'invalid Note: published timestamp is malformed');
|
||||
}
|
||||
|
||||
if (actor) {
|
||||
const attribution = (object.attributedTo) ? getOneApId(object.attributedTo) : actor.uri;
|
||||
if (attribution !== actor.uri) {
|
||||
|
|
@ -118,10 +122,6 @@ export class ApNoteService {
|
|||
}
|
||||
}
|
||||
|
||||
if (object.published && !this.idService.isSafeT(new Date(object.published).valueOf())) {
|
||||
return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', 'invalid Note: published timestamp is malformed');
|
||||
}
|
||||
|
||||
if (note) {
|
||||
const url = (object.url) ? getOneApId(object.url) : note.url;
|
||||
if (url && url !== note.url) {
|
||||
|
|
@ -183,7 +183,7 @@ export class ApNoteService {
|
|||
}
|
||||
|
||||
if (this.utilityService.punyHost(url) !== this.utilityService.punyHost(note.id)) {
|
||||
throw new Error(`note url <> uri host mismatch: ${url} <> ${note.id}`);
|
||||
throw new Error(`note url & uri host mismatch: note url: ${url}, note uri: ${note.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -421,9 +421,6 @@ export class ApNoteService {
|
|||
|
||||
const url = getOneApHrefNullable(note.url);
|
||||
|
||||
if (url && !checkHttps(url)) {
|
||||
throw new Error('unexpected schema of note url: ' + url);
|
||||
}
|
||||
|
||||
if (url != null) {
|
||||
if (!checkHttps(url)) {
|
||||
|
|
@ -431,7 +428,7 @@ export class ApNoteService {
|
|||
}
|
||||
|
||||
if (this.utilityService.punyHost(url) !== this.utilityService.punyHost(note.id)) {
|
||||
throw new Error(`note url <> id host mismatch: ${url} <> ${note.id}`);
|
||||
throw new Error(`note url & uri host mismatch: note url: ${url}, note uri: ${note.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -253,6 +253,12 @@ export class ApPersonService implements OnModuleInit {
|
|||
if (user == null) throw new Error('failed to create user: user is null');
|
||||
|
||||
const [avatar, banner, background] = await Promise.all([icon, image, bgimg].map(img => {
|
||||
// icon and image may be arrays
|
||||
// see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-icon
|
||||
if (Array.isArray(img)) {
|
||||
img = img.find(item => item && item.url) ?? null;
|
||||
}
|
||||
|
||||
// if we have an explicitly missing image, return an
|
||||
// explicitly-null set of values
|
||||
if ((img == null) || (typeof img === 'object' && img.url == null)) {
|
||||
|
|
@ -393,7 +399,7 @@ export class ApPersonService implements OnModuleInit {
|
|||
usernameLower: person.preferredUsername?.toLowerCase(),
|
||||
host,
|
||||
inbox: person.inbox,
|
||||
sharedInbox: person.sharedInbox ?? person.endpoints?.sharedInbox,
|
||||
sharedInbox: person.sharedInbox ?? person.endpoints?.sharedInbox ?? null,
|
||||
notesCount: outboxcollection?.totalItems ?? 0,
|
||||
followersCount: followerscollection?.totalItems ?? 0,
|
||||
followingCount: followingcollection?.totalItems ?? 0,
|
||||
|
|
@ -404,6 +410,9 @@ export class ApPersonService implements OnModuleInit {
|
|||
isBot,
|
||||
isCat: (person as any).isCat === true,
|
||||
speakAsCat: (person as any).speakAsCat != null ? (person as any).speakAsCat === true : (person as any).isCat === true,
|
||||
requireSigninToViewContents: (person as any).requireSigninToViewContents === true,
|
||||
makeNotesFollowersOnlyBefore: (person as any).makeNotesFollowersOnlyBefore ?? null,
|
||||
makeNotesHiddenBefore: (person as any).makeNotesHiddenBefore ?? null,
|
||||
emojis,
|
||||
})) as MiRemoteUser;
|
||||
|
||||
|
|
@ -571,7 +580,7 @@ export class ApPersonService implements OnModuleInit {
|
|||
const updates = {
|
||||
lastFetchedAt: new Date(),
|
||||
inbox: person.inbox,
|
||||
sharedInbox: person.sharedInbox ?? person.endpoints?.sharedInbox,
|
||||
sharedInbox: person.sharedInbox ?? person.endpoints?.sharedInbox ?? null,
|
||||
followersUri: person.followers ? getApId(person.followers) : undefined,
|
||||
featured: person.featured,
|
||||
emojis: emojiNames,
|
||||
|
|
@ -647,7 +656,7 @@ export class ApPersonService implements OnModuleInit {
|
|||
// 該当ユーザーが既にフォロワーになっていた場合はFollowingもアップデートする
|
||||
await this.followingsRepository.update(
|
||||
{ followerId: exist.id },
|
||||
{ followerSharedInbox: person.sharedInbox ?? person.endpoints?.sharedInbox },
|
||||
{ followerSharedInbox: person.sharedInbox ?? person.endpoints?.sharedInbox ?? null },
|
||||
);
|
||||
|
||||
await this.updateFeatured(exist.id, resolver).catch(err => this.logger.error(err));
|
||||
|
|
|
|||
|
|
@ -11,8 +11,8 @@ import type { IPoll } from '@/models/Poll.js';
|
|||
import type { MiRemoteUser } from '@/models/User.js';
|
||||
import type Logger from '@/logger.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { getOneApId, isQuestion } from '../type.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { ApLoggerService } from '../ApLoggerService.js';
|
||||
import { ApResolverService } from '../ApResolverService.js';
|
||||
import type { Resolver } from '../ApResolverService.js';
|
||||
|
|
|
|||
|
|
@ -16,6 +16,9 @@ export interface IObject {
|
|||
summary?: string | null;
|
||||
_misskey_summary?: string;
|
||||
_misskey_followedMessage?: string | null;
|
||||
_misskey_requireSigninToViewContents?: boolean;
|
||||
_misskey_makeNotesFollowersOnlyBefore?: number | null;
|
||||
_misskey_makeNotesHiddenBefore?: number | null;
|
||||
published?: string;
|
||||
cc?: ApObject;
|
||||
to?: ApObject;
|
||||
|
|
|
|||
|
|
@ -109,57 +109,81 @@ export class NoteEntityService implements OnModuleInit {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
private async hideNote(packedNote: Packed<'Note'>, meId: MiUser['id'] | null) {
|
||||
private async hideNote(packedNote: Packed<'Note'>, meId: MiUser['id'] | null): Promise<void> {
|
||||
// FIXME: このvisibility変更処理が当関数にあるのは若干不自然かもしれない(関数名を treatVisibility とかに変える手もある)
|
||||
if (packedNote.visibility === 'public' || packedNote.visibility === 'home') {
|
||||
const followersOnlyBefore = packedNote.user.makeNotesFollowersOnlyBefore;
|
||||
if ((followersOnlyBefore != null)
|
||||
&& (
|
||||
(followersOnlyBefore <= 0 && (Date.now() - new Date(packedNote.createdAt).getTime() > 0 - (followersOnlyBefore * 1000)))
|
||||
|| (followersOnlyBefore > 0 && (new Date(packedNote.createdAt).getTime() < followersOnlyBefore * 1000))
|
||||
)
|
||||
) {
|
||||
packedNote.visibility = 'followers';
|
||||
}
|
||||
}
|
||||
|
||||
if (meId === packedNote.userId) return;
|
||||
|
||||
// TODO: isVisibleForMe を使うようにしても良さそう(型違うけど)
|
||||
let hide = false;
|
||||
|
||||
// visibility が specified かつ自分が指定されていなかったら非表示
|
||||
if (packedNote.visibility === 'specified') {
|
||||
if (meId == null) {
|
||||
hide = true;
|
||||
} else if (meId === packedNote.userId) {
|
||||
hide = false;
|
||||
} else {
|
||||
// 指定されているかどうか
|
||||
const specified = packedNote.visibleUserIds!.some(id => meId === id);
|
||||
if (packedNote.user.requireSigninToViewContents && meId == null) {
|
||||
hide = true;
|
||||
}
|
||||
|
||||
if (specified) {
|
||||
if (!hide) {
|
||||
const hiddenBefore = packedNote.user.makeNotesHiddenBefore;
|
||||
if ((hiddenBefore != null)
|
||||
&& (
|
||||
(hiddenBefore <= 0 && (Date.now() - new Date(packedNote.createdAt).getTime() > 0 - (hiddenBefore * 1000)))
|
||||
|| (hiddenBefore > 0 && (new Date(packedNote.createdAt).getTime() < hiddenBefore * 1000))
|
||||
)
|
||||
) {
|
||||
hide = true;
|
||||
}
|
||||
}
|
||||
|
||||
// visibility が specified かつ自分が指定されていなかったら非表示
|
||||
if (!hide) {
|
||||
if (packedNote.visibility === 'specified') {
|
||||
if (meId == null) {
|
||||
hide = true;
|
||||
} else if (meId === packedNote.userId) {
|
||||
hide = false;
|
||||
} else {
|
||||
hide = true;
|
||||
// 指定されているかどうか
|
||||
const specified = packedNote.visibleUserIds!.some(id => meId === id);
|
||||
|
||||
if (!specified) {
|
||||
hide = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示
|
||||
if (packedNote.visibility === 'followers') {
|
||||
if (meId == null) {
|
||||
hide = true;
|
||||
} else if (meId === packedNote.userId) {
|
||||
hide = false;
|
||||
} else if (packedNote.reply && (meId === packedNote.reply.userId)) {
|
||||
// 自分の投稿に対するリプライ
|
||||
hide = false;
|
||||
} else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) {
|
||||
// 自分へのメンション
|
||||
hide = false;
|
||||
} else if (packedNote.renote && (meId === packedNote.renote.userId)) {
|
||||
hide = false;
|
||||
} else {
|
||||
if (packedNote.renote) {
|
||||
const isFollowing = await this.followingsRepository.exists({
|
||||
where: {
|
||||
followeeId: packedNote.renote.userId,
|
||||
followerId: meId,
|
||||
},
|
||||
});
|
||||
|
||||
hide = !isFollowing;
|
||||
if (!hide) {
|
||||
if (packedNote.visibility === 'followers') {
|
||||
if (meId == null) {
|
||||
hide = true;
|
||||
} else if (meId === packedNote.userId) {
|
||||
hide = false;
|
||||
} else if (packedNote.reply && (meId === packedNote.reply.userId)) {
|
||||
// 自分の投稿に対するリプライ
|
||||
hide = false;
|
||||
} else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) {
|
||||
// 自分へのメンション
|
||||
hide = false;
|
||||
} else if (packedNote.renote && (meId === packedNote.renote.userId)) {
|
||||
hide = false;
|
||||
} else {
|
||||
// フォロワーかどうか
|
||||
// TODO: 当関数呼び出しごとにクエリが走るのは重そうだからなんとかする
|
||||
const appearNote = packedNote.renote ?? packedNote;
|
||||
const isFollowing = await this.followingsRepository.exists({
|
||||
where: {
|
||||
followeeId: packedNote.userId,
|
||||
followeeId: appearNote.userId,
|
||||
followerId: meId,
|
||||
},
|
||||
});
|
||||
|
|
@ -189,6 +213,7 @@ export class NoteEntityService implements OnModuleInit {
|
|||
packedNote.reactionEmojis = undefined;
|
||||
packedNote.reactions = undefined;
|
||||
packedNote.isHidden = true;
|
||||
// TODO: hiddenReason みたいなのを提供しても良さそう
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -542,6 +542,9 @@ export class UserEntityService implements OnModuleInit {
|
|||
isSilenced: user.isSilenced || this.roleService.getUserPolicies(user.id).then(r => !r.canPublicNote),
|
||||
speakAsCat: user.speakAsCat ?? false,
|
||||
approved: user.approved,
|
||||
requireSigninToViewContents: user.requireSigninToViewContents === false ? undefined : true,
|
||||
makeNotesFollowersOnlyBefore: user.makeNotesFollowersOnlyBefore ?? undefined,
|
||||
makeNotesHiddenBefore: user.makeNotesHiddenBefore ?? undefined,
|
||||
instance: user.host ? this.federatedInstanceService.federatedInstanceCache.fetch(user.host).then(instance => instance ? {
|
||||
name: instance.name,
|
||||
softwareName: instance.softwareName,
|
||||
|
|
|
|||
|
|
@ -4,5 +4,5 @@
|
|||
*/
|
||||
|
||||
export function sqlLikeEscape(s: string) {
|
||||
return s.replace(/([%_\\])/g, '\\$1');
|
||||
return s.replace(/([\\%_])/g, '\\$1');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typ
|
|||
import { id } from './util/id.js';
|
||||
import { MiUser } from './User.js';
|
||||
|
||||
export type AbuseReportResolveType = 'accept' | 'reject';
|
||||
|
||||
@Entity('abuse_user_report')
|
||||
export class MiAbuseUserReport {
|
||||
@PrimaryColumn(id())
|
||||
|
|
@ -76,7 +78,7 @@ export class MiAbuseUserReport {
|
|||
@Column('varchar', {
|
||||
length: 128, nullable: true,
|
||||
})
|
||||
public resolvedAs: 'accept' | 'reject' | null;
|
||||
public resolvedAs: AbuseReportResolveType | null;
|
||||
|
||||
//#region Denormalized fields
|
||||
@Index()
|
||||
|
|
|
|||
|
|
@ -244,6 +244,23 @@ export class MiUser {
|
|||
})
|
||||
public isHibernated: boolean;
|
||||
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
})
|
||||
public requireSigninToViewContents: boolean;
|
||||
|
||||
// in sec, マイナスで相対時間
|
||||
@Column('integer', {
|
||||
nullable: true,
|
||||
})
|
||||
public makeNotesFollowersOnlyBefore: number | null;
|
||||
|
||||
// in sec, マイナスで相対時間
|
||||
@Column('integer', {
|
||||
nullable: true,
|
||||
})
|
||||
public makeNotesHiddenBefore: number | null;
|
||||
|
||||
// アカウントが削除されたかどうかのフラグだが、完全に削除される際は物理削除なので実質削除されるまでの「削除が進行しているかどうか」のフラグ
|
||||
@Column('boolean', {
|
||||
default: false,
|
||||
|
|
|
|||
|
|
@ -146,6 +146,18 @@ export const packedUserLiteSchema = {
|
|||
type: 'boolean',
|
||||
nullable: false, optional: false,
|
||||
},
|
||||
requireSigninToViewContents: {
|
||||
type: 'boolean',
|
||||
nullable: false, optional: true,
|
||||
},
|
||||
makeNotesFollowersOnlyBefore: {
|
||||
type: 'number',
|
||||
nullable: true, optional: true,
|
||||
},
|
||||
makeNotesHiddenBefore: {
|
||||
type: 'number',
|
||||
nullable: true, optional: true,
|
||||
},
|
||||
instance: {
|
||||
type: 'object',
|
||||
nullable: false, optional: true,
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
|||
import { bindThis } from '@/decorators.js';
|
||||
import { IActivity } from '@/core/activitypub/type.js';
|
||||
import { isQuote, isRenote } from '@/misc/is-renote.js';
|
||||
import * as Acct from '@/misc/acct.js';
|
||||
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions, FastifyBodyParser } from 'fastify';
|
||||
import type { FindOptionsWhere } from 'typeorm';
|
||||
import type Logger from '@/logger.js';
|
||||
|
|
@ -229,7 +230,7 @@ export class ActivityPubServerService {
|
|||
let signature;
|
||||
|
||||
try {
|
||||
signature = httpSignature.parseRequest(request.raw, { 'headers': ['(request-target)', 'digest', 'host', 'date'], authorizationHeaderName: 'signature' });
|
||||
signature = httpSignature.parseRequest(request.raw, { 'headers': ['(request-target)', 'host', 'date'], authorizationHeaderName: 'signature' });
|
||||
} catch (e) {
|
||||
reply.code(401);
|
||||
return;
|
||||
|
|
@ -619,7 +620,18 @@ export class ActivityPubServerService {
|
|||
return;
|
||||
}
|
||||
|
||||
// リモートだったらリダイレクト
|
||||
if (user.host != null) {
|
||||
if (user.uri == null || this.utilityService.isSelfHost(user.host)) {
|
||||
reply.code(500);
|
||||
return;
|
||||
}
|
||||
reply.redirect(user.uri, 301);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.config.checkActivityPubGetSignature) reply.header('Cache-Control', 'public, max-age=180');
|
||||
|
||||
this.setResponseType(request, reply);
|
||||
return (this.apRendererService.addContext(await this.apRendererService.renderPerson(user as MiLocalUser)));
|
||||
}
|
||||
|
|
@ -795,21 +807,22 @@ export class ActivityPubServerService {
|
|||
|
||||
const user = await this.usersRepository.findOneBy({
|
||||
id: userId,
|
||||
host: IsNull(),
|
||||
isSuspended: false,
|
||||
});
|
||||
|
||||
return await this.userInfo(request, reply, user);
|
||||
});
|
||||
|
||||
fastify.get<{ Params: { user: string; } }>('/@:user', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => {
|
||||
fastify.get<{ Params: { acct: string; } }>('/@:acct', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => {
|
||||
if (await this.shouldRefuseGetRequest(request, reply, request.params.user)) return;
|
||||
|
||||
vary(reply.raw, 'Accept');
|
||||
|
||||
const acct = Acct.parse(request.params.acct);
|
||||
|
||||
const user = await this.usersRepository.findOneBy({
|
||||
usernameLower: request.params.user.toLowerCase(),
|
||||
host: IsNull(),
|
||||
usernameLower: acct.username,
|
||||
host: acct.host ?? IsNull(),
|
||||
isSuspended: false,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -42,6 +42,17 @@ export class GetterService {
|
|||
return note;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async getNoteWithUser(noteId: MiNote['id']) {
|
||||
const note = await this.notesRepository.findOne({ where: { id: noteId }, relations: ['user'] });
|
||||
|
||||
if (note == null) {
|
||||
throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', 'No such note.');
|
||||
}
|
||||
|
||||
return note;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get note for API processing
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -46,7 +46,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
throw new Error('cannot delete a root account');
|
||||
}
|
||||
|
||||
await this.deleteAccoountService.deleteAccount(user);
|
||||
await this.deleteAccoountService.deleteAccount(user, me);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,7 @@ export const paramDef = {
|
|||
properties: {
|
||||
title: { type: 'string', minLength: 1 },
|
||||
text: { type: 'string', minLength: 1 },
|
||||
imageUrl: { type: 'string', nullable: true, minLength: 1 },
|
||||
imageUrl: { type: 'string', nullable: true, minLength: 0 },
|
||||
icon: { type: 'string', enum: ['info', 'warning', 'error', 'success'], default: 'info' },
|
||||
display: { type: 'string', enum: ['normal', 'banner', 'dialog'], default: 'normal' },
|
||||
forExistingUsers: { type: 'boolean', default: false },
|
||||
|
|
@ -76,7 +76,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
updatedAt: null,
|
||||
title: ps.title,
|
||||
text: ps.text,
|
||||
imageUrl: ps.imageUrl,
|
||||
/* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- 空の文字列の場合、nullを渡すようにするため */
|
||||
imageUrl: ps.imageUrl || null,
|
||||
icon: ps.icon,
|
||||
display: ps.display,
|
||||
forExistingUsers: ps.forExistingUsers,
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { AvatarDecorationService } from '@/core/AvatarDecorationService.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
|
@ -13,6 +14,49 @@ export const meta = {
|
|||
requireCredential: true,
|
||||
requireRolePolicy: 'canManageAvatarDecorations',
|
||||
kind: 'write:admin:avatar-decorations',
|
||||
|
||||
res: {
|
||||
type: 'object',
|
||||
optional: false, nullable: false,
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'id',
|
||||
},
|
||||
createdAt: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'date-time',
|
||||
},
|
||||
updatedAt: {
|
||||
type: 'string',
|
||||
optional: false, nullable: true,
|
||||
format: 'date-time',
|
||||
},
|
||||
name: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
url: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
},
|
||||
roleIdsThatCanBeUsedThisDecoration: {
|
||||
type: 'array',
|
||||
optional: false, nullable: false,
|
||||
items: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
format: 'id',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
|
|
@ -32,14 +76,25 @@ export const paramDef = {
|
|||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
private avatarDecorationService: AvatarDecorationService,
|
||||
private idService: IdService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
await this.avatarDecorationService.create({
|
||||
const created = await this.avatarDecorationService.create({
|
||||
name: ps.name,
|
||||
description: ps.description,
|
||||
url: ps.url,
|
||||
roleIdsThatCanBeUsedThisDecoration: ps.roleIdsThatCanBeUsedThisDecoration,
|
||||
}, me);
|
||||
|
||||
return {
|
||||
id: created.id,
|
||||
createdAt: this.idService.parse(created.id).date.toISOString(),
|
||||
updatedAt: null,
|
||||
name: created.name,
|
||||
description: created.description,
|
||||
url: created.url,
|
||||
roleIdsThatCanBeUsedThisDecoration: created.roleIdsThatCanBeUsedThisDecoration,
|
||||
};
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,7 @@
|
|||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { AnnouncementsRepository, AnnouncementReadsRepository } from '@/models/_.js';
|
||||
import type { MiAnnouncement } from '@/models/Announcement.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { AvatarDecorationService } from '@/core/AvatarDecorationService.js';
|
||||
|
|
|
|||
|
|
@ -33,13 +33,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
|
||||
private deleteAccountService: DeleteAccountService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps) => {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const user = await this.usersRepository.findOneByOrFail({ id: ps.userId });
|
||||
if (user.isDeleted) {
|
||||
return;
|
||||
}
|
||||
|
||||
await this.deleteAccountService.deleteAccount(user);
|
||||
await this.deleteAccountService.deleteAccount(user, me);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -137,6 +137,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
if (local != null) return local;
|
||||
}
|
||||
|
||||
// 同一ユーザーの情報を再度処理するので、使用済みのresolverを再利用してはいけない
|
||||
return await this.mergePack(
|
||||
me,
|
||||
isActor(object) ? await this.apPersonService.createPerson(getApId(object)) : null,
|
||||
|
|
|
|||
|
|
@ -195,6 +195,9 @@ export const paramDef = {
|
|||
noCrawle: { type: 'boolean' },
|
||||
preventAiLearning: { type: 'boolean' },
|
||||
noindex: { type: 'boolean' },
|
||||
requireSigninToViewContents: { type: 'boolean' },
|
||||
makeNotesFollowersOnlyBefore: { type: 'integer', nullable: true },
|
||||
makeNotesHiddenBefore: { type: 'integer', nullable: true },
|
||||
isBot: { type: 'boolean' },
|
||||
isCat: { type: 'boolean' },
|
||||
speakAsCat: { type: 'boolean' },
|
||||
|
|
@ -353,6 +356,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
if (typeof ps.carefulBot === 'boolean') profileUpdates.carefulBot = ps.carefulBot;
|
||||
if (typeof ps.autoAcceptFollowed === 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed;
|
||||
if (typeof ps.noCrawle === 'boolean') profileUpdates.noCrawle = ps.noCrawle;
|
||||
if (typeof ps.requireSigninToViewContents === 'boolean') updates.requireSigninToViewContents = ps.requireSigninToViewContents;
|
||||
if ((typeof ps.makeNotesFollowersOnlyBefore === 'number') || (ps.makeNotesFollowersOnlyBefore === null)) updates.makeNotesFollowersOnlyBefore = ps.makeNotesFollowersOnlyBefore;
|
||||
if ((typeof ps.makeNotesHiddenBefore === 'number') || (ps.makeNotesHiddenBefore === null)) updates.makeNotesHiddenBefore = ps.makeNotesHiddenBefore;
|
||||
if (typeof ps.isCat === 'boolean') updates.isCat = ps.isCat;
|
||||
if (typeof ps.speakAsCat === 'boolean') updates.speakAsCat = ps.speakAsCat;
|
||||
if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote;
|
||||
|
|
@ -495,6 +501,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
const newName = updates.name === undefined ? user.name : updates.name;
|
||||
const newDescription = profileUpdates.description === undefined ? profile.description : profileUpdates.description;
|
||||
const newFields = profileUpdates.fields === undefined ? profile.fields : profileUpdates.fields;
|
||||
const newFollowedMessage = profileUpdates.followedMessage === undefined ? profile.followedMessage : profileUpdates.followedMessage;
|
||||
|
||||
if (newName != null) {
|
||||
let hasProhibitedWords = false;
|
||||
|
|
@ -524,6 +531,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
]);
|
||||
}
|
||||
|
||||
if (newFollowedMessage != null) {
|
||||
const tokens = mfm.parse(newFollowedMessage);
|
||||
emojis = emojis.concat(extractCustomEmojisFromMfm(tokens));
|
||||
}
|
||||
|
||||
updates.emojis = emojis;
|
||||
updates.tags = tags;
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,6 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
|
|||
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { NotesRepository } from '@/models/_.js';
|
||||
import { QueryService } from '@/core/QueryService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
|
|
@ -28,6 +27,12 @@ export const meta = {
|
|||
code: 'NO_SUCH_NOTE',
|
||||
id: '24fcbfc6-2e37-42b6-8388-c29b3861a08d',
|
||||
},
|
||||
|
||||
signinRequired: {
|
||||
message: 'Signin required.',
|
||||
code: 'SIGNIN_REQUIRED',
|
||||
id: '8e75455b-738c-471d-9f80-62693f33372e',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
|
|
@ -44,25 +49,29 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
constructor(
|
||||
@Inject(DI.notesRepository)
|
||||
private notesRepository: NotesRepository,
|
||||
|
||||
private noteEntityService: NoteEntityService,
|
||||
private queryService: QueryService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const query = await this.notesRepository.createQueryBuilder('note')
|
||||
.where('note.id = :noteId', { noteId: ps.noteId });
|
||||
.where('note.id = :noteId', { noteId: ps.noteId })
|
||||
.innerJoinAndSelect('user');
|
||||
|
||||
this.queryService.generateVisibilityQuery(query, me);
|
||||
if (me) {
|
||||
this.queryService.generateBlockedUserQuery(query, me);
|
||||
}
|
||||
|
||||
|
||||
const note = await query.getOne();
|
||||
|
||||
if (note === null) {
|
||||
throw new ApiError(meta.errors.noSuchNote);
|
||||
}
|
||||
|
||||
if (note.user!.requireSigninToViewContents && me == null) {
|
||||
throw new ApiError(meta.errors.signinRequired);
|
||||
}
|
||||
|
||||
return await this.noteEntityService.pack(note, me, {
|
||||
detail: true,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -43,6 +43,12 @@ export const meta = {
|
|||
code: 'BOTH_WITH_REPLIES_AND_WITH_FILES',
|
||||
id: '91c8cb9f-36ed-46e7-9ca2-7df96ed6e222',
|
||||
},
|
||||
|
||||
signinRequired: {
|
||||
message: 'Signin required.',
|
||||
code: 'SIGNIN_REQUIRED',
|
||||
id: 'd1588a9e-4b4d-4c07-807f-16f1486577a2',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import type {
|
|||
EndedPollNotificationQueue,
|
||||
InboxQueue,
|
||||
ObjectStorageQueue,
|
||||
RelationshipQueue,
|
||||
SystemQueue,
|
||||
UserWebhookDeliverQueue,
|
||||
SystemWebhookDeliverQueue,
|
||||
|
|
@ -41,13 +42,26 @@ import { MetaEntityService } from '@/core/entities/MetaEntityService.js';
|
|||
import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js';
|
||||
import { ClipEntityService } from '@/core/entities/ClipEntityService.js';
|
||||
import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js';
|
||||
import type { ChannelsRepository, ClipsRepository, FlashsRepository, GalleryPostsRepository, MiMeta, NotesRepository, PagesRepository, ReversiGamesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
||||
import type {
|
||||
AnnouncementsRepository,
|
||||
ChannelsRepository,
|
||||
ClipsRepository,
|
||||
FlashsRepository,
|
||||
GalleryPostsRepository,
|
||||
MiMeta,
|
||||
NotesRepository,
|
||||
PagesRepository,
|
||||
ReversiGamesRepository,
|
||||
UserProfilesRepository,
|
||||
UsersRepository,
|
||||
} from '@/models/_.js';
|
||||
import type Logger from '@/logger.js';
|
||||
import { handleRequestRedirectToOmitSearch } from '@/misc/fastify-hook-handlers.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { FlashEntityService } from '@/core/entities/FlashEntityService.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js';
|
||||
import { AnnouncementEntityService } from '@/core/entities/AnnouncementEntityService.js';
|
||||
import { FeedService } from './FeedService.js';
|
||||
import { UrlPreviewService } from './UrlPreviewService.js';
|
||||
import { ClientLoggerService } from './ClientLoggerService.js';
|
||||
|
|
@ -102,6 +116,9 @@ export class ClientServerService {
|
|||
@Inject(DI.reversiGamesRepository)
|
||||
private reversiGamesRepository: ReversiGamesRepository,
|
||||
|
||||
@Inject(DI.announcementsRepository)
|
||||
private announcementsRepository: AnnouncementsRepository,
|
||||
|
||||
private flashEntityService: FlashEntityService,
|
||||
private userEntityService: UserEntityService,
|
||||
private noteEntityService: NoteEntityService,
|
||||
|
|
@ -111,6 +128,7 @@ export class ClientServerService {
|
|||
private clipEntityService: ClipEntityService,
|
||||
private channelEntityService: ChannelEntityService,
|
||||
private reversiGameEntityService: ReversiGameEntityService,
|
||||
private announcementEntityService: AnnouncementEntityService,
|
||||
private urlPreviewService: UrlPreviewService,
|
||||
private feedService: FeedService,
|
||||
private roleService: RoleService,
|
||||
|
|
@ -121,6 +139,7 @@ export class ClientServerService {
|
|||
@Inject('queue:deliver') public deliverQueue: DeliverQueue,
|
||||
@Inject('queue:inbox') public inboxQueue: InboxQueue,
|
||||
@Inject('queue:db') public dbQueue: DbQueue,
|
||||
@Inject('queue:relationship') public relationshipQueue: RelationshipQueue,
|
||||
@Inject('queue:objectStorage') public objectStorageQueue: ObjectStorageQueue,
|
||||
@Inject('queue:userWebhookDeliver') public userWebhookDeliverQueue: UserWebhookDeliverQueue,
|
||||
@Inject('queue:systemWebhookDeliver') public systemWebhookDeliverQueue: SystemWebhookDeliverQueue,
|
||||
|
|
@ -251,6 +270,7 @@ export class ClientServerService {
|
|||
this.deliverQueue,
|
||||
this.inboxQueue,
|
||||
this.dbQueue,
|
||||
this.relationshipQueue,
|
||||
this.objectStorageQueue,
|
||||
this.userWebhookDeliverQueue,
|
||||
this.systemWebhookDeliverQueue,
|
||||
|
|
@ -557,7 +577,7 @@ export class ClientServerService {
|
|||
}
|
||||
});
|
||||
|
||||
//#region SSR (for crawlers)
|
||||
//#region SSR
|
||||
// User
|
||||
fastify.get<{ Params: { user: string; sub?: string; } }>('/@:user/:sub?', async (request, reply) => {
|
||||
const { username, host } = Acct.parse(request.params.user);
|
||||
|
|
@ -582,11 +602,17 @@ export class ClientServerService {
|
|||
reply.header('X-Robots-Tag', 'noimageai');
|
||||
reply.header('X-Robots-Tag', 'noai');
|
||||
}
|
||||
|
||||
const _user = await this.userEntityService.pack(user);
|
||||
|
||||
return await reply.view('user', {
|
||||
user, profile, me,
|
||||
avatarUrl: user.avatarUrl ?? this.userEntityService.getIdenticonUrl(user),
|
||||
sub: request.params.sub,
|
||||
...await this.generateCommonPugData(this.meta),
|
||||
clientCtx: htmlSafeJsonStringify({
|
||||
user: _user,
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
// リモートユーザーなので
|
||||
|
|
@ -616,12 +642,15 @@ export class ClientServerService {
|
|||
fastify.get<{ Params: { note: string; } }>('/notes/:note', async (request, reply) => {
|
||||
vary(reply.raw, 'Accept');
|
||||
|
||||
const note = await this.notesRepository.findOneBy({
|
||||
id: request.params.note,
|
||||
visibility: In(['public', 'home']),
|
||||
const note = await this.notesRepository.findOne({
|
||||
where: {
|
||||
id: request.params.note,
|
||||
visibility: In(['public', 'home']),
|
||||
},
|
||||
relations: ['user'],
|
||||
});
|
||||
|
||||
if (note) {
|
||||
if (note && !note.user!.requireSigninToViewContents) {
|
||||
const _note = await this.noteEntityService.pack(note);
|
||||
const profile = await this.userProfilesRepository.findOneByOrFail({ userId: note.userId });
|
||||
reply.header('Cache-Control', 'public, max-age=15');
|
||||
|
|
@ -636,6 +665,9 @@ export class ClientServerService {
|
|||
// TODO: Let locale changeable by instance setting
|
||||
summary: getNoteSummary(_note),
|
||||
...await this.generateCommonPugData(this.meta),
|
||||
clientCtx: htmlSafeJsonStringify({
|
||||
note: _note,
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
return await renderBase(reply);
|
||||
|
|
@ -724,6 +756,9 @@ export class ClientServerService {
|
|||
profile,
|
||||
avatarUrl: _clip.user.avatarUrl,
|
||||
...await this.generateCommonPugData(this.meta),
|
||||
clientCtx: htmlSafeJsonStringify({
|
||||
clip: _clip,
|
||||
}),
|
||||
});
|
||||
} else {
|
||||
return await renderBase(reply);
|
||||
|
|
@ -788,6 +823,24 @@ export class ClientServerService {
|
|||
return await renderBase(reply);
|
||||
}
|
||||
});
|
||||
|
||||
// 個別お知らせページ
|
||||
fastify.get<{ Params: { announcementId: string; } }>('/announcements/:announcementId', async (request, reply) => {
|
||||
const announcement = await this.announcementsRepository.findOneBy({
|
||||
id: request.params.announcementId,
|
||||
});
|
||||
|
||||
if (announcement) {
|
||||
const _announcement = await this.announcementEntityService.pack(announcement);
|
||||
reply.header('Cache-Control', 'public, max-age=3600');
|
||||
return await reply.view('announcement', {
|
||||
announcement: _announcement,
|
||||
...await this.generateCommonPugData(this.meta),
|
||||
});
|
||||
} else {
|
||||
return await renderBase(reply);
|
||||
}
|
||||
});
|
||||
//#endregion
|
||||
|
||||
//#region noindex pages
|
||||
|
|
|
|||
21
packages/backend/src/server/web/views/announcement.pug
Normal file
21
packages/backend/src/server/web/views/announcement.pug
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
extends ./base
|
||||
|
||||
block vars
|
||||
- const title = announcement.title;
|
||||
- const description = announcement.text.length > 100 ? announcement.text.slice(0, 100) + '…' : announcement.text;
|
||||
- const url = `${config.url}/announcements/${announcement.id}`;
|
||||
|
||||
block title
|
||||
= `${title} | ${instanceName}`
|
||||
|
||||
block desc
|
||||
meta(name='description' content=description)
|
||||
|
||||
block og
|
||||
meta(property='og:type' content='article')
|
||||
meta(property='og:title' content= title)
|
||||
meta(property='og:description' content= description)
|
||||
meta(property='og:url' content= url)
|
||||
if announcement.imageUrl
|
||||
meta(property='og:image' content=announcement.imageUrl)
|
||||
meta(property='twitter:card' content='summary_large_image')
|
||||
|
|
@ -2,6 +2,7 @@ block vars
|
|||
|
||||
block loadClientEntry
|
||||
- const entry = config.frontendEntry;
|
||||
- const baseUrl = config.url;
|
||||
|
||||
doctype html
|
||||
|
||||
|
|
@ -36,7 +37,7 @@ html
|
|||
link(rel='icon' href= icon || '/favicon.ico')
|
||||
link(rel='apple-touch-icon' href= appleTouchIcon || '/apple-touch-icon.png')
|
||||
link(rel='manifest' href='/manifest.json')
|
||||
link(rel='search' type='application/opensearchdescription+xml' title=(title || "Sharkey") href=`${url}/opensearch.xml`)
|
||||
link(rel='search' type='application/opensearchdescription+xml' title=(title || "Sharkey") href=`${baseUrl}/opensearch.xml`)
|
||||
link(rel='prefetch' href=serverErrorImageUrl)
|
||||
link(rel='prefetch' href=infoImageUrl)
|
||||
link(rel='prefetch' href=notFoundImageUrl)
|
||||
|
|
@ -79,6 +80,9 @@ html
|
|||
script(type='application/json' id='misskey_meta' data-generated-at=now)
|
||||
!= metaJson
|
||||
|
||||
script(type='application/json' id='misskey_clientCtx' data-generated-at=now)
|
||||
!= clientCtx
|
||||
|
||||
script
|
||||
include ../boot.js
|
||||
|
||||
|
|
|
|||
|
|
@ -19,7 +19,6 @@ proxyBypassHosts:
|
|||
- challenges.cloudflare.com
|
||||
proxyRemoteFiles: true
|
||||
signToActivityPubGet: true
|
||||
allowedPrivateNetworks: [
|
||||
'127.0.0.1/32',
|
||||
'172.20.0.0/16'
|
||||
]
|
||||
allowedPrivateNetworks:
|
||||
- 127.0.0.1/32
|
||||
- 172.20.0.0/16
|
||||
|
|
|
|||
|
|
@ -232,7 +232,6 @@ export async function isFired<C extends keyof Misskey.Channels, T extends keyof
|
|||
params?: Misskey.Channels[C]['params'],
|
||||
): Promise<boolean> {
|
||||
return new Promise<boolean>(async (resolve, reject) => {
|
||||
// @ts-expect-error TODO: why?
|
||||
const stream = new Misskey.Stream(`wss://${host}`, { token: user.i }, { WebSocket });
|
||||
const connection = stream.useChannel(channel, params);
|
||||
connection.on(type as any, ((msg: any) => {
|
||||
|
|
@ -266,7 +265,6 @@ export async function isNoteUpdatedEventFired(
|
|||
cond: (msg: Parameters<Misskey.StreamEvents['noteUpdated']>[0]) => boolean,
|
||||
): Promise<boolean> {
|
||||
return new Promise<boolean>(async (resolve, reject) => {
|
||||
// @ts-expect-error TODO: why?
|
||||
const stream = new Misskey.Stream(`wss://${host}`, { token: user.i }, { WebSocket });
|
||||
stream.send('s', { id: noteId });
|
||||
stream.on('noteUpdated', msg => {
|
||||
|
|
|
|||
|
|
@ -230,6 +230,7 @@ describe('Webリソース', () => {
|
|||
path: path('xxxxxxxxxx'),
|
||||
type: HTML,
|
||||
}));
|
||||
test.todo('HTMLとしてGETできる。(リモートユーザーでもリダイレクトせず)');
|
||||
});
|
||||
|
||||
describe.each([
|
||||
|
|
@ -249,6 +250,7 @@ describe('Webリソース', () => {
|
|||
path: path('xxxxxxxxxx'),
|
||||
accept,
|
||||
}));
|
||||
test.todo('はオリジナルにリダイレクトされる。(リモートユーザー)');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@
|
|||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { beforeAll, describe, jest } from '@jest/globals';
|
||||
import { WebhookTestService } from '@/core/WebhookTestService.js';
|
||||
import { UserWebhookService } from '@/core/UserWebhookService.js';
|
||||
import { UserWebhookPayload, UserWebhookService } from '@/core/UserWebhookService.js';
|
||||
import { SystemWebhookService } from '@/core/SystemWebhookService.js';
|
||||
import { GlobalModule } from '@/GlobalModule.js';
|
||||
import { MiSystemWebhook, MiUser, MiWebhook, UserProfilesRepository, UsersRepository } from '@/models/_.js';
|
||||
|
|
@ -122,7 +122,7 @@ describe('WebhookTestService', () => {
|
|||
const calls = queueService.userWebhookDeliver.mock.calls[0];
|
||||
expect((calls[0] as any).id).toBe('dummy-webhook');
|
||||
expect(calls[1]).toBe('note');
|
||||
expect((calls[2] as any).id).toBe('dummy-note-1');
|
||||
expect((calls[2] as UserWebhookPayload<'note'>).note.id).toBe('dummy-note-1');
|
||||
});
|
||||
|
||||
test('reply', async () => {
|
||||
|
|
@ -131,7 +131,7 @@ describe('WebhookTestService', () => {
|
|||
const calls = queueService.userWebhookDeliver.mock.calls[0];
|
||||
expect((calls[0] as any).id).toBe('dummy-webhook');
|
||||
expect(calls[1]).toBe('reply');
|
||||
expect((calls[2] as any).id).toBe('dummy-reply-1');
|
||||
expect((calls[2] as UserWebhookPayload<'reply'>).note.id).toBe('dummy-reply-1');
|
||||
});
|
||||
|
||||
test('renote', async () => {
|
||||
|
|
@ -140,7 +140,7 @@ describe('WebhookTestService', () => {
|
|||
const calls = queueService.userWebhookDeliver.mock.calls[0];
|
||||
expect((calls[0] as any).id).toBe('dummy-webhook');
|
||||
expect(calls[1]).toBe('renote');
|
||||
expect((calls[2] as any).id).toBe('dummy-renote-1');
|
||||
expect((calls[2] as UserWebhookPayload<'renote'>).note.id).toBe('dummy-renote-1');
|
||||
});
|
||||
|
||||
test('mention', async () => {
|
||||
|
|
@ -149,7 +149,7 @@ describe('WebhookTestService', () => {
|
|||
const calls = queueService.userWebhookDeliver.mock.calls[0];
|
||||
expect((calls[0] as any).id).toBe('dummy-webhook');
|
||||
expect(calls[1]).toBe('mention');
|
||||
expect((calls[2] as any).id).toBe('dummy-mention-1');
|
||||
expect((calls[2] as UserWebhookPayload<'mention'>).note.id).toBe('dummy-mention-1');
|
||||
});
|
||||
|
||||
test('follow', async () => {
|
||||
|
|
@ -158,7 +158,7 @@ describe('WebhookTestService', () => {
|
|||
const calls = queueService.userWebhookDeliver.mock.calls[0];
|
||||
expect((calls[0] as any).id).toBe('dummy-webhook');
|
||||
expect(calls[1]).toBe('follow');
|
||||
expect((calls[2] as any).id).toBe('dummy-user-1');
|
||||
expect((calls[2] as UserWebhookPayload<'follow'>).user.id).toBe('dummy-user-1');
|
||||
});
|
||||
|
||||
test('followed', async () => {
|
||||
|
|
@ -167,7 +167,7 @@ describe('WebhookTestService', () => {
|
|||
const calls = queueService.userWebhookDeliver.mock.calls[0];
|
||||
expect((calls[0] as any).id).toBe('dummy-webhook');
|
||||
expect(calls[1]).toBe('followed');
|
||||
expect((calls[2] as any).id).toBe('dummy-user-2');
|
||||
expect((calls[2] as UserWebhookPayload<'followed'>).user.id).toBe('dummy-user-2');
|
||||
});
|
||||
|
||||
test('unfollow', async () => {
|
||||
|
|
@ -176,7 +176,7 @@ describe('WebhookTestService', () => {
|
|||
const calls = queueService.userWebhookDeliver.mock.calls[0];
|
||||
expect((calls[0] as any).id).toBe('dummy-webhook');
|
||||
expect(calls[1]).toBe('unfollow');
|
||||
expect((calls[2] as any).id).toBe('dummy-user-3');
|
||||
expect((calls[2] as UserWebhookPayload<'unfollow'>).user.id).toBe('dummy-user-3');
|
||||
});
|
||||
|
||||
describe('NoSuchWebhookError', () => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue