implement mandatory CW for notes (resolves #910)
This commit is contained in:
parent
6f8d831e09
commit
92750240eb
29 changed files with 305 additions and 11 deletions
22
locales/index.d.ts
vendored
22
locales/index.d.ts
vendored
|
|
@ -9212,6 +9212,10 @@ export interface Locale extends ILocale {
|
|||
* Apply mandatory CW on users
|
||||
*/
|
||||
"write:admin:cw-user": string;
|
||||
/**
|
||||
* Apply mandatory CW on notes
|
||||
*/
|
||||
"write:admin:cw-note": string;
|
||||
/**
|
||||
* Silence users
|
||||
*/
|
||||
|
|
@ -10948,6 +10952,10 @@ export interface Locale extends ILocale {
|
|||
* Set content warning for user
|
||||
*/
|
||||
"setMandatoryCW": string;
|
||||
/**
|
||||
* Set content warning for note
|
||||
*/
|
||||
"setMandatoryCWForNote": string;
|
||||
/**
|
||||
* Set remote instance as NSFW
|
||||
*/
|
||||
|
|
@ -12088,6 +12096,10 @@ export interface Locale extends ILocale {
|
|||
* {name} is flagged: "{cw}"
|
||||
*/
|
||||
"userIsFlaggedAs": ParameterizedString<"name" | "cw">;
|
||||
/**
|
||||
* Note is flagged: "{cw}"
|
||||
*/
|
||||
"noteIsFlaggedAs": ParameterizedString<"cw">;
|
||||
/**
|
||||
* Mark all media from user as NSFW
|
||||
*/
|
||||
|
|
@ -13038,9 +13050,17 @@ export interface Locale extends ILocale {
|
|||
*/
|
||||
"mandatoryCW": string;
|
||||
/**
|
||||
* Applies a content warning to all posts created by this user. If the post already has a CW, then this is appended to the end.
|
||||
* Applies a content warning to all posts created by this user. The forced warnings will appear like a word mute to distinguish them from the author's own content warnings.
|
||||
*/
|
||||
"mandatoryCWDescription": string;
|
||||
/**
|
||||
* Add content warning
|
||||
*/
|
||||
"mandatoryCWForNote": string;
|
||||
/**
|
||||
* Applies an additional content warning to this post. The new warning will appear like a word mute to distinguish it from the author's own content warning.
|
||||
*/
|
||||
"mandatoryCWForNoteDescription": string;
|
||||
/**
|
||||
* Fetch linked note
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class AddNoteMandatoryCW1751077195277 {
|
||||
name = 'AddNoteMandatoryCW1751077195277'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "note" ADD "mandatoryCW" text`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "mandatoryCW"`);
|
||||
}
|
||||
}
|
||||
|
|
@ -127,6 +127,7 @@ function generateDummyNote(override?: Partial<MiNote>): MiNote {
|
|||
renoteUserInstance: null,
|
||||
updatedAt: null,
|
||||
processErrors: [],
|
||||
mandatoryCW: null,
|
||||
...override,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -497,6 +497,9 @@ export class ApRendererService {
|
|||
let summary = note.cw === '' ? String.fromCharCode(0x200B) : note.cw;
|
||||
|
||||
// Apply mandatory CW, if applicable
|
||||
if (note.mandatoryCW) {
|
||||
summary = appendContentWarning(summary, note.mandatoryCW);
|
||||
}
|
||||
if (author.mandatoryCW) {
|
||||
summary = appendContentWarning(summary, author.mandatoryCW);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -651,6 +651,7 @@ export class NoteEntityService implements OnModuleInit {
|
|||
user: packedUsers?.get(note.userId) ?? this.userEntityService.pack(note.user ?? note.userId, me),
|
||||
text: text,
|
||||
cw: note.cw,
|
||||
mandatoryCW: note.mandatoryCW,
|
||||
visibility: note.visibility,
|
||||
localOnly: note.localOnly,
|
||||
reactionAcceptance: note.reactionAcceptance,
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ export const getNoteSummary = (note: Packed<'Note'>): string => {
|
|||
|
||||
// Append mandatory CW, if applicable
|
||||
let cw = note.cw;
|
||||
if (note.mandatoryCW) {
|
||||
cw = appendContentWarning(cw, note.mandatoryCW);
|
||||
}
|
||||
if (note.user.mandatoryCW) {
|
||||
cw = appendContentWarning(cw, note.user.mandatoryCW);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -228,6 +228,15 @@ export class MiNote {
|
|||
})
|
||||
public processErrors: string[] | null;
|
||||
|
||||
/**
|
||||
* Specifies a Content Warning that should be forcibly attached to this note.
|
||||
* Does not replace the user's own CW.
|
||||
*/
|
||||
@Column('text', {
|
||||
nullable: true,
|
||||
})
|
||||
public mandatoryCW: string | null;
|
||||
|
||||
//#region Denormalized fields
|
||||
@Column('varchar', {
|
||||
length: 128, nullable: true,
|
||||
|
|
|
|||
|
|
@ -41,6 +41,10 @@ export const packedNoteSchema = {
|
|||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
mandatoryCW: {
|
||||
type: 'string',
|
||||
optional: true, nullable: true,
|
||||
},
|
||||
userId: {
|
||||
type: 'string',
|
||||
optional: false, nullable: false,
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ export * as 'admin/avatar-decorations/list' from './endpoints/admin/avatar-decor
|
|||
export * as 'admin/avatar-decorations/update' from './endpoints/admin/avatar-decorations/update.js';
|
||||
export * as 'admin/captcha/current' from './endpoints/admin/captcha/current.js';
|
||||
export * as 'admin/captcha/save' from './endpoints/admin/captcha/save.js';
|
||||
export * as 'admin/cw-note' from './endpoints/admin/cw-note.js';
|
||||
export * as 'admin/cw-user' from './endpoints/admin/cw-user.js';
|
||||
export * as 'admin/decline-user' from './endpoints/admin/decline-user.js';
|
||||
export * as 'admin/delete-account' from './endpoints/admin/delete-account.js';
|
||||
|
|
|
|||
63
packages/backend/src/server/api/endpoints/admin/cw-note.ts
Normal file
63
packages/backend/src/server/api/endpoints/admin/cw-note.ts
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: hazelnoot and other Sharkey contributors
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import type { MiNote, MiUser, NotesRepository } from '@/models/_.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { ModerationLogService } from '@/core/ModerationLogService.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
kind: 'write:admin:cw-note',
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
noteId: { type: 'string', format: 'misskey:id' },
|
||||
cw: { type: 'string', nullable: true },
|
||||
},
|
||||
required: ['noteId', 'cw'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
@Inject(DI.notesRepository)
|
||||
private readonly notesRepository: NotesRepository,
|
||||
|
||||
private readonly moderationLogService: ModerationLogService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const note = await this.notesRepository.findOneOrFail({
|
||||
where: { id: ps.noteId },
|
||||
relations: { user: true },
|
||||
}) as MiNote & { user: MiUser };
|
||||
|
||||
// Skip if there's nothing to do
|
||||
if (note.mandatoryCW === ps.cw) return;
|
||||
|
||||
// Log event first.
|
||||
// This ensures that we don't "lose" the log if an error occurs
|
||||
await this.moderationLogService.log(me, 'setMandatoryCWForNote', {
|
||||
newCW: ps.cw,
|
||||
oldCW: note.mandatoryCW,
|
||||
noteId: note.id,
|
||||
noteUserId: note.user.id,
|
||||
noteUserUsername: note.user.username,
|
||||
noteUserHost: note.user.host,
|
||||
});
|
||||
|
||||
await this.notesRepository.update(ps.noteId, {
|
||||
// Collapse empty strings to null
|
||||
mandatoryCW: ps.cw || null,
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -46,7 +46,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
|||
|
||||
await this.usersRepository.update(ps.userId, {
|
||||
// Collapse empty strings to null
|
||||
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
||||
mandatoryCW: ps.cw || null,
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -224,7 +224,13 @@ export class MastodonConverters {
|
|||
// TODO avoid re-packing files for each edit
|
||||
const files = await this.driveFileEntityService.packManyByIds(edit.fileIds);
|
||||
|
||||
const cw = appendContentWarning(edit.cw, noteUser.mandatoryCW) ?? '';
|
||||
let cw = edit.cw ?? '';
|
||||
if (note.mandatoryCW) {
|
||||
cw = appendContentWarning(cw, note.mandatoryCW);
|
||||
}
|
||||
if (noteUser.mandatoryCW) {
|
||||
cw = appendContentWarning(cw, noteUser.mandatoryCW);
|
||||
}
|
||||
|
||||
const isQuote = renote && (edit.cw || edit.newText || edit.fileIds.length > 0 || note.replyId);
|
||||
const quoteUri = isQuote
|
||||
|
|
@ -299,7 +305,13 @@ export class MastodonConverters {
|
|||
? quoteUri.then(quote => this.mfmService.toMastoApiHtml(mfm.parse(text), mentionedRemoteUsers, false, quote) ?? escapeMFM(text))
|
||||
: '';
|
||||
|
||||
const cw = appendContentWarning(note.cw, noteUser.mandatoryCW) ?? '';
|
||||
let cw = note.cw ?? '';
|
||||
if (note.mandatoryCW) {
|
||||
cw = appendContentWarning(cw, note.mandatoryCW);
|
||||
}
|
||||
if (noteUser.mandatoryCW) {
|
||||
cw = appendContentWarning(cw, noteUser.mandatoryCW);
|
||||
}
|
||||
|
||||
const reblogged = await this.mastodonDataService.hasReblog(note.id, me);
|
||||
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ export const moderationLogTypes = [
|
|||
'deleteUserAnnouncement',
|
||||
'resetPassword',
|
||||
'setMandatoryCW',
|
||||
'setMandatoryCWForNote',
|
||||
'setRemoteInstanceNSFW',
|
||||
'unsetRemoteInstanceNSFW',
|
||||
'suspendRemoteInstance',
|
||||
|
|
@ -294,6 +295,14 @@ export type ModerationLogPayloads = {
|
|||
userUsername: string;
|
||||
userHost: string | null;
|
||||
};
|
||||
setMandatoryCWForNote: {
|
||||
newCW: string | null;
|
||||
oldCW: string | null;
|
||||
noteId: string;
|
||||
noteUserId: string;
|
||||
noteUserUsername: string;
|
||||
noteUserHost: string | null;
|
||||
};
|
||||
setRemoteInstanceNSFW: {
|
||||
id: string;
|
||||
host: string;
|
||||
|
|
|
|||
|
|
@ -65,6 +65,7 @@ describe('NoteCreateService', () => {
|
|||
renoteUserHost: null,
|
||||
renoteUserInstance: null,
|
||||
processErrors: [],
|
||||
mandatoryCW: null,
|
||||
};
|
||||
|
||||
const poll: IPoll = {
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ const base: MiNote = {
|
|||
renoteUserHost: null,
|
||||
renoteUserInstance: null,
|
||||
processErrors: [],
|
||||
mandatoryCW: null,
|
||||
};
|
||||
|
||||
describe('misc:is-renote', () => {
|
||||
|
|
|
|||
|
|
@ -9,6 +9,9 @@ import { appendContentWarning } from '@@/js/append-content-warning.js';
|
|||
export function computeMergedCw(note: Misskey.entities.Note): string | null {
|
||||
let cw = note.cw;
|
||||
|
||||
if (note.mandatoryCW) {
|
||||
cw = appendContentWarning(cw, note.mandatoryCW);
|
||||
}
|
||||
if (note.user.mandatoryCW) {
|
||||
cw = appendContentWarning(cw, note.user.mandatoryCW);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,11 @@ Displays a placeholder for a muted note.
|
|||
<!-- If hard muted, we want to hide *everything*, including the placeholders and controls to expand. -->
|
||||
<div v-else-if="!mute.hardMuted" :class="[$style.muted, mutedClass]" class="_gaps_s" @click.stop="expandNote = true">
|
||||
<!-- Mandatory CWs -->
|
||||
<I18n v-if="mute.noteMandatoryCW" :src="i18n.ts.noteIsFlaggedAs" tag="small">
|
||||
<template #cw>
|
||||
{{ mute.noteMandatoryCW }}
|
||||
</template>
|
||||
</I18n>
|
||||
<I18n v-if="mute.userMandatoryCW" :src="i18n.ts.userIsFlaggedAs" tag="small">
|
||||
<template #name>
|
||||
<MkUserName :user="note.user"/>
|
||||
|
|
@ -84,7 +89,7 @@ const expandNote = ref(false);
|
|||
|
||||
const mute = computed(() => checkMute(props.note, props.withHardMute));
|
||||
const mutedWords = computed(() => mute.value.softMutedWords?.join(', '));
|
||||
const isMuted = computed(() => mute.value.hardMuted || mutedWords.value || mute.value.userMandatoryCW || mute.value.noteMuted || mute.value.threadMuted || mute.value.sensitiveMuted);
|
||||
const isMuted = computed(() => mute.value.hardMuted || mutedWords.value || mute.value.noteMandatoryCW || mute.value.userMandatoryCW || mute.value.noteMuted || mute.value.threadMuted || mute.value.sensitiveMuted);
|
||||
const isExpanded = computed(() => expandNote.value || !isMuted.value);
|
||||
const rootClass = computed(() => isExpanded.value ? props.expandedClass : undefined);
|
||||
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
'markSensitiveDriveFile',
|
||||
'resetPassword',
|
||||
'setMandatoryCW',
|
||||
'setMandatoryCWForNote',
|
||||
'suspendRemoteInstance',
|
||||
'setRemoteInstanceNSFW',
|
||||
'unsetRemoteInstanceNSFW',
|
||||
|
|
@ -79,6 +80,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<span v-else-if="log.type === 'unsuspend'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
|
||||
<span v-else-if="log.type === 'resetPassword'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
|
||||
<span v-else-if="log.type === 'setMandatoryCW'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
|
||||
<span v-else-if="log.type === 'setMandatoryCWForNote'">: @{{ log.info.noteUserUsername }}{{ log.info.noteUserHost ? '@' + log.info.noteUserHost : '' }}</span>
|
||||
<span v-else-if="log.type === 'assignRole'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }} <i class="ti ti-arrow-right"></i> {{ log.info.roleName }}</span>
|
||||
<span v-else-if="log.type === 'unassignRole'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }} <i class="ti ti-equal-not"></i> {{ log.info.roleName }}</span>
|
||||
<span v-else-if="log.type === 'createRole'">: {{ log.info.role.name }}</span>
|
||||
|
|
@ -208,6 +210,13 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
<CodeDiff :context="0" :hideHeader="true" :oldString="log.info.oldCW ?? ''" :newString="log.info.newCW ?? ''" maxHeight="150px"/>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="log.type === 'setMandatoryCWForNote'">
|
||||
<div>{{ i18n.ts.user }}: <MkA :to="`/admin/user/${log.info.noteUserId}`" class="_link">@{{ log.info.noteUserUsername }}{{ log.info.noteUserHost ? '@' + log.info.noteUserHost : '' }}</MkA></div>
|
||||
<div>{{ i18n.ts.note }}: <MkA :to="`/notes/${log.info.noteId}`" class="_link">{{ log.info.noteId }}</MkA></div>
|
||||
<div :class="$style.diff">
|
||||
<CodeDiff :context="0" :hideHeader="true" :oldString="log.info.oldCW ?? ''" :newString="log.info.newCW ?? ''" maxHeight="150px"/>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="log.type === 'unsuspend'">
|
||||
<div>{{ i18n.ts.user }}: <MkA :to="`/admin/user/${log.info.userId}`" class="_link">@{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</MkA></div>
|
||||
</template>
|
||||
|
|
@ -323,6 +332,8 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||
</div>
|
||||
</template>
|
||||
|
||||
<MkUrlPreview v-if="'noteId' in log.info" :url="`${url}/notes/${log.info.noteId}`" :compact="false" :detail="false" :showAsQuote="true"></MkUrlPreview>
|
||||
|
||||
<details>
|
||||
<summary>raw</summary>
|
||||
<pre>{{ JSON5.stringify(log, null, '\t') }}</pre>
|
||||
|
|
@ -338,6 +349,8 @@ import JSON5 from 'json5';
|
|||
import { i18n } from '@/i18n.js';
|
||||
import MkFolder from '@/components/MkFolder.vue';
|
||||
import SkFetchNote from '@/components/SkFetchNote.vue';
|
||||
import MkUrlPreview from '@/components/MkUrlPreview.vue';
|
||||
import { url } from '@@/js/config.js';
|
||||
|
||||
const props = defineProps<{
|
||||
log: Misskey.entities.ModerationLog;
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ export interface Mute {
|
|||
threadMuted?: boolean;
|
||||
noteMuted?: boolean;
|
||||
|
||||
noteMandatoryCW?: string | null;
|
||||
// TODO show this as a single block on user timelines
|
||||
userMandatoryCW?: string | null;
|
||||
}
|
||||
|
|
@ -32,21 +33,22 @@ export function checkMute(note: Misskey.entities.Note, withHardMute?: boolean):
|
|||
|
||||
const threadMuted = note.isMutingThread;
|
||||
const noteMuted = note.isMutingNote;
|
||||
const noteMandatoryCW = note.mandatoryCW;
|
||||
const userMandatoryCW = note.user.mandatoryCW;
|
||||
|
||||
// Hard mute
|
||||
if (withHardMute && isHardMuted(note)) {
|
||||
return { hardMuted: true, sensitiveMuted, threadMuted, noteMuted, userMandatoryCW };
|
||||
return { hardMuted: true, sensitiveMuted, threadMuted, noteMuted, noteMandatoryCW, userMandatoryCW };
|
||||
}
|
||||
|
||||
// Soft mute
|
||||
const softMutedWords = isSoftMuted(note);
|
||||
if (softMutedWords.length > 0) {
|
||||
return { softMutedWords, sensitiveMuted, threadMuted, noteMuted, userMandatoryCW };
|
||||
return { softMutedWords, sensitiveMuted, threadMuted, noteMuted, noteMandatoryCW, userMandatoryCW };
|
||||
}
|
||||
|
||||
// Other / no mute
|
||||
return { sensitiveMuted, threadMuted, noteMuted, userMandatoryCW };
|
||||
return { sensitiveMuted, threadMuted, noteMuted, noteMandatoryCW, userMandatoryCW };
|
||||
}
|
||||
|
||||
function isHardMuted(note: Misskey.entities.Note): boolean {
|
||||
|
|
|
|||
|
|
@ -151,6 +151,28 @@ export function getAbuseNoteMenu(note: Misskey.entities.Note, text: string): Men
|
|||
};
|
||||
}
|
||||
|
||||
export function getMandatoryCWMenu(note: Misskey.entities.Note): MenuItem {
|
||||
return {
|
||||
icon: 'ph-warning ph-bold ph-lg',
|
||||
text: i18n.ts.mandatoryCWForNote,
|
||||
action: async () => {
|
||||
const result = await os.inputText({
|
||||
type: 'text',
|
||||
title: i18n.ts.mandatoryCWForNote,
|
||||
text: i18n.ts.mandatoryCWForNoteDescription,
|
||||
default: note.mandatoryCW ?? '',
|
||||
});
|
||||
|
||||
if (result.canceled) return;
|
||||
|
||||
await os.apiWithDialog('admin/cw-note', {
|
||||
noteId: note.id,
|
||||
cw: result.result || null,
|
||||
});
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export function getCopyNoteLinkMenu(note: Misskey.entities.Note, text: string): MenuItem {
|
||||
return {
|
||||
icon: 'ti ti-link',
|
||||
|
|
@ -442,6 +464,10 @@ export function getNoteMenu(props: {
|
|||
if (appearNote.userId !== $i.id) {
|
||||
menuItems.push({ type: 'divider' });
|
||||
menuItems.push(getAbuseNoteMenu(appearNote, i18n.ts.reportAbuse));
|
||||
|
||||
if ($i.isModerator || $i.isAdmin) {
|
||||
menuItems.push(getMandatoryCWMenu(appearNote));
|
||||
}
|
||||
}
|
||||
|
||||
if (appearNote.channel && (appearNote.channel.userId === $i.id || $i.isModerator || $i.isAdmin)) {
|
||||
|
|
|
|||
|
|
@ -28,6 +28,9 @@ export const getNoteSummary = (note?: Misskey.entities.Note | null): string => {
|
|||
|
||||
// Append mandatory CW, if applicable
|
||||
let cw = note.cw;
|
||||
if (note.mandatoryCW) {
|
||||
cw = appendContentWarning(cw, note.mandatoryCW);
|
||||
}
|
||||
if (note.user.mandatoryCW) {
|
||||
cw = appendContentWarning(cw, note.user.mandatoryCW);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -152,6 +152,9 @@ type AdminCaptchaCurrentResponse = operations['admin___captcha___current']['resp
|
|||
// @public (undocumented)
|
||||
type AdminCaptchaSaveRequest = operations['admin___captcha___save']['requestBody']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type AdminCwNoteRequest = operations['admin___cw-note']['requestBody']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type AdminCwUserRequest = operations['admin___cw-user']['requestBody']['content']['application/json'];
|
||||
|
||||
|
|
@ -1542,6 +1545,7 @@ declare namespace entities {
|
|||
AdminAvatarDecorationsUpdateRequest,
|
||||
AdminCaptchaCurrentResponse,
|
||||
AdminCaptchaSaveRequest,
|
||||
AdminCwNoteRequest,
|
||||
AdminCwUserRequest,
|
||||
AdminDeclineUserRequest,
|
||||
AdminDeleteAccountRequest,
|
||||
|
|
@ -3097,7 +3101,7 @@ type ModerationLog = {
|
|||
});
|
||||
|
||||
// @public (undocumented)
|
||||
export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "approve", "decline", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "createRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "setMandatoryCW", "setRemoteInstanceNSFW", "unsetRemoteInstanceNSFW", "suspendRemoteInstance", "unsuspendRemoteInstance", "rejectRemoteInstanceReports", "acceptRemoteInstanceReports", "updateRemoteInstanceNote", "markSensitiveDriveFile", "unmarkSensitiveDriveFile", "resolveAbuseReport", "forwardAbuseReport", "updateAbuseReportNote", "createInvitation", "createAd", "updateAd", "deleteAd", "createAvatarDecoration", "updateAvatarDecoration", "deleteAvatarDecoration", "unsetUserAvatar", "unsetUserBanner", "createSystemWebhook", "updateSystemWebhook", "deleteSystemWebhook", "createAbuseReportNotificationRecipient", "updateAbuseReportNotificationRecipient", "deleteAbuseReportNotificationRecipient", "deleteAccount", "deletePage", "deleteFlash", "deleteGalleryPost", "deleteChatRoom"];
|
||||
export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "approve", "decline", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "createRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "setMandatoryCW", "setMandatoryCWForNote", "setRemoteInstanceNSFW", "unsetRemoteInstanceNSFW", "suspendRemoteInstance", "unsuspendRemoteInstance", "rejectRemoteInstanceReports", "acceptRemoteInstanceReports", "updateRemoteInstanceNote", "markSensitiveDriveFile", "unmarkSensitiveDriveFile", "resolveAbuseReport", "forwardAbuseReport", "updateAbuseReportNote", "createInvitation", "createAd", "updateAd", "deleteAd", "createAvatarDecoration", "updateAvatarDecoration", "deleteAvatarDecoration", "unsetUserAvatar", "unsetUserBanner", "createSystemWebhook", "updateSystemWebhook", "deleteSystemWebhook", "createAbuseReportNotificationRecipient", "updateAbuseReportNotificationRecipient", "deleteAbuseReportNotificationRecipient", "deleteAccount", "deletePage", "deleteFlash", "deleteGalleryPost", "deleteChatRoom"];
|
||||
|
||||
// @public (undocumented)
|
||||
type MuteCreateRequest = operations['mute___create']['requestBody']['content']['application/json'];
|
||||
|
|
@ -3407,7 +3411,7 @@ type PartialRolePolicyOverride = Partial<{
|
|||
}>;
|
||||
|
||||
// @public (undocumented)
|
||||
export const permissions: readonly ["read:account", "write:account", "read:blocks", "write:blocks", "read:drive", "write:drive", "read:favorites", "write:favorites", "read:following", "write:following", "read:messaging", "write:messaging", "read:mutes", "write:mutes", "write:notes", "read:notes-schedule", "write:notes-schedule", "read:notifications", "write:notifications", "read:reactions", "write:reactions", "write:votes", "read:pages", "write:pages", "write:page-likes", "read:page-likes", "read:user-groups", "write:user-groups", "read:channels", "write:channels", "read:gallery", "write:gallery", "read:gallery-likes", "write:gallery-likes", "read:flash", "write:flash", "read:flash-likes", "write:flash-likes", "read:admin:abuse-user-reports", "write:admin:delete-account", "write:admin:delete-all-files-of-a-user", "read:admin:index-stats", "read:admin:table-stats", "read:admin:user-ips", "read:admin:meta", "write:admin:reset-password", "write:admin:resolve-abuse-user-report", "write:admin:send-email", "read:admin:server-info", "read:admin:show-moderation-log", "read:admin:show-user", "write:admin:suspend-user", "write:admin:approve-user", "write:admin:decline-user", "write:admin:nsfw-user", "write:admin:unnsfw-user", "write:admin:cw-user", "write:admin:silence-user", "write:admin:unsilence-user", "write:admin:unset-user-avatar", "write:admin:unset-user-banner", "write:admin:unsuspend-user", "write:admin:reject-quotes", "write:admin:meta", "write:admin:user-note", "write:admin:roles", "read:admin:roles", "write:admin:relays", "read:admin:relays", "write:admin:invite-codes", "read:admin:invite-codes", "write:admin:announcements", "read:admin:announcements", "write:admin:avatar-decorations", "read:admin:avatar-decorations", "write:admin:federation", "write:admin:account", "read:admin:account", "write:admin:emoji", "read:admin:emoji", "write:admin:queue", "read:admin:queue", "write:admin:promo", "write:admin:drive", "read:admin:drive", "write:admin:ad", "read:admin:ad", "write:invite-codes", "read:invite-codes", "write:clip-favorite", "read:clip-favorite", "read:federation", "write:report-abuse", "write:chat", "read:chat"];
|
||||
export const permissions: readonly ["read:account", "write:account", "read:blocks", "write:blocks", "read:drive", "write:drive", "read:favorites", "write:favorites", "read:following", "write:following", "read:messaging", "write:messaging", "read:mutes", "write:mutes", "write:notes", "read:notes-schedule", "write:notes-schedule", "read:notifications", "write:notifications", "read:reactions", "write:reactions", "write:votes", "read:pages", "write:pages", "write:page-likes", "read:page-likes", "read:user-groups", "write:user-groups", "read:channels", "write:channels", "read:gallery", "write:gallery", "read:gallery-likes", "write:gallery-likes", "read:flash", "write:flash", "read:flash-likes", "write:flash-likes", "read:admin:abuse-user-reports", "write:admin:delete-account", "write:admin:delete-all-files-of-a-user", "read:admin:index-stats", "read:admin:table-stats", "read:admin:user-ips", "read:admin:meta", "write:admin:reset-password", "write:admin:resolve-abuse-user-report", "write:admin:send-email", "read:admin:server-info", "read:admin:show-moderation-log", "read:admin:show-user", "write:admin:suspend-user", "write:admin:approve-user", "write:admin:decline-user", "write:admin:nsfw-user", "write:admin:unnsfw-user", "write:admin:cw-user", "write:admin:cw-note", "write:admin:silence-user", "write:admin:unsilence-user", "write:admin:unset-user-avatar", "write:admin:unset-user-banner", "write:admin:unsuspend-user", "write:admin:reject-quotes", "write:admin:meta", "write:admin:user-note", "write:admin:roles", "read:admin:roles", "write:admin:relays", "read:admin:relays", "write:admin:invite-codes", "read:admin:invite-codes", "write:admin:announcements", "read:admin:announcements", "write:admin:avatar-decorations", "read:admin:avatar-decorations", "write:admin:federation", "write:admin:account", "read:admin:account", "write:admin:emoji", "read:admin:emoji", "write:admin:queue", "read:admin:queue", "write:admin:promo", "write:admin:drive", "read:admin:drive", "write:admin:ad", "read:admin:ad", "write:invite-codes", "read:invite-codes", "write:clip-favorite", "read:clip-favorite", "read:federation", "write:report-abuse", "write:chat", "read:chat"];
|
||||
|
||||
// @public (undocumented)
|
||||
type PingResponse = operations['ping']['responses']['200']['content']['application/json'];
|
||||
|
|
|
|||
|
|
@ -272,6 +272,17 @@ declare module '../api.js' {
|
|||
credential?: string | null,
|
||||
): Promise<SwitchCaseResponseType<E, P>>;
|
||||
|
||||
/**
|
||||
* No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *write:admin:cw-note*
|
||||
*/
|
||||
request<E extends 'admin/cw-note', P extends Endpoints[E]['req']>(
|
||||
endpoint: E,
|
||||
params: P,
|
||||
credential?: string | null,
|
||||
): Promise<SwitchCaseResponseType<E, P>>;
|
||||
|
||||
/**
|
||||
* No description provided.
|
||||
*
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@ import type {
|
|||
AdminAvatarDecorationsUpdateRequest,
|
||||
AdminCaptchaCurrentResponse,
|
||||
AdminCaptchaSaveRequest,
|
||||
AdminCwNoteRequest,
|
||||
AdminCwUserRequest,
|
||||
AdminDeclineUserRequest,
|
||||
AdminDeleteAccountRequest,
|
||||
|
|
@ -689,6 +690,7 @@ export type Endpoints = {
|
|||
'admin/avatar-decorations/update': { req: AdminAvatarDecorationsUpdateRequest; res: EmptyResponse };
|
||||
'admin/captcha/current': { req: EmptyRequest; res: AdminCaptchaCurrentResponse };
|
||||
'admin/captcha/save': { req: AdminCaptchaSaveRequest; res: EmptyResponse };
|
||||
'admin/cw-note': { req: AdminCwNoteRequest; res: EmptyResponse };
|
||||
'admin/cw-user': { req: AdminCwUserRequest; res: EmptyResponse };
|
||||
'admin/decline-user': { req: AdminDeclineUserRequest; res: EmptyResponse };
|
||||
'admin/delete-account': { req: AdminDeleteAccountRequest; res: EmptyResponse };
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@ export type AdminAvatarDecorationsListResponse = operations['admin___avatar-deco
|
|||
export type AdminAvatarDecorationsUpdateRequest = operations['admin___avatar-decorations___update']['requestBody']['content']['application/json'];
|
||||
export type AdminCaptchaCurrentResponse = operations['admin___captcha___current']['responses']['200']['content']['application/json'];
|
||||
export type AdminCaptchaSaveRequest = operations['admin___captcha___save']['requestBody']['content']['application/json'];
|
||||
export type AdminCwNoteRequest = operations['admin___cw-note']['requestBody']['content']['application/json'];
|
||||
export type AdminCwUserRequest = operations['admin___cw-user']['requestBody']['content']['application/json'];
|
||||
export type AdminDeclineUserRequest = operations['admin___decline-user']['requestBody']['content']['application/json'];
|
||||
export type AdminDeleteAccountRequest = operations['admin___delete-account']['requestBody']['content']['application/json'];
|
||||
|
|
|
|||
|
|
@ -233,6 +233,15 @@ export type paths = {
|
|||
*/
|
||||
post: operations['admin___captcha___save'];
|
||||
};
|
||||
'/admin/cw-note': {
|
||||
/**
|
||||
* admin/cw-note
|
||||
* @description No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *write:admin:cw-note*
|
||||
*/
|
||||
post: operations['admin___cw-note'];
|
||||
};
|
||||
'/admin/cw-user': {
|
||||
/**
|
||||
* admin/cw-user
|
||||
|
|
@ -4671,6 +4680,7 @@ export type components = {
|
|||
deletedAt?: string | null;
|
||||
text: string | null;
|
||||
cw?: string | null;
|
||||
mandatoryCW?: string | null;
|
||||
/** Format: id */
|
||||
userId: string;
|
||||
user: components['schemas']['UserLite'];
|
||||
|
|
@ -7389,6 +7399,59 @@ export type operations = {
|
|||
};
|
||||
};
|
||||
};
|
||||
/**
|
||||
* admin/cw-note
|
||||
* @description No description provided.
|
||||
*
|
||||
* **Credential required**: *Yes* / **Permission**: *write:admin:cw-note*
|
||||
*/
|
||||
'admin___cw-note': {
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
/** Format: misskey:id */
|
||||
noteId: string;
|
||||
cw: string | null;
|
||||
};
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
/** @description OK (without any results) */
|
||||
204: {
|
||||
content: never;
|
||||
};
|
||||
/** @description Client error */
|
||||
400: {
|
||||
content: {
|
||||
'application/json': components['schemas']['Error'];
|
||||
};
|
||||
};
|
||||
/** @description Authentication error */
|
||||
401: {
|
||||
content: {
|
||||
'application/json': components['schemas']['Error'];
|
||||
};
|
||||
};
|
||||
/** @description Forbidden error */
|
||||
403: {
|
||||
content: {
|
||||
'application/json': components['schemas']['Error'];
|
||||
};
|
||||
};
|
||||
/** @description I'm Ai */
|
||||
418: {
|
||||
content: {
|
||||
'application/json': components['schemas']['Error'];
|
||||
};
|
||||
};
|
||||
/** @description Internal server error */
|
||||
500: {
|
||||
content: {
|
||||
'application/json': components['schemas']['Error'];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
/**
|
||||
* admin/cw-user
|
||||
* @description No description provided.
|
||||
|
|
|
|||
|
|
@ -81,6 +81,7 @@ export const permissions = [
|
|||
'write:admin:nsfw-user',
|
||||
'write:admin:unnsfw-user',
|
||||
'write:admin:cw-user',
|
||||
'write:admin:cw-note',
|
||||
'write:admin:silence-user',
|
||||
'write:admin:unsilence-user',
|
||||
'write:admin:unset-user-avatar',
|
||||
|
|
@ -148,6 +149,7 @@ export const moderationLogTypes = [
|
|||
'deleteUserAnnouncement',
|
||||
'resetPassword',
|
||||
'setMandatoryCW',
|
||||
'setMandatoryCWForNote',
|
||||
'setRemoteInstanceNSFW',
|
||||
'unsetRemoteInstanceNSFW',
|
||||
'suspendRemoteInstance',
|
||||
|
|
@ -343,6 +345,14 @@ export type ModerationLogPayloads = {
|
|||
userUsername: string;
|
||||
userHost: string | null;
|
||||
};
|
||||
setMandatoryCWForNote: {
|
||||
newCW: string | null;
|
||||
oldCW: string | null;
|
||||
noteId: string;
|
||||
noteUserId: string;
|
||||
noteUserUsername: string;
|
||||
noteUserHost: string | null;
|
||||
};
|
||||
setRemoteInstanceNSFW: {
|
||||
id: string;
|
||||
host: string;
|
||||
|
|
|
|||
|
|
@ -133,6 +133,9 @@ export type ModerationLog = {
|
|||
} | {
|
||||
type: 'setMandatoryCW';
|
||||
info: ModerationLogPayloads['setMandatoryCW'];
|
||||
} | {
|
||||
type: 'setMandatoryCWForNote';
|
||||
info: ModerationLogPayloads['setMandatoryCWForNote'];
|
||||
} | {
|
||||
type: 'setRemoteInstanceNSFW';
|
||||
info: ModerationLogPayloads['setRemoteInstanceNSFW'];
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ unmuteNote: "Unmute note"
|
|||
userSaysSomethingInMutedNote: "{name} said something in a muted post"
|
||||
userSaysSomethingInMutedThread: "{name} said something in a muted thread"
|
||||
userIsFlaggedAs: "{name} is flagged: \"{cw}\""
|
||||
noteIsFlaggedAs: "Note is flagged: \"{cw}\""
|
||||
markAsNSFW: "Mark all media from user as NSFW"
|
||||
markInstanceAsNSFW: "Mark as NSFW"
|
||||
nsfwConfirm: "Are you sure that you want to mark all media from this account as NSFW?"
|
||||
|
|
@ -344,6 +345,7 @@ _moderationLogTypes:
|
|||
approve: "Approved"
|
||||
decline: "Declined"
|
||||
setMandatoryCW: "Set content warning for user"
|
||||
setMandatoryCWForNote: "Set content warning for note"
|
||||
setRemoteInstanceNSFW: "Set remote instance as NSFW"
|
||||
unsetRemoteInstanceNSFW: "Unset remote instance as NSFW"
|
||||
rejectRemoteInstanceReports: "Rejected reports from remote instance"
|
||||
|
|
@ -504,6 +506,7 @@ _permissions:
|
|||
"write:admin:nsfw-user": "Mark users as NSFW"
|
||||
"write:admin:unnsfw-user": "Mark users as not NSFW"
|
||||
"write:admin:cw-user": "Apply mandatory CW on users"
|
||||
"write:admin:cw-note": "Apply mandatory CW on notes"
|
||||
"write:admin:silence-user": "Silence users"
|
||||
"write:admin:unsilence-user": "Un-silence users"
|
||||
"write:admin:reject-quotes": "Allow/Prohibit quote posts from a user"
|
||||
|
|
@ -542,7 +545,9 @@ _noteSearch:
|
|||
id: "ID"
|
||||
|
||||
mandatoryCW: "Force content warning"
|
||||
mandatoryCWDescription: "Applies a content warning to all posts created by this user. If the post already has a CW, then this is appended to the end."
|
||||
mandatoryCWDescription: "Applies a content warning to all posts created by this user. The forced warnings will appear like a word mute to distinguish them from the author's own content warnings."
|
||||
mandatoryCWForNote: "Force content warning"
|
||||
mandatoryCWForNoteDescription: "Applies an additional content warning to this post. The new warning will appear like a word mute to distinguish it from the author's own content warning."
|
||||
fetchLinkedNote: "Fetch linked note"
|
||||
showTranslationButtonInNoteFooter: "Add \"Translate\" to note action menu"
|
||||
translationFailed: "Failed to translate note. Please try again later or contact an administrator for assistance."
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue