feat: note.isDeletable
This commit is contained in:
parent
9927231add
commit
d485432661
@ -1340,7 +1340,7 @@ endingCreditMembers: "Users to display in the closing credits"
|
||||
endingCreditMembersDescription: "IDs of users to display on the server information page, one per line."
|
||||
emailAddressLogin: "Login with email address"
|
||||
usernameLogin: "Login with username"
|
||||
|
||||
unarchive: "Unarchive"
|
||||
_bubbleGame:
|
||||
howToPlay: "How to play"
|
||||
hold: "Hold"
|
||||
|
4
locales/index.d.ts
vendored
4
locales/index.d.ts
vendored
@ -5528,6 +5528,10 @@ export interface Locale extends ILocale {
|
||||
* ユーザー名でログイン
|
||||
*/
|
||||
"usernameLogin": string;
|
||||
/**
|
||||
* アーカイブ解除
|
||||
*/
|
||||
"unarchive": string;
|
||||
"_bubbleGame": {
|
||||
/**
|
||||
* 遊び方
|
||||
|
@ -1375,6 +1375,7 @@ endingCreditMembers: "スタッフロールに表示するユーザー"
|
||||
endingCreditMembersDescription: "サーバー情報ページに表示するユーザーのIDを一行に一つずつ書きます。"
|
||||
emailAddressLogin: "メールアドレスでログイン"
|
||||
usernameLogin: "ユーザー名でログイン"
|
||||
unarchive: "アーカイブ解除"
|
||||
|
||||
_bubbleGame:
|
||||
howToPlay: "遊び方"
|
||||
|
@ -1296,8 +1296,8 @@ passkeyVerificationSucceededButPasswordlessLoginDisabled: "패스키를 검증
|
||||
messageToFollower: "팔로워에 보낼 메시지"
|
||||
target: "대상"
|
||||
testCaptchaWarning: "CAPTCHA를 테스트하기 위한 기능입니다. <strong>실제 환경에서는 사용하지 마세요.</strong>"
|
||||
prohibitedWordsForNameOfUser: "금지 단어 (사용자 이름)"
|
||||
prohibitedWordsForNameOfUserDescription: "이 목록에 포함되는 키워드가 사용자 이름에 있는 경우, 일반 사용자는 이름을 바꿀 수 없습니다. 모더레이터 권한을 가진 사용자는 제한 대상에서 제외됩니다."
|
||||
prohibitedWordsForNameOfUser: "금지 단어 (닉네임)"
|
||||
prohibitedWordsForNameOfUserDescription: "이 목록에 포함되는 키워드가 닉네임에 있는 경우, 일반 유저는 이름을 바꿀 수 없습니다. 중재자는 제한 대상에서 제외됩니다."
|
||||
yourNameContainsProhibitedWords: "바꾸려는 이름에 금지된 키워드가 포함되어 있습니다."
|
||||
yourNameContainsProhibitedWordsDescription: "이름에 금지된 키워드가 있습니다. 이름을 사용해야 하는 경우, 서버 관리자에 문의하세요."
|
||||
lockdown: "잠금"
|
||||
@ -1346,7 +1346,7 @@ normalizeConfirm: "정상화 이후에는 계정을 되돌릴 수 없게 됩니
|
||||
normalizeDescription: "정상화는 유저의 일괄적인 데이터 말소 및 계정 정지를 위한 기능으로, 계정 삭제와 비슷한 효과를 가지며, 실행 후에는 모든 신고가 닫히게 됩니다. 기능을 실행하고 나면 되돌릴 수 없는 점을 유의하시기 바랍니다."
|
||||
useNormalization: "정상화 메뉴를 표시하기"
|
||||
gtagConsentCustomize: "데이터 수집 및 개인정보 설정"
|
||||
gtagConsentCustomizeDescription: "{host}에서 수집하는 데이터 범위를 사용자 지정할 수 있습니다.\n다만, 인증 기능, 부정 행위 방지, 기타 사용자 보호 등 보안과 관련된 정보 수집은 비활성화할 수 없습니다."
|
||||
gtagConsentCustomizeDescription: "{host}에서 수집하는 데이터 범위를 설정할 수 있습니다.\n다만, 인증 기능, 부정 행위 방지, 기타 유저 보호 등 보안과 관련된 정보 수집은 비활성화할 수 없습니다."
|
||||
gtagConsentAnalytics: "통계 정보 수집"
|
||||
gtagConsentAnalyticsDescription: "사이트 체류 시간 등 분석 관련 정보 저장(쿠키 등)을 활성화합니다."
|
||||
gtagConsentFunctionality: "기능 및 설정 사용 정보 수집"
|
||||
@ -1354,7 +1354,7 @@ gtagConsentFunctionalityDescription: "언어 설정 등 웹사이트나 앱의
|
||||
gtagConsentPersonalization: "개인 맞춤형 정보 수집"
|
||||
gtagConsentPersonalizationDescription: "추천 게시물 등 개인화 관련 정보 저장을 활성화합니다."
|
||||
helpUsImproveUserExperience: "Misskey의 미래를 위해,\n데이터 수집에 협조해 주세요!"
|
||||
pleaseConsentToTracking: "{host}는 [개인정보 처리방침]({privacyPolicyUrl})에 따라 서비스 제공, 운영, 사용자 경험 향상을 위해 사용 중인 IP 주소, 이용 현황, 디바이스 정보 등 개인 정보를 포함할 수 있는 정보를 수집할 수 있습니다.\n\n수집된 데이터는 향후 기능 개발, 운영 방침 결정, 서비스 개선점 파악에 활용됩니다."
|
||||
pleaseConsentToTracking: "{host}는 [개인정보 처리방침]({privacyPolicyUrl})에 따라 서비스 제공, 운영, 유저 경험 향상을 위해 사용 중인 IP 주소, 이용 현황, 디바이스 정보 등 개인 정보를 포함할 수 있는 정보를 수집할 수 있습니다.\n\n수집된 데이터는 향후 기능 개발, 운영 방침 결정, 서비스 개선점 파악에 활용됩니다."
|
||||
consentEssential: "필수 항목만 허용"
|
||||
consentAll: "모두 허용"
|
||||
consentSelected: "선택한 항목만 허용"
|
||||
@ -1364,8 +1364,8 @@ unmuteNotification: "알림 뮤트를 해제하기"
|
||||
endingCreditMembers: "엔딩 크레딧에 표시할 유저"
|
||||
endingCreditMembersDescription: "서버 정보 페이지에 표시할 유저의 ID를 한 줄에 하나씩 적습니다."
|
||||
emailAddressLogin: "이메일 주소로 로그인"
|
||||
usernameLogin: "사용자명으로 로그인"
|
||||
|
||||
usernameLogin: "유저명으로 로그인"
|
||||
unarchive: "아카이브 해제"
|
||||
_bubbleGame:
|
||||
howToPlay: "설명"
|
||||
hold: "홀드"
|
||||
@ -1412,7 +1412,7 @@ _announcement:
|
||||
needConfirmationToRead: "읽음으로 표시하기 전에 확인하기"
|
||||
needConfirmationToReadDescription: "활성화하면 이 공지사항을 읽음으로 표시하기 전에 확인 알림창을 띄웁니다."
|
||||
needEnrollmentTutorialToRead: "읽음으로 표시하기 전에 튜토리얼 진행하기"
|
||||
needEnrollmentTutorialToReadDescription: "활성화하면 이 공지사항을 읽음으로 표시하기 전에 사용자에게 튜토리얼을 진행하도록 요구합니다."
|
||||
needEnrollmentTutorialToReadDescription: "활성화하면 이 공지사항을 읽음으로 표시하기 전에 유저가 튜토리얼을 진행하도록 요구합니다."
|
||||
end: "공지에서 내리기"
|
||||
tooManyActiveAnnouncementDescription: "공지사항이 너무 많을 경우, 유저 경험에 영향을 끼칠 가능성이 있습니다. 오래된 공지사항은 아카이브하시는 것을 권장드립니다."
|
||||
readConfirmTitle: "읽음으로 표시합니까?"
|
||||
@ -2835,6 +2835,6 @@ _hideSensitiveInformation:
|
||||
_selfXssPrevention:
|
||||
warning: "경고"
|
||||
title: "「이 화면에 무언가를 붙여넣으라는 메시지」는 모두 *사기*입니다."
|
||||
description1: "여기에 무언가를 붙여넣으면, 악의를 가진 사용자에게 계정을 탈취당하거나 개인정보를 훔쳐갈 수 있습니다."
|
||||
description1: "여기에 무언가를 붙여넣으면, 악의를 가진 다른 유저에게 계정을 탈취당하거나 개인정보를 훔쳐갈 수 있습니다."
|
||||
description2: "붙여넣으려는 것이 무엇인지 정확히 이해하지 못하면, %c지금 작업을 중단하고 이 창을 닫으십시오."
|
||||
description3: "자세한 내용은 여기를 확인하십시오. {link}"
|
||||
|
@ -1,3 +1,8 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class NotificationMuting1730885274028 {
|
||||
name = 'NotificationMuting1730885274028'
|
||||
|
||||
|
16
packages/backend/migration/1736233575620-NoteIsDeletable.js
Normal file
16
packages/backend/migration/1736233575620-NoteIsDeletable.js
Normal file
@ -0,0 +1,16 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
export class NoteIsDeletable1736233575620 {
|
||||
name = 'NoteIsDeletable1736233575620'
|
||||
|
||||
async up(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "note" ADD "isDeletable" boolean NOT NULL DEFAULT true`);
|
||||
}
|
||||
|
||||
async down(queryRunner) {
|
||||
await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "isDeletable"`);
|
||||
}
|
||||
}
|
@ -549,11 +549,15 @@ export class ApInboxService {
|
||||
const note = await this.apDbResolverService.getNoteFromApId(uri);
|
||||
|
||||
if (note == null) {
|
||||
return 'message not found';
|
||||
return 'no such note';
|
||||
}
|
||||
|
||||
if (note.userId !== actor.id) {
|
||||
return '投稿を削除しようとしているユーザーは投稿の作成者ではありません';
|
||||
return 'actor and note author is not same';
|
||||
}
|
||||
|
||||
if (!note.isDeletable) {
|
||||
return 'note is not deletable'
|
||||
}
|
||||
|
||||
await this.noteDeleteService.delete(actor, note);
|
||||
|
@ -43,11 +43,11 @@ export class AutoNoteRemovalProcessorService {
|
||||
|
||||
@bindThis
|
||||
public async process(job: Bull.Job<Record<string, unknown>>): Promise<void> {
|
||||
this.logger.info('Checking notes that to remove automatically...');
|
||||
this.logger.info('Checking users that enabled note auto-removal');
|
||||
this.logger.info('Checking notes to remove automatically...');
|
||||
this.logger.info('Checking users that enabled note auto removal');
|
||||
const users = await this.usersRepository.find({ where: { autoRemoval: true } });
|
||||
if (users.length < 1) {
|
||||
this.logger.info('Does not have any user that enabled autoRemoval');
|
||||
this.logger.info('No user that enabled note auto removal');
|
||||
return;
|
||||
}
|
||||
const now = Date.now();
|
||||
@ -81,6 +81,7 @@ export class AutoNoteRemovalProcessorService {
|
||||
const notes = await this.notesRepository.find({
|
||||
where: {
|
||||
userId: user.id,
|
||||
isDeletable: true,
|
||||
...(cursor ? {
|
||||
id: And(Not(In(condition)), MoreThan(cursor)),
|
||||
} : {
|
||||
@ -110,9 +111,9 @@ export class AutoNoteRemovalProcessorService {
|
||||
}
|
||||
|
||||
await job.updateProgress(100);
|
||||
this.logger.succ('All of auto-removable notes deleted');
|
||||
this.logger.succ('All auto removable notes deleted');
|
||||
}
|
||||
|
||||
this.logger.succ('All notes to auto-remove has beed removed.');
|
||||
this.logger.succ('Note auto removal job completed.');
|
||||
}
|
||||
}
|
||||
|
@ -288,6 +288,7 @@ import * as ep___renoteMute_delete from './endpoints/renote-mute/delete.js';
|
||||
import * as ep___renoteMute_list from './endpoints/renote-mute/list.js';
|
||||
import * as ep___my_apps from './endpoints/my/apps.js';
|
||||
import * as ep___notes from './endpoints/notes.js';
|
||||
import * as ep___notes_archive from './endpoints/notes/archive.js';
|
||||
import * as ep___notes_children from './endpoints/notes/children.js';
|
||||
import * as ep___notes_clips from './endpoints/notes/clips.js';
|
||||
import * as ep___notes_conversation from './endpoints/notes/conversation.js';
|
||||
@ -315,6 +316,7 @@ import * as ep___notes_threadMuting_create from './endpoints/notes/thread-muting
|
||||
import * as ep___notes_threadMuting_delete from './endpoints/notes/thread-muting/delete.js';
|
||||
import * as ep___notes_timeline from './endpoints/notes/timeline.js';
|
||||
import * as ep___notes_translate from './endpoints/notes/translate.js';
|
||||
import * as ep___notes_unarchive from './endpoints/notes/unarchive.js';
|
||||
import * as ep___notes_unrenote from './endpoints/notes/unrenote.js';
|
||||
import * as ep___notes_userListTimeline from './endpoints/notes/user-list-timeline.js';
|
||||
import * as ep___notifications_create from './endpoints/notifications/create.js';
|
||||
@ -690,6 +692,7 @@ const $renoteMute_delete: Provider = { provide: 'ep:renote-mute/delete', useClas
|
||||
const $renoteMute_list: Provider = { provide: 'ep:renote-mute/list', useClass: ep___renoteMute_list.default };
|
||||
const $my_apps: Provider = { provide: 'ep:my/apps', useClass: ep___my_apps.default };
|
||||
const $notes: Provider = { provide: 'ep:notes', useClass: ep___notes.default };
|
||||
const $notes_archive: Provider = { provide: 'ep:notes/archive', useClass: ep___notes_archive.default };
|
||||
const $notes_children: Provider = { provide: 'ep:notes/children', useClass: ep___notes_children.default };
|
||||
const $notes_clips: Provider = { provide: 'ep:notes/clips', useClass: ep___notes_clips.default };
|
||||
const $notes_conversation: Provider = { provide: 'ep:notes/conversation', useClass: ep___notes_conversation.default };
|
||||
@ -717,6 +720,7 @@ const $notes_threadMuting_create: Provider = { provide: 'ep:notes/thread-muting/
|
||||
const $notes_threadMuting_delete: Provider = { provide: 'ep:notes/thread-muting/delete', useClass: ep___notes_threadMuting_delete.default };
|
||||
const $notes_timeline: Provider = { provide: 'ep:notes/timeline', useClass: ep___notes_timeline.default };
|
||||
const $notes_translate: Provider = { provide: 'ep:notes/translate', useClass: ep___notes_translate.default };
|
||||
const $notes_unarchive: Provider = { provide: 'ep:notes/unarchive', useClass: ep___notes_unarchive.default };
|
||||
const $notes_unrenote: Provider = { provide: 'ep:notes/unrenote', useClass: ep___notes_unrenote.default };
|
||||
const $notes_userListTimeline: Provider = { provide: 'ep:notes/user-list-timeline', useClass: ep___notes_userListTimeline.default };
|
||||
const $notifications_create: Provider = { provide: 'ep:notifications/create', useClass: ep___notifications_create.default };
|
||||
@ -1096,6 +1100,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
||||
$renoteMute_list,
|
||||
$my_apps,
|
||||
$notes,
|
||||
$notes_archive,
|
||||
$notes_children,
|
||||
$notes_clips,
|
||||
$notes_conversation,
|
||||
@ -1123,6 +1128,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
||||
$notes_threadMuting_delete,
|
||||
$notes_timeline,
|
||||
$notes_translate,
|
||||
$notes_unarchive,
|
||||
$notes_unrenote,
|
||||
$notes_userListTimeline,
|
||||
$notifications_create,
|
||||
@ -1496,6 +1502,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
||||
$renoteMute_list,
|
||||
$my_apps,
|
||||
$notes,
|
||||
$notes_archive,
|
||||
$notes_children,
|
||||
$notes_clips,
|
||||
$notes_conversation,
|
||||
@ -1523,6 +1530,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
|
||||
$notes_threadMuting_delete,
|
||||
$notes_timeline,
|
||||
$notes_translate,
|
||||
$notes_unarchive,
|
||||
$notes_unrenote,
|
||||
$notes_userListTimeline,
|
||||
$notifications_create,
|
||||
|
@ -288,6 +288,7 @@ import * as ep___renoteMute_delete from './endpoints/renote-mute/delete.js';
|
||||
import * as ep___renoteMute_list from './endpoints/renote-mute/list.js';
|
||||
import * as ep___my_apps from './endpoints/my/apps.js';
|
||||
import * as ep___notes from './endpoints/notes.js';
|
||||
import * as ep___notes_archive from './endpoints/notes/archive.js';
|
||||
import * as ep___notes_children from './endpoints/notes/children.js';
|
||||
import * as ep___notes_clips from './endpoints/notes/clips.js';
|
||||
import * as ep___notes_conversation from './endpoints/notes/conversation.js';
|
||||
@ -315,6 +316,7 @@ import * as ep___notes_threadMuting_create from './endpoints/notes/thread-muting
|
||||
import * as ep___notes_threadMuting_delete from './endpoints/notes/thread-muting/delete.js';
|
||||
import * as ep___notes_timeline from './endpoints/notes/timeline.js';
|
||||
import * as ep___notes_translate from './endpoints/notes/translate.js';
|
||||
import * as ep___notes_unarchive from './endpoints/notes/unarchive.js';
|
||||
import * as ep___notes_unrenote from './endpoints/notes/unrenote.js';
|
||||
import * as ep___notes_userListTimeline from './endpoints/notes/user-list-timeline.js';
|
||||
import * as ep___notifications_create from './endpoints/notifications/create.js';
|
||||
@ -688,6 +690,7 @@ const eps = [
|
||||
['renote-mute/list', ep___renoteMute_list],
|
||||
['my/apps', ep___my_apps],
|
||||
['notes', ep___notes],
|
||||
['notes/archive', ep___notes_archive],
|
||||
['notes/children', ep___notes_children],
|
||||
['notes/clips', ep___notes_clips],
|
||||
['notes/conversation', ep___notes_conversation],
|
||||
@ -715,6 +718,7 @@ const eps = [
|
||||
['notes/thread-muting/delete', ep___notes_threadMuting_delete],
|
||||
['notes/timeline', ep___notes_timeline],
|
||||
['notes/translate', ep___notes_translate],
|
||||
['notes/unarchive', ep___notes_unarchive],
|
||||
['notes/unrenote', ep___notes_unrenote],
|
||||
['notes/user-list-timeline', ep___notes_userListTimeline],
|
||||
['notifications/create', ep___notifications_create],
|
||||
|
65
packages/backend/src/server/api/endpoints/notes/archive.ts
Normal file
65
packages/backend/src/server/api/endpoints/notes/archive.ts
Normal file
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import ms from 'ms';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { NotesRepository } from '@/models/_.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { GetterService } from '@/server/api/GetterService.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['notes'],
|
||||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
secure: true,
|
||||
|
||||
kind: 'write:notes',
|
||||
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
max: 300,
|
||||
minInterval: ms('1sec'),
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchNote: {
|
||||
message: 'No such note.',
|
||||
code: 'NO_SUCH_NOTE',
|
||||
id: '490be23f-8c1f-4796-819f-94cb4f9d1630',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
noteId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['noteId'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
@Inject(DI.notesRepository)
|
||||
private notesRepository: NotesRepository,
|
||||
|
||||
private getterService: GetterService,
|
||||
private roleService: RoleService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const note = await this.getterService.getNote(ps.noteId).catch(err => {
|
||||
if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
|
||||
throw err;
|
||||
});
|
||||
|
||||
await this.notesRepository.update(note.id, { isDeletable: false });
|
||||
});
|
||||
}
|
||||
}
|
@ -34,6 +34,12 @@ export const meta = {
|
||||
id: '490be23f-8c1f-4796-819f-94cb4f9d1630',
|
||||
},
|
||||
|
||||
deletionNotAllowed: {
|
||||
message: 'Note is not allowed to delete.',
|
||||
code: 'DELETION_NOT_ALLOWED',
|
||||
id: 'c6e6ad27-3ec2-49a7-bb55-09fb70f6e2fd',
|
||||
},
|
||||
|
||||
accessDenied: {
|
||||
message: 'Access denied.',
|
||||
code: 'ACCESS_DENIED',
|
||||
@ -66,8 +72,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
|
||||
throw err;
|
||||
});
|
||||
|
||||
if (note.id === 'a2h9mk6pav') {
|
||||
throw new ApiError(meta.errors.accessDenied);
|
||||
if (note.isDeletable === false) {
|
||||
throw new ApiError(meta.errors.deletionNotAllowed);
|
||||
}
|
||||
|
||||
if (!await this.roleService.isModerator(me) && (note.userId !== me.id)) {
|
||||
|
65
packages/backend/src/server/api/endpoints/notes/unarchive.ts
Normal file
65
packages/backend/src/server/api/endpoints/notes/unarchive.ts
Normal file
@ -0,0 +1,65 @@
|
||||
/*
|
||||
* SPDX-FileCopyrightText: syuilo and misskey-project
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import ms from 'ms';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import type { NotesRepository } from '@/models/_.js';
|
||||
import { Endpoint } from '@/server/api/endpoint-base.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import { GetterService } from '@/server/api/GetterService.js';
|
||||
import { RoleService } from '@/core/RoleService.js';
|
||||
import { ApiError } from '../../error.js';
|
||||
|
||||
export const meta = {
|
||||
tags: ['notes'],
|
||||
|
||||
requireCredential: true,
|
||||
requireModerator: true,
|
||||
secure: true,
|
||||
|
||||
kind: 'write:notes',
|
||||
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
max: 300,
|
||||
minInterval: ms('1sec'),
|
||||
},
|
||||
|
||||
errors: {
|
||||
noSuchNote: {
|
||||
message: 'No such note.',
|
||||
code: 'NO_SUCH_NOTE',
|
||||
id: '490be23f-8c1f-4796-819f-94cb4f9d1630',
|
||||
},
|
||||
},
|
||||
} as const;
|
||||
|
||||
export const paramDef = {
|
||||
type: 'object',
|
||||
properties: {
|
||||
noteId: { type: 'string', format: 'misskey:id' },
|
||||
},
|
||||
required: ['noteId'],
|
||||
} as const;
|
||||
|
||||
@Injectable()
|
||||
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
|
||||
constructor(
|
||||
@Inject(DI.notesRepository)
|
||||
private notesRepository: NotesRepository,
|
||||
|
||||
private getterService: GetterService,
|
||||
private roleService: RoleService,
|
||||
) {
|
||||
super(meta, paramDef, async (ps, me) => {
|
||||
const note = await this.getterService.getNote(ps.noteId).catch(err => {
|
||||
if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote);
|
||||
throw err;
|
||||
});
|
||||
|
||||
await this.notesRepository.update(note.id, { isDeletable: true });
|
||||
});
|
||||
}
|
||||
}
|
@ -226,6 +226,12 @@ export function getNoteMenu(props: {
|
||||
});
|
||||
}
|
||||
|
||||
function toggleDeletable(archive: boolean): void {
|
||||
os.apiWithDialog(archive ? 'notes/archive' : 'notes/unarchive', {
|
||||
noteId: appearNote.id,
|
||||
});
|
||||
}
|
||||
|
||||
function copyContent(): void {
|
||||
copyToClipboard(appearNote.text);
|
||||
os.success();
|
||||
@ -355,6 +361,20 @@ export function getNoteMenu(props: {
|
||||
text: i18n.ts.muteThread,
|
||||
action: () => toggleThreadMute(true),
|
||||
}),
|
||||
...(iAmModerator ? appearNote.isDeletable ? [
|
||||
{
|
||||
icon: 'ti ti-archive',
|
||||
text: i18n.ts.archive,
|
||||
action: () => toggleDeletable(true),
|
||||
}]
|
||||
: [
|
||||
{
|
||||
icon: 'ti ti-archive-off',
|
||||
text: i18n.ts.unarchive,
|
||||
action: () => toggleDeletable(false),
|
||||
}]
|
||||
: []
|
||||
),
|
||||
appearNote.userId === $i.id ? ($i.pinnedNoteIds ?? []).includes(appearNote.id) ? {
|
||||
icon: 'ti ti-pinned-off',
|
||||
text: i18n.ts.unpin,
|
||||
@ -432,11 +452,13 @@ export function getNoteMenu(props: {
|
||||
appearNote.userId === $i.id ? {
|
||||
icon: 'ti ti-edit',
|
||||
text: i18n.ts.deleteAndEdit,
|
||||
disabled: !appearNote.isDeletable,
|
||||
action: delEdit,
|
||||
} : undefined,
|
||||
{
|
||||
icon: 'ti ti-trash',
|
||||
text: i18n.ts.delete,
|
||||
disabled: !appearNote.isDeletable,
|
||||
danger: true,
|
||||
action: del,
|
||||
}]
|
||||
|
@ -1642,6 +1642,7 @@ declare namespace entities {
|
||||
MyAppsResponse,
|
||||
NotesRequest,
|
||||
NotesResponse,
|
||||
NotesArchiveRequest,
|
||||
NotesChildrenRequest,
|
||||
NotesChildrenResponse,
|
||||
NotesClipsRequest,
|
||||
@ -1688,6 +1689,7 @@ declare namespace entities {
|
||||
NotesTimelineResponse,
|
||||
NotesTranslateRequest,
|
||||
NotesTranslateResponse,
|
||||
NotesUnarchiveRequest,
|
||||
NotesUnrenoteRequest,
|
||||
NotesUserListTimelineRequest,
|
||||
NotesUserListTimelineResponse,
|
||||
@ -2604,6 +2606,9 @@ type NoteFavorite = components['schemas']['NoteFavorite'];
|
||||
// @public (undocumented)
|
||||
type NoteReaction = components['schemas']['NoteReaction'];
|
||||
|
||||
// @public (undocumented)
|
||||
type NotesArchiveRequest = operations['notes___archive']['requestBody']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type NotesChildrenRequest = operations['notes___children']['requestBody']['content']['application/json'];
|
||||
|
||||
@ -2748,6 +2753,9 @@ type NotesTranslateRequest = operations['notes___translate']['requestBody']['con
|
||||
// @public (undocumented)
|
||||
type NotesTranslateResponse = operations['notes___translate']['responses']['200']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type NotesUnarchiveRequest = operations['notes___unarchive']['requestBody']['content']['application/json'];
|
||||
|
||||
// @public (undocumented)
|
||||
type NotesUnrenoteRequest = operations['notes___unrenote']['requestBody']['content']['application/json'];
|
||||
|
||||
|
@ -3144,6 +3144,18 @@ declare module '../api.js' {
|
||||
credential?: string | null,
|
||||
): Promise<SwitchCaseResponseType<E, P>>;
|
||||
|
||||
/**
|
||||
* No description provided.
|
||||
*
|
||||
* **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
|
||||
* **Credential required**: *Yes* / **Permission**: *write:notes*
|
||||
*/
|
||||
request<E extends 'notes/archive', P extends Endpoints[E]['req']>(
|
||||
endpoint: E,
|
||||
params: P,
|
||||
credential?: string | null,
|
||||
): Promise<SwitchCaseResponseType<E, P>>;
|
||||
|
||||
/**
|
||||
* No description provided.
|
||||
*
|
||||
@ -3441,6 +3453,18 @@ declare module '../api.js' {
|
||||
credential?: string | null,
|
||||
): Promise<SwitchCaseResponseType<E, P>>;
|
||||
|
||||
/**
|
||||
* No description provided.
|
||||
*
|
||||
* **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
|
||||
* **Credential required**: *Yes* / **Permission**: *write:notes*
|
||||
*/
|
||||
request<E extends 'notes/unarchive', P extends Endpoints[E]['req']>(
|
||||
endpoint: E,
|
||||
params: P,
|
||||
credential?: string | null,
|
||||
): Promise<SwitchCaseResponseType<E, P>>;
|
||||
|
||||
/**
|
||||
* No description provided.
|
||||
*
|
||||
|
@ -414,6 +414,7 @@ import type {
|
||||
MyAppsResponse,
|
||||
NotesRequest,
|
||||
NotesResponse,
|
||||
NotesArchiveRequest,
|
||||
NotesChildrenRequest,
|
||||
NotesChildrenResponse,
|
||||
NotesClipsRequest,
|
||||
@ -460,6 +461,7 @@ import type {
|
||||
NotesTimelineResponse,
|
||||
NotesTranslateRequest,
|
||||
NotesTranslateResponse,
|
||||
NotesUnarchiveRequest,
|
||||
NotesUnrenoteRequest,
|
||||
NotesUserListTimelineRequest,
|
||||
NotesUserListTimelineResponse,
|
||||
@ -879,6 +881,7 @@ export type Endpoints = {
|
||||
'renote-mute/list': { req: RenoteMuteListRequest; res: RenoteMuteListResponse };
|
||||
'my/apps': { req: MyAppsRequest; res: MyAppsResponse };
|
||||
'notes': { req: NotesRequest; res: NotesResponse };
|
||||
'notes/archive': { req: NotesArchiveRequest; res: EmptyResponse };
|
||||
'notes/children': { req: NotesChildrenRequest; res: NotesChildrenResponse };
|
||||
'notes/clips': { req: NotesClipsRequest; res: NotesClipsResponse };
|
||||
'notes/conversation': { req: NotesConversationRequest; res: NotesConversationResponse };
|
||||
@ -906,6 +909,7 @@ export type Endpoints = {
|
||||
'notes/thread-muting/delete': { req: NotesThreadMutingDeleteRequest; res: EmptyResponse };
|
||||
'notes/timeline': { req: NotesTimelineRequest; res: NotesTimelineResponse };
|
||||
'notes/translate': { req: NotesTranslateRequest; res: NotesTranslateResponse };
|
||||
'notes/unarchive': { req: NotesUnarchiveRequest; res: EmptyResponse };
|
||||
'notes/unrenote': { req: NotesUnrenoteRequest; res: EmptyResponse };
|
||||
'notes/user-list-timeline': { req: NotesUserListTimelineRequest; res: NotesUserListTimelineResponse };
|
||||
'notifications/create': { req: NotificationsCreateRequest; res: EmptyResponse };
|
||||
|
@ -417,6 +417,7 @@ export type MyAppsRequest = operations['my___apps']['requestBody']['content']['a
|
||||
export type MyAppsResponse = operations['my___apps']['responses']['200']['content']['application/json'];
|
||||
export type NotesRequest = operations['notes']['requestBody']['content']['application/json'];
|
||||
export type NotesResponse = operations['notes']['responses']['200']['content']['application/json'];
|
||||
export type NotesArchiveRequest = operations['notes___archive']['requestBody']['content']['application/json'];
|
||||
export type NotesChildrenRequest = operations['notes___children']['requestBody']['content']['application/json'];
|
||||
export type NotesChildrenResponse = operations['notes___children']['responses']['200']['content']['application/json'];
|
||||
export type NotesClipsRequest = operations['notes___clips']['requestBody']['content']['application/json'];
|
||||
@ -463,6 +464,7 @@ export type NotesTimelineRequest = operations['notes___timeline']['requestBody']
|
||||
export type NotesTimelineResponse = operations['notes___timeline']['responses']['200']['content']['application/json'];
|
||||
export type NotesTranslateRequest = operations['notes___translate']['requestBody']['content']['application/json'];
|
||||
export type NotesTranslateResponse = operations['notes___translate']['responses']['200']['content']['application/json'];
|
||||
export type NotesUnarchiveRequest = operations['notes___unarchive']['requestBody']['content']['application/json'];
|
||||
export type NotesUnrenoteRequest = operations['notes___unrenote']['requestBody']['content']['application/json'];
|
||||
export type NotesUserListTimelineRequest = operations['notes___user-list-timeline']['requestBody']['content']['application/json'];
|
||||
export type NotesUserListTimelineResponse = operations['notes___user-list-timeline']['responses']['200']['content']['application/json'];
|
||||
|
@ -2722,6 +2722,16 @@ export type paths = {
|
||||
*/
|
||||
post: operations['notes'];
|
||||
};
|
||||
'/notes/archive': {
|
||||
/**
|
||||
* notes/archive
|
||||
* @description No description provided.
|
||||
*
|
||||
* **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
|
||||
* **Credential required**: *Yes* / **Permission**: *write:notes*
|
||||
*/
|
||||
post: operations['notes___archive'];
|
||||
};
|
||||
'/notes/children': {
|
||||
/**
|
||||
* notes/children
|
||||
@ -2979,6 +2989,16 @@ export type paths = {
|
||||
*/
|
||||
post: operations['notes___translate'];
|
||||
};
|
||||
'/notes/unarchive': {
|
||||
/**
|
||||
* notes/unarchive
|
||||
* @description No description provided.
|
||||
*
|
||||
* **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
|
||||
* **Credential required**: *Yes* / **Permission**: *write:notes*
|
||||
*/
|
||||
post: operations['notes___unarchive'];
|
||||
};
|
||||
'/notes/unrenote': {
|
||||
/**
|
||||
* notes/unrenote
|
||||
@ -4352,6 +4372,7 @@ export type components = {
|
||||
url?: string;
|
||||
reactionAndUserPairCache?: string[];
|
||||
clippedCount?: number;
|
||||
isDeletable: boolean;
|
||||
myReaction?: string | null;
|
||||
};
|
||||
NoteReaction: {
|
||||
@ -23523,6 +23544,65 @@ export type operations = {
|
||||
};
|
||||
};
|
||||
};
|
||||
/**
|
||||
* notes/archive
|
||||
* @description No description provided.
|
||||
*
|
||||
* **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
|
||||
* **Credential required**: *Yes* / **Permission**: *write:notes*
|
||||
*/
|
||||
notes___archive: {
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
/** Format: misskey:id */
|
||||
noteId: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
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 To many requests */
|
||||
429: {
|
||||
content: {
|
||||
'application/json': components['schemas']['Error'];
|
||||
};
|
||||
};
|
||||
/** @description Internal server error */
|
||||
500: {
|
||||
content: {
|
||||
'application/json': components['schemas']['Error'];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
/**
|
||||
* notes/children
|
||||
* @description No description provided.
|
||||
@ -25307,6 +25387,65 @@ export type operations = {
|
||||
};
|
||||
};
|
||||
};
|
||||
/**
|
||||
* notes/unarchive
|
||||
* @description No description provided.
|
||||
*
|
||||
* **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties.
|
||||
* **Credential required**: *Yes* / **Permission**: *write:notes*
|
||||
*/
|
||||
notes___unarchive: {
|
||||
requestBody: {
|
||||
content: {
|
||||
'application/json': {
|
||||
/** Format: misskey:id */
|
||||
noteId: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
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 To many requests */
|
||||
429: {
|
||||
content: {
|
||||
'application/json': components['schemas']['Error'];
|
||||
};
|
||||
};
|
||||
/** @description Internal server error */
|
||||
500: {
|
||||
content: {
|
||||
'application/json': components['schemas']['Error'];
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
/**
|
||||
* notes/unrenote
|
||||
* @description No description provided.
|
||||
|
Loading…
Reference in New Issue
Block a user