Merge pull request MisskeyIO#246 from MisskeyIO/merge-upstream

Merge misskey-dev/develop
This commit is contained in:
まっちゃとーにゅ 2023-11-22 07:45:36 +09:00 committed by GitHub
commit 9fb84a2254
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
46 changed files with 903 additions and 325 deletions

View file

@ -0,0 +1,18 @@
/*
* SPDX-FileCopyrightText: syuilo and other misskey contributors
* SPDX-License-Identifier: AGPL-3.0-only
*/
export class SupportVerifyMailApi1700303245007 {
name = 'SupportVerifyMailApi1700303245007'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" ADD "verifymailAuthKey" character varying(1024)`);
await queryRunner.query(`ALTER TABLE "meta" ADD "enableVerifymailApi" boolean NOT NULL DEFAULT false`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableVerifymailApi"`);
await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "verifymailAuthKey"`);
}
}

View file

@ -7,8 +7,8 @@
"node": ">=18.16.0"
},
"scripts": {
"start": "node ./built/index.js",
"start:test": "NODE_ENV=test node ./built/index.js",
"start": "node ./built/boot/entry.js",
"start:test": "NODE_ENV=test node ./built/boot/entry.js",
"migrate": "pnpm typeorm migration:run -d ormconfig.js",
"revert": "pnpm typeorm migration:revert -d ormconfig.js",
"check:connect": "node ./check_connect.js",
@ -165,7 +165,7 @@
"tsconfig-paths": "4.2.0",
"twemoji-parser": "14.0.0",
"typeorm": "0.3.17",
"typescript": "5.2.2",
"typescript": "5.3.2",
"ulid": "2.3.0",
"vary": "1.1.2",
"web-push": "3.6.6",

View file

@ -61,12 +61,23 @@ export class AntennaService implements OnApplicationShutdown {
lastUsedAt: new Date(body.lastUsedAt),
});
break;
case 'antennaUpdated':
this.antennas[this.antennas.findIndex(a => a.id === body.id)] = {
...body,
createdAt: new Date(body.createdAt),
lastUsedAt: new Date(body.lastUsedAt),
};
case 'antennaUpdated': {
const idx = this.antennas.findIndex(a => a.id === body.id);
if (idx >= 0) {
this.antennas[idx] = {
...body,
createdAt: new Date(body.createdAt),
lastUsedAt: new Date(body.lastUsedAt),
};
} else {
// サーバ起動時にactiveじゃなかった場合、リストに持っていないので追加する必要あり
this.antennas.push({
...body,
createdAt: new Date(body.createdAt),
lastUsedAt: new Date(body.lastUsedAt),
});
}
}
break;
case 'antennaDeleted':
this.antennas = this.antennas.filter(a => a.id !== body.id);

View file

@ -3,9 +3,11 @@
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { URLSearchParams } from 'node:url';
import * as nodemailer from 'nodemailer';
import { Inject, Injectable } from '@nestjs/common';
import { validate as validateEmail } from 'deep-email-validator';
import { SubOutputFormat } from 'deep-email-validator/dist/output/output.js';
import { MetaService } from '@/core/MetaService.js';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
@ -13,6 +15,7 @@ import type Logger from '@/logger.js';
import type { UserProfilesRepository } from '@/models/_.js';
import { LoggerService } from '@/core/LoggerService.js';
import { bindThis } from '@/decorators.js';
import { HttpRequestService } from '@/core/HttpRequestService.js';
@Injectable()
export class EmailService {
@ -27,6 +30,7 @@ export class EmailService {
private metaService: MetaService,
private loggerService: LoggerService,
private httpRequestService: HttpRequestService,
) {
this.logger = this.loggerService.getLogger('email');
}
@ -160,14 +164,25 @@ export class EmailService {
email: emailAddress,
});
const validated = meta.enableActiveEmailValidation ? await validateEmail({
email: emailAddress,
validateRegex: true,
validateMx: true,
validateTypo: false, // TLDを見ているみたいだけどclubとか弾かれるので
validateDisposable: true, // 捨てアドかどうかチェック
validateSMTP: false, // 日本だと25ポートが殆どのプロバイダーで塞がれていてタイムアウトになるので
}) : { valid: true, reason: null };
const verifymailApi = meta.enableVerifymailApi && meta.verifymailAuthKey != null;
let validated;
if (meta.enableActiveEmailValidation && meta.verifymailAuthKey) {
if (verifymailApi) {
validated = await this.verifyMail(emailAddress, meta.verifymailAuthKey);
} else {
validated = meta.enableActiveEmailValidation ? await validateEmail({
email: emailAddress,
validateRegex: true,
validateMx: true,
validateTypo: false, // TLDを見ているみたいだけどclubとか弾かれるので
validateDisposable: true, // 捨てアドかどうかチェック
validateSMTP: false, // 日本だと25ポートが殆どのプロバイダーで塞がれていてタイムアウトになるので
}) : { valid: true, reason: null };
}
} else {
validated = { valid: true, reason: null };
}
const available = exist === 0 && validated.valid;
@ -182,4 +197,65 @@ export class EmailService {
null,
};
}
private async verifyMail(emailAddress: string, verifymailAuthKey: string): Promise<{
valid: boolean;
reason: 'used' | 'format' | 'disposable' | 'mx' | 'smtp' | null;
}> {
const endpoint = 'https://verifymail.io/api/' + emailAddress + '?key=' + verifymailAuthKey;
const res = await this.httpRequestService.send(endpoint, {
method: 'GET',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
Accept: 'application/json, */*',
},
});
const json = (await res.json()) as {
block: boolean;
catch_all: boolean;
deliverable_email: boolean;
disposable: boolean;
domain: string;
email_address: string;
email_provider: string;
mx: boolean;
mx_fallback: boolean;
mx_host: string[];
mx_ip: string[];
mx_priority: { [key: string]: number };
privacy: boolean;
related_domains: string[];
};
if (json.email_address === undefined) {
return {
valid: false,
reason: 'format',
};
}
if (json.deliverable_email !== undefined && !json.deliverable_email) {
return {
valid: false,
reason: 'smtp',
};
}
if (json.disposable) {
return {
valid: false,
reason: 'disposable',
};
}
if (json.mx !== undefined && !json.mx) {
return {
valid: false,
reason: 'mx',
};
}
return {
valid: true,
reason: null,
};
}
}

View file

@ -177,7 +177,7 @@ export class NotificationEntityService implements OnModuleInit {
) : undefined;
if (notification.type === 'reaction:grouped') {
const reactions = await Promise.all(notification.reactions.map(async reaction => {
const reactions = await Promise.allSettled(notification.reactions.map(async reaction => {
const user = hint?.packedUsers != null
? hint.packedUsers.get(reaction.userId)!
: await this.userEntityService.pack(reaction.userId, { id: meId }, {
@ -193,23 +193,27 @@ export class NotificationEntityService implements OnModuleInit {
createdAt: new Date(notification.createdAt).toISOString(),
type: notification.type,
note: noteIfNeed,
reactions,
reactions: reactions.filter(result => result.status === 'fulfilled')
.map(result => (result as PromiseFulfilledResult<{ user: Packed<'User'>; reaction: string; }>).value),
});
} else if (notification.type === 'renote:grouped') {
const users = await Promise.all(notification.userIds.map(userId => {
const user = hint?.packedUsers != null
? hint.packedUsers.get(userId)
: this.userEntityService.pack(userId!, { id: meId }, {
detail: false,
});
return user;
const users = await Promise.allSettled(notification.userIds.map(userId => {
const packedUser = hint?.packedUsers != null ? hint.packedUsers.get(userId) : null;
if (packedUser) {
return packedUser;
}
return this.userEntityService.pack(userId, { id: meId }, {
detail: false,
});
}));
return await awaitAll({
id: notification.id,
createdAt: new Date(notification.createdAt).toISOString(),
type: notification.type,
note: noteIfNeed,
users,
users: users.filter(result => result.status === 'fulfilled')
.map(result => (result as PromiseFulfilledResult<Packed<'User'>>).value),
});
}
@ -278,9 +282,11 @@ export class NotificationEntityService implements OnModuleInit {
validNotifications = validNotifications.filter(x => (x.type !== 'receiveFollowRequest') || reqs.some(r => r.followerId === x.notifierId));
}
return await Promise.all(validNotifications.map(x => this.packGrouped(x, meId, {}, {
return (await Promise.allSettled(validNotifications.map(x => this.packGrouped(x, meId, {}, {
packedNotes,
packedUsers,
})));
}))))
.filter(result => result.status === 'fulfilled')
.map(result => (result as PromiseFulfilledResult<Packed<'Notification'>>).value);
}
}

View file

@ -446,6 +446,17 @@ export class MiMeta {
})
public enableActiveEmailValidation: boolean;
@Column('boolean', {
default: false,
})
public enableVerifymailApi: boolean;
@Column('varchar', {
length: 1024,
nullable: true,
})
public verifymailAuthKey: string | null;
@Column('boolean', {
default: true,
})

View file

@ -42,11 +42,11 @@ export const packedAnnouncementSchema = {
type: 'string',
optional: false, nullable: false,
},
forYou: {
needConfirmationToRead: {
type: 'boolean',
optional: false, nullable: false,
},
needConfirmationToRead: {
forYou: {
type: 'boolean',
optional: false, nullable: false,
},

View file

@ -19,7 +19,7 @@ export const packedChannelSchema = {
},
lastNotedAt: {
type: 'string',
optional: false, nullable: true,
nullable: true, optional: false,
format: 'date-time',
},
name: {
@ -28,38 +28,18 @@ export const packedChannelSchema = {
},
description: {
type: 'string',
nullable: true, optional: false,
},
bannerUrl: {
type: 'string',
format: 'url',
nullable: true, optional: false,
},
isArchived: {
type: 'boolean',
optional: false, nullable: false,
},
notesCount: {
type: 'number',
nullable: false, optional: false,
},
usersCount: {
type: 'number',
nullable: false, optional: false,
},
isFollowing: {
type: 'boolean',
optional: true, nullable: false,
},
isFavorited: {
type: 'boolean',
optional: true, nullable: false,
optional: false, nullable: true,
},
userId: {
type: 'string',
nullable: true, optional: false,
format: 'id',
},
bannerUrl: {
type: 'string',
format: 'url',
nullable: true, optional: false,
},
pinnedNoteIds: {
type: 'array',
nullable: false, optional: false,
@ -72,6 +52,18 @@ export const packedChannelSchema = {
type: 'string',
optional: false, nullable: false,
},
isArchived: {
type: 'boolean',
optional: false, nullable: false,
},
usersCount: {
type: 'number',
nullable: false, optional: false,
},
notesCount: {
type: 'number',
nullable: false, optional: false,
},
isSensitive: {
type: 'boolean',
optional: false, nullable: false,
@ -80,5 +72,22 @@ export const packedChannelSchema = {
type: 'boolean',
optional: false, nullable: false,
},
isFollowing: {
type: 'boolean',
optional: true, nullable: false,
},
isFavorited: {
type: 'boolean',
optional: true, nullable: false,
},
pinnedNotes: {
type: 'array',
optional: true, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
ref: 'Note',
},
},
},
} as const;

View file

@ -44,13 +44,13 @@ export const packedClipSchema = {
type: 'boolean',
optional: false, nullable: false,
},
isFavorited: {
type: 'boolean',
optional: true, nullable: false,
},
favoritedCount: {
type: 'number',
optional: false, nullable: false,
},
isFavorited: {
type: 'boolean',
optional: true, nullable: false,
},
},
} as const;

View file

@ -74,7 +74,7 @@ export const packedDriveFileSchema = {
},
url: {
type: 'string',
optional: false, nullable: true,
optional: false, nullable: false,
format: 'url',
},
thumbnailUrl: {

View file

@ -21,6 +21,12 @@ export const packedDriveFolderSchema = {
type: 'string',
optional: false, nullable: false,
},
parentId: {
type: 'string',
optional: false, nullable: true,
format: 'id',
example: 'xxxxxxxxxx',
},
foldersCount: {
type: 'number',
optional: true, nullable: false,
@ -29,12 +35,6 @@ export const packedDriveFolderSchema = {
type: 'number',
optional: true, nullable: false,
},
parentId: {
type: 'string',
optional: false, nullable: true,
format: 'id',
example: 'xxxxxxxxxx',
},
parent: {
type: 'object',
optional: true, nullable: true,

View file

@ -79,6 +79,10 @@ export const packedFederationInstanceSchema = {
type: 'string',
optional: false, nullable: true,
},
isSilenced: {
type: 'boolean',
optional: false, nullable: false,
},
iconUrl: {
type: 'string',
optional: false, nullable: true,
@ -93,11 +97,6 @@ export const packedFederationInstanceSchema = {
type: 'string',
optional: false, nullable: true,
},
isSilenced: {
type: "boolean",
optional: false,
nullable: false,
},
infoUpdatedAt: {
type: 'string',
optional: false, nullable: true,

View file

@ -22,6 +22,16 @@ export const packedFlashSchema = {
optional: false, nullable: false,
format: 'date-time',
},
userId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
user: {
type: 'object',
ref: 'UserLite',
optional: false, nullable: false,
},
title: {
type: 'string',
optional: false, nullable: false,
@ -34,16 +44,6 @@ export const packedFlashSchema = {
type: 'string',
optional: false, nullable: false,
},
userId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
user: {
type: 'object',
ref: 'UserLite',
optional: false, nullable: false,
},
likedCount: {
type: 'number',
optional: false, nullable: true,

View file

@ -22,16 +22,16 @@ export const packedFollowingSchema = {
optional: false, nullable: false,
format: 'id',
},
followee: {
type: 'object',
optional: true, nullable: false,
ref: 'UserDetailed',
},
followerId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
followee: {
type: 'object',
optional: true, nullable: false,
ref: 'UserDetailed',
},
follower: {
type: 'object',
optional: true, nullable: false,

View file

@ -22,14 +22,6 @@ export const packedGalleryPostSchema = {
optional: false, nullable: false,
format: 'date-time',
},
title: {
type: 'string',
optional: false, nullable: false,
},
description: {
type: 'string',
optional: false, nullable: true,
},
userId: {
type: 'string',
optional: false, nullable: false,
@ -40,6 +32,14 @@ export const packedGalleryPostSchema = {
ref: 'UserLite',
optional: false, nullable: false,
},
title: {
type: 'string',
optional: false, nullable: false,
},
description: {
type: 'string',
optional: false, nullable: true,
},
fileIds: {
type: 'array',
optional: true, nullable: false,
@ -70,6 +70,14 @@ export const packedGalleryPostSchema = {
type: 'boolean',
optional: false, nullable: false,
},
likedCount: {
type: 'number',
optional: false, nullable: false,
},
isLiked: {
type: 'boolean',
optional: true, nullable: false,
},
},
} as const;

View file

@ -127,22 +127,26 @@ export const packedNoteSchema = {
channel: {
type: 'object',
optional: true, nullable: true,
items: {
type: 'object',
optional: false, nullable: false,
properties: {
id: {
type: 'string',
optional: false, nullable: false,
},
name: {
type: 'string',
optional: false, nullable: true,
},
isSensitive: {
type: 'boolean',
optional: true, nullable: false,
},
properties: {
id: {
type: 'string',
optional: false, nullable: false,
},
name: {
type: 'string',
optional: false, nullable: false,
},
color: {
type: 'string',
optional: false, nullable: false,
},
isSensitive: {
type: 'boolean',
optional: false, nullable: false,
},
allowRenoteToExternal: {
type: 'boolean',
optional: false, nullable: false,
},
},
},

View file

@ -42,13 +42,9 @@ export const packedNotificationSchema = {
type: 'string',
optional: true, nullable: true,
},
choice: {
type: 'number',
optional: true, nullable: true,
},
invitation: {
type: 'object',
optional: true, nullable: true,
achievement: {
type: 'string',
optional: true, nullable: false,
},
body: {
type: 'string',
@ -81,14 +77,14 @@ export const packedNotificationSchema = {
required: ['user', 'reaction'],
},
},
},
users: {
type: 'array',
optional: true, nullable: true,
items: {
type: 'object',
ref: 'UserLite',
optional: false, nullable: false,
users: {
type: 'array',
optional: true, nullable: true,
items: {
type: 'object',
ref: 'UserLite',
optional: false, nullable: false,
},
},
},
} as const;

View file

@ -22,6 +22,32 @@ export const packedPageSchema = {
optional: false, nullable: false,
format: 'date-time',
},
userId: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
user: {
type: 'object',
ref: 'UserLite',
optional: false, nullable: false,
},
content: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
},
},
variables: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
},
},
title: {
type: 'string',
optional: false, nullable: false,
@ -34,24 +60,48 @@ export const packedPageSchema = {
type: 'string',
optional: false, nullable: true,
},
content: {
type: 'array',
hideTitleWhenPinned: {
type: 'boolean',
optional: false, nullable: false,
},
variables: {
type: 'array',
alignCenter: {
type: 'boolean',
optional: false, nullable: false,
},
userId: {
font: {
type: 'string',
optional: false, nullable: false,
format: 'id',
},
user: {
type: 'object',
ref: 'UserLite',
script: {
type: 'string',
optional: false, nullable: false,
},
eyeCatchingImageId: {
type: 'string',
optional: false, nullable: true,
},
eyeCatchingImage: {
type: 'object',
optional: false, nullable: true,
ref: 'DriveFile',
},
attachedFiles: {
type: 'array',
optional: false, nullable: false,
items: {
type: 'object',
optional: false, nullable: false,
ref: 'DriveFile',
},
},
likedCount: {
type: 'number',
optional: false, nullable: false,
},
isLiked: {
type: 'boolean',
optional: true, nullable: false,
},
},
} as const;

View file

@ -49,11 +49,6 @@ export const packedUserLiteSchema = {
nullable: false, optional: false,
format: 'id',
},
url: {
type: 'string',
format: 'url',
nullable: false, optional: false,
},
angle: {
type: 'number',
nullable: false, optional: true,
@ -62,19 +57,14 @@ export const packedUserLiteSchema = {
type: 'boolean',
nullable: false, optional: true,
},
url: {
type: 'string',
format: 'url',
nullable: false, optional: false,
},
},
},
},
isAdmin: {
type: 'boolean',
nullable: false, optional: true,
default: false,
},
isModerator: {
type: 'boolean',
nullable: false, optional: true,
default: false,
},
isBot: {
type: 'boolean',
nullable: false, optional: true,
@ -83,12 +73,67 @@ export const packedUserLiteSchema = {
type: 'boolean',
nullable: false, optional: true,
},
instance: {
type: 'object',
nullable: false, optional: true,
properties: {
name: {
type: 'string',
nullable: true, optional: false,
},
softwareName: {
type: 'string',
nullable: true, optional: false,
},
softwareVersion: {
type: 'string',
nullable: true, optional: false,
},
iconUrl: {
type: 'string',
nullable: true, optional: false,
},
faviconUrl: {
type: 'string',
nullable: true, optional: false,
},
themeColor: {
type: 'string',
nullable: true, optional: false,
},
},
},
emojis: {
type: 'object',
nullable: false, optional: false,
},
onlineStatus: {
type: 'string',
format: 'url',
nullable: true, optional: false,
nullable: false, optional: false,
enum: ['unknown', 'online', 'active', 'offline'],
},
badgeRoles: {
type: 'array',
nullable: false, optional: true,
items: {
type: 'object',
nullable: false, optional: false,
properties: {
name: {
type: 'string',
nullable: false, optional: false,
},
iconUrl: {
type: 'string',
nullable: true, optional: false,
},
displayOrder: {
type: 'number',
nullable: false, optional: false,
},
},
},
},
},
} as const;
@ -105,21 +150,18 @@ export const packedUserDetailedNotMeOnlySchema = {
format: 'uri',
nullable: true, optional: false,
},
movedToUri: {
movedTo: {
type: 'string',
format: 'uri',
nullable: true,
optional: false,
nullable: true, optional: false,
},
alsoKnownAs: {
type: 'array',
nullable: true,
optional: false,
nullable: true, optional: false,
items: {
type: 'string',
format: 'id',
nullable: false,
optional: false,
nullable: false, optional: false,
},
},
createdAt: {
@ -253,6 +295,11 @@ export const packedUserDetailedNotMeOnlySchema = {
type: 'boolean',
nullable: false, optional: false,
},
ffVisibility: {
type: 'string',
nullable: false, optional: false,
enum: ['public', 'followers', 'private'],
},
twoFactorEnabled: {
type: 'boolean',
nullable: false, optional: false,
@ -268,6 +315,57 @@ export const packedUserDetailedNotMeOnlySchema = {
nullable: false, optional: false,
default: false,
},
roles: {
type: 'array',
nullable: false, optional: false,
items: {
type: 'object',
nullable: false, optional: false,
properties: {
id: {
type: 'string',
nullable: false, optional: false,
format: 'id',
},
name: {
type: 'string',
nullable: false, optional: false,
},
color: {
type: 'string',
nullable: true, optional: false,
},
iconUrl: {
type: 'string',
nullable: true, optional: false,
},
description: {
type: 'string',
nullable: false, optional: false,
},
isModerator: {
type: 'boolean',
nullable: false, optional: false,
},
isAdministrator: {
type: 'boolean',
nullable: false, optional: false,
},
displayOrder: {
type: 'number',
nullable: false, optional: false,
},
},
},
},
memo: {
type: 'string',
nullable: true, optional: false,
},
moderationNote: {
type: 'string',
nullable: false, optional: true,
},
//#region relations
isFollowing: {
type: 'boolean',
@ -301,10 +399,6 @@ export const packedUserDetailedNotMeOnlySchema = {
type: 'boolean',
nullable: false, optional: true,
},
memo: {
type: 'string',
nullable: false, optional: true,
},
notify: {
type: 'string',
nullable: false, optional: true,
@ -330,29 +424,37 @@ export const packedMeDetailedOnlySchema = {
nullable: true, optional: false,
format: 'id',
},
injectFeaturedNote: {
isModerator: {
type: 'boolean',
nullable: true, optional: false,
},
isAdmin: {
type: 'boolean',
nullable: true, optional: false,
},
injectFeaturedNote: {
type: 'boolean',
nullable: false, optional: false,
},
receiveAnnouncementEmail: {
type: 'boolean',
nullable: true, optional: false,
nullable: false, optional: false,
},
alwaysMarkNsfw: {
type: 'boolean',
nullable: true, optional: false,
nullable: false, optional: false,
},
autoSensitive: {
type: 'boolean',
nullable: true, optional: false,
nullable: false, optional: false,
},
carefulBot: {
type: 'boolean',
nullable: true, optional: false,
nullable: false, optional: false,
},
autoAcceptFollowed: {
type: 'boolean',
nullable: true, optional: false,
nullable: false, optional: false,
},
noCrawle: {
type: 'boolean',
@ -391,10 +493,23 @@ export const packedMeDetailedOnlySchema = {
type: 'boolean',
nullable: false, optional: false,
},
unreadAnnouncements: {
type: 'array',
nullable: false, optional: false,
items: {
type: 'object',
nullable: false, optional: false,
ref: 'Announcement',
},
},
hasUnreadAntenna: {
type: 'boolean',
nullable: false, optional: false,
},
hasUnreadChannel: {
type: 'boolean',
nullable: false, optional: false,
},
hasUnreadNotification: {
type: 'boolean',
nullable: false, optional: false,
@ -433,12 +548,132 @@ export const packedMeDetailedOnlySchema = {
},
emailNotificationTypes: {
type: 'array',
nullable: true, optional: false,
nullable: false, optional: false,
items: {
type: 'string',
nullable: false, optional: false,
},
},
achievements: {
type: 'array',
nullable: false, optional: false,
items: {
type: 'object',
nullable: false, optional: false,
properties: {
name: {
type: 'string',
nullable: false, optional: false,
},
unlockedAt: {
type: 'number',
nullable: false, optional: false,
},
},
},
},
loggedInDays: {
type: 'number',
nullable: false, optional: false,
},
policies: {
type: 'object',
nullable: false, optional: false,
properties: {
gtlAvailable: {
type: 'boolean',
nullable: false, optional: false,
},
ltlAvailable: {
type: 'boolean',
nullable: false, optional: false,
},
canPublicNote: {
type: 'boolean',
nullable: false, optional: false,
},
canInvite: {
type: 'boolean',
nullable: false, optional: false,
},
inviteLimit: {
type: 'number',
nullable: false, optional: false,
},
inviteLimitCycle: {
type: 'number',
nullable: false, optional: false,
},
inviteExpirationTime: {
type: 'number',
nullable: false, optional: false,
},
canManageCustomEmojis: {
type: 'boolean',
nullable: false, optional: false,
},
canManageAvatarDecorations: {
type: 'boolean',
nullable: false, optional: false,
},
canSearchNotes: {
type: 'boolean',
nullable: false, optional: false,
},
canUseTranslator: {
type: 'boolean',
nullable: false, optional: false,
},
canHideAds: {
type: 'boolean',
nullable: false, optional: false,
},
driveCapacityMb: {
type: 'number',
nullable: false, optional: false,
},
alwaysMarkNsfw: {
type: 'boolean',
nullable: false, optional: false,
},
pinLimit: {
type: 'number',
nullable: false, optional: false,
},
antennaLimit: {
type: 'number',
nullable: false, optional: false,
},
wordMuteLimit: {
type: 'number',
nullable: false, optional: false,
},
webhookLimit: {
type: 'number',
nullable: false, optional: false,
},
clipLimit: {
type: 'number',
nullable: false, optional: false,
},
noteEachClipsLimit: {
type: 'number',
nullable: false, optional: false,
},
userListLimit: {
type: 'number',
nullable: false, optional: false,
},
userEachUserListsLimit: {
type: 'number',
nullable: false, optional: false,
},
rateLimitFactor: {
type: 'number',
nullable: false, optional: false,
},
},
},
//#region secrets
email: {
type: 'string',
@ -515,5 +750,13 @@ export const packedUserSchema = {
type: 'object',
ref: 'UserDetailed',
},
{
type: 'object',
ref: 'UserDetailedNotMe',
},
{
type: 'object',
ref: 'MeDetailed',
},
],
} as const;

View file

@ -28,8 +28,8 @@ import * as ep___admin_avatarDecorations_delete from './endpoints/admin/avatar-d
import * as ep___admin_avatarDecorations_list from './endpoints/admin/avatar-decorations/list.js';
import * as ep___admin_avatarDecorations_update from './endpoints/admin/avatar-decorations/update.js';
import * as ep___admin_deleteAllFilesOfAUser from './endpoints/admin/delete-all-files-of-a-user.js';
import * as ep___admin_deleteUserAvatar from './endpoints/admin/delete-user-avatar.js';
import * as ep___admin_deleteUserBanner from './endpoints/admin/delete-user-banner.js';
import * as ep___admin_unsetUserAvatar from './endpoints/admin/unset-user-avatar.js';
import * as ep___admin_unsetUserBanner from './endpoints/admin/unset-user-banner.js';
import * as ep___admin_drive_cleanRemoteFiles from './endpoints/admin/drive/clean-remote-files.js';
import * as ep___admin_drive_cleanup from './endpoints/admin/drive/cleanup.js';
import * as ep___admin_drive_files from './endpoints/admin/drive/files.js';
@ -393,8 +393,8 @@ const $admin_avatarDecorations_delete: Provider = { provide: 'ep:admin/avatar-de
const $admin_avatarDecorations_list: Provider = { provide: 'ep:admin/avatar-decorations/list', useClass: ep___admin_avatarDecorations_list.default };
const $admin_avatarDecorations_update: Provider = { provide: 'ep:admin/avatar-decorations/update', useClass: ep___admin_avatarDecorations_update.default };
const $admin_deleteAllFilesOfAUser: Provider = { provide: 'ep:admin/delete-all-files-of-a-user', useClass: ep___admin_deleteAllFilesOfAUser.default };
const $admin_deleteUserAvatar: Provider = { provide: 'ep:admin/delete-user-avatar', useClass: ep___admin_deleteUserAvatar.default };
const $admin_deleteUserBanner: Provider = { provide: 'ep:admin/delete-user-banner', useClass: ep___admin_deleteUserBanner.default };
const $admin_unsetUserAvatar: Provider = { provide: 'ep:admin/unset-user-avatar', useClass: ep___admin_unsetUserAvatar.default };
const $admin_unsetUserBanner: Provider = { provide: 'ep:admin/unset-user-banner', useClass: ep___admin_unsetUserBanner.default };
const $admin_drive_cleanRemoteFiles: Provider = { provide: 'ep:admin/drive/clean-remote-files', useClass: ep___admin_drive_cleanRemoteFiles.default };
const $admin_drive_cleanup: Provider = { provide: 'ep:admin/drive/cleanup', useClass: ep___admin_drive_cleanup.default };
const $admin_drive_files: Provider = { provide: 'ep:admin/drive/files', useClass: ep___admin_drive_files.default };
@ -762,8 +762,8 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$admin_avatarDecorations_list,
$admin_avatarDecorations_update,
$admin_deleteAllFilesOfAUser,
$admin_deleteUserAvatar,
$admin_deleteUserBanner,
$admin_unsetUserAvatar,
$admin_unsetUserBanner,
$admin_drive_cleanRemoteFiles,
$admin_drive_cleanup,
$admin_drive_files,
@ -1125,8 +1125,8 @@ const $retention: Provider = { provide: 'ep:retention', useClass: ep___retention
$admin_avatarDecorations_list,
$admin_avatarDecorations_update,
$admin_deleteAllFilesOfAUser,
$admin_deleteUserAvatar,
$admin_deleteUserBanner,
$admin_unsetUserAvatar,
$admin_unsetUserBanner,
$admin_drive_cleanRemoteFiles,
$admin_drive_cleanup,
$admin_drive_files,

View file

@ -28,8 +28,8 @@ import * as ep___admin_avatarDecorations_delete from './endpoints/admin/avatar-d
import * as ep___admin_avatarDecorations_list from './endpoints/admin/avatar-decorations/list.js';
import * as ep___admin_avatarDecorations_update from './endpoints/admin/avatar-decorations/update.js';
import * as ep___admin_deleteAllFilesOfAUser from './endpoints/admin/delete-all-files-of-a-user.js';
import * as ep___admin_deleteUserAvatar from './endpoints/admin/delete-user-avatar.js';
import * as ep___admin_deleteUserBanner from './endpoints/admin/delete-user-banner.js';
import * as ep___admin_unsetUserAvatar from './endpoints/admin/unset-user-avatar.js';
import * as ep___admin_unsetUserBanner from './endpoints/admin/unset-user-banner.js';
import * as ep___admin_drive_cleanRemoteFiles from './endpoints/admin/drive/clean-remote-files.js';
import * as ep___admin_drive_cleanup from './endpoints/admin/drive/cleanup.js';
import * as ep___admin_drive_files from './endpoints/admin/drive/files.js';
@ -391,8 +391,8 @@ const eps = [
['admin/avatar-decorations/list', ep___admin_avatarDecorations_list],
['admin/avatar-decorations/update', ep___admin_avatarDecorations_update],
['admin/delete-all-files-of-a-user', ep___admin_deleteAllFilesOfAUser],
['admin/delete-user-avatar', ep___admin_deleteUserAvatar],
['admin/delete-user-banner', ep___admin_deleteUserBanner],
['admin/unset-user-avatar', ep___admin_unsetUserAvatar],
['admin/unset-user-banner', ep___admin_unsetUserBanner],
['admin/drive/clean-remote-files', ep___admin_drive_cleanRemoteFiles],
['admin/drive/cleanup', ep___admin_drive_cleanup],
['admin/drive/files', ep___admin_drive_files],

View file

@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
import type { UsersRepository } from '@/models/_.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = {
tags: ['admin'],
@ -29,6 +30,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
private moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps, me) => {
const user = await this.usersRepository.findOneBy({ id: ps.userId });
@ -37,12 +40,21 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
throw new Error('user not found');
}
if (user.avatarId == null) return;
await this.usersRepository.update(user.id, {
avatar: null,
avatarId: null,
avatarUrl: null,
avatarBlurhash: null,
});
this.moderationLogService.log(me, 'unsetUserAvatar', {
userId: user.id,
userUsername: user.username,
userHost: user.host,
fileId: user.avatarId,
});
});
}
}

View file

@ -7,6 +7,7 @@ import { Inject, Injectable } from '@nestjs/common';
import type { UsersRepository } from '@/models/_.js';
import { Endpoint } from '@/server/api/endpoint-base.js';
import { DI } from '@/di-symbols.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
export const meta = {
tags: ['admin'],
@ -29,6 +30,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
constructor(
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
private moderationLogService: ModerationLogService,
) {
super(meta, paramDef, async (ps, me) => {
const user = await this.usersRepository.findOneBy({ id: ps.userId });
@ -37,12 +40,21 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
throw new Error('user not found');
}
if (user.bannerId == null) return;
await this.usersRepository.update(user.id, {
banner: null,
bannerId: null,
bannerUrl: null,
bannerBlurhash: null,
});
this.moderationLogService.log(me, 'unsetUserBanner', {
userId: user.id,
userUsername: user.username,
userHost: user.host,
fileId: user.bannerId,
});
});
}
}

View file

@ -113,6 +113,8 @@ export const paramDef = {
objectStorageS3ForcePathStyle: { type: 'boolean' },
enableIpLogging: { type: 'boolean' },
enableActiveEmailValidation: { type: 'boolean' },
enableVerifymailApi: { type: 'boolean' },
verifymailAuthKey: { type: 'string', nullable: true },
enableChartsForRemoteUser: { type: 'boolean' },
enableChartsForFederatedInstances: { type: 'boolean' },
enableServerMachineStats: { type: 'boolean' },
@ -462,6 +464,18 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
set.enableActiveEmailValidation = ps.enableActiveEmailValidation;
}
if (ps.enableVerifymailApi !== undefined) {
set.enableVerifymailApi = ps.enableVerifymailApi;
}
if (ps.verifymailAuthKey !== undefined) {
if (ps.verifymailAuthKey === '') {
set.verifymailAuthKey = null;
} else {
set.verifymailAuthKey = ps.verifymailAuthKey;
}
}
if (ps.enableChartsForRemoteUser !== undefined) {
set.enableChartsForRemoteUser = ps.enableChartsForRemoteUser;
}

View file

@ -13,6 +13,7 @@ import { DI } from '@/di-symbols.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { IdService } from '@/core/IdService.js';
import { FunoutTimelineService } from '@/core/FunoutTimelineService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { ApiError } from '../../error.js';
export const meta = {
@ -71,6 +72,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
private queryService: QueryService,
private noteReadService: NoteReadService,
private funoutTimelineService: FunoutTimelineService,
private globalEventService: GlobalEventService,
) {
super(meta, paramDef, async (ps, me) => {
const untilId = ps.untilId ?? (ps.untilDate ? this.idService.gen(ps.untilDate!) : null);
@ -85,10 +87,16 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
throw new ApiError(meta.errors.noSuchAntenna);
}
this.antennasRepository.update(antenna.id, {
isActive: true,
lastUsedAt: new Date(),
});
// falseだった場合はアンテナの配信先が増えたことを通知したい
const needPublishEvent = !antenna.isActive;
antenna.isActive = true;
antenna.lastUsedAt = new Date();
this.antennasRepository.update(antenna.id, antenna);
if (needPublishEvent) {
this.globalEventService.publishInternalEvent('antennaUpdated', antenna);
}
let noteIds = await this.funoutTimelineService.get(`antennaTimeline:${antenna.id}`, untilId, sinceId);
noteIds = noteIds.slice(0, ps.limit);

View file

@ -63,6 +63,8 @@ export const moderationLogTypes = [
'createAvatarDecoration',
'updateAvatarDecoration',
'deleteAvatarDecoration',
'unsetUserAvatar',
'unsetUserBanner',
] as const;
export type ModerationLogPayloads = {
@ -237,6 +239,18 @@ export type ModerationLogPayloads = {
avatarDecorationId: string;
avatarDecoration: any;
};
unsetUserAvatar: {
userId: string;
userUsername: string;
userHost: string | null;
fileId: string;
};
unsetUserBanner: {
userId: string;
userUsername: string;
userHost: string | null;
fileId: string;
};
};
export type Serialized<T> = {

View file

@ -70,7 +70,7 @@
"tsc-alias": "1.8.8",
"tsconfig-paths": "4.2.0",
"twemoji-parser": "14.0.0",
"typescript": "5.2.2",
"typescript": "5.3.2",
"uuid": "9.0.1",
"v-code-diff": "1.7.2",
"vanilla-tilt": "1.8.1",

View file

@ -114,7 +114,6 @@ const props = defineProps<{
& + article {
left: 0;
width: 100%;
}
}
}
@ -124,6 +123,7 @@ const props = defineProps<{
> .thumbnail {
height: 80px;
overflow: clip;
}
> article {

View file

@ -123,12 +123,11 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
</MkFolder>
<MkButton v-if="$i.isAdmin" inline danger @click="deleteAccount">{{ i18n.ts.deleteAccount }}</MkButton>
<div>
<MkButton v-if="iAmModerator" inline danger style="margin-right: 8px;" @click="deleteUserAvatar"><i class="ti ti-user-circle"></i> {{ i18n.ts.deleteUserAvatar }}</MkButton>
<MkButton v-if="iAmModerator" inline danger @click="deleteUserBanner"><i class="ti ti-photo"></i> {{ i18n.ts.deleteUserBanner }}</MkButton>
<MkButton v-if="iAmModerator" inline danger style="margin-right: 8px;" @click="unsetUserAvatar"><i class="ti ti-user-circle"></i> {{ i18n.ts.unsetUserAvatar }}</MkButton>
<MkButton v-if="iAmModerator" inline danger @click="unsetUserBanner"><i class="ti ti-photo"></i> {{ i18n.ts.unsetUserBanner }}</MkButton>
</div>
<MkButton v-if="$i.isAdmin" inline danger @click="deleteAccount">{{ i18n.ts.deleteAccount }}</MkButton>
</div>
</FormSection>
</div>
@ -330,14 +329,14 @@ async function toggleSuspend(v) {
}
}
async function deleteUserAvatar() {
async function unsetUserAvatar() {
const confirm = await os.confirm({
type: 'warning',
text: i18n.ts.deleteUserAvatarConfirm,
text: i18n.ts.unsetUserAvatarConfirm,
});
if (confirm.canceled) return;
const process = async () => {
await os.api('admin/delete-user-avatar', { userId: user.id });
await os.api('admin/unset-user-avatar', { userId: user.id });
os.success();
};
await process().catch(err => {
@ -349,14 +348,14 @@ async function deleteUserAvatar() {
refreshUser();
}
async function deleteUserBanner() {
async function unsetUserBanner() {
const confirm = await os.confirm({
type: 'warning',
text: i18n.ts.deleteUserBannerConfirm,
text: i18n.ts.unsetUserBannerConfirm,
});
if (confirm.canceled) return;
const process = async () => {
await os.api('admin/delete-user-banner', { userId: user.id });
await os.api('admin/unset-user-banner', { userId: user.id });
os.success();
};
await process().catch(err => {

View file

@ -38,6 +38,7 @@ import { } from 'vue';
import XHeader from './_header_.vue';
import MkInput from '@/components/MkInput.vue';
import MkButton from '@/components/MkButton.vue';
import MkSwitch from '@/components/MkSwitch.vue';
import FormSuspense from '@/components/form/suspense.vue';
import FormSection from '@/components/form/section.vue';
import * as os from '@/os.js';

View file

@ -73,6 +73,13 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="enableActiveEmailValidation" @update:modelValue="save">
<template #label>Enable</template>
</MkSwitch>
<MkSwitch v-model="enableVerifymailApi" @update:modelValue="save">
<template #label>Use Verifymail API</template>
</MkSwitch>
<MkInput v-model="verifymailAuthKey" @update:modelValue="save">
<template #prefix><i class="ti ti-key"></i></template>
<template #label>Verifymail API Auth Key</template>
</MkInput>
</div>
</MkFolder>
@ -132,6 +139,8 @@ let setSensitiveFlagAutomatically: boolean = $ref(false);
let enableSensitiveMediaDetectionForVideos: boolean = $ref(false);
let enableIpLogging: boolean = $ref(false);
let enableActiveEmailValidation: boolean = $ref(false);
let enableVerifymailApi: boolean = $ref(false);
let verifymailAuthKey: string | null = $ref(null);
async function init() {
const meta = await os.api('admin/meta');
@ -150,6 +159,8 @@ async function init() {
enableSensitiveMediaDetectionForVideos = meta.enableSensitiveMediaDetectionForVideos;
enableIpLogging = meta.enableIpLogging;
enableActiveEmailValidation = meta.enableActiveEmailValidation;
enableVerifymailApi = meta.enableVerifymailApi;
verifymailAuthKey = meta.verifymailAuthKey;
}
function save() {
@ -167,6 +178,8 @@ function save() {
enableSensitiveMediaDetectionForVideos,
enableIpLogging,
enableActiveEmailValidation,
enableVerifymailApi,
verifymailAuthKey,
}).then(() => {
fetchInstance();
});

View file

@ -54,22 +54,24 @@ import { miLocalStorage } from '@/local-storage.js';
const { t, ts } = i18n;
const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [
'collapseRenotes',
'menu',
'visibility',
'localOnly',
'statusbars',
'widgets',
'tl',
'pinnedUserLists',
'overridedDeviceKind',
'serverDisconnectedBehavior',
'collapseRenotes',
'showNoteActionsOnlyHover',
'nsfw',
'highlightSensitiveMedia',
'animation',
'animatedMfm',
'advancedMfm',
'loadRawImages',
'imageNewTab',
'enableDataSaverMode',
'disableShowingAnimatedImages',
'emojiStyle',
'disableDrawer',
@ -89,9 +91,28 @@ const defaultStoreSaveKeys: (keyof typeof defaultStore['state'])[] = [
'menuDisplay',
'reportError',
'squareAvatars',
'showAvatarDecorations',
'numberOfPageCache',
'showNoteActionsOnlyHover',
'showClipButtonInNoteFooter',
'reactionsDisplaySize',
'forceShowAds',
'aiChanMode',
'devMode',
'mediaListWithOneImageAppearance',
'notificationPosition',
'notificationStackAxis',
'enableCondensedLineForAcct',
'keepScreenOn',
'defaultWithReplies',
'disableStreamingTimeline',
'useGroupedNotifications',
'sound_masterVolume',
'sound_note',
'sound_noteMy',
'sound_notification',
'sound_antenna',
'sound_channel',
];
const coldDeviceStorageSaveKeys: (keyof typeof ColdDeviceStorage.default)[] = [
'lightTheme',

View file

@ -122,7 +122,7 @@ export const soundsTypes = [
'Copyright_Misskey.io/ThinaticSystem/yonderuzo3',
] as const;
export async function getAudio(file: string, useCache = true) {
export async function loadAudio(file: string, useCache = true) {
if (useCache && cache.has(file)) {
return cache.get(file)!;
}
@ -138,12 +138,6 @@ export async function getAudio(file: string, useCache = true) {
return audioBuffer;
}
export function setVolume(audio: HTMLAudioElement, volume: number): HTMLAudioElement {
const masterVolume = defaultStore.state.sound_masterVolume;
audio.volume = masterVolume - ((1 - volume) * masterVolume);
return audio;
}
export function play(type: 'noteMy' | 'note' | 'antenna' | 'channel' | 'notification') {
const sound = defaultStore.state[`sound_${type}`];
if (_DEV_) console.log('play', type, sound);
@ -152,16 +146,22 @@ export function play(type: 'noteMy' | 'note' | 'antenna' | 'channel' | 'notifica
}
export async function playFile(file: string, volume: number) {
const buffer = await loadAudio(file);
createSourceNode(buffer, volume)?.start();
}
export function createSourceNode(buffer: AudioBuffer, volume: number) : AudioBufferSourceNode | null {
const masterVolume = defaultStore.state.sound_masterVolume;
if (masterVolume === 0 || volume === 0) {
return;
return null;
}
const gainNode = ctx.createGain();
gainNode.gain.value = masterVolume * volume;
const soundSource = ctx.createBufferSource();
soundSource.buffer = await getAudio(file);
soundSource.buffer = buffer;
soundSource.connect(gainNode).connect(ctx.destination);
soundSource.start();
return soundSource;
}

View file

@ -99,7 +99,10 @@ const current = reactive({
},
});
const prev = reactive({} as typeof current);
const jammedSound = sound.setVolume(sound.getAudio('syuilo/queue-jammed'), 1);
let jammedAudioBuffer: AudioBuffer | null = $ref(null);
let jammedSoundNodePlaying: boolean = $ref(false);
sound.loadAudio('syuilo/queue-jammed').then(buf => jammedAudioBuffer = buf);
for (const domain of ['inbox', 'deliver']) {
prev[domain] = deepClone(current[domain]);
@ -113,8 +116,13 @@ const onStats = (stats) => {
current[domain].waiting = stats[domain].waiting;
current[domain].delayed = stats[domain].delayed;
if (current[domain].waiting > 0 && widgetProps.sound && jammedSound.paused) {
jammedSound.play();
if (current[domain].waiting > 0 && widgetProps.sound && jammedAudioBuffer && !jammedSoundNodePlaying) {
const soundNode = sound.createSourceNode(jammedAudioBuffer, 1);
if (soundNode) {
jammedSoundNodePlaying = true;
soundNode.onended = () => jammedSoundNodePlaying = false;
soundNode.start();
}
}
}
};

View file

@ -352,13 +352,13 @@ export type Endpoints = {
};
res: null;
};
'admin/delete-user-avatar': {
'admin/unset-user-avatar': {
req: {
userId: User['id'];
};
res: null;
};
'admin/delete-user-banner': {
'admin/unset-user-banner': {
req: {
userId: User['id'];
};
@ -2708,10 +2708,16 @@ type ModerationLog = {
} | {
type: 'resolveAbuseReport';
info: ModerationLogPayloads['resolveAbuseReport'];
} | {
type: 'unsetUserAvatar';
info: ModerationLogPayloads['unsetUserAvatar'];
} | {
type: 'unsetUserBanner';
info: ModerationLogPayloads['unsetUserBanner'];
});
// @public (undocumented)
export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "createRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance", "markSensitiveDriveFile", "unmarkSensitiveDriveFile", "resolveAbuseReport", "createInvitation", "createAd", "updateAd", "deleteAd", "createAvatarDecoration", "updateAvatarDecoration", "deleteAvatarDecoration"];
export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "createRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance", "markSensitiveDriveFile", "unmarkSensitiveDriveFile", "resolveAbuseReport", "createInvitation", "createAd", "updateAd", "deleteAd", "createAvatarDecoration", "updateAvatarDecoration", "deleteAvatarDecoration", "unsetUserAvatar", "unsetUserBanner"];
// @public (undocumented)
export const mutedNoteReasons: readonly ["word", "manual", "spam", "other"];

View file

@ -32,7 +32,7 @@
"jest-websocket-mock": "2.5.0",
"mock-socket": "9.3.1",
"tsd": "0.29.0",
"typescript": "5.2.2"
"typescript": "5.3.2"
},
"files": [
"built"

View file

@ -15,8 +15,8 @@ export type Endpoints = {
// admin
'admin/abuse-user-reports': { req: TODO; res: TODO; };
'admin/delete-all-files-of-a-user': { req: { userId: User['id']; }; res: null; };
'admin/delete-user-avatar': { req: { userId: User['id']; }; res: null; };
'admin/delete-user-banner': { req: { userId: User['id']; }; res: null; };
'admin/unset-user-avatar': { req: { userId: User['id']; }; res: null; };
'admin/unset-user-banner': { req: { userId: User['id']; }; res: null; };
'admin/delete-logs': { req: NoParams; res: null; };
'admin/get-index-stats': { req: TODO; res: TODO; };
'admin/get-table-stats': { req: TODO; res: TODO; };

View file

@ -81,6 +81,8 @@ export const moderationLogTypes = [
'createAvatarDecoration',
'updateAvatarDecoration',
'deleteAvatarDecoration',
'unsetUserAvatar',
'unsetUserBanner',
] as const;
export type ModerationLogPayloads = {
@ -255,4 +257,16 @@ export type ModerationLogPayloads = {
avatarDecorationId: string;
avatarDecoration: any;
};
unsetUserAvatar: {
userId: string;
userUsername: string;
userHost: string | null;
fileId: string;
};
unsetUserBanner: {
userId: string;
userUsername: string;
userHost: string | null;
fileId: string;
};
};

View file

@ -737,4 +737,10 @@ export type ModerationLog = {
} | {
type: 'resolveAbuseReport';
info: ModerationLogPayloads['resolveAbuseReport'];
} | {
type: 'unsetUserAvatar';
info: ModerationLogPayloads['unsetUserAvatar'];
} | {
type: 'unsetUserBanner';
info: ModerationLogPayloads['unsetUserBanner'];
});

View file

@ -18,7 +18,7 @@
"@typescript/lib-webworker": "npm:@types/serviceworker@0.0.67",
"eslint": "8.53.0",
"eslint-plugin-import": "2.29.0",
"typescript": "5.2.2"
"typescript": "5.3.2"
},
"type": "module"
}