Compare commits

...

10 Commits

Author SHA1 Message Date
かっこかり
42e2df192e
Merge d27389c3a5 into 3c81926f71 2024-12-22 13:58:42 +09:00
かっこかり
3c81926f71
fix(frontend): serverContextの値を利用する条件が間違っていたのを修正 (#15166)
Some checks are pending
Check SPDX-License-Identifier / check-spdx-license-id (push) Waiting to run
Check copyright year / check_copyright_year (push) Waiting to run
Publish Docker image (develop) / Build (linux/amd64) (push) Waiting to run
Publish Docker image (develop) / Build (linux/arm64) (push) Waiting to run
Publish Docker image (develop) / merge (push) Blocked by required conditions
Dockle / dockle (push) Waiting to run
Lint / pnpm_install (push) Waiting to run
Lint / lint (backend) (push) Blocked by required conditions
Lint / lint (frontend) (push) Blocked by required conditions
Lint / lint (frontend-embed) (push) Blocked by required conditions
Lint / lint (frontend-shared) (push) Blocked by required conditions
Lint / lint (misskey-bubble-game) (push) Blocked by required conditions
Lint / lint (misskey-js) (push) Blocked by required conditions
Lint / lint (misskey-reversi) (push) Blocked by required conditions
Lint / lint (sw) (push) Blocked by required conditions
Lint / typecheck (backend) (push) Blocked by required conditions
Lint / typecheck (misskey-js) (push) Blocked by required conditions
Lint / typecheck (sw) (push) Blocked by required conditions
Storybook / build (push) Waiting to run
Test (frontend) / vitest (22.11.0) (push) Waiting to run
Test (frontend) / e2e (chrome, 22.11.0) (push) Waiting to run
Test (production install and build) / production (22.11.0) (push) Waiting to run
2024-12-22 13:36:17 +09:00
かっこかり
f123be38b9
enhance(frontend): 照会の際にエラーを表示するように (#15147)
Some checks failed
Check copyright year / check_copyright_year (push) Has been cancelled
Check SPDX-License-Identifier / check-spdx-license-id (push) Has been cancelled
Publish Docker image (develop) / Build (linux/amd64) (push) Has been cancelled
Publish Docker image (develop) / Build (linux/arm64) (push) Has been cancelled
Dockle / dockle (push) Has been cancelled
Lint / pnpm_install (push) Has been cancelled
Lint / locale_verify (push) Has been cancelled
Release Manager: sync changelog with PR / edit (push) Has been cancelled
Storybook / build (push) Has been cancelled
Test (backend) / unit (22.11.0) (push) Has been cancelled
Test (backend) / e2e (22.11.0) (push) Has been cancelled
Test (federation) / test (22.11.0) (push) Has been cancelled
Test (frontend) / vitest (22.11.0) (push) Has been cancelled
Test (frontend) / e2e (chrome, 22.11.0) (push) Has been cancelled
Test (production install and build) / production (22.11.0) (push) Has been cancelled
Test (backend) / validate-api-json (22.11.0) (push) Has been cancelled
Lint / typecheck (misskey-js) (push) Has been cancelled
Publish Docker image (develop) / merge (push) Has been cancelled
Lint / lint (backend) (push) Has been cancelled
Lint / lint (frontend) (push) Has been cancelled
Lint / lint (frontend-embed) (push) Has been cancelled
Lint / lint (frontend-shared) (push) Has been cancelled
Lint / lint (misskey-bubble-game) (push) Has been cancelled
Lint / lint (misskey-js) (push) Has been cancelled
Lint / lint (misskey-reversi) (push) Has been cancelled
Lint / lint (sw) (push) Has been cancelled
Lint / typecheck (backend) (push) Has been cancelled
Lint / typecheck (sw) (push) Has been cancelled
* enhance: 照会の失敗理由を表示するように

* Update Changelog

* fix

* fix test

* lookupErrors-> remoteLookupErrors
2024-12-19 16:05:33 +09:00
かっこかり
0804092426
fix(frontend): serverContextの型エラーを修正 (#15131)
Some checks failed
Lint / lint (misskey-js) (push) Has been cancelled
API report (misskey.js) / report (push) Has been cancelled
Check SPDX-License-Identifier / check-spdx-license-id (push) Has been cancelled
Check copyright year / check_copyright_year (push) Has been cancelled
Publish Docker image (develop) / Build (linux/amd64) (push) Has been cancelled
Publish Docker image (develop) / Build (linux/arm64) (push) Has been cancelled
Publish Docker image (develop) / merge (push) Has been cancelled
Lint / lint (misskey-reversi) (push) Has been cancelled
Dockle / dockle (push) Has been cancelled
Lint / pnpm_install (push) Has been cancelled
Lint / lint (backend) (push) Has been cancelled
Lint / lint (frontend) (push) Has been cancelled
Lint / lint (frontend-embed) (push) Has been cancelled
Lint / lint (frontend-shared) (push) Has been cancelled
Lint / lint (misskey-bubble-game) (push) Has been cancelled
Lint / lint (sw) (push) Has been cancelled
Lint / typecheck (backend) (push) Has been cancelled
Lint / typecheck (misskey-js) (push) Has been cancelled
Lint / typecheck (sw) (push) Has been cancelled
Release Manager: sync changelog with PR / edit (push) Has been cancelled
Storybook / build (push) Has been cancelled
Test (backend) / unit (22.11.0) (push) Has been cancelled
Test (backend) / e2e (22.11.0) (push) Has been cancelled
Test (federation) / test (22.11.0) (push) Has been cancelled
Test (frontend) / vitest (22.11.0) (push) Has been cancelled
Test (frontend) / e2e (chrome, 22.11.0) (push) Has been cancelled
Test (misskey.js) / test (22.11.0) (push) Has been cancelled
Test (production install and build) / production (22.11.0) (push) Has been cancelled
* fix(frontend): serverContextの型エラーを修正

* add comment
2024-12-16 09:03:46 +09:00
かっこかり
3e0fcaeca8
fix(frontend): 絵文字管理画面で絵文字が表示されないことがある問題を修正 (#15128)
* fix(frontend): 絵文字管理画面で絵文字が表示されないことがある問題を修正

* Update Changelog

* optimize
2024-12-16 09:02:38 +09:00
かっこかり
5a2b29a3b4
enhance(frontend): PC画面でチャンネルが複数列で表示されるように (#15129)
* チャンネル一覧の列を最大3列にした (Otaku-Social#13)

* fix

* fix

* fix

* 🎨

* fix

* 🎨

* Update Changelog

* Update Changelog

* 要らない_marginを消す

---------

Co-authored-by: tmorio <morikapusan@morikapu-denki.com>
2024-12-16 08:57:37 +09:00
FineArchs
234d91a884
misskey-js: APIClientにURL末尾の/を除去する処理を追加 (#15132) 2024-12-16 08:55:34 +09:00
kakkokari-gtyih
d27389c3a5 re-run misskey-js type generator 2024-11-23 17:26:47 +09:00
kakkokari-gtyih
7f59e6298c Update Changelog 2024-11-23 17:18:27 +09:00
kakkokari-gtyih
001fc04c80 enhance: お知らせの既読をリセットできるように 2024-11-23 17:18:05 +09:00
33 changed files with 547 additions and 48 deletions

View File

@ -1,14 +1,18 @@
## 2024.11.1
### General
-
- Enhance: お知らせの既読をリセットできるように
### Client
- Enhance: PC画面でチャンネルが複数列で表示されるように
(Cherry-picked from https://github.com/Otaku-Social/maniakey/pull/13)
- Enhance: 照会に失敗した場合、その理由を表示するように
- Fix: 画面サイズが変わった際にナビゲーションバーが自動で折りたたまれない問題を修正
- Fix: サーバー情報メニューに区切り線が不足していたのを修正
- Fix: ノートがログインしているユーザーしか見れない場合にログインダイアログを閉じるとその後の動線がなくなる問題を修正
- Fix: 公開範囲がホームのノートの埋め込みウィジェットが読み込まれない問題を修正
(Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/803)
- Fix: 絵文字管理画面で一部の絵文字が表示されない問題を修正
### Server
- Fix: ユーザーのプロフィール画面をアドレス入力などで直接表示した際に概要タブの描画に失敗する問題の修正( #15032 )

75
locales/index.d.ts vendored
View File

@ -5222,6 +5222,14 @@ export interface Locale extends ILocale {
*
*/
"acknowledgeNotesAndEnable": string;
/**
*
*/
"resetReads": string;
/**
* {x}
*/
"resetReadsAreYouSure": ParameterizedString<"x">;
"_accountSettings": {
/**
*
@ -9910,6 +9918,14 @@ export interface Locale extends ILocale {
*
*/
"deleteUserAnnouncement": string;
/**
*
*/
"resetReadsForGlobalAnnouncement": string;
/**
*
*/
"resetReadsForUserAnnouncement": string;
/**
*
*/
@ -10601,6 +10617,65 @@ export interface Locale extends ILocale {
*/
"sent": string;
};
"_remoteLookupErrors": {
"_federationNotAllowed": {
/**
*
*/
"title": string;
/**
*
*
*/
"description": string;
};
"_uriInvalid": {
/**
* URIが不正です
*/
"title": string;
/**
* URIに問題がありますURIに使用できない文字を入力していないか確認してください
*/
"description": string;
};
"_requestFailed": {
/**
*
*/
"title": string;
/**
* URIや存在しないURIを入力していないか確認してください
*/
"description": string;
};
"_responseInvalid": {
/**
*
*/
"title": string;
/**
*
*/
"description": string;
};
"_responseInvalidIdHostNotMatch": {
/**
* URIのドメインと最終的に得られたURIのドメインとが異なりますURIを使用して照会し直してください
*/
"description": string;
};
"_noSuchObject": {
/**
*
*/
"title": string;
/**
* URIをもう一度お確かめください
*/
"description": string;
};
};
}
declare const locales: {
[lang: string]: Locale;

View File

@ -1301,6 +1301,8 @@ lockdown: "ロックダウン"
pleaseSelectAccount: "アカウントを選択してください"
availableRoles: "利用可能なロール"
acknowledgeNotesAndEnable: "注意事項を理解した上でオンにします。"
resetReads: "既読をリセット"
resetReadsAreYouSure: "「{x}」の既読をリセットしますか?"
_accountSettings:
requireSigninToViewContents: "コンテンツの表示にログインを必須にする"
@ -2627,6 +2629,8 @@ _moderationLogTypes:
updateUserAnnouncement: "ユーザーのお知らせを更新"
deleteGlobalAnnouncement: "全体のお知らせを削除"
deleteUserAnnouncement: "ユーザーのお知らせを削除"
resetReadsForGlobalAnnouncement: "全体のお知らせの既読をリセット"
resetReadsForUserAnnouncement: "ユーザーのお知らせの既読をリセット"
resetPassword: "パスワードをリセット"
suspendRemoteInstance: "リモートサーバーを停止"
unsuspendRemoteInstance: "リモートサーバーを再開"
@ -2826,3 +2830,22 @@ _selfXssPrevention:
_followRequest:
recieved: "受け取った申請"
sent: "送った申請"
_remoteLookupErrors:
_federationNotAllowed:
title: "このサーバーとは通信できません"
description: "このサーバーとの通信が無効化されているか、このサーバーをブロックしている・ブロックされている可能性があります。\nサーバー管理者にお問い合わせください。"
_uriInvalid:
title: "URIが不正です"
description: "入力されたURIに問題があります。URIに使用できない文字を入力していないか確認してください。"
_requestFailed:
title: "リクエストに失敗しました"
description: "このサーバーとの通信に失敗しました。相手サーバーがダウンしている可能性があります。また、不正なURIや存在しないURIを入力していないか確認してください。"
_responseInvalid:
title: "レスポンスが不正です"
description: "このサーバーと通信することはできましたが、得られたデータが不正なものでした。"
_responseInvalidIdHostNotMatch:
description: "入力されたURIのドメインと最終的に得られたURIのドメインとが異なります。第三者のサーバーを介してリモートのコンテンツを照会している場合は、発信元のサーバーで取得できるURIを使用して照会し直してください。"
_noSuchObject:
title: "見つかりません"
description: "要求されたリソースは見つかりませんでした。URIをもう一度お確かめください。"

View File

@ -179,6 +179,32 @@ export class AnnouncementService {
}
}
@bindThis
public async resetReads(announcementId: MiAnnouncement['id'], moderator?: MiUser): Promise<void> {
await this.announcementReadsRepository.delete({
announcementId: announcementId,
});
if (moderator) {
const announcement = await this.announcementsRepository.findOneByOrFail({ id: announcementId });
if (announcement.userId) {
const user = await this.usersRepository.findOneByOrFail({ id: announcement.userId });
this.moderationLogService.log(moderator, 'resetReadsForUserAnnouncement', {
announcementId: announcement.id,
announcement: announcement,
userId: announcement.userId,
userUsername: user.username,
userHost: user.host,
});
} else {
this.moderationLogService.log(moderator, 'resetReadsForGlobalAnnouncement', {
announcementId: announcement.id,
announcement: announcement,
});
}
}
}
@bindThis
public async getAnnouncement(announcementId: MiAnnouncement['id'], me: MiUser | null): Promise<Packed<'Announcement'>> {
const announcement = await this.announcementsRepository.findOneByOrFail({ id: announcementId });

View File

@ -20,6 +20,7 @@ import { ApDbResolverService } from './ApDbResolverService.js';
import { ApRendererService } from './ApRendererService.js';
import { ApRequestService } from './ApRequestService.js';
import type { IObject, ICollection, IOrderedCollection } from './type.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
export class Resolver {
private history: Set<string>;
@ -66,7 +67,7 @@ export class Resolver {
if (isCollectionOrOrderedCollection(collection)) {
return collection;
} else {
throw new Error(`unrecognized collection type: ${collection.type}`);
throw new IdentifiableError('f100eccf-f347-43fb-9b45-96a0831fb635', `unrecognized collection type: ${collection.type}`);
}
}
@ -80,15 +81,15 @@ export class Resolver {
// URLs with fragment parts cannot be resolved correctly because
// the fragment part does not get transmitted over HTTP(S).
// Avoid strange behaviour by not trying to resolve these at all.
throw new Error(`cannot resolve URL with fragment: ${value}`);
throw new IdentifiableError('b94fd5b1-0e3b-4678-9df2-dad4cd515ab2', `cannot resolve URL with fragment: ${value}`);
}
if (this.history.has(value)) {
throw new Error('cannot resolve already resolved one');
throw new IdentifiableError('0dc86cf6-7cd6-4e56-b1e6-5903d62d7ea5', 'cannot resolve already resolved one');
}
if (this.history.size > this.recursionLimit) {
throw new Error(`hit recursion limit: ${this.utilityService.extractDbHost(value)}`);
throw new IdentifiableError('d592da9f-822f-4d91-83d7-4ceefabcf3d2', `hit recursion limit: ${this.utilityService.extractDbHost(value)}`);
}
this.history.add(value);
@ -99,7 +100,7 @@ export class Resolver {
}
if (!this.utilityService.isFederationAllowedHost(host)) {
throw new Error('Instance is blocked');
throw new IdentifiableError('09d79f9e-64f1-4316-9cfa-e75c4d091574', 'Instance is blocked');
}
if (this.config.signToActivityPubGet && !this.user) {
@ -115,7 +116,7 @@ export class Resolver {
!(object['@context'] as unknown[]).includes('https://www.w3.org/ns/activitystreams') :
object['@context'] !== 'https://www.w3.org/ns/activitystreams'
) {
throw new Error('invalid response');
throw new IdentifiableError('72180409-793c-4973-868e-5a118eb5519b', 'invalid response');
}
// HttpRequestService / ApRequestService have already checked that
@ -123,11 +124,11 @@ export class Resolver {
// object after redirects; here we double-check that no redirects
// bounced between hosts
if (object.id == null) {
throw new Error('invalid AP object: missing id');
throw new IdentifiableError('ad2dc287-75c1-44c4-839d-3d2e64576675', 'invalid AP object: missing id');
}
if (this.utilityService.punyHost(object.id) !== this.utilityService.punyHost(value)) {
throw new Error(`invalid AP object ${value}: id ${object.id} has different host`);
throw new IdentifiableError('fd93c2fa-69a8-440f-880b-bf178e0ec877', `invalid AP object ${value}: id ${object.id} has different host`);
}
return object;
@ -136,7 +137,7 @@ export class Resolver {
@bindThis
private resolveLocal(url: string): Promise<IObject> {
const parsed = this.apDbResolverService.parseUri(url);
if (!parsed.local) throw new Error('resolveLocal: not local');
if (!parsed.local) throw new IdentifiableError('02b40cd0-fa92-4b0c-acc9-fb2ada952ab8', 'resolveLocal: not local');
switch (parsed.type) {
case 'notes':
@ -165,7 +166,7 @@ export class Resolver {
case 'follows':
return this.followRequestsRepository.findOneBy({ id: parsed.id })
.then(async followRequest => {
if (followRequest == null) throw new Error('resolveLocal: invalid follow request ID');
if (followRequest == null) throw new IdentifiableError('a9d946e5-d276-47f8-95fb-f04230289bb0', 'resolveLocal: invalid follow request ID');
const [follower, followee] = await Promise.all([
this.usersRepository.findOneBy({
id: followRequest.followerId,
@ -177,12 +178,12 @@ export class Resolver {
}),
]);
if (follower == null || followee == null) {
throw new Error('resolveLocal: follower or followee does not exist');
throw new IdentifiableError('06ae3170-1796-4d93-a697-2611ea6d83b6', 'resolveLocal: follower or followee does not exist');
}
return this.apRendererService.addContext(this.apRendererService.renderFollow(follower as MiLocalUser | MiRemoteUser, followee as MiLocalUser | MiRemoteUser, url));
});
default:
throw new Error(`resolveLocal: type ${parsed.type} unhandled`);
throw new IdentifiableError('7a5d2fc0-94bc-4db6-b8b8-1bf24a2e23d0', `resolveLocal: type ${parsed.type} unhandled`);
}
}
}

View File

@ -23,6 +23,7 @@ import * as ep___admin_ad_update from './endpoints/admin/ad/update.js';
import * as ep___admin_announcements_create from './endpoints/admin/announcements/create.js';
import * as ep___admin_announcements_delete from './endpoints/admin/announcements/delete.js';
import * as ep___admin_announcements_list from './endpoints/admin/announcements/list.js';
import * as ep___admin_announcements_resetReads from './endpoints/admin/announcements/reset-reads.js';
import * as ep___admin_announcements_update from './endpoints/admin/announcements/update.js';
import * as ep___admin_avatarDecorations_create from './endpoints/admin/avatar-decorations/create.js';
import * as ep___admin_avatarDecorations_delete from './endpoints/admin/avatar-decorations/delete.js';
@ -411,6 +412,7 @@ const $admin_ad_update: Provider = { provide: 'ep:admin/ad/update', useClass: ep
const $admin_announcements_create: Provider = { provide: 'ep:admin/announcements/create', useClass: ep___admin_announcements_create.default };
const $admin_announcements_delete: Provider = { provide: 'ep:admin/announcements/delete', useClass: ep___admin_announcements_delete.default };
const $admin_announcements_list: Provider = { provide: 'ep:admin/announcements/list', useClass: ep___admin_announcements_list.default };
const $admin_announcements_resetReads: Provider = { provide: 'ep:admin/announcements/reset-reads', useClass: ep___admin_announcements_resetReads.default };
const $admin_announcements_update: Provider = { provide: 'ep:admin/announcements/update', useClass: ep___admin_announcements_update.default };
const $admin_avatarDecorations_create: Provider = { provide: 'ep:admin/avatar-decorations/create', useClass: ep___admin_avatarDecorations_create.default };
const $admin_avatarDecorations_delete: Provider = { provide: 'ep:admin/avatar-decorations/delete', useClass: ep___admin_avatarDecorations_delete.default };
@ -803,6 +805,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$admin_announcements_create,
$admin_announcements_delete,
$admin_announcements_list,
$admin_announcements_resetReads,
$admin_announcements_update,
$admin_avatarDecorations_create,
$admin_avatarDecorations_delete,
@ -1189,6 +1192,7 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__
$admin_announcements_create,
$admin_announcements_delete,
$admin_announcements_list,
$admin_announcements_resetReads,
$admin_announcements_update,
$admin_avatarDecorations_create,
$admin_avatarDecorations_delete,

View File

@ -28,6 +28,7 @@ import * as ep___admin_ad_update from './endpoints/admin/ad/update.js';
import * as ep___admin_announcements_create from './endpoints/admin/announcements/create.js';
import * as ep___admin_announcements_delete from './endpoints/admin/announcements/delete.js';
import * as ep___admin_announcements_list from './endpoints/admin/announcements/list.js';
import * as ep___admin_announcements_resetReads from './endpoints/admin/announcements/reset-reads.js';
import * as ep___admin_announcements_update from './endpoints/admin/announcements/update.js';
import * as ep___admin_avatarDecorations_create from './endpoints/admin/avatar-decorations/create.js';
import * as ep___admin_avatarDecorations_delete from './endpoints/admin/avatar-decorations/delete.js';
@ -415,6 +416,7 @@ const eps = [
['admin/announcements/create', ep___admin_announcements_create],
['admin/announcements/delete', ep___admin_announcements_delete],
['admin/announcements/list', ep___admin_announcements_list],
['admin/announcements/reset-reads', ep___admin_announcements_resetReads],
['admin/announcements/update', ep___admin_announcements_update],
['admin/avatar-decorations/create', ep___admin_avatarDecorations_create],
['admin/avatar-decorations/delete', ep___admin_avatarDecorations_delete],

View File

@ -53,6 +53,37 @@ export const meta = {
type: 'string',
optional: false, nullable: true,
},
icon: {
type: 'string',
optional: false, nullable: false,
enum: ['info', 'warning', 'error', 'success'],
},
display: {
type: 'string',
optional: false, nullable: false,
enum: ['normal', 'banner', 'dialog'],
},
isActive: {
type: 'boolean',
optional: false, nullable: false,
},
forExistingUsers: {
type: 'boolean',
optional: false, nullable: false,
},
silence: {
type: 'boolean',
optional: false, nullable: false,
},
needConfirmationToRead: {
type: 'boolean',
optional: false, nullable: false,
},
userId: {
type: 'string',
optional: false, nullable: true,
format: 'id',
},
reads: {
type: 'number',
optional: false, nullable: false,
@ -125,7 +156,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
silence: announcement.silence,
needConfirmationToRead: announcement.needConfirmationToRead,
userId: announcement.userId,
reads: reads.get(announcement)!,
reads: reads.get(announcement) ?? 0,
}));
});
}

View File

@ -0,0 +1,53 @@
/*
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import { Inject, Injectable } from '@nestjs/common';
import { Endpoint } from '@/server/api/endpoint-base.js';
import type { AnnouncementsRepository } from '@/models/_.js';
import { DI } from '@/di-symbols.js';
import { AnnouncementService } from '@/core/AnnouncementService.js';
import { ApiError } from '../../../error.js';
export const meta = {
tags: ['admin'],
requireCredential: true,
requireModerator: true,
kind: 'write:admin:announcements',
errors: {
noSuchAnnouncement: {
message: 'No such announcement.',
code: 'NO_SUCH_ANNOUNCEMENT',
id: 'd3aae5a7-6372-4cb4-b61c-f511ffc2d7cc',
},
},
} as const;
export const paramDef = {
type: 'object',
properties: {
announcementId: { type: 'string', format: 'misskey:id' },
},
required: ['announcementId'],
} as const;
@Injectable()
export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export
constructor(
@Inject(DI.announcementsRepository)
private announcementsRepository: AnnouncementsRepository,
private announcementService: AnnouncementService,
) {
super(meta, paramDef, async (ps, me) => {
const announcement = await this.announcementsRepository.findOneBy({ id: ps.announcementId });
if (announcement == null) throw new ApiError(meta.errors.noSuchAnnouncement);
await this.announcementService.resetReads(announcement.id, me);
});
}
}

View File

@ -19,6 +19,7 @@ import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { UtilityService } from '@/core/UtilityService.js';
import { bindThis } from '@/decorators.js';
import { ApiError } from '../../error.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
export const meta = {
tags: ['federation'],
@ -32,6 +33,31 @@ export const meta = {
},
errors: {
federationNotAllowed: {
message: 'Federation for this host is not allowed.',
code: 'FEDERATION_NOT_ALLOWED',
id: '974b799e-1a29-4889-b706-18d4dd93e266',
},
uriInvalid: {
message: 'URI is invalid.',
code: 'URI_INVALID',
id: '1a5eab56-e47b-48c2-8d5e-217b897d70db',
},
requestFailed: {
message: 'Request failed.',
code: 'REQUEST_FAILED',
id: '81b539cf-4f57-4b29-bc98-032c33c0792e',
},
responseInvalid: {
message: 'Response from remote server is invalid.',
code: 'RESPONSE_INVALID',
id: '70193c39-54f3-4813-82f0-70a680f7495b',
},
responseInvalidIdHostNotMatch: {
message: 'Requested URI and response URI host does not match.',
code: 'RESPONSE_INVALID_ID_HOST_NOT_MATCH',
id: 'a2c9c61a-cb72-43ab-a964-3ca5fddb410a',
},
noSuchObject: {
message: 'No such object.',
code: 'NO_SUCH_OBJECT',
@ -110,7 +136,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
*/
@bindThis
private async fetchAny(uri: string, me: MiLocalUser | null | undefined): Promise<SchemaType<typeof meta['res']> | null> {
if (!this.utilityService.isFederationAllowedUri(uri)) return null;
if (!this.utilityService.isFederationAllowedUri(uri)) {
throw new ApiError(meta.errors.federationNotAllowed);
}
let local = await this.mergePack(me, ...await Promise.all([
this.apDbResolverService.getUserFromApId(uri),
@ -125,7 +153,40 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
// リモートから一旦オブジェクトフェッチ
const resolver = this.apResolverService.createResolver();
const object = await resolver.resolve(uri) as any;
const object = await resolver.resolve(uri).catch((err) => {
if (err instanceof IdentifiableError) {
switch (err.id) {
// resolve
case 'b94fd5b1-0e3b-4678-9df2-dad4cd515ab2':
throw new ApiError(meta.errors.uriInvalid);
case '0dc86cf6-7cd6-4e56-b1e6-5903d62d7ea5':
case 'd592da9f-822f-4d91-83d7-4ceefabcf3d2':
throw new ApiError(meta.errors.requestFailed);
case '09d79f9e-64f1-4316-9cfa-e75c4d091574':
throw new ApiError(meta.errors.federationNotAllowed);
case '72180409-793c-4973-868e-5a118eb5519b':
case 'ad2dc287-75c1-44c4-839d-3d2e64576675':
throw new ApiError(meta.errors.responseInvalid);
case 'fd93c2fa-69a8-440f-880b-bf178e0ec877':
throw new ApiError(meta.errors.responseInvalidIdHostNotMatch);
// resolveLocal
case '02b40cd0-fa92-4b0c-acc9-fb2ada952ab8':
throw new ApiError(meta.errors.uriInvalid);
case 'a9d946e5-d276-47f8-95fb-f04230289bb0':
case '06ae3170-1796-4d93-a697-2611ea6d83b6':
throw new ApiError(meta.errors.noSuchObject);
case '7a5d2fc0-94bc-4db6-b8b8-1bf24a2e23d0':
throw new ApiError(meta.errors.responseInvalid);
}
}
throw new ApiError(meta.errors.requestFailed);
});
if (object.id == null) {
throw new ApiError(meta.errors.responseInvalid);
}
// /@user のような正規id以外で取得できるURIが指定されていた場合、ここで初めて正規URIが確定する
// これはDBに存在する可能性があるため再度DB検索

View File

@ -92,6 +92,8 @@ export const moderationLogTypes = [
'updateUserAnnouncement',
'deleteGlobalAnnouncement',
'deleteUserAnnouncement',
'resetReadsForGlobalAnnouncement',
'resetReadsForUserAnnouncement',
'resetPassword',
'suspendRemoteInstance',
'unsuspendRemoteInstance',
@ -235,6 +237,17 @@ export type ModerationLogPayloads = {
userUsername: string;
userHost: string | null;
};
resetReadsForGlobalAnnouncement: {
announcementId: string;
announcement: any;
};
resetReadsForUserAnnouncement: {
announcementId: string;
announcement: any;
userId: string;
userUsername: string;
userHost: string | null;
};
resetPassword: {
userId: string;
userUsername: string;

View File

@ -131,11 +131,7 @@ describe('Note', () => {
rejects(
async () => await bob.client.request('ap/show', { uri: `https://a.test/notes/${note.id}` }),
(err: any) => {
/**
* FIXME: this error is not handled
* @see https://github.com/misskey-dev/misskey/issues/12736
*/
strictEqual(err.code, 'INTERNAL_ERROR');
strictEqual(err.code, 'REQUEST_FAILED');
return true;
},
);

View File

@ -125,7 +125,9 @@ const bannerStyle = computed(() => {
position: absolute;
top: 16px;
left: 16px;
max-width: calc(100% - 32px);
padding: 12px 16px;
box-sizing: border-box;
background: rgba(0, 0, 0, 0.7);
color: #fff;
font-size: 1.2em;

View File

@ -39,7 +39,10 @@ SPDX-License-Identifier: AGPL-3.0-only
{{ i18n.ts._announcement.needConfirmationToRead }}
<template #caption>{{ i18n.ts._announcement.needConfirmationToReadDescription }}</template>
</MkSwitch>
<MkButton v-if="announcement" danger @click="del()"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
<div class="_buttons">
<MkButton v-if="announcement" @click="resetReads()">{{ i18n.ts.resetReads }}</MkButton>
<MkButton v-if="announcement" danger @click="del()"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
</div>
</div>
</MkSpacer>
<div :class="$style.footer">
@ -117,6 +120,20 @@ async function done() {
}
}
async function resetReads() {
if (!props.announcement) return;
const { canceled } = await os.confirm({
type: 'warning',
text: i18n.tsx.resetReadsAreYouSure({ x: props.announcement.title }),
});
if (canceled) return;
await os.apiWithDialog('admin/announcements/reset-reads', {
announcementId: props.announcement.id,
});
}
async function del() {
const { canceled } = await os.confirm({
type: 'warning',

View File

@ -151,7 +151,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<div v-else-if="tab === 'announcements'" class="_gaps">
<MkButton primary rounded @click="createAnnouncement"><i class="ti ti-plus"></i> {{ i18n.ts.new }}</MkButton>
<MkButton primary rounded @click="createAnnouncement"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton>
<MkSelect v-model="announcementsStatus">
<template #label>{{ i18n.ts.filter }}</template>

View File

@ -34,6 +34,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkButton rounded primary @click="save(announcement)"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton>
<MkButton v-if="announcement.id != null && announcement.isActive" rounded @click="archive(announcement)"><i class="ti ti-check"></i> {{ i18n.ts._announcement.end }} ({{ i18n.ts.archive }})</MkButton>
<MkButton v-if="announcement.id != null && !announcement.isActive" rounded @click="unarchive(announcement)"><i class="ti ti-restore"></i> {{ i18n.ts.unarchive }}</MkButton>
<MkButton v-if="announcement.id != null && announcement.isActive && announcement.reads > 0" rounded @click="resetReads(announcement)">{{ i18n.ts.resetReads }}</MkButton>
<MkButton v-if="announcement.id != null" rounded danger @click="del(announcement)"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton>
</div>
</template>
@ -71,7 +72,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<MkSwitch v-model="announcement.needConfirmationToRead" :helpText="i18n.ts._announcement.needConfirmationToReadDescription">
{{ i18n.ts._announcement.needConfirmationToRead }}
</MkSwitch>
<p v-if="announcement.reads">{{ i18n.tsx.nUsersRead({ n: announcement.reads }) }}</p>
<p v-if="announcement.reads > 0">{{ i18n.tsx.nUsersRead({ n: announcement.reads }) }}</p>
</div>
</MkFolder>
<MkLoading v-if="loadingMore"/>
@ -85,6 +86,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template>
<script lang="ts" setup>
import * as Misskey from 'misskey-js';
import { ref, computed, watch } from 'vue';
import XHeader from './_header_.vue';
import MkButton from '@/components/MkButton.vue';
@ -143,6 +145,19 @@ function del(announcement) {
});
}
async function resetReads(announcement) {
const { canceled } = await os.confirm({
type: 'warning',
text: i18n.tsx.resetReadsAreYouSure({ x: announcement.title }),
});
if (canceled) return;
await os.apiWithDialog('admin/announcements/reset-reads', {
announcementId: announcement.id,
});
refresh();
}
async function archive(announcement) {
await os.apiWithDialog('admin/announcements/update', {
...announcement,

View File

@ -62,9 +62,11 @@ SPDX-License-Identifier: AGPL-3.0-only
<span v-else-if="log.type === 'createGlobalAnnouncement'">: {{ log.info.announcement.title }}</span>
<span v-else-if="log.type === 'updateGlobalAnnouncement'">: {{ log.info.before.title }}</span>
<span v-else-if="log.type === 'deleteGlobalAnnouncement'">: {{ log.info.announcement.title }}</span>
<span v-else-if="log.type === 'resetReadsForGlobalAnnouncement'">: {{ log.info.announcement.title }}</span>
<span v-else-if="log.type === 'createUserAnnouncement'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
<span v-else-if="log.type === 'updateUserAnnouncement'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
<span v-else-if="log.type === 'deleteUserAnnouncement'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
<span v-else-if="log.type === 'resetReadsForUserAnnouncement'">: @{{ log.info.userUsername }}{{ log.info.userHost ? '@' + log.info.userHost : '' }}</span>
<span v-else-if="log.type === 'deleteNote'">: @{{ log.info.noteUserUsername }}{{ log.info.noteUserHost ? '@' + log.info.noteUserHost : '' }}</span>
<span v-else-if="log.type === 'deleteDriveFile'">: @{{ log.info.fileUserUsername }}{{ log.info.fileUserHost ? '@' + log.info.fileUserHost : '' }}</span>
<span v-else-if="log.type === 'createAvatarDecoration'">: {{ log.info.avatarDecoration.name }}</span>

View File

@ -6,9 +6,9 @@ SPDX-License-Identifier: AGPL-3.0-only
<template>
<MkStickyContainer>
<template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template>
<MkSpacer :contentMax="700">
<MkSpacer :contentMax="1200">
<MkHorizontalSwipe v-model:tab="tab" :tabs="headerTabs">
<div v-if="tab === 'search'" key="search">
<div v-if="tab === 'search'" key="search" :class="$style.searchRoot">
<div class="_gaps">
<MkInput v-model="searchQuery" :large="true" :autofocus="true" type="search" @enter="search">
<template #prefix><i class="ti ti-search"></i></template>
@ -27,23 +27,31 @@ SPDX-License-Identifier: AGPL-3.0-only
</div>
<div v-if="tab === 'featured'" key="featured">
<MkPagination v-slot="{items}" :pagination="featuredPagination">
<MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
<div :class="$style.root">
<MkChannelPreview v-for="channel in items" :key="channel.id" :channel="channel"/>
</div>
</MkPagination>
</div>
<div v-else-if="tab === 'favorites'" key="favorites">
<MkPagination v-slot="{items}" :pagination="favoritesPagination">
<MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
<div :class="$style.root">
<MkChannelPreview v-for="channel in items" :key="channel.id" :channel="channel"/>
</div>
</MkPagination>
</div>
<div v-else-if="tab === 'following'" key="following">
<MkPagination v-slot="{items}" :pagination="followingPagination">
<MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
<div :class="$style.root">
<MkChannelPreview v-for="channel in items" :key="channel.id" :channel="channel"/>
</div>
</MkPagination>
</div>
<div v-else-if="tab === 'owned'" key="owned">
<MkButton class="new" @click="create()"><i class="ti ti-plus"></i></MkButton>
<MkPagination v-slot="{items}" :pagination="ownedPagination">
<MkChannelPreview v-for="channel in items" :key="channel.id" class="_margin" :channel="channel"/>
<div :class="$style.root">
<MkChannelPreview v-for="channel in items" :key="channel.id" :channel="channel"/>
</div>
</MkPagination>
</div>
</MkHorizontalSwipe>
@ -85,6 +93,7 @@ onMounted(() => {
const featuredPagination = {
endpoint: 'channels/featured' as const,
limit: 10,
noPaging: true,
};
const favoritesPagination = {
@ -157,3 +166,17 @@ definePageMetadata(() => ({
icon: 'ti ti-device-tv',
}));
</script>
<style lang="scss" module>
.searchRoot {
width: 100%;
max-width: 700px;
margin: 0 auto;
}
.root {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(360px, 1fr));
gap: var(--MI-margin);
}
</style>

View File

@ -46,9 +46,10 @@ import { clipsCache } from '@/cache.js';
import { isSupportShare } from '@/scripts/navigator.js';
import { copyToClipboard } from '@/scripts/copy-to-clipboard.js';
import { genEmbedCode } from '@/scripts/get-embed-code.js';
import { getServerContext } from '@/server-context.js';
import { assertServerContext, serverContext } from '@/server-context.js';
const CTX_CLIP = getServerContext('clip');
// context
const CTX_CLIP = !$i && assertServerContext(serverContext, 'clip') ? serverContext.clip : null;
const props = defineProps<{
clipId: string,

View File

@ -31,7 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #default="{items}">
<div class="ldhfsamy">
<button v-for="emoji in items" :key="emoji.id" class="emoji _panel _button" :class="{ selected: selectedEmojis.includes(emoji.id) }" @click="selectMode ? toggleSelect(emoji) : edit(emoji)">
<img :src="`/emoji/${emoji.name}.webp`" class="img" :alt="emoji.name"/>
<img :src="emoji.url" class="img" :alt="emoji.name"/>
<div class="body">
<div class="name _monospace">{{ emoji.name }}</div>
<div class="info">{{ emoji.category }}</div>
@ -57,7 +57,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template #default="{items}">
<div class="ldhfsamy">
<div v-for="emoji in items" :key="emoji.id" class="emoji _panel _button" @click="remoteMenu(emoji, $event)">
<img :src="`/emoji/${emoji.name}@${emoji.host}.webp`" class="img" :alt="emoji.name"/>
<img :src="getProxiedImageUrl(emoji.url, 'emoji')" class="img" :alt="emoji.name"/>
<div class="body">
<div class="name _monospace">{{ emoji.name }}</div>
<div class="info">{{ emoji.host }}</div>
@ -83,6 +83,7 @@ import FormSplit from '@/components/form/split.vue';
import { selectFile } from '@/scripts/select-file.js';
import * as os from '@/os.js';
import { misskeyApi } from '@/scripts/misskey-api.js';
import { getProxiedImageUrl } from '@/scripts/media-proxy.js';
import { i18n } from '@/i18n.js';
import { definePageMetadata } from '@/scripts/page-metadata.js';

View File

@ -118,7 +118,7 @@ watch(roleIdsThatCanBeUsedThisEmojiAsReaction, async () => {
rolesThatCanBeUsedThisEmojiAsReaction.value = (await Promise.all(roleIdsThatCanBeUsedThisEmojiAsReaction.value.map((id) => misskeyApi('admin/roles/show', { roleId: id }).catch(() => null)))).filter(x => x != null);
}, { immediate: true });
const imgUrl = computed(() => file.value ? file.value.url : props.emoji ? `/emoji/${props.emoji.name}.webp` : null);
const imgUrl = computed(() => file.value ? file.value.url : props.emoji ? props.emoji.url : null);
async function changeImage(ev: Event) {
file.value = await selectFile(ev.currentTarget ?? ev.target, null);

View File

@ -63,9 +63,11 @@ import { dateString } from '@/filters/date.js';
import MkClipPreview from '@/components/MkClipPreview.vue';
import { defaultStore } from '@/store.js';
import { pleaseLogin } from '@/scripts/please-login.js';
import { getServerContext } from '@/server-context.js';
import { serverContext, assertServerContext } from '@/server-context.js';
import { $i } from '@/account.js';
const CTX_NOTE = getServerContext('note');
// context
const CTX_NOTE = !$i && assertServerContext(serverContext, 'note') ? serverContext.note : null;
const props = defineProps<{
noteId: string;

View File

@ -39,7 +39,7 @@ import { definePageMetadata } from '@/scripts/page-metadata.js';
import { i18n } from '@/i18n.js';
import { $i } from '@/account.js';
import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue';
import { getServerContext } from '@/server-context.js';
import { serverContext, assertServerContext } from '@/server-context.js';
const XHome = defineAsyncComponent(() => import('./home.vue'));
const XTimeline = defineAsyncComponent(() => import('./index.timeline.vue'));
@ -53,7 +53,8 @@ const XFlashs = defineAsyncComponent(() => import('./flashs.vue'));
const XGallery = defineAsyncComponent(() => import('./gallery.vue'));
const XRaw = defineAsyncComponent(() => import('./raw.vue'));
const CTX_USER = getServerContext('user');
// context
const CTX_USER = !$i && assertServerContext(serverContext, 'user') ? serverContext.user : null;
const props = withDefaults(defineProps<{
acct: string;

View File

@ -33,7 +33,43 @@ export async function lookup(router?: Router) {
uri: query,
});
os.promiseDialog(promise, null, null, i18n.ts.fetchingAsApObject);
os.promiseDialog(promise, null, (err) => {
let title = i18n.ts.somethingHappened;
let text = err.message + '\n' + err.id;
switch (err.id) {
case '974b799e-1a29-4889-b706-18d4dd93e266':
title = i18n.ts._remoteLookupErrors._federationNotAllowed.title;
text = i18n.ts._remoteLookupErrors._federationNotAllowed.description;
break;
case '1a5eab56-e47b-48c2-8d5e-217b897d70db':
title = i18n.ts._remoteLookupErrors._uriInvalid.title;
text = i18n.ts._remoteLookupErrors._uriInvalid.description;
break;
case '81b539cf-4f57-4b29-bc98-032c33c0792e':
title = i18n.ts._remoteLookupErrors._requestFailed.title;
text = i18n.ts._remoteLookupErrors._requestFailed.description;
break;
case '70193c39-54f3-4813-82f0-70a680f7495b':
title = i18n.ts._remoteLookupErrors._responseInvalid.title;
text = i18n.ts._remoteLookupErrors._responseInvalid.description;
break;
case 'a2c9c61a-cb72-43ab-a964-3ca5fddb410a':
title = i18n.ts._remoteLookupErrors._responseInvalid.title;
text = i18n.ts._remoteLookupErrors._responseInvalidIdHostNotMatch.description;
break;
case 'dc94d745-1262-4e63-a17d-fecaa57efc82':
title = i18n.ts._remoteLookupErrors._noSuchObject.title;
text = i18n.ts._remoteLookupErrors._noSuchObject.description;
break;
}
os.alert({
type: 'error',
title,
text,
});
}, i18n.ts.fetchingAsApObject);
const res = await promise;

View File

@ -2,22 +2,20 @@
* SPDX-FileCopyrightText: syuilo and misskey-project
* SPDX-License-Identifier: AGPL-3.0-only
*/
import * as Misskey from 'misskey-js';
import { $i } from '@/account.js';
const providedContextEl = document.getElementById('misskey_clientCtx');
export type ServerContext = {
clip?: Misskey.entities.Clip;
note?: Misskey.entities.Note;
user?: Misskey.entities.UserLite;
user?: Misskey.entities.UserDetailed;
} | null;
export const serverContext: ServerContext = (providedContextEl && providedContextEl.textContent) ? JSON.parse(providedContextEl.textContent) : null;
export function getServerContext<K extends keyof NonNullable<ServerContext>>(entity: K): Required<Pick<NonNullable<ServerContext>, K>> | null {
// contextは非ログイン状態の情報しかないためログイン時は利用できない
if ($i) return null;
return serverContext ? (serverContext[entity] ?? null) : null;
export function assertServerContext<K extends keyof NonNullable<ServerContext>>(ctx: ServerContext, entity: K): ctx is Required<Pick<NonNullable<ServerContext>, K>> {
if (ctx == null) return false;
return entity in ctx && ctx[entity] != null;
}

View File

@ -115,6 +115,9 @@ type AdminAnnouncementsListRequest = operations['admin___announcements___list'][
// @public (undocumented)
type AdminAnnouncementsListResponse = operations['admin___announcements___list']['responses']['200']['content']['application/json'];
// @public (undocumented)
type AdminAnnouncementsResetReadsRequest = operations['admin___announcements___reset-reads']['requestBody']['content']['application/json'];
// @public (undocumented)
type AdminAnnouncementsUpdateRequest = operations['admin___announcements___update']['requestBody']['content']['application/json'];
@ -1254,6 +1257,7 @@ declare namespace entities {
AdminAnnouncementsDeleteRequest,
AdminAnnouncementsListRequest,
AdminAnnouncementsListResponse,
AdminAnnouncementsResetReadsRequest,
AdminAnnouncementsUpdateRequest,
AdminAvatarDecorationsCreateRequest,
AdminAvatarDecorationsCreateResponse,
@ -2525,6 +2529,12 @@ type ModerationLog = {
} | {
type: 'deleteUserAnnouncement';
info: ModerationLogPayloads['deleteUserAnnouncement'];
} | {
type: 'resetReadsForGlobalAnnouncement';
info: ModerationLogPayloads['resetReadsForGlobalAnnouncement'];
} | {
type: 'resetReadsForUserAnnouncement';
info: ModerationLogPayloads['resetReadsForUserAnnouncement'];
} | {
type: 'resetPassword';
info: ModerationLogPayloads['resetPassword'];

View File

@ -44,7 +44,7 @@ export class APIClient {
credential?: APIClient['credential'];
fetch?: APIClient['fetch'] | null | undefined;
}) {
this.origin = opts.origin;
this.origin = opts.origin.replace(/\/$/, '');
this.credential = opts.credential;
// ネイティブ関数をそのまま変数に代入して使おうとするとChromiumではIllegal invocationエラーが発生するため、
// 環境で実装されているfetchを使う場合は無名関数でラップして使用する

View File

@ -195,6 +195,17 @@ declare module '../api.js' {
credential?: string | null,
): Promise<SwitchCaseResponseType<E, P>>;
/**
* No description provided.
*
* **Credential required**: *Yes* / **Permission**: *write:admin:announcements*
*/
request<E extends 'admin/announcements/reset-reads', P extends Endpoints[E]['req']>(
endpoint: E,
params: P,
credential?: string | null,
): Promise<SwitchCaseResponseType<E, P>>;
/**
* No description provided.
*

View File

@ -29,6 +29,7 @@ import type {
AdminAnnouncementsDeleteRequest,
AdminAnnouncementsListRequest,
AdminAnnouncementsListResponse,
AdminAnnouncementsResetReadsRequest,
AdminAnnouncementsUpdateRequest,
AdminAvatarDecorationsCreateRequest,
AdminAvatarDecorationsCreateResponse,
@ -599,6 +600,7 @@ export type Endpoints = {
'admin/announcements/create': { req: AdminAnnouncementsCreateRequest; res: AdminAnnouncementsCreateResponse };
'admin/announcements/delete': { req: AdminAnnouncementsDeleteRequest; res: EmptyResponse };
'admin/announcements/list': { req: AdminAnnouncementsListRequest; res: AdminAnnouncementsListResponse };
'admin/announcements/reset-reads': { req: AdminAnnouncementsResetReadsRequest; res: EmptyResponse };
'admin/announcements/update': { req: AdminAnnouncementsUpdateRequest; res: EmptyResponse };
'admin/avatar-decorations/create': { req: AdminAvatarDecorationsCreateRequest; res: AdminAvatarDecorationsCreateResponse };
'admin/avatar-decorations/delete': { req: AdminAvatarDecorationsDeleteRequest; res: EmptyResponse };

View File

@ -32,6 +32,7 @@ export type AdminAnnouncementsCreateResponse = operations['admin___announcements
export type AdminAnnouncementsDeleteRequest = operations['admin___announcements___delete']['requestBody']['content']['application/json'];
export type AdminAnnouncementsListRequest = operations['admin___announcements___list']['requestBody']['content']['application/json'];
export type AdminAnnouncementsListResponse = operations['admin___announcements___list']['responses']['200']['content']['application/json'];
export type AdminAnnouncementsResetReadsRequest = operations['admin___announcements___reset-reads']['requestBody']['content']['application/json'];
export type AdminAnnouncementsUpdateRequest = operations['admin___announcements___update']['requestBody']['content']['application/json'];
export type AdminAvatarDecorationsCreateRequest = operations['admin___avatar-decorations___create']['requestBody']['content']['application/json'];
export type AdminAvatarDecorationsCreateResponse = operations['admin___avatar-decorations___create']['responses']['200']['content']['application/json'];

View File

@ -170,6 +170,15 @@ export type paths = {
*/
post: operations['admin___announcements___list'];
};
'/admin/announcements/reset-reads': {
/**
* admin/announcements/reset-reads
* @description No description provided.
*
* **Credential required**: *Yes* / **Permission**: *write:admin:announcements*
*/
post: operations['admin___announcements___reset-reads'];
};
'/admin/announcements/update': {
/**
* admin/announcements/update
@ -6216,6 +6225,16 @@ export type operations = {
text: string;
title: string;
imageUrl: string | null;
/** @enum {string} */
icon: 'info' | 'warning' | 'error' | 'success';
/** @enum {string} */
display: 'normal' | 'banner' | 'dialog';
isActive: boolean;
forExistingUsers: boolean;
silence: boolean;
needConfirmationToRead: boolean;
/** Format: id */
userId: string | null;
reads: number;
})[];
};
@ -6252,6 +6271,58 @@ export type operations = {
};
};
};
/**
* admin/announcements/reset-reads
* @description No description provided.
*
* **Credential required**: *Yes* / **Permission**: *write:admin:announcements*
*/
'admin___announcements___reset-reads': {
requestBody: {
content: {
'application/json': {
/** Format: misskey:id */
announcementId: 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 Internal server error */
500: {
content: {
'application/json': components['schemas']['Error'];
};
};
};
};
/**
* admin/announcements/update
* @description No description provided.

View File

@ -298,6 +298,17 @@ export type ModerationLogPayloads = {
userUsername: string;
userHost: string | null;
};
resetReadsForGlobalAnnouncement: {
announcementId: string;
announcement: Announcement;
};
resetReadsForUserAnnouncement: {
announcementId: string;
announcement: Announcement;
userId: string;
userUsername: string;
userHost: string | null;
};
resetPassword: {
userId: string;
userUsername: string;

View File

@ -111,6 +111,12 @@ export type ModerationLog = {
} | {
type: 'deleteUserAnnouncement';
info: ModerationLogPayloads['deleteUserAnnouncement'];
} | {
type: 'resetReadsForGlobalAnnouncement';
info: ModerationLogPayloads['resetReadsForGlobalAnnouncement'];
} | {
type: 'resetReadsForUserAnnouncement';
info: ModerationLogPayloads['resetReadsForUserAnnouncement'];
} | {
type: 'resetPassword';
info: ModerationLogPayloads['resetPassword'];