From 6f489b58a18310fa9d8aef695d984f7ceb312102 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Wed, 17 Apr 2024 10:48:42 +0900 Subject: [PATCH 001/154] =?UTF-8?q?enhance(frontend):=20=E3=83=9A=E3=83=BC?= =?UTF-8?q?=E3=82=B8=E3=81=AE=E8=A1=A8=E7=A4=BA=E9=83=A8=E4=B8=8A=E9=83=A8?= =?UTF-8?q?=E3=81=AB=E7=B7=A8=E9=9B=86=E3=83=AA=E3=83=B3=E3=82=AF=E3=82=92?= =?UTF-8?q?=E8=BF=BD=E5=8A=A0=20(#13724)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/pages/page.vue | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/packages/frontend/src/pages/page.vue b/packages/frontend/src/pages/page.vue index ab44533b81..893c2deebf 100644 --- a/packages/frontend/src/pages/page.vue +++ b/packages/frontend/src/pages/page.vue @@ -12,6 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only :leaveActiveClass="defaultStore.state.animation ? $style.fadeLeaveActive : ''" :enterFromClass="defaultStore.state.animation ? $style.fadeEnterFrom : ''" :leaveToClass="defaultStore.state.animation ? $style.fadeLeaveTo : ''" + mode="out-in" >
@@ -41,8 +42,14 @@ SPDX-License-Identifier: AGPL-3.0-only

{{ page.title || page.name }}

-
- +
+
+ +
+
+ + +
@@ -355,8 +362,15 @@ definePageMetadata(() => ({ margin: 0; } + .pageBannerTitleSub { + display: flex; + align-items: center; + width: 100%; + } + .pageBannerTitleUser { --height: 32px; + flex-shrink: 0; .avatar { height: var(--height); @@ -365,6 +379,14 @@ definePageMetadata(() => ({ line-height: var(--height); } + + .pageBannerTitleSubActions { + flex-shrink: 0; + display: flex; + align-items: center; + gap: var(--marginHalf); + margin-left: auto; + } } } From 977e2d2c09c3fbc2fd2eaead8fc7314d8d6f9fc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Wed, 17 Apr 2024 10:53:16 +0900 Subject: [PATCH 002/154] =?UTF-8?q?enhance(frontend):=20=E3=83=95=E3=82=A9?= =?UTF-8?q?=E3=83=AD=E3=83=BC=E3=81=99=E3=82=8B=E3=81=8B=E3=81=A9=E3=81=86?= =?UTF-8?q?=E3=81=8B=E3=81=AE=E7=A2=BA=E8=AA=8D=E3=83=80=E3=82=A4=E3=82=A2?= =?UTF-8?q?=E3=83=AD=E3=82=B0=E3=82=92=E5=87=BA=E3=81=9B=E3=82=8B=E3=82=88?= =?UTF-8?q?=E3=81=86=E3=81=AB=20(#13723)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(frontend): フォローするかどうかの確認ダイアログを出せるように * Update Changelog --- CHANGELOG.md | 1 + locales/index.d.ts | 4 ++++ locales/ja-JP.yml | 1 + packages/frontend/src/components/MkFollowButton.vue | 12 ++++++++++++ packages/frontend/src/pages/settings/general.vue | 3 +++ packages/frontend/src/store.ts | 4 ++++ 6 files changed, 25 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4aad65d837..d27979d88f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,7 @@ - Enhance: ノートについているリアクションの「もっと!」から、リアクションの一覧を表示できるように - Enhance: リプライにて引用がある場合テキストが空でもノートできるように - 引用したいノートのURLをコピーしリプライ投稿画面にペーストして添付することで達成できます +- Enhance: フォローするかどうかの確認ダイアログを出せるように - Fix: 一部のページ内リンクが正しく動作しない問題を修正 - Fix: 周年の実績が閏年を考慮しない問題を修正 - Fix: ローカルURLのプレビューポップアップが左上に表示される diff --git a/locales/index.d.ts b/locales/index.d.ts index cbea39f1cd..8e31fc8d59 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -4948,6 +4948,10 @@ export interface Locale extends ILocale { * 説明文はありません */ "noDescription": string; + /** + * フォローの際常に確認する + */ + "alwaysConfirmFollow": string; "_bubbleGame": { /** * 遊び方 diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 4ab2f5adb0..f598459792 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1233,6 +1233,7 @@ useNativeUIForVideoAudioPlayer: "動画・音声の再生にブラウザのUIを keepOriginalFilename: "オリジナルのファイル名を保持" keepOriginalFilenameDescription: "この設定をオフにすると、アップロード時にファイル名が自動でランダム文字列に置き換えられます。" noDescription: "説明文はありません" +alwaysConfirmFollow: "フォローの際常に確認する" _bubbleGame: howToPlay: "遊び方" diff --git a/packages/frontend/src/components/MkFollowButton.vue b/packages/frontend/src/components/MkFollowButton.vue index 28450e11fc..636e61db8f 100644 --- a/packages/frontend/src/components/MkFollowButton.vue +++ b/packages/frontend/src/components/MkFollowButton.vue @@ -93,6 +93,18 @@ async function onClick() { userId: props.user.id, }); } else { + if (defaultStore.state.alwaysConfirmFollow) { + const { canceled } = await os.confirm({ + type: 'question', + text: i18n.tsx.followConfirm({ name: props.user.name || props.user.username }), + }); + + if (canceled) { + wait.value = false; + return; + } + } + if (hasPendingFollowRequestFromYou.value) { await misskeyApi('following/requests/cancel', { userId: props.user.id, diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue index f2f82c4808..55d514ddf9 100644 --- a/packages/frontend/src/pages/settings/general.vue +++ b/packages/frontend/src/pages/settings/general.vue @@ -165,6 +165,7 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts.keepScreenOn }} {{ i18n.ts.disableStreamingTimeline }} {{ i18n.ts.enableHorizontalSwipe }} + {{ i18n.ts.alwaysConfirmFollow }}
@@ -310,6 +311,7 @@ const useGroupedNotifications = computed(defaultStore.makeGetterSetter('useGroup const enableSeasonalScreenEffect = computed(defaultStore.makeGetterSetter('enableSeasonalScreenEffect')); const enableHorizontalSwipe = computed(defaultStore.makeGetterSetter('enableHorizontalSwipe')); const useNativeUIForVideoAudioPlayer = computed(defaultStore.makeGetterSetter('useNativeUIForVideoAudioPlayer')); +const alwaysConfirmFollow = computed(defaultStore.makeGetterSetter('alwaysConfirmFollow')); watch(lang, () => { miLocalStorage.setItem('lang', lang.value as string); @@ -351,6 +353,7 @@ watch([ keepScreenOn, disableStreamingTimeline, enableSeasonalScreenEffect, + alwaysConfirmFollow, ], async () => { await reloadAsk(); }); diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index 9b5011739a..e6a348b79f 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -450,6 +450,10 @@ export const defaultStore = markRaw(new Storage('base', { where: 'device', default: true, }, + alwaysConfirmFollow: { + where: 'device', + default: true, + }, sound_masterVolume: { where: 'device', From e423b8ce4b28ecbe4e300fc67389e4def3761eb6 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Wed, 17 Apr 2024 14:23:41 +0900 Subject: [PATCH 003/154] =?UTF-8?q?=E7=B4=B0=E3=81=8B=E3=81=84=E3=83=9F?= =?UTF-8?q?=E3=83=A5=E3=83=BC=E3=83=88=E3=81=AE=E5=87=A6=E7=90=86=E3=81=AE?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20(#13695)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: some replies are removed from global timeline * refactor: 各チャンネルのミュートとブロックの処理をまとめる * fix: リノートをミュートでその人のノートのリノートをミュートしていたを修正 * refactor: isPureRenotePackedを他のところでも使う * docs(changelog): CHANGELOGを更新 * test: withReplies = falseでフォローしてる人によるリプライが流れてくる * test: ノートミュートしているユーザーの通常ノートのリノートが流れてくる/含まれる --- CHANGELOG.md | 3 ++ .../src/core/FanoutTimelineEndpointService.ts | 2 +- packages/backend/src/misc/is-renote.ts | 31 +++++++++++++++++++ .../backend/src/server/api/stream/channel.ts | 22 +++++++++++++ .../src/server/api/stream/channels/antenna.ts | 8 +---- .../src/server/api/stream/channels/channel.ts | 11 ++----- .../api/stream/channels/global-timeline.ts | 25 +++------------ .../src/server/api/stream/channels/hashtag.ts | 11 ++----- .../api/stream/channels/home-timeline.ts | 18 +++-------- .../api/stream/channels/hybrid-timeline.ts | 16 ++-------- .../api/stream/channels/local-timeline.ts | 14 +++------ .../api/stream/channels/role-timeline.ts | 9 +----- .../server/api/stream/channels/user-list.ts | 17 +++------- packages/backend/test/e2e/renote-mute.ts | 29 +++++++++++++++++ packages/backend/test/e2e/streaming.ts | 12 ++++++- 15 files changed, 124 insertions(+), 104 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d27979d88f..36632e5024 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -55,6 +55,9 @@ - Fix: 登録にメール認証が必須になっている場合、登録されているメールアドレスを削除できないように (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/606) - Fix: nginx経由で/files/にRangeリクエストされた場合に正しく応答できないのを修正 +- Fix: 一部のタイムラインのストリーミングでインスタンスミュートが効かない問題を修正 +- Fix: グローバルタイムラインで返信が表示されないことがある問題を修正 +- Fix: リノートをミュートしたユーザの投稿のリノートがミュートされる問題を修正 ## 2024.3.1 diff --git a/packages/backend/src/core/FanoutTimelineEndpointService.ts b/packages/backend/src/core/FanoutTimelineEndpointService.ts index 006433df7a..884723ff81 100644 --- a/packages/backend/src/core/FanoutTimelineEndpointService.ts +++ b/packages/backend/src/core/FanoutTimelineEndpointService.ts @@ -116,7 +116,7 @@ export class FanoutTimelineEndpointService { filter = (note) => { if (isUserRelated(note, userIdsWhoBlockingMe, ps.ignoreAuthorFromBlock)) return false; if (isUserRelated(note, userIdsWhoMeMuting, ps.ignoreAuthorFromMute)) return false; - if (isRenote(note) && !isQuote(note) && isUserRelated(note, userIdsWhoMeMutingRenotes, ps.ignoreAuthorFromMute)) return false; + if (!ps.ignoreAuthorFromMute && isRenote(note) && !isQuote(note) && userIdsWhoMeMutingRenotes.has(note.userId)) return false; if (isInstanceMuted(note, userMutedInstances)) return false; return parentFilter(note); diff --git a/packages/backend/src/misc/is-renote.ts b/packages/backend/src/misc/is-renote.ts index 5d48aba360..48f821806c 100644 --- a/packages/backend/src/misc/is-renote.ts +++ b/packages/backend/src/misc/is-renote.ts @@ -4,6 +4,7 @@ */ import type { MiNote } from '@/models/Note.js'; +import type { Packed } from '@/misc/json-schema.js'; type Renote = MiNote & { @@ -34,3 +35,33 @@ export function isQuote(note: Renote): note is Quote { note.hasPoll || note.fileIds.length > 0; } + +type PackedRenote = + Packed<'Note'> & { + renoteId: NonNullable['renoteId']> + }; + +type PackedQuote = + PackedRenote & ({ + text: NonNullable['text']> + } | { + cw: NonNullable['cw']> + } | { + replyId: NonNullable['replyId']> + } | { + poll: NonNullable['poll']> + } | { + fileIds: NonNullable['fileIds']> + }); + +export function isRenotePacked(note: Packed<'Note'>): note is PackedRenote { + return note.renoteId != null; +} + +export function isQuotePacked(note: PackedRenote): note is PackedQuote { + return note.text != null || + note.cw != null || + note.replyId != null || + note.poll != null || + (note.fileIds != null && note.fileIds.length > 0); +} diff --git a/packages/backend/src/server/api/stream/channel.ts b/packages/backend/src/server/api/stream/channel.ts index 44a143538b..a267d27fba 100644 --- a/packages/backend/src/server/api/stream/channel.ts +++ b/packages/backend/src/server/api/stream/channel.ts @@ -4,6 +4,10 @@ */ import { bindThis } from '@/decorators.js'; +import { isInstanceMuted } from '@/misc/is-instance-muted.js'; +import { isUserRelated } from '@/misc/is-user-related.js'; +import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js'; +import type { Packed } from '@/misc/json-schema.js'; import type Connection from './Connection.js'; /** @@ -54,6 +58,24 @@ export default abstract class Channel { return this.connection.subscriber; } + /* + * ミュートとブロックされてるを処理する + */ + protected isNoteMutedOrBlocked(note: Packed<'Note'>): boolean { + // 流れてきたNoteがインスタンスミュートしたインスタンスが関わる + if (isInstanceMuted(note, new Set(this.userProfile?.mutedInstances ?? []))) return true; + + // 流れてきたNoteがミュートしているユーザーが関わる + if (isUserRelated(note, this.userIdsWhoMeMuting)) return true; + // 流れてきたNoteがブロックされているユーザーが関わる + if (isUserRelated(note, this.userIdsWhoBlockingMe)) return true; + + // 流れてきたNoteがリノートをミュートしてるユーザが行ったもの + if (isRenotePacked(note) && !isQuotePacked(note) && this.userIdsWhoMeMutingRenotes.has(note.user.id)) return true; + + return false; + } + constructor(id: string, connection: Connection) { this.id = id; this.connection = connection; diff --git a/packages/backend/src/server/api/stream/channels/antenna.ts b/packages/backend/src/server/api/stream/channels/antenna.ts index 135d162e63..4a1d2dd109 100644 --- a/packages/backend/src/server/api/stream/channels/antenna.ts +++ b/packages/backend/src/server/api/stream/channels/antenna.ts @@ -4,7 +4,6 @@ */ import { Injectable } from '@nestjs/common'; -import { isUserRelated } from '@/misc/is-user-related.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { bindThis } from '@/decorators.js'; import type { GlobalEvents } from '@/core/GlobalEventService.js'; @@ -40,12 +39,7 @@ class AntennaChannel extends Channel { if (data.type === 'note') { const note = await this.noteEntityService.pack(data.body.id, this.user, { detail: true }); - // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する - if (isUserRelated(note, this.userIdsWhoMeMuting)) return; - // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する - if (isUserRelated(note, this.userIdsWhoBlockingMe)) return; - - if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return; + if (this.isNoteMutedOrBlocked(note)) return; this.connection.cacheNote(note); diff --git a/packages/backend/src/server/api/stream/channels/channel.ts b/packages/backend/src/server/api/stream/channels/channel.ts index 90ee1ecda5..140dd3dd9b 100644 --- a/packages/backend/src/server/api/stream/channels/channel.ts +++ b/packages/backend/src/server/api/stream/channels/channel.ts @@ -4,10 +4,10 @@ */ import { Injectable } from '@nestjs/common'; -import { isUserRelated } from '@/misc/is-user-related.js'; import type { Packed } from '@/misc/json-schema.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { bindThis } from '@/decorators.js'; +import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js'; import Channel, { type MiChannelService } from '../channel.js'; class ChannelChannel extends Channel { @@ -38,14 +38,9 @@ class ChannelChannel extends Channel { private async onNote(note: Packed<'Note'>) { if (note.channelId !== this.channelId) return; - // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する - if (isUserRelated(note, this.userIdsWhoMeMuting)) return; - // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する - if (isUserRelated(note, this.userIdsWhoBlockingMe)) return; + if (this.isNoteMutedOrBlocked(note)) return; - if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return; - - if (this.user && note.renoteId && !note.text) { + if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { if (note.renote && Object.keys(note.renote.reactions).length > 0) { const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); note.renote.myReaction = myRenoteReaction; diff --git a/packages/backend/src/server/api/stream/channels/global-timeline.ts b/packages/backend/src/server/api/stream/channels/global-timeline.ts index 723b89c908..17116258d8 100644 --- a/packages/backend/src/server/api/stream/channels/global-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/global-timeline.ts @@ -4,14 +4,12 @@ */ import { Injectable } from '@nestjs/common'; -import { checkWordMute } from '@/misc/check-word-mute.js'; -import { isInstanceMuted } from '@/misc/is-instance-muted.js'; -import { isUserRelated } from '@/misc/is-user-related.js'; import type { Packed } from '@/misc/json-schema.js'; import { MetaService } from '@/core/MetaService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { bindThis } from '@/decorators.js'; import { RoleService } from '@/core/RoleService.js'; +import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js'; import Channel, { type MiChannelService } from '../channel.js'; class GlobalTimelineChannel extends Channel { @@ -52,26 +50,11 @@ class GlobalTimelineChannel extends Channel { if (note.visibility !== 'public') return; if (note.channelId != null) return; - // 関係ない返信は除外 - if (note.reply && !this.following[note.userId]?.withReplies) { - const reply = note.reply; - // 「チャンネル接続主への返信」でもなければ、「チャンネル接続主が行った返信」でもなければ、「投稿者の投稿者自身への返信」でもない場合 - if (reply.userId !== this.user!.id && note.userId !== this.user!.id && reply.userId !== note.userId) return; - } - - if (note.renote && note.text == null && (note.fileIds == null || note.fileIds.length === 0) && !this.withRenotes) return; - - // Ignore notes from instances the user has muted - if (isInstanceMuted(note, new Set(this.userProfile?.mutedInstances ?? []))) return; - - // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する - if (isUserRelated(note, this.userIdsWhoMeMuting)) return; - // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する - if (isUserRelated(note, this.userIdsWhoBlockingMe)) return; + if (isRenotePacked(note) && !isQuotePacked(note) && !this.withRenotes) return; - if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return; + if (this.isNoteMutedOrBlocked(note)) return; - if (this.user && note.renoteId && !note.text) { + if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { if (note.renote && Object.keys(note.renote.reactions).length > 0) { const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); note.renote.myReaction = myRenoteReaction; diff --git a/packages/backend/src/server/api/stream/channels/hashtag.ts b/packages/backend/src/server/api/stream/channels/hashtag.ts index 377b1a0162..57bada5d9c 100644 --- a/packages/backend/src/server/api/stream/channels/hashtag.ts +++ b/packages/backend/src/server/api/stream/channels/hashtag.ts @@ -5,10 +5,10 @@ import { Injectable } from '@nestjs/common'; import { normalizeForSearch } from '@/misc/normalize-for-search.js'; -import { isUserRelated } from '@/misc/is-user-related.js'; import type { Packed } from '@/misc/json-schema.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { bindThis } from '@/decorators.js'; +import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js'; import Channel, { type MiChannelService } from '../channel.js'; class HashtagChannel extends Channel { @@ -43,14 +43,9 @@ class HashtagChannel extends Channel { const matched = this.q.some(tags => tags.every(tag => noteTags.includes(normalizeForSearch(tag)))); if (!matched) return; - // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する - if (isUserRelated(note, this.userIdsWhoMeMuting)) return; - // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する - if (isUserRelated(note, this.userIdsWhoBlockingMe)) return; + if (this.isNoteMutedOrBlocked(note)) return; - if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return; - - if (this.user && note.renoteId && !note.text) { + if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { if (note.renote && Object.keys(note.renote.reactions).length > 0) { const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); note.renote.myReaction = myRenoteReaction; diff --git a/packages/backend/src/server/api/stream/channels/home-timeline.ts b/packages/backend/src/server/api/stream/channels/home-timeline.ts index f45bf8622e..878a3180cb 100644 --- a/packages/backend/src/server/api/stream/channels/home-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/home-timeline.ts @@ -4,12 +4,10 @@ */ import { Injectable } from '@nestjs/common'; -import { checkWordMute } from '@/misc/check-word-mute.js'; -import { isUserRelated } from '@/misc/is-user-related.js'; -import { isInstanceMuted } from '@/misc/is-instance-muted.js'; import type { Packed } from '@/misc/json-schema.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { bindThis } from '@/decorators.js'; +import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js'; import Channel, { type MiChannelService } from '../channel.js'; class HomeTimelineChannel extends Channel { @@ -51,9 +49,6 @@ class HomeTimelineChannel extends Channel { if (!isMe && !Object.hasOwn(this.following, note.userId)) return; } - // Ignore notes from instances the user has muted - if (isInstanceMuted(note, new Set(this.userProfile!.mutedInstances))) return; - if (note.visibility === 'followers') { if (!isMe && !Object.hasOwn(this.following, note.userId)) return; } else if (note.visibility === 'specified') { @@ -72,7 +67,7 @@ class HomeTimelineChannel extends Channel { } // 純粋なリノート(引用リノートでないリノート)の場合 - if (note.renote && note.text == null && (note.fileIds == null || note.fileIds.length === 0) && note.poll == null) { + if (isRenotePacked(note) && !isQuotePacked(note) && note.renote) { if (!this.withRenotes) return; if (note.renote.reply) { const reply = note.renote.reply; @@ -81,14 +76,9 @@ class HomeTimelineChannel extends Channel { } } - // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する - if (isUserRelated(note, this.userIdsWhoMeMuting)) return; - // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する - if (isUserRelated(note, this.userIdsWhoBlockingMe)) return; - - if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return; + if (this.isNoteMutedOrBlocked(note)) return; - if (this.user && note.renoteId && !note.text) { + if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { if (note.renote && Object.keys(note.renote.reactions).length > 0) { const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); note.renote.myReaction = myRenoteReaction; diff --git a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts index d67da6f565..575d23d53c 100644 --- a/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/hybrid-timeline.ts @@ -4,14 +4,12 @@ */ import { Injectable } from '@nestjs/common'; -import { checkWordMute } from '@/misc/check-word-mute.js'; -import { isUserRelated } from '@/misc/is-user-related.js'; -import { isInstanceMuted } from '@/misc/is-instance-muted.js'; import type { Packed } from '@/misc/json-schema.js'; import { MetaService } from '@/core/MetaService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { bindThis } from '@/decorators.js'; import { RoleService } from '@/core/RoleService.js'; +import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js'; import Channel, { type MiChannelService } from '../channel.js'; class HybridTimelineChannel extends Channel { @@ -71,8 +69,7 @@ class HybridTimelineChannel extends Channel { if (!isMe && !note.visibleUserIds!.includes(this.user!.id)) return; } - // Ignore notes from instances the user has muted - if (isInstanceMuted(note, new Set(this.userProfile!.mutedInstances))) return; + if (this.isNoteMutedOrBlocked(note)) return; if (note.reply) { const reply = note.reply; @@ -85,14 +82,7 @@ class HybridTimelineChannel extends Channel { } } - if (note.renote && note.text == null && (note.fileIds == null || note.fileIds.length === 0) && !this.withRenotes) return; - - // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する - if (isUserRelated(note, this.userIdsWhoMeMuting)) return; - // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する - if (isUserRelated(note, this.userIdsWhoBlockingMe)) return; - - if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return; + if (isRenotePacked(note) && !isQuotePacked(note) && !this.withRenotes) return; if (this.user && note.renoteId && !note.text) { if (note.renote && Object.keys(note.renote.reactions).length > 0) { diff --git a/packages/backend/src/server/api/stream/channels/local-timeline.ts b/packages/backend/src/server/api/stream/channels/local-timeline.ts index 43d26124ef..442d08ae51 100644 --- a/packages/backend/src/server/api/stream/channels/local-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/local-timeline.ts @@ -4,13 +4,12 @@ */ import { Injectable } from '@nestjs/common'; -import { checkWordMute } from '@/misc/check-word-mute.js'; -import { isUserRelated } from '@/misc/is-user-related.js'; import type { Packed } from '@/misc/json-schema.js'; import { MetaService } from '@/core/MetaService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { bindThis } from '@/decorators.js'; import { RoleService } from '@/core/RoleService.js'; +import { isQuotePacked, isRenotePacked } from '@/misc/is-renote.js'; import Channel, { type MiChannelService } from '../channel.js'; class LocalTimelineChannel extends Channel { @@ -61,16 +60,11 @@ class LocalTimelineChannel extends Channel { if (reply.userId !== this.user.id && note.userId !== this.user.id && reply.userId !== note.userId) return; } - if (note.renote && note.text == null && (note.fileIds == null || note.fileIds.length === 0) && !this.withRenotes) return; + if (isRenotePacked(note) && !isQuotePacked(note) && !this.withRenotes) return; - // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する - if (isUserRelated(note, this.userIdsWhoMeMuting)) return; - // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する - if (isUserRelated(note, this.userIdsWhoBlockingMe)) return; + if (this.isNoteMutedOrBlocked(note)) return; - if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return; - - if (this.user && note.renoteId && !note.text) { + if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { if (note.renote && Object.keys(note.renote.reactions).length > 0) { const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); note.renote.myReaction = myRenoteReaction; diff --git a/packages/backend/src/server/api/stream/channels/role-timeline.ts b/packages/backend/src/server/api/stream/channels/role-timeline.ts index 80aab4b35e..6a4ad22460 100644 --- a/packages/backend/src/server/api/stream/channels/role-timeline.ts +++ b/packages/backend/src/server/api/stream/channels/role-timeline.ts @@ -4,8 +4,6 @@ */ import { Injectable } from '@nestjs/common'; -import { isUserRelated } from '@/misc/is-user-related.js'; -import type { Packed } from '@/misc/json-schema.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { bindThis } from '@/decorators.js'; import { RoleService } from '@/core/RoleService.js'; @@ -46,12 +44,7 @@ class RoleTimelineChannel extends Channel { } if (note.visibility !== 'public') return; - // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する - if (isUserRelated(note, this.userIdsWhoMeMuting)) return; - // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する - if (isUserRelated(note, this.userIdsWhoBlockingMe)) return; - - if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return; + if (this.isNoteMutedOrBlocked(note)) return; this.send('note', note); } else { diff --git a/packages/backend/src/server/api/stream/channels/user-list.ts b/packages/backend/src/server/api/stream/channels/user-list.ts index f7bb106c03..14b30a157c 100644 --- a/packages/backend/src/server/api/stream/channels/user-list.ts +++ b/packages/backend/src/server/api/stream/channels/user-list.ts @@ -5,12 +5,11 @@ import { Inject, Injectable } from '@nestjs/common'; import type { MiUserListMembership, UserListMembershipsRepository, UserListsRepository } from '@/models/_.js'; -import { isUserRelated } from '@/misc/is-user-related.js'; import type { Packed } from '@/misc/json-schema.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; -import { isInstanceMuted } from '@/misc/is-instance-muted.js'; +import { isRenotePacked, isQuotePacked } from '@/misc/is-renote.js'; import Channel, { type MiChannelService } from '../channel.js'; class UserListChannel extends Channel { @@ -106,25 +105,17 @@ class UserListChannel extends Channel { } } - if (note.renote && note.text == null && (note.fileIds == null || note.fileIds.length === 0) && !this.withRenotes) return; + if (isRenotePacked(note) && !isQuotePacked(note) && !this.withRenotes) return; - // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する - if (isUserRelated(note, this.userIdsWhoMeMuting)) return; - // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する - if (isUserRelated(note, this.userIdsWhoBlockingMe)) return; + if (this.isNoteMutedOrBlocked(note)) return; - if (note.renote && !note.text && isUserRelated(note, this.userIdsWhoMeMutingRenotes)) return; - - if (this.user && note.renoteId && !note.text) { + if (this.user && isRenotePacked(note) && !isQuotePacked(note)) { if (note.renote && Object.keys(note.renote.reactions).length > 0) { const myRenoteReaction = await this.noteEntityService.populateMyReaction(note.renote, this.user.id); note.renote.myReaction = myRenoteReaction; } } - // 流れてきたNoteがミュートしているインスタンスに関わるものだったら無視する - if (isInstanceMuted(note, this.userMutedInstances)) return; - this.connection.cacheNote(note); this.send('note', note); diff --git a/packages/backend/test/e2e/renote-mute.ts b/packages/backend/test/e2e/renote-mute.ts index 9826068e48..1abbb4f044 100644 --- a/packages/backend/test/e2e/renote-mute.ts +++ b/packages/backend/test/e2e/renote-mute.ts @@ -63,6 +63,22 @@ describe('Renote Mute', () => { assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true); }); + // #12956 + test('タイムラインにリノートミュートしているユーザーの通常ノートのリノートが含まれる', async () => { + const carolNote = await post(carol, { text: 'hi' }); + const bobRenote = await post(bob, { renoteId: carolNote.id }); + + // redisに追加されるのを待つ + await sleep(100); + + const res = await api('notes/local-timeline', {}, alice); + + assert.strictEqual(res.status, 200); + assert.strictEqual(Array.isArray(res.body), true); + assert.strictEqual(res.body.some((note: any) => note.id === carolNote.id), true); + assert.strictEqual(res.body.some((note: any) => note.id === bobRenote.id), true); + }); + test('ストリームにリノートミュートしているユーザーのリノートが流れない', async () => { const bobNote = await post(bob, { text: 'hi' }); @@ -86,4 +102,17 @@ describe('Renote Mute', () => { assert.strictEqual(fired, true); }); + + // #12956 + test('ストリームにリノートミュートしているユーザーの通常ノートのリノートが流れてくる', async () => { + const carolbNote = await post(carol, { text: 'hi' }); + + const fired = await waitFire( + alice, 'localTimeline', + () => api('notes/create', { renoteId: carolbNote.id }, bob), + msg => msg.type === 'note' && msg.body.userId === bob.id, + ); + + assert.strictEqual(fired, true); + }); }); diff --git a/packages/backend/test/e2e/streaming.ts b/packages/backend/test/e2e/streaming.ts index 26bb68ec6c..b0a70074c6 100644 --- a/packages/backend/test/e2e/streaming.ts +++ b/packages/backend/test/e2e/streaming.ts @@ -63,7 +63,7 @@ describe('Streaming', () => { takumiNote = await post(takumi, { text: 'piyo' }); // Follow: ayano => kyoko - await api('following/create', { userId: kyoko.id }, ayano); + await api('following/create', { userId: kyoko.id, withReplies: false }, ayano); // Follow: ayano => akari await follow(ayano, akari); @@ -509,6 +509,16 @@ describe('Streaming', () => { assert.strictEqual(fired, false); }); + + test('withReplies = falseでフォローしてる人によるリプライが流れてくる', async () => { + const fired = await waitFire( + ayano, 'globalTimeline', // ayano:Global + () => api('notes/create', { text: 'foo', replyId: kanakoNote.id }, kyoko), // kyoko posts + msg => msg.type === 'note' && msg.body.userId === kyoko.id, // wait kyoko + ); + + assert.strictEqual(fired, true); + }); }); describe('UserList Timeline', () => { From ea9aa6fdb41d3d5c0611f17fdacedee4861fdd37 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Wed, 17 Apr 2024 18:29:35 +0900 Subject: [PATCH 004/154] =?UTF-8?q?:art:=20=EF=BC=88=E3=83=9A=E3=83=BC?= =?UTF-8?q?=E3=82=B8=E8=A1=A8=E7=A4=BA=E9=83=A8=E4=B8=8A=E9=83=A8=E3=81=AE?= =?UTF-8?q?=E3=83=9C=E3=82=BF=E3=83=B3=E9=A0=86=E5=BA=8F=E3=82=92=E5=A4=89?= =?UTF-8?q?=E6=9B=B4=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix https://github.com/misskey-dev/misskey/pull/13724#discussion_r1568179954 --- packages/frontend/src/pages/page.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/frontend/src/pages/page.vue b/packages/frontend/src/pages/page.vue index 893c2deebf..e73d032000 100644 --- a/packages/frontend/src/pages/page.vue +++ b/packages/frontend/src/pages/page.vue @@ -47,8 +47,8 @@ SPDX-License-Identifier: AGPL-3.0-only
- +
From cd7f7271ca5595cae95f6fb0280fac9dee77d751 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8A=E3=81=95=E3=82=80=E3=81=AE=E3=81=B2=E3=81=A8?= <46447427+samunohito@users.noreply.github.com> Date: Fri, 19 Apr 2024 15:22:23 +0900 Subject: [PATCH 005/154] =?UTF-8?q?enhance:=20=E6=96=B0=E3=81=97=E3=81=84?= =?UTF-8?q?=E3=82=B3=E3=83=B3=E3=83=87=E3=82=A3=E3=82=B7=E3=83=A7=E3=83=8A?= =?UTF-8?q?=E3=83=AB=E3=83=AD=E3=83=BC=E3=83=AB=E6=9D=A1=E4=BB=B6=E3=81=AE?= =?UTF-8?q?=E5=AE=9F=E8=A3=85=20(#13732)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * enhance: 新しいコンディショナルロールの実装 * fix: CHANGELOG.md --- CHANGELOG.md | 6 + locales/index.d.ts | 20 + locales/ja-JP.yml | 5 + packages/backend/src/core/RoleService.ts | 34 ++ packages/backend/src/misc/json-schema.ts | 2 + packages/backend/src/models/Role.ts | 85 +++ .../backend/src/models/json-schema/role.ts | 17 + packages/backend/test/unit/RoleService.ts | 498 +++++++++++++++--- .../src/pages/admin/RolesEditorFormula.vue | 5 + packages/misskey-js/etc/misskey-js.api.md | 4 + packages/misskey-js/src/autogen/models.ts | 1 + packages/misskey-js/src/autogen/types.ts | 7 +- 12 files changed, 617 insertions(+), 67 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36632e5024..c13fa664dd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,12 @@ - Enhance: アンテナでBotによるノートを除外できるように (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/545) - Enhance: クリップのノート数を表示するように +- Enhance: コンディショナルロールの条件として以下を新たに追加 (#13667) + - 猫ユーザーか + - botユーザーか + - サスペンド済みユーザーか + - 鍵アカウントユーザーか + - 「アカウントを見つけやすくする」が有効なユーザーか - Fix: Play作成時に設定した公開範囲が機能していない問題を修正 ### Client diff --git a/locales/index.d.ts b/locales/index.d.ts index 8e31fc8d59..9bcd1979af 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -6592,6 +6592,26 @@ export interface Locale extends ILocale { * リモートユーザー */ "isRemote": string; + /** + * 猫ユーザー + */ + "isCat": string; + /** + * botユーザー + */ + "isBot": string; + /** + * サスペンド済みユーザー + */ + "isSuspended": string; + /** + * 鍵アカウントユーザー + */ + "isLocked": string; + /** + * 「アカウントを見つけやすくする」が有効なユーザー + */ + "isExplorable": string; /** * アカウント作成から~以内 */ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index f598459792..5f7715b210 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1703,6 +1703,11 @@ _role: roleAssignedTo: "マニュアルロールにアサイン済み" isLocal: "ローカルユーザー" isRemote: "リモートユーザー" + isCat: "猫ユーザー" + isBot: "botユーザー" + isSuspended: "サスペンド済みユーザー" + isLocked: "鍵アカウントユーザー" + isExplorable: "「アカウントを見つけやすくする」が有効なユーザー" createdLessThan: "アカウント作成から~以内" createdMoreThan: "アカウント作成から~経過" followersLessThanOrEq: "フォロワー数が~以下" diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index 09f3097114..70c537f9ab 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -205,45 +205,79 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit { private evalCond(user: MiUser, roles: MiRole[], value: RoleCondFormulaValue): boolean { try { switch (value.type) { + // ~かつ~ case 'and': { return value.values.every(v => this.evalCond(user, roles, v)); } + // ~または~ case 'or': { return value.values.some(v => this.evalCond(user, roles, v)); } + // ~ではない case 'not': { return !this.evalCond(user, roles, value.value); } + // マニュアルロールがアサインされている case 'roleAssignedTo': { return roles.some(r => r.id === value.roleId); } + // ローカルユーザのみ case 'isLocal': { return this.userEntityService.isLocalUser(user); } + // リモートユーザのみ case 'isRemote': { return this.userEntityService.isRemoteUser(user); } + // サスペンド済みユーザである + case 'isSuspended': { + return user.isSuspended; + } + // 鍵アカウントユーザである + case 'isLocked': { + return user.isLocked; + } + // botユーザである + case 'isBot': { + return user.isBot; + } + // 猫である + case 'isCat': { + return user.isCat; + } + // 「ユーザを見つけやすくする」が有効なアカウント + case 'isExplorable': { + return user.isExplorable; + } + // ユーザが作成されてから指定期間経過した case 'createdLessThan': { return this.idService.parse(user.id).date.getTime() > (Date.now() - (value.sec * 1000)); } + // ユーザが作成されてから指定期間経っていない case 'createdMoreThan': { return this.idService.parse(user.id).date.getTime() < (Date.now() - (value.sec * 1000)); } + // フォロワー数が指定値以下 case 'followersLessThanOrEq': { return user.followersCount <= value.value; } + // フォロワー数が指定値以上 case 'followersMoreThanOrEq': { return user.followersCount >= value.value; } + // フォロー数が指定値以下 case 'followingLessThanOrEq': { return user.followingCount <= value.value; } + // フォロー数が指定値以上 case 'followingMoreThanOrEq': { return user.followingCount >= value.value; } + // ノート数が指定値以下 case 'notesLessThanOrEq': { return user.notesCount <= value.value; } + // ノート数が指定値以上 case 'notesMoreThanOrEq': { return user.notesCount >= value.value; } diff --git a/packages/backend/src/misc/json-schema.ts b/packages/backend/src/misc/json-schema.ts index 46b0bb2fab..a620d7c94b 100644 --- a/packages/backend/src/misc/json-schema.ts +++ b/packages/backend/src/misc/json-schema.ts @@ -48,6 +48,7 @@ import { packedRoleCondFormulaValueCreatedSchema, packedRoleCondFormulaFollowersOrFollowingOrNotesSchema, packedRoleCondFormulaValueSchema, + packedRoleCondFormulaValueUserSettingBooleanSchema, } from '@/models/json-schema/role.js'; import { packedAdSchema } from '@/models/json-schema/ad.js'; import { packedReversiGameLiteSchema, packedReversiGameDetailedSchema } from '@/models/json-schema/reversi-game.js'; @@ -97,6 +98,7 @@ export const refs = { RoleCondFormulaLogics: packedRoleCondFormulaLogicsSchema, RoleCondFormulaValueNot: packedRoleCondFormulaValueNot, RoleCondFormulaValueIsLocalOrRemote: packedRoleCondFormulaValueIsLocalOrRemoteSchema, + RoleCondFormulaValueUserSettingBooleanSchema: packedRoleCondFormulaValueUserSettingBooleanSchema, RoleCondFormulaValueAssignedRole: packedRoleCondFormulaValueAssignedRoleSchema, RoleCondFormulaValueCreated: packedRoleCondFormulaValueCreatedSchema, RoleCondFormulaFollowersOrFollowingOrNotes: packedRoleCondFormulaFollowersOrFollowingOrNotesSchema, diff --git a/packages/backend/src/models/Role.ts b/packages/backend/src/models/Role.ts index 058abe3118..a173971b2c 100644 --- a/packages/backend/src/models/Role.ts +++ b/packages/backend/src/models/Role.ts @@ -6,69 +6,149 @@ import { Entity, Column, PrimaryColumn } from 'typeorm'; import { id } from './util/id.js'; +/** + * ~かつ~ + * 複数の条件を同時に満たす場合のみ成立とする + */ type CondFormulaValueAnd = { type: 'and'; values: RoleCondFormulaValue[]; }; +/** + * ~または~ + * 複数の条件のうち、いずれかを満たす場合のみ成立とする + */ type CondFormulaValueOr = { type: 'or'; values: RoleCondFormulaValue[]; }; +/** + * ~ではない + * 条件を満たさない場合のみ成立とする + */ type CondFormulaValueNot = { type: 'not'; value: RoleCondFormulaValue; }; +/** + * ローカルユーザーのみ成立とする + */ type CondFormulaValueIsLocal = { type: 'isLocal'; }; +/** + * リモートユーザーのみ成立とする + */ type CondFormulaValueIsRemote = { type: 'isRemote'; }; +/** + * 既に指定のマニュアルロールにアサインされている場合のみ成立とする + */ type CondFormulaValueRoleAssignedTo = { type: 'roleAssignedTo'; roleId: string; }; +/** + * サスペンド済みアカウントの場合のみ成立とする + */ +type CondFormulaValueIsSuspended = { + type: 'isSuspended'; +}; + +/** + * 鍵アカウントの場合のみ成立とする + */ +type CondFormulaValueIsLocked = { + type: 'isLocked'; +}; + +/** + * botアカウントの場合のみ成立とする + */ +type CondFormulaValueIsBot = { + type: 'isBot'; +}; + +/** + * 猫アカウントの場合のみ成立とする + */ +type CondFormulaValueIsCat = { + type: 'isCat'; +}; + +/** + * 「ユーザを見つけやすくする」が有効なアカウントの場合のみ成立とする + */ +type CondFormulaValueIsExplorable = { + type: 'isExplorable'; +}; + +/** + * ユーザが作成されてから指定期間経過した場合のみ成立とする + */ type CondFormulaValueCreatedLessThan = { type: 'createdLessThan'; sec: number; }; +/** + * ユーザが作成されてから指定期間経っていない場合のみ成立とする + */ type CondFormulaValueCreatedMoreThan = { type: 'createdMoreThan'; sec: number; }; +/** + * フォロワー数が指定値以下の場合のみ成立とする + */ type CondFormulaValueFollowersLessThanOrEq = { type: 'followersLessThanOrEq'; value: number; }; +/** + * フォロワー数が指定値以上の場合のみ成立とする + */ type CondFormulaValueFollowersMoreThanOrEq = { type: 'followersMoreThanOrEq'; value: number; }; +/** + * フォロー数が指定値以下の場合のみ成立とする + */ type CondFormulaValueFollowingLessThanOrEq = { type: 'followingLessThanOrEq'; value: number; }; +/** + * フォロー数が指定値以上の場合のみ成立とする + */ type CondFormulaValueFollowingMoreThanOrEq = { type: 'followingMoreThanOrEq'; value: number; }; +/** + * 投稿数が指定値以下の場合のみ成立とする + */ type CondFormulaValueNotesLessThanOrEq = { type: 'notesLessThanOrEq'; value: number; }; +/** + * 投稿数が指定値以上の場合のみ成立とする + */ type CondFormulaValueNotesMoreThanOrEq = { type: 'notesMoreThanOrEq'; value: number; @@ -80,6 +160,11 @@ export type RoleCondFormulaValue = { id: string } & ( CondFormulaValueNot | CondFormulaValueIsLocal | CondFormulaValueIsRemote | + CondFormulaValueIsSuspended | + CondFormulaValueIsLocked | + CondFormulaValueIsBot | + CondFormulaValueIsCat | + CondFormulaValueIsExplorable | CondFormulaValueRoleAssignedTo | CondFormulaValueCreatedLessThan | CondFormulaValueCreatedMoreThan | diff --git a/packages/backend/src/models/json-schema/role.ts b/packages/backend/src/models/json-schema/role.ts index c770250503..d9987a70c3 100644 --- a/packages/backend/src/models/json-schema/role.ts +++ b/packages/backend/src/models/json-schema/role.ts @@ -57,6 +57,20 @@ export const packedRoleCondFormulaValueIsLocalOrRemoteSchema = { }, } as const; +export const packedRoleCondFormulaValueUserSettingBooleanSchema = { + type: 'object', + properties: { + id: { + type: 'string', optional: false, + }, + type: { + type: 'string', + nullable: false, optional: false, + enum: ['isSuspended', 'isLocked', 'isBot', 'isCat', 'isExplorable'], + }, + }, +} as const; + export const packedRoleCondFormulaValueAssignedRoleSchema = { type: 'object', properties: { @@ -135,6 +149,9 @@ export const packedRoleCondFormulaValueSchema = { { ref: 'RoleCondFormulaValueIsLocalOrRemote', }, + { + ref: 'RoleCondFormulaValueUserSettingBooleanSchema', + }, { ref: 'RoleCondFormulaValueAssignedRole', }, diff --git a/packages/backend/test/unit/RoleService.ts b/packages/backend/test/unit/RoleService.ts index 19d03570e0..ec441735d7 100644 --- a/packages/backend/test/unit/RoleService.ts +++ b/packages/backend/test/unit/RoleService.ts @@ -3,6 +3,8 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +import { UserEntityService } from '@/core/entities/UserEntityService.js'; + process.env.NODE_ENV = 'test'; import { jest } from '@jest/globals'; @@ -20,6 +22,7 @@ import { IdService } from '@/core/IdService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { secureRndstr } from '@/misc/secure-rndstr.js'; import { NotificationService } from '@/core/NotificationService.js'; +import { RoleCondFormulaValue } from '@/models/Role.js'; import { sleep } from '../utils.js'; import type { TestingModule } from '@nestjs/testing'; import type { MockFunctionMetadata } from 'jest-mock'; @@ -52,12 +55,26 @@ describe('RoleService', () => { id: genAidx(Date.now()), updatedAt: new Date(), lastUsedAt: new Date(), + name: '', description: '', ...data, }) .then(x => rolesRepository.findOneByOrFail(x.identifiers[0])); } + function createConditionalRole(condFormula: RoleCondFormulaValue, data: Partial = {}) { + return createRole({ + name: `[conditional] ${condFormula.type}`, + target: 'conditional', + condFormula: condFormula, + ...data, + }); + } + + function aidx() { + return genAidx(Date.now()); + } + beforeEach(async () => { clock = lolex.install({ now: new Date(), @@ -73,6 +90,7 @@ describe('RoleService', () => { CacheService, IdService, GlobalEventService, + UserEntityService, { provide: NotificationService, useFactory: () => ({ @@ -209,15 +227,9 @@ describe('RoleService', () => { expect(result.driveCapacityMb).toBe(100); }); - test('conditional role', async () => { - const user1 = await createUser({ - id: genAidx(Date.now() - (1000 * 60 * 60 * 24 * 365)), - }); - const user2 = await createUser({ - id: genAidx(Date.now() - (1000 * 60 * 60 * 24 * 365)), - followersCount: 10, - }); - await createRole({ + test('expired role', async () => { + const user = await createUser(); + const role = await createRole({ name: 'a', policies: { canManageCustomEmojis: { @@ -226,35 +238,133 @@ describe('RoleService', () => { value: true, }, }, - target: 'conditional', - condFormula: { - id: '232a4221-9816-49a6-a967-ae0fac52ec5e', - type: 'and', - values: [{ - id: '2a37ef43-2d93-4c4d-87f6-f2fdb7d9b530', - type: 'followersMoreThanOrEq', - value: 10, - }, { - id: '1bd67839-b126-4f92-bad0-4e285dab453b', - type: 'createdMoreThan', - sec: 60 * 60 * 24 * 7, - }], - }, }); - + await roleService.assign(user.id, role.id, new Date(Date.now() + (1000 * 60 * 60 * 24))); metaService.fetch.mockResolvedValue({ policies: { canManageCustomEmojis: false, }, } as any); - const user1Policies = await roleService.getUserPolicies(user1.id); - const user2Policies = await roleService.getUserPolicies(user2.id); - expect(user1Policies.canManageCustomEmojis).toBe(false); - expect(user2Policies.canManageCustomEmojis).toBe(true); + const result = await roleService.getUserPolicies(user.id); + expect(result.canManageCustomEmojis).toBe(true); + + clock.tick('25:00:00'); + + const resultAfter25h = await roleService.getUserPolicies(user.id); + expect(resultAfter25h.canManageCustomEmojis).toBe(false); + + await roleService.assign(user.id, role.id); + + // ストリーミング経由で反映されるまでちょっと待つ + clock.uninstall(); + await sleep(100); + + const resultAfter25hAgain = await roleService.getUserPolicies(user.id); + expect(resultAfter25hAgain.canManageCustomEmojis).toBe(true); + }); + }); + + describe('conditional role', () => { + test('~かつ~', async () => { + const [user1, user2, user3, user4] = await Promise.all([ + createUser({ isBot: true, isCat: false, isSuspended: false }), + createUser({ isBot: false, isCat: true, isSuspended: false }), + createUser({ isBot: true, isCat: true, isSuspended: false }), + createUser({ isBot: false, isCat: false, isSuspended: true }), + ]); + const role1 = await createConditionalRole({ + id: aidx(), + type: 'isBot', + }); + const role2 = await createConditionalRole({ + id: aidx(), + type: 'isCat', + }); + const role3 = await createConditionalRole({ + id: aidx(), + type: 'isSuspended', + }); + const role4 = await createConditionalRole({ + id: aidx(), + type: 'and', + values: [role1.condFormula, role2.condFormula], + }); + + const actual1 = await roleService.getUserRoles(user1.id); + const actual2 = await roleService.getUserRoles(user2.id); + const actual3 = await roleService.getUserRoles(user3.id); + const actual4 = await roleService.getUserRoles(user4.id); + expect(actual1.some(r => r.id === role4.id)).toBe(false); + expect(actual2.some(r => r.id === role4.id)).toBe(false); + expect(actual3.some(r => r.id === role4.id)).toBe(true); + expect(actual4.some(r => r.id === role4.id)).toBe(false); + }); + + test('~または~', async () => { + const [user1, user2, user3, user4] = await Promise.all([ + createUser({ isBot: true, isCat: false, isSuspended: false }), + createUser({ isBot: false, isCat: true, isSuspended: false }), + createUser({ isBot: true, isCat: true, isSuspended: false }), + createUser({ isBot: false, isCat: false, isSuspended: true }), + ]); + const role1 = await createConditionalRole({ + id: aidx(), + type: 'isBot', + }); + const role2 = await createConditionalRole({ + id: aidx(), + type: 'isCat', + }); + const role3 = await createConditionalRole({ + id: aidx(), + type: 'isSuspended', + }); + const role4 = await createConditionalRole({ + id: aidx(), + type: 'or', + values: [role1.condFormula, role2.condFormula], + }); + + const actual1 = await roleService.getUserRoles(user1.id); + const actual2 = await roleService.getUserRoles(user2.id); + const actual3 = await roleService.getUserRoles(user3.id); + const actual4 = await roleService.getUserRoles(user4.id); + expect(actual1.some(r => r.id === role4.id)).toBe(true); + expect(actual2.some(r => r.id === role4.id)).toBe(true); + expect(actual3.some(r => r.id === role4.id)).toBe(true); + expect(actual4.some(r => r.id === role4.id)).toBe(false); }); - test('コンディショナルロール: マニュアルロールにアサイン済み', async () => { + test('~ではない', async () => { + const [user1, user2, user3] = await Promise.all([ + createUser({ isBot: true, isCat: false, isSuspended: false }), + createUser({ isBot: false, isCat: true, isSuspended: false }), + createUser({ isBot: true, isCat: true, isSuspended: false }), + ]); + const role1 = await createConditionalRole({ + id: aidx(), + type: 'isBot', + }); + const role2 = await createConditionalRole({ + id: aidx(), + type: 'isCat', + }); + const role4 = await createConditionalRole({ + id: aidx(), + type: 'not', + value: role1.condFormula, + }); + + const actual1 = await roleService.getUserRoles(user1.id); + const actual2 = await roleService.getUserRoles(user2.id); + const actual3 = await roleService.getUserRoles(user3.id); + expect(actual1.some(r => r.id === role4.id)).toBe(false); + expect(actual2.some(r => r.id === role4.id)).toBe(true); + expect(actual3.some(r => r.id === role4.id)).toBe(false); + }); + + test('マニュアルロールにアサイン済み', async () => { const [user1, user2, role1] = await Promise.all([ createUser(), createUser(), @@ -262,15 +372,10 @@ describe('RoleService', () => { name: 'manual role', }), ]); - const role2 = await createRole({ - name: 'conditional role', - target: 'conditional', - condFormula: { - // idはバックエンドのロジックに必要ない? - id: 'bdc612bd-9d54-4675-ae83-0499c82ea670', - type: 'roleAssignedTo', - roleId: role1.id, - }, + const role2 = await createConditionalRole({ + id: aidx(), + type: 'roleAssignedTo', + roleId: role1.id, }); await roleService.assign(user2.id, role1.id); @@ -282,41 +387,302 @@ describe('RoleService', () => { expect(u2role.some(r => r.id === role2.id)).toBe(true); }); - test('expired role', async () => { - const user = await createUser(); - const role = await createRole({ - name: 'a', - policies: { - canManageCustomEmojis: { - useDefault: false, - priority: 0, - value: true, - }, - }, + test('ローカルユーザのみ', async () => { + const [user1, user2] = await Promise.all([ + createUser({ host: null }), + createUser({ host: 'example.com' }), + ]); + const role = await createConditionalRole({ + id: aidx(), + type: 'isLocal', }); - await roleService.assign(user.id, role.id, new Date(Date.now() + (1000 * 60 * 60 * 24))); - metaService.fetch.mockResolvedValue({ - policies: { - canManageCustomEmojis: false, - }, - } as any); - const result = await roleService.getUserPolicies(user.id); - expect(result.canManageCustomEmojis).toBe(true); + const actual1 = await roleService.getUserRoles(user1.id); + const actual2 = await roleService.getUserRoles(user2.id); + expect(actual1.some(r => r.id === role.id)).toBe(true); + expect(actual2.some(r => r.id === role.id)).toBe(false); + }); - clock.tick('25:00:00'); + test('リモートユーザのみ', async () => { + const [user1, user2] = await Promise.all([ + createUser({ host: null }), + createUser({ host: 'example.com' }), + ]); + const role = await createConditionalRole({ + id: aidx(), + type: 'isRemote', + }); - const resultAfter25h = await roleService.getUserPolicies(user.id); - expect(resultAfter25h.canManageCustomEmojis).toBe(false); + const actual1 = await roleService.getUserRoles(user1.id); + const actual2 = await roleService.getUserRoles(user2.id); + expect(actual1.some(r => r.id === role.id)).toBe(false); + expect(actual2.some(r => r.id === role.id)).toBe(true); + }); - await roleService.assign(user.id, role.id); + test('サスペンド済みユーザである', async () => { + const [user1, user2] = await Promise.all([ + createUser({ isSuspended: false }), + createUser({ isSuspended: true }), + ]); + const role = await createConditionalRole({ + id: aidx(), + type: 'isSuspended', + }); - // ストリーミング経由で反映されるまでちょっと待つ - clock.uninstall(); - await sleep(100); + const actual1 = await roleService.getUserRoles(user1.id); + const actual2 = await roleService.getUserRoles(user2.id); + expect(actual1.some(r => r.id === role.id)).toBe(false); + expect(actual2.some(r => r.id === role.id)).toBe(true); + }); - const resultAfter25hAgain = await roleService.getUserPolicies(user.id); - expect(resultAfter25hAgain.canManageCustomEmojis).toBe(true); + test('鍵アカウントユーザである', async () => { + const [user1, user2] = await Promise.all([ + createUser({ isLocked: false }), + createUser({ isLocked: true }), + ]); + const role = await createConditionalRole({ + id: aidx(), + type: 'isLocked', + }); + + const actual1 = await roleService.getUserRoles(user1.id); + const actual2 = await roleService.getUserRoles(user2.id); + expect(actual1.some(r => r.id === role.id)).toBe(false); + expect(actual2.some(r => r.id === role.id)).toBe(true); + }); + + test('botユーザである', async () => { + const [user1, user2] = await Promise.all([ + createUser({ isBot: false }), + createUser({ isBot: true }), + ]); + const role = await createConditionalRole({ + id: aidx(), + type: 'isBot', + }); + + const actual1 = await roleService.getUserRoles(user1.id); + const actual2 = await roleService.getUserRoles(user2.id); + expect(actual1.some(r => r.id === role.id)).toBe(false); + expect(actual2.some(r => r.id === role.id)).toBe(true); + }); + + test('猫である', async () => { + const [user1, user2] = await Promise.all([ + createUser({ isCat: false }), + createUser({ isCat: true }), + ]); + const role = await createConditionalRole({ + id: aidx(), + type: 'isCat', + }); + + const actual1 = await roleService.getUserRoles(user1.id); + const actual2 = await roleService.getUserRoles(user2.id); + expect(actual1.some(r => r.id === role.id)).toBe(false); + expect(actual2.some(r => r.id === role.id)).toBe(true); + }); + + test('「ユーザを見つけやすくする」が有効なアカウント', async () => { + const [user1, user2] = await Promise.all([ + createUser({ isExplorable: false }), + createUser({ isExplorable: true }), + ]); + const role = await createConditionalRole({ + id: aidx(), + type: 'isExplorable', + }); + + const actual1 = await roleService.getUserRoles(user1.id); + const actual2 = await roleService.getUserRoles(user2.id); + expect(actual1.some(r => r.id === role.id)).toBe(false); + expect(actual2.some(r => r.id === role.id)).toBe(true); + }); + + test('ユーザが作成されてから指定期間経過した', async () => { + const base = new Date(); + base.setMinutes(base.getMinutes() - 5); + + const d1 = new Date(base); + const d2 = new Date(base); + const d3 = new Date(base); + d1.setSeconds(d1.getSeconds() - 1); + d3.setSeconds(d3.getSeconds() + 1); + + const [user1, user2, user3] = await Promise.all([ + // 4:59 + createUser({ id: genAidx(d1.getTime()) }), + // 5:00 + createUser({ id: genAidx(d2.getTime()) }), + // 5:01 + createUser({ id: genAidx(d3.getTime()) }), + ]); + const role = await createConditionalRole({ + id: aidx(), + type: 'createdLessThan', + // 5 minutes + sec: 300, + }); + + const actual1 = await roleService.getUserRoles(user1.id); + const actual2 = await roleService.getUserRoles(user2.id); + const actual3 = await roleService.getUserRoles(user3.id); + expect(actual1.some(r => r.id === role.id)).toBe(false); + expect(actual2.some(r => r.id === role.id)).toBe(false); + expect(actual3.some(r => r.id === role.id)).toBe(true); + }); + + test('ユーザが作成されてから指定期間経っていない', async () => { + const base = new Date(); + base.setMinutes(base.getMinutes() - 5); + + const d1 = new Date(base); + const d2 = new Date(base); + const d3 = new Date(base); + d1.setSeconds(d1.getSeconds() - 1); + d3.setSeconds(d3.getSeconds() + 1); + + const [user1, user2, user3] = await Promise.all([ + // 4:59 + createUser({ id: genAidx(d1.getTime()) }), + // 5:00 + createUser({ id: genAidx(d2.getTime()) }), + // 5:01 + createUser({ id: genAidx(d3.getTime()) }), + ]); + const role = await createConditionalRole({ + id: aidx(), + type: 'createdMoreThan', + // 5 minutes + sec: 300, + }); + + const actual1 = await roleService.getUserRoles(user1.id); + const actual2 = await roleService.getUserRoles(user2.id); + const actual3 = await roleService.getUserRoles(user3.id); + expect(actual1.some(r => r.id === role.id)).toBe(true); + expect(actual2.some(r => r.id === role.id)).toBe(false); + expect(actual3.some(r => r.id === role.id)).toBe(false); + }); + + test('フォロワー数が指定値以下', async () => { + const [user1, user2, user3] = await Promise.all([ + createUser({ followersCount: 99 }), + createUser({ followersCount: 100 }), + createUser({ followersCount: 101 }), + ]); + const role = await createConditionalRole({ + id: aidx(), + type: 'followersLessThanOrEq', + value: 100, + }); + + const actual1 = await roleService.getUserRoles(user1.id); + const actual2 = await roleService.getUserRoles(user2.id); + const actual3 = await roleService.getUserRoles(user3.id); + expect(actual1.some(r => r.id === role.id)).toBe(true); + expect(actual2.some(r => r.id === role.id)).toBe(true); + expect(actual3.some(r => r.id === role.id)).toBe(false); + }); + + test('フォロワー数が指定値以下', async () => { + const [user1, user2, user3] = await Promise.all([ + createUser({ followersCount: 99 }), + createUser({ followersCount: 100 }), + createUser({ followersCount: 101 }), + ]); + const role = await createConditionalRole({ + id: aidx(), + type: 'followersMoreThanOrEq', + value: 100, + }); + + const actual1 = await roleService.getUserRoles(user1.id); + const actual2 = await roleService.getUserRoles(user2.id); + const actual3 = await roleService.getUserRoles(user3.id); + expect(actual1.some(r => r.id === role.id)).toBe(false); + expect(actual2.some(r => r.id === role.id)).toBe(true); + expect(actual3.some(r => r.id === role.id)).toBe(true); + }); + + test('フォロー数が指定値以下', async () => { + const [user1, user2, user3] = await Promise.all([ + createUser({ followingCount: 99 }), + createUser({ followingCount: 100 }), + createUser({ followingCount: 101 }), + ]); + const role = await createConditionalRole({ + id: aidx(), + type: 'followingLessThanOrEq', + value: 100, + }); + + const actual1 = await roleService.getUserRoles(user1.id); + const actual2 = await roleService.getUserRoles(user2.id); + const actual3 = await roleService.getUserRoles(user3.id); + expect(actual1.some(r => r.id === role.id)).toBe(true); + expect(actual2.some(r => r.id === role.id)).toBe(true); + expect(actual3.some(r => r.id === role.id)).toBe(false); + }); + + test('フォロー数が指定値以上', async () => { + const [user1, user2, user3] = await Promise.all([ + createUser({ followingCount: 99 }), + createUser({ followingCount: 100 }), + createUser({ followingCount: 101 }), + ]); + const role = await createConditionalRole({ + id: aidx(), + type: 'followingMoreThanOrEq', + value: 100, + }); + + const actual1 = await roleService.getUserRoles(user1.id); + const actual2 = await roleService.getUserRoles(user2.id); + const actual3 = await roleService.getUserRoles(user3.id); + expect(actual1.some(r => r.id === role.id)).toBe(false); + expect(actual2.some(r => r.id === role.id)).toBe(true); + expect(actual3.some(r => r.id === role.id)).toBe(true); + }); + + test('ノート数が指定値以下', async () => { + const [user1, user2, user3] = await Promise.all([ + createUser({ notesCount: 9 }), + createUser({ notesCount: 10 }), + createUser({ notesCount: 11 }), + ]); + const role = await createConditionalRole({ + id: aidx(), + type: 'notesLessThanOrEq', + value: 10, + }); + + const actual1 = await roleService.getUserRoles(user1.id); + const actual2 = await roleService.getUserRoles(user2.id); + const actual3 = await roleService.getUserRoles(user3.id); + expect(actual1.some(r => r.id === role.id)).toBe(true); + expect(actual2.some(r => r.id === role.id)).toBe(true); + expect(actual3.some(r => r.id === role.id)).toBe(false); + }); + + test('ノート数が指定値以上', async () => { + const [user1, user2, user3] = await Promise.all([ + createUser({ notesCount: 9 }), + createUser({ notesCount: 10 }), + createUser({ notesCount: 11 }), + ]); + const role = await createConditionalRole({ + id: aidx(), + type: 'notesMoreThanOrEq', + value: 10, + }); + + const actual1 = await roleService.getUserRoles(user1.id); + const actual2 = await roleService.getUserRoles(user2.id); + const actual3 = await roleService.getUserRoles(user3.id); + expect(actual1.some(r => r.id === role.id)).toBe(false); + expect(actual2.some(r => r.id === role.id)).toBe(true); + expect(actual3.some(r => r.id === role.id)).toBe(true); }); }); diff --git a/packages/frontend/src/pages/admin/RolesEditorFormula.vue b/packages/frontend/src/pages/admin/RolesEditorFormula.vue index 2f5b4c47d8..f001a4ac20 100644 --- a/packages/frontend/src/pages/admin/RolesEditorFormula.vue +++ b/packages/frontend/src/pages/admin/RolesEditorFormula.vue @@ -9,6 +9,11 @@ SPDX-License-Identifier: AGPL-3.0-only + + + + + diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index 360724d2a9..9720b04e39 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -1713,6 +1713,7 @@ declare namespace entities { RoleCondFormulaLogics, RoleCondFormulaValueNot, RoleCondFormulaValueIsLocalOrRemote, + RoleCondFormulaValueUserSettingBooleanSchema, RoleCondFormulaValueAssignedRole, RoleCondFormulaValueCreated, RoleCondFormulaFollowersOrFollowingOrNotes, @@ -2745,6 +2746,9 @@ type RoleCondFormulaValueIsLocalOrRemote = components['schemas']['RoleCondFormul // @public (undocumented) type RoleCondFormulaValueNot = components['schemas']['RoleCondFormulaValueNot']; +// @public (undocumented) +type RoleCondFormulaValueUserSettingBooleanSchema = components['schemas']['RoleCondFormulaValueUserSettingBooleanSchema']; + // @public (undocumented) type RoleLite = components['schemas']['RoleLite']; diff --git a/packages/misskey-js/src/autogen/models.ts b/packages/misskey-js/src/autogen/models.ts index 6f61458600..a6e5fbe689 100644 --- a/packages/misskey-js/src/autogen/models.ts +++ b/packages/misskey-js/src/autogen/models.ts @@ -38,6 +38,7 @@ export type Signin = components['schemas']['Signin']; export type RoleCondFormulaLogics = components['schemas']['RoleCondFormulaLogics']; export type RoleCondFormulaValueNot = components['schemas']['RoleCondFormulaValueNot']; export type RoleCondFormulaValueIsLocalOrRemote = components['schemas']['RoleCondFormulaValueIsLocalOrRemote']; +export type RoleCondFormulaValueUserSettingBooleanSchema = components['schemas']['RoleCondFormulaValueUserSettingBooleanSchema']; export type RoleCondFormulaValueAssignedRole = components['schemas']['RoleCondFormulaValueAssignedRole']; export type RoleCondFormulaValueCreated = components['schemas']['RoleCondFormulaValueCreated']; export type RoleCondFormulaFollowersOrFollowingOrNotes = components['schemas']['RoleCondFormulaFollowersOrFollowingOrNotes']; diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index ae001cf874..131d20f09b 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -4586,6 +4586,11 @@ export type components = { /** @enum {string} */ type: 'isLocal' | 'isRemote'; }; + RoleCondFormulaValueUserSettingBooleanSchema: { + id: string; + /** @enum {string} */ + type: 'isSuspended' | 'isLocked' | 'isBot' | 'isCat' | 'isExplorable'; + }; RoleCondFormulaValueAssignedRole: { id: string; /** @enum {string} */ @@ -4608,7 +4613,7 @@ export type components = { type: 'followersLessThanOrEq' | 'followersMoreThanOrEq' | 'followingLessThanOrEq' | 'followingMoreThanOrEq' | 'notesLessThanOrEq' | 'notesMoreThanOrEq'; value: number; }; - RoleCondFormulaValue: components['schemas']['RoleCondFormulaLogics'] | components['schemas']['RoleCondFormulaValueNot'] | components['schemas']['RoleCondFormulaValueIsLocalOrRemote'] | components['schemas']['RoleCondFormulaValueAssignedRole'] | components['schemas']['RoleCondFormulaValueCreated'] | components['schemas']['RoleCondFormulaFollowersOrFollowingOrNotes']; + RoleCondFormulaValue: components['schemas']['RoleCondFormulaLogics'] | components['schemas']['RoleCondFormulaValueNot'] | components['schemas']['RoleCondFormulaValueIsLocalOrRemote'] | components['schemas']['RoleCondFormulaValueUserSettingBooleanSchema'] | components['schemas']['RoleCondFormulaValueAssignedRole'] | components['schemas']['RoleCondFormulaValueCreated'] | components['schemas']['RoleCondFormulaFollowersOrFollowingOrNotes']; RoleLite: { /** * Format: id From f9aed8f2bf994902386878d1212912caa3a57b0d Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Fri, 19 Apr 2024 19:42:01 +0900 Subject: [PATCH 006/154] =?UTF-8?q?fix:=20=E6=AD=A3=E8=A6=8F=E5=8C=96?= =?UTF-8?q?=E3=81=95=E3=82=8C=E3=81=A6=E3=81=84=E3=81=AA=E3=81=84=E7=8A=B6?= =?UTF-8?q?=E6=85=8B=E3=81=AEhashtag=E3=81=8C=E9=80=A3=E5=90=88=E3=81=95?= =?UTF-8?q?=E3=82=8C=E3=81=A6=E3=81=8D=E3=81=9Fhtml=E3=81=AB=E5=90=AB?= =?UTF-8?q?=E3=81=BE=E3=82=8C=E3=81=A6=E3=81=84=E3=82=8B=E3=81=A8hashtag?= =?UTF-8?q?=E3=81=8C=E6=AD=A3=E3=81=97=E3=81=8Fhashtag=E3=81=AB=E5=BE=A9?= =?UTF-8?q?=E5=85=83=E3=81=95=E3=82=8C=E3=81=AA=E3=81=84=E5=95=8F=E9=A1=8C?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3=20(#13733)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 1 + packages/backend/src/core/MfmService.ts | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c13fa664dd..8c8bcf0ea4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - 鍵アカウントユーザーか - 「アカウントを見つけやすくする」が有効なユーザーか - Fix: Play作成時に設定した公開範囲が機能していない問題を修正 +- Fix: 正規化されていない状態のhashtagが連合されてきたhtmlに含まれているとhashtagが正しくhashtagに復元されない問題を修正 ### Client - Feat: アップロードするファイルの名前をランダム文字列にできるように diff --git a/packages/backend/src/core/MfmService.ts b/packages/backend/src/core/MfmService.ts index c62ee5a642..2fb731201b 100644 --- a/packages/backend/src/core/MfmService.ts +++ b/packages/backend/src/core/MfmService.ts @@ -10,6 +10,7 @@ import { Window } from 'happy-dom'; import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import { intersperse } from '@/misc/prelude/array.js'; +import { normalizeForSearch } from '@/misc/normalize-for-search.js'; import type { IMentionedRemoteUsers } from '@/models/Note.js'; import { bindThis } from '@/decorators.js'; import * as TreeAdapter from '../../node_modules/parse5/dist/tree-adapters/default.js'; @@ -33,6 +34,8 @@ export class MfmService { // some AP servers like Pixelfed use br tags as well as newlines html = html.replace(/\r?\n/gi, '\n'); + const normalizedHashtagNames = hashtagNames == null ? undefined : new Set(hashtagNames.map(x => normalizeForSearch(x))); + const dom = parse5.parseFragment(html); let text = ''; @@ -85,7 +88,7 @@ export class MfmService { const href = node.attrs.find(x => x.name === 'href'); // ハッシュタグ - if (hashtagNames && href && hashtagNames.map(x => x.toLowerCase()).includes(txt.toLowerCase())) { + if (normalizedHashtagNames && href && normalizedHashtagNames.has(normalizeForSearch(txt))) { text += txt; // メンション } else if (txt.startsWith('@') && !(rel && rel.value.startsWith('me '))) { From 553ba8479298fa70f4c168b679ae42f8364df17f Mon Sep 17 00:00:00 2001 From: FineArchs <133759614+FineArchs@users.noreply.github.com> Date: Thu, 25 Apr 2024 10:34:26 +0900 Subject: [PATCH 007/154] =?UTF-8?q?AiScript=E3=81=AE=E3=83=90=E3=83=BC?= =?UTF-8?q?=E3=82=B8=E3=83=A7=E3=83=B3=E3=82=920.18.0=E3=81=AB=E4=B8=8A?= =?UTF-8?q?=E3=81=92=E3=82=8B=20(#13743)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update package.json * Update autogen files * Update flash-edit.vue * Update flash-edit.vue * Update CHANGELOG.md * revert --- CHANGELOG.md | 1 + packages/frontend/package.json | 2 +- .../frontend/src/pages/flash/flash-edit.vue | 34 +++++++++---------- pnpm-lock.yaml | 14 ++++---- 4 files changed, 26 insertions(+), 25 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c8bcf0ea4..87db026183 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ - Enhance: リプライにて引用がある場合テキストが空でもノートできるように - 引用したいノートのURLをコピーしリプライ投稿画面にペーストして添付することで達成できます - Enhance: フォローするかどうかの確認ダイアログを出せるように +- Chore: AiScriptを0.18.0にバージョンアップ - Fix: 一部のページ内リンクが正しく動作しない問題を修正 - Fix: 周年の実績が閏年を考慮しない問題を修正 - Fix: ローカルURLのプレビューポップアップが左上に表示される diff --git a/packages/frontend/package.json b/packages/frontend/package.json index cbf4e59592..95980ac0fc 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -24,7 +24,7 @@ "@rollup/plugin-json": "6.1.0", "@rollup/plugin-replace": "5.0.5", "@rollup/pluginutils": "5.1.0", - "@syuilo/aiscript": "0.17.0", + "@syuilo/aiscript": "0.18.0", "@tabler/icons-webfont": "2.44.0", "@twemoji/parser": "15.0.0", "@vitejs/plugin-vue": "5.0.4", diff --git a/packages/frontend/src/pages/flash/flash-edit.vue b/packages/frontend/src/pages/flash/flash-edit.vue index 3625bc1164..bff45094a1 100644 --- a/packages/frontend/src/pages/flash/flash-edit.vue +++ b/packages/frontend/src/pages/flash/flash-edit.vue @@ -48,7 +48,7 @@ import MkInput from '@/components/MkInput.vue'; import MkSelect from '@/components/MkSelect.vue'; import { useRouter } from '@/router/supplier.js'; -const PRESET_DEFAULT = `/// @ 0.16.0 +const PRESET_DEFAULT = `/// @ 0.18.0 var name = "" @@ -60,13 +60,13 @@ Ui:render([ Ui:C:button({ text: "Hello" onClick: @() { - Mk:dialog(null \`Hello, {name}!\`) + Mk:dialog(null, \`Hello, {name}!\`) } }) ]) `; -const PRESET_OMIKUJI = `/// @ 0.16.0 +const PRESET_OMIKUJI = `/// @ 0.18.0 // ユーザーごとに日替わりのおみくじのプリセット // 選択肢 @@ -81,11 +81,11 @@ let choices = [ "大凶" ] -// シードが「ユーザーID+今日の日付」である乱数生成器を用意 -let random = Math:gen_rng(\`{USER_ID}{Date:year()}{Date:month()}{Date:day()}\`) +// シードが「PlayID+ユーザーID+今日の日付」である乱数生成器を用意 +let random = Math:gen_rng(\`{THIS_ID}{USER_ID}{Date:year()}{Date:month()}{Date:day()}\`) // ランダムに選択肢を選ぶ -let chosen = choices[random(0 (choices.len - 1))] +let chosen = choices[random(0, (choices.len - 1))] // 結果のテキスト let result = \`今日のあなたの運勢は **{chosen}** です。\` @@ -109,7 +109,7 @@ Ui:render([ ]) `; -const PRESET_SHUFFLE = `/// @ 0.16.0 +const PRESET_SHUFFLE = `/// @ 0.18.0 // 巻き戻し可能な文字シャッフルのプリセット let string = "ペペロンチーノ" @@ -123,13 +123,13 @@ var cursor = 0 @do() { if (cursor != 0) { - results = results.slice(0 (cursor + 1)) + results = results.slice(0, (cursor + 1)) cursor = 0 } let chars = [] for (let i, length) { - let r = Math:rnd(0 (length - 1)) + let r = Math:rnd(0, (length - 1)) chars.push(string.pick(r)) } let result = chars.join("") @@ -188,27 +188,27 @@ var cursor = 0 do() `; -const PRESET_QUIZ = `/// @ 0.16.0 +const PRESET_QUIZ = `/// @ 0.18.0 let title = '地理クイズ' let qas = [{ q: 'オーストラリアの首都は?' - choices: ['シドニー' 'キャンベラ' 'メルボルン'] + choices: ['シドニー', 'キャンベラ', 'メルボルン'] a: 'キャンベラ' aDescription: '最大の都市はシドニーですが首都はキャンベラです。' } { q: '国土面積2番目の国は?' - choices: ['カナダ' 'アメリカ' '中国'] + choices: ['カナダ', 'アメリカ', '中国'] a: 'カナダ' aDescription: '大きい順にロシア、カナダ、アメリカ、中国です。' } { q: '二重内陸国ではないのは?' - choices: ['リヒテンシュタイン' 'ウズベキスタン' 'レソト'] + choices: ['リヒテンシュタイン', 'ウズベキスタン', 'レソト'] a: 'レソト' aDescription: 'レソトは(一重)内陸国です。' } { q: '閘門がない運河は?' - choices: ['キール運河' 'スエズ運河' 'パナマ運河'] + choices: ['キール運河', 'スエズ運河', 'パナマ運河'] a: 'スエズ運河' aDescription: 'スエズ運河は高低差がないので閘門はありません。' }] @@ -296,12 +296,12 @@ qaEls.push(Ui:C:container({ onClick: finish }) ] -} 'footer')) +}, 'footer')) Ui:render(qaEls) `; -const PRESET_TIMELINE = `/// @ 0.16.0 +const PRESET_TIMELINE = `/// @ 0.18.0 // APIリクエストを行いローカルタイムラインを表示するプリセット @fetch() { @@ -315,7 +315,7 @@ const PRESET_TIMELINE = `/// @ 0.16.0 ]) // タイムライン取得 - let notes = Mk:api("notes/local-timeline" {}) + let notes = Mk:api("notes/local-timeline", {}) // それぞれのノートごとにUI要素作成 let noteEls = [] diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1dbb172b5d..c7625fd89f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -707,8 +707,8 @@ importers: specifier: 5.1.0 version: 5.1.0(rollup@4.12.0) '@syuilo/aiscript': - specifier: 0.17.0 - version: 0.17.0 + specifier: 0.18.0 + version: 0.18.0 '@tabler/icons-webfont': specifier: 2.44.0 version: 2.44.0 @@ -6678,7 +6678,7 @@ packages: ts-dedent: 2.2.0 type-fest: 2.19.0 vue: 3.4.21(typescript@5.3.3) - vue-component-type-helpers: 2.0.10 + vue-component-type-helpers: 2.0.14 transitivePeerDependencies: - encoding - supports-color @@ -6944,8 +6944,8 @@ packages: dev: false optional: true - /@syuilo/aiscript@0.17.0: - resolution: {integrity: sha512-3JtQ1rWJHMxQ3153zLCXMUOwrOgjPPYGBl0dPHhR0ohm4tn7okMQRugxMCT0t3YxByemb9FfiM6TUjd0tEGxdA==} + /@syuilo/aiscript@0.18.0: + resolution: {integrity: sha512-/iY9Vv4LLjtW/KUzId1QwXC4BlpIEPCMcoT7dyRhYdyxtwhS3Hx4b/4j1HYP+n3Pq9XKyW5zvkY72/+DNu4g6Q==} dependencies: seedrandom: 3.0.5 stringz: 2.1.0 @@ -19347,8 +19347,8 @@ packages: resolution: {integrity: sha512-6bnLkn8O0JJyiFSIF0EfCogzeqNXpnjJ0vW/SZzNHfe6sPx30lTtTXlE5TFs2qhJlAtDFybStVNpL73cPe3OMQ==} dev: true - /vue-component-type-helpers@2.0.10: - resolution: {integrity: sha512-FC5fKJjDks3Ue/KRSYBdsiCaZa0kUPQfs8yQpb8W9mlO6BenV8G1z58xobeRMzevnmEcDa09LLwuXDwb4f6NMQ==} + /vue-component-type-helpers@2.0.14: + resolution: {integrity: sha512-DInfgOyXlMyliyqAAD9frK28tTfch0+tMi4qoWJcZlRxUf+NFAtraJBnAsKLep+FOyLMiajkhfyEb3xLK08i7w==} dev: true /vue-demi@0.14.7(vue@3.4.21): From 85339ca751401ff856814ca21283c07a7c66f5ce Mon Sep 17 00:00:00 2001 From: Cocoa Hoto Date: Thu, 25 Apr 2024 11:03:34 +0900 Subject: [PATCH 008/154] feat: improve emoji endpoint (#13742) --- packages/backend/src/server/ServerService.ts | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts index 671dd31eb1..1324cd1361 100644 --- a/packages/backend/src/server/ServerService.ts +++ b/packages/backend/src/server/ServerService.ts @@ -120,12 +120,20 @@ export class ServerService implements OnApplicationShutdown { return; } - const name = path.split('@')[0].replace(/\.webp$/i, ''); - const host = path.split('@')[1]?.replace(/\.webp$/i, ''); + const emojiPath = path.replace(/\.webp$/i, ''); + const pathChunks = emojiPath.split('@'); + + if (pathChunks.length > 2) { + reply.code(400); + return; + } + + const name = pathChunks.shift(); + const host = pathChunks.pop(); const emoji = await this.emojisRepository.findOneBy({ // `@.` is the spec of ReactionService.decodeReaction - host: (host == null || host === '.') ? IsNull() : host, + host: (host === undefined || host === '.') ? IsNull() : host, name: name, }); From 6abb8c49943a0d9002118fb3b50e20940aa1e3ba Mon Sep 17 00:00:00 2001 From: MeiMei <30769358+mei23@users.noreply.github.com> Date: Sat, 27 Apr 2024 12:57:00 +0900 Subject: [PATCH 009/154] Merge pull request from GHSA-m9qf-3pfj-2r86 * Add Cache-Control to Bull Board * CHANGELOG --------- Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com> --- CHANGELOG.md | 1 + packages/backend/src/server/web/ClientServerService.ts | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87db026183..5933a4383c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,7 @@ - Fix: リプライのみの引用リノートと、CWのみの引用リノートが純粋なリノートとして誤って扱われてしまう問題を修正 - Fix: 登録にメール認証が必須になっている場合、登録されているメールアドレスを削除できないように (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/606) +- Fix: Add Cache-Control to Bull Board - Fix: nginx経由で/files/にRangeリクエストされた場合に正しく応答できないのを修正 - Fix: 一部のタイムラインのストリーミングでインスタンスミュートが効かない問題を修正 - Fix: グローバルタイムラインで返信が表示されないことがある問題を修正 diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index b1af0c3df6..ba2f8b4324 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -202,6 +202,10 @@ export class ClientServerService { // %71ueueとかでリクエストされたら困るため const url = decodeURI(request.routeOptions.url); if (url === bullBoardPath || url.startsWith(bullBoardPath + '/')) { + if (!url.startsWith(bullBoardPath + '/static/')) { + reply.header('Cache-Control', 'private, max-age=0, must-revalidate'); + } + const token = request.cookies.token; if (token == null) { reply.code(401).send('Login required'); From f53e22d72c2e67cf4f89dec3c55c7a9a1a970dc8 Mon Sep 17 00:00:00 2001 From: salano_ym <53254905+salano-ym@users.noreply.github.com> Date: Sat, 27 Apr 2024 16:12:00 +0900 Subject: [PATCH 010/154] add comma (#13746) --- packages/frontend/src/pages/flash/flash-edit.vue | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/frontend/src/pages/flash/flash-edit.vue b/packages/frontend/src/pages/flash/flash-edit.vue index bff45094a1..3445da26a2 100644 --- a/packages/frontend/src/pages/flash/flash-edit.vue +++ b/packages/frontend/src/pages/flash/flash-edit.vue @@ -163,11 +163,11 @@ var cursor = 0 text: "←" disabled: !(results.len > 1 && (results.len - cursor) > 1) onClick: back - } { + }, { text: "→" disabled: !(results.len > 1 && cursor > 0) onClick: forward - } { + }, { text: "引き直す" onClick: do }] @@ -196,17 +196,17 @@ let qas = [{ choices: ['シドニー', 'キャンベラ', 'メルボルン'] a: 'キャンベラ' aDescription: '最大の都市はシドニーですが首都はキャンベラです。' -} { +}, { q: '国土面積2番目の国は?' choices: ['カナダ', 'アメリカ', '中国'] a: 'カナダ' aDescription: '大きい順にロシア、カナダ、アメリカ、中国です。' -} { +}, { q: '二重内陸国ではないのは?' choices: ['リヒテンシュタイン', 'ウズベキスタン', 'レソト'] a: 'レソト' aDescription: 'レソトは(一重)内陸国です。' -} { +}, { q: '閘門がない運河は?' choices: ['キール運河', 'スエズ運河', 'パナマ運河'] a: 'スエズ運河' @@ -244,9 +244,9 @@ each (let qa, qas) { }) Ui:C:container({ children: [] - } \`{qa.id}:a\`) + }, \`{qa.id}:a\`) ] - } qa.id)) + }, qa.id)) } @finish() { From 0a31e132c74cc2d8029cdadd103ea66a3ce16b6a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Sat, 27 Apr 2024 16:48:04 +0900 Subject: [PATCH 011/154] =?UTF-8?q?fix(frontend):=20Play=E3=81=AEAiScript?= =?UTF-8?q?=E3=83=A9=E3=83=B3=E3=82=BF=E3=82=A4=E3=83=A0=E3=81=8C=E5=81=9C?= =?UTF-8?q?=E6=AD=A2=E3=81=97=E3=81=9F=E3=81=A8=E3=81=8D=E3=81=AB=E7=94=BB?= =?UTF-8?q?=E9=9D=A2=E3=81=8C=E5=88=9D=E6=9C=9F=E5=8C=96=E3=81=95=E3=82=8C?= =?UTF-8?q?=E3=81=A6=E3=81=84=E3=81=AA=E3=81=84=E5=95=8F=E9=A1=8C=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20(#13747)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(frontend): PlayのAiScriptランタイムが停止したときに画面が初期化されていない問題を修正 * fix * Update Changelog * typo --- CHANGELOG.md | 2 + packages/frontend/src/pages/flash/flash.vue | 82 ++++++++++++++++----- 2 files changed, 66 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5933a4383c..e4605fe746 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -35,6 +35,7 @@ - Enhance: リプライにて引用がある場合テキストが空でもノートできるように - 引用したいノートのURLをコピーしリプライ投稿画面にペーストして添付することで達成できます - Enhance: フォローするかどうかの確認ダイアログを出せるように +- Enhance: Playを手動でリロードできるように - Chore: AiScriptを0.18.0にバージョンアップ - Fix: 一部のページ内リンクが正しく動作しない問題を修正 - Fix: 周年の実績が閏年を考慮しない問題を修正 @@ -50,6 +51,7 @@ - Fix: ノート詳細ページにおいてCW付き引用リノートのCWボタンのラベルに「引用」が含まれていない問題を修正 - Fix: ダイアログの入力で字数制限に違反していてもEnterキーが押せてしまう問題を修正 - Fix: ダイレクト投稿の宛先が保存されない問題を修正 +- Fix: Playのページを離れたときに、Playが正常に初期化されない問題を修正 ### Server - Enhance: エンドポイント`antennas/update`の必須項目を`antennaId`のみに diff --git a/packages/frontend/src/pages/flash/flash.vue b/packages/frontend/src/pages/flash/flash.vue index 4aa3ce1672..40499fde0e 100644 --- a/packages/frontend/src/pages/flash/flash.vue +++ b/packages/frontend/src/pages/flash/flash.vue @@ -15,11 +15,15 @@ SPDX-License-Identifier: AGPL-3.0-only
- {{ flash.likedCount }} - {{ flash.likedCount }} - - - +
+ +
+
+ {{ flash.likedCount }} + {{ flash.likedCount }} + + +
@@ -49,7 +53,7 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts._play.editThisPage }}
- + @@ -94,12 +98,33 @@ function fetchFlash() { }); } +function share(ev: MouseEvent) { + if (!flash.value) return; + + os.popupMenu([ + { + text: i18n.ts.shareWithNote, + icon: 'ti ti-pencil', + action: shareWithNote, + }, + ...(isSupportShare() ? [{ + text: i18n.ts.share, + icon: 'ti ti-share', + action: shareWithNavigator, + }] : []), + ], ev.currentTarget ?? ev.target); +} + function copyLink() { + if (!flash.value) return; + copyToClipboard(`${url}/play/${flash.value.id}`); os.success(); } -function share() { +function shareWithNavigator() { + if (!flash.value) return; + navigator.share({ title: flash.value.title, text: flash.value.summary, @@ -108,21 +133,28 @@ function share() { } function shareWithNote() { + if (!flash.value) return; + os.post({ - initialText: `${flash.value.title} ${url}/play/${flash.value.id}`, + initialText: `${flash.value.title}\n${url}/play/${flash.value.id}`, + instant: true, }); } function like() { + if (!flash.value) return; + os.apiWithDialog('flash/like', { flashId: flash.value.id, }).then(() => { - flash.value.isLiked = true; - flash.value.likedCount++; + flash.value!.isLiked = true; + flash.value!.likedCount++; }); } async function unlike() { + if (!flash.value) return; + const confirm = await os.confirm({ type: 'warning', text: i18n.ts.unlikeConfirm, @@ -131,8 +163,8 @@ async function unlike() { os.apiWithDialog('flash/unlike', { flashId: flash.value.id, }).then(() => { - flash.value.isLiked = false; - flash.value.likedCount--; + flash.value!.isLiked = false; + flash.value!.likedCount--; }); } @@ -152,6 +184,7 @@ function start() { async function run() { if (aiscript.value) aiscript.value.abort(); + if (!flash.value) return; aiscript.value = new Interpreter({ ...createAiScriptEnv({ @@ -193,12 +226,17 @@ async function run() { } } -onDeactivated(() => { +function reset() { if (aiscript.value) aiscript.value.abort(); + started.value = false; +} + +onDeactivated(() => { + reset(); }); onUnmounted(() => { - if (aiscript.value) aiscript.value.abort(); + reset(); }); const headerActions = computed(() => []); @@ -265,11 +303,19 @@ definePageMetadata(() => ({ } > .actions { - display: flex; - justify-content: center; - gap: 12px; margin-top: 16px; - padding: 16px; + + > .items { + display: flex; + justify-content: center; + gap: 12px; + padding: 16px; + border-bottom: 1px solid var(--divider); + + &:last-child { + border-bottom: none; + } + } } } } From cb5d8bdcddf76e26b9d0b80855955faa38ec6c36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Sat, 27 Apr 2024 18:53:28 +0900 Subject: [PATCH 012/154] =?UTF-8?q?fix(backend):=20=E3=83=9A=E3=83=BC?= =?UTF-8?q?=E3=82=B8=E3=81=AEOGP=20URL=E3=81=8C=E9=81=95=E3=81=86=E3=81=AE?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3=20(#13749)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(backend): ページのOGP URLが違うのを修正 * Update Changelog * typo --- CHANGELOG.md | 1 + packages/backend/src/server/web/views/page.pug | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e4605fe746..a263680782 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -52,6 +52,7 @@ - Fix: ダイアログの入力で字数制限に違反していてもEnterキーが押せてしまう問題を修正 - Fix: ダイレクト投稿の宛先が保存されない問題を修正 - Fix: Playのページを離れたときに、Playが正常に初期化されない問題を修正 +- Fix: ページのOGP URLが間違っているのを修正 ### Server - Enhance: エンドポイント`antennas/update`の必須項目を`antennaId`のみに diff --git a/packages/backend/src/server/web/views/page.pug b/packages/backend/src/server/web/views/page.pug index 08bb08ffe7..03c50eca8a 100644 --- a/packages/backend/src/server/web/views/page.pug +++ b/packages/backend/src/server/web/views/page.pug @@ -3,7 +3,7 @@ extends ./base block vars - const user = page.user; - const title = page.title; - - const url = `${config.url}/@${user.username}/${page.name}`; + - const url = `${config.url}/@${user.username}/pages/${page.name}`; block title = `${title} | ${instanceName}` From 7ce6a9bbaffddc6019ce2eab8b7a06c119ff2f69 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Sat, 27 Apr 2024 19:59:30 +0900 Subject: [PATCH 013/154] =?UTF-8?q?fix(frontend):=20=E3=82=B0=E3=83=AB?= =?UTF-8?q?=E3=83=BC=E3=83=97=E9=80=9A=E7=9F=A5=E3=81=AE=E4=BA=BA=E6=95=B0?= =?UTF-8?q?=E3=82=92=E3=81=A1=E3=82=83=E3=82=93=E3=81=A8=E6=95=B0=E3=81=88?= =?UTF-8?q?=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=20(#13751)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(frontend): グループ通知の人数をちゃんと数えるように * Update Changelog --- CHANGELOG.md | 1 + packages/frontend/src/components/MkNotification.vue | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a263680782..1a43649fdb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -53,6 +53,7 @@ - Fix: ダイレクト投稿の宛先が保存されない問題を修正 - Fix: Playのページを離れたときに、Playが正常に初期化されない問題を修正 - Fix: ページのOGP URLが間違っているのを修正 +- Fix: 通知をグループ化している際に、人数が正常に表示されないことがある問題を修正 ### Server - Enhance: エンドポイント`antennas/update`の必須項目を`antennaId`のみに diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue index 0d3a5c13ba..73cd7cd5b3 100644 --- a/packages/frontend/src/components/MkNotification.vue +++ b/packages/frontend/src/components/MkNotification.vue @@ -58,8 +58,8 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts._notification.achievementEarned }} {{ i18n.ts._notification.testNotification }} - {{ i18n.tsx._notification.likedBySomeUsers({ n: notification.reactions.length }) }} - {{ i18n.tsx._notification.reactedBySomeUsers({ n: notification.reactions.length }) }} + {{ i18n.tsx._notification.likedBySomeUsers({ n: getActualReactedUsersCount(notification) }) }} + {{ i18n.tsx._notification.reactedBySomeUsers({ n: getActualReactedUsersCount(notification) }) }} {{ i18n.tsx._notification.renotedBySomeUsers({ n: notification.users.length }) }} {{ notification.header }} @@ -72,7 +72,7 @@ SPDX-License-Identifier: AGPL-3.0-only - + @@ -174,6 +174,11 @@ const rejectFollowRequest = () => { followRequestDone.value = true; misskeyApi('following/requests/reject', { userId: props.notification.user.id }); }; + +function getActualReactedUsersCount(notification: Misskey.entities.Notification) { + if (notification.type !== 'reaction:grouped') return 0; + return new Set(notification.reactions.map((reaction) => reaction.user.id)).size; +} diff --git a/packages/frontend/src/pages/announcements.vue b/packages/frontend/src/pages/announcements.vue index bcd6eb7c0f..e50b208775 100644 --- a/packages/frontend/src/pages/announcements.vue +++ b/packages/frontend/src/pages/announcements.vue @@ -21,14 +21,19 @@ SPDX-License-Identifier: AGPL-3.0-only - {{ announcement.title }} + {{ announcement.title }}
-
- -
+ +
+ {{ i18n.ts.createdAt }}: +
+
+ {{ i18n.ts.updatedAt }}: +
+
{{ i18n.ts.gotIt }} @@ -73,24 +78,24 @@ const paginationEl = ref>(); const tab = ref('current'); -async function read(announcement) { - if (announcement.needConfirmationToRead) { +async function read(target) { + if (target.needConfirmationToRead) { const confirm = await os.confirm({ type: 'question', title: i18n.ts._announcement.readConfirmTitle, - text: i18n.tsx._announcement.readConfirmText({ title: announcement.title }), + text: i18n.tsx._announcement.readConfirmText({ title: target.title }), }); if (confirm.canceled) return; } if (!paginationEl.value) return; - paginationEl.value.updateItem(announcement.id, a => { + paginationEl.value.updateItem(target.id, a => { a.isRead = true; return a; }); - misskeyApi('i/read-announcement', { announcementId: announcement.id }); + misskeyApi('i/read-announcement', { announcementId: target.id }); updateAccount({ - unreadAnnouncements: $i!.unreadAnnouncements.filter(a => a.id !== announcement.id), + unreadAnnouncements: $i!.unreadAnnouncements.filter(a => a.id !== target.id), }); } diff --git a/packages/frontend/src/router/definition.ts b/packages/frontend/src/router/definition.ts index c5b576f505..c12ae0fa57 100644 --- a/packages/frontend/src/router/definition.ts +++ b/packages/frontend/src/router/definition.ts @@ -193,6 +193,9 @@ const routes: RouteDef[] = [{ }, { path: '/announcements', component: page(() => import('@/pages/announcements.vue')), +}, { + path: '/announcements/:announcementId', + component: page(() => import('@/pages/announcement.vue')), }, { path: '/about', component: page(() => import('@/pages/about.vue')), diff --git a/packages/frontend/src/ui/_common_/announcements.vue b/packages/frontend/src/ui/_common_/announcements.vue index 362c29e6c2..374bc20b54 100644 --- a/packages/frontend/src/ui/_common_/announcements.vue +++ b/packages/frontend/src/ui/_common_/announcements.vue @@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only v-for="announcement in $i.unreadAnnouncements.filter(x => x.display === 'banner')" :key="announcement.id" :class="$style.item" - to="/announcements" + :to="`/announcements/${announcement.id}`" > diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index 9cdb61da87..6ff711cabb 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -336,6 +336,12 @@ type AnnouncementsRequest = operations['announcements']['requestBody']['content' // @public (undocumented) type AnnouncementsResponse = operations['announcements']['responses']['200']['content']['application/json']; +// @public (undocumented) +type AnnouncementsShowRequest = operations['announcements___show']['requestBody']['content']['application/json']; + +// @public (undocumented) +type AnnouncementsShowResponse = operations['announcements___show']['responses']['200']['content']['application/json']; + // @public (undocumented) type Antenna = components['schemas']['Antenna']; @@ -1224,6 +1230,8 @@ declare namespace entities { AdminRolesUsersResponse, AnnouncementsRequest, AnnouncementsResponse, + AnnouncementsShowRequest, + AnnouncementsShowResponse, AntennasCreateRequest, AntennasCreateResponse, AntennasDeleteRequest, diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts index 729107a78d..181f7274b7 100644 --- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts +++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts @@ -851,6 +851,17 @@ declare module '../api.js' { credential?: string | null, ): Promise>; + /** + * No description provided. + * + * **Credential required**: *No* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + /** * No description provided. * diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts index b0982e1e55..ab3baf1670 100644 --- a/packages/misskey-js/src/autogen/endpoint.ts +++ b/packages/misskey-js/src/autogen/endpoint.ts @@ -101,6 +101,8 @@ import type { AdminRolesUsersResponse, AnnouncementsRequest, AnnouncementsResponse, + AnnouncementsShowRequest, + AnnouncementsShowResponse, AntennasCreateRequest, AntennasCreateResponse, AntennasDeleteRequest, @@ -631,6 +633,7 @@ export type Endpoints = { 'admin/roles/update-default-policies': { req: AdminRolesUpdateDefaultPoliciesRequest; res: EmptyResponse }; 'admin/roles/users': { req: AdminRolesUsersRequest; res: AdminRolesUsersResponse }; 'announcements': { req: AnnouncementsRequest; res: AnnouncementsResponse }; + 'announcements/show': { req: AnnouncementsShowRequest; res: AnnouncementsShowResponse }; 'antennas/create': { req: AntennasCreateRequest; res: AntennasCreateResponse }; 'antennas/delete': { req: AntennasDeleteRequest; res: EmptyResponse }; 'antennas/list': { req: EmptyRequest; res: AntennasListResponse }; diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts index 60bf6659c0..02ca932d8a 100644 --- a/packages/misskey-js/src/autogen/entities.ts +++ b/packages/misskey-js/src/autogen/entities.ts @@ -104,6 +104,8 @@ export type AdminRolesUsersRequest = operations['admin___roles___users']['reques export type AdminRolesUsersResponse = operations['admin___roles___users']['responses']['200']['content']['application/json']; export type AnnouncementsRequest = operations['announcements']['requestBody']['content']['application/json']; export type AnnouncementsResponse = operations['announcements']['responses']['200']['content']['application/json']; +export type AnnouncementsShowRequest = operations['announcements___show']['requestBody']['content']['application/json']; +export type AnnouncementsShowResponse = operations['announcements___show']['responses']['200']['content']['application/json']; export type AntennasCreateRequest = operations['antennas___create']['requestBody']['content']['application/json']; export type AntennasCreateResponse = operations['antennas___create']['responses']['200']['content']['application/json']; export type AntennasDeleteRequest = operations['antennas___delete']['requestBody']['content']['application/json']; diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 1e9c190ca5..208f03dc3e 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -706,6 +706,15 @@ export type paths = { */ post: operations['announcements']; }; + '/announcements/show': { + /** + * announcements/show + * @description No description provided. + * + * **Credential required**: *No* + */ + post: operations['announcements___show']; + }; '/antennas/create': { /** * antennas/create @@ -9662,6 +9671,60 @@ export type operations = { }; }; }; + /** + * announcements/show + * @description No description provided. + * + * **Credential required**: *No* + */ + announcements___show: { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + announcementId: string; + }; + }; + }; + responses: { + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['Announcement']; + }; + }; + /** @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']; + }; + }; + }; + }; /** * antennas/create * @description No description provided. From 1df8ea824e5dace883f0d6855d7342984c8032d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Mon, 27 May 2024 17:15:42 +0900 Subject: [PATCH 070/154] =?UTF-8?q?fix(backend):=20`/@`=20=E3=81=AB?= =?UTF-8?q?=E3=82=A2=E3=82=AF=E3=82=BB=E3=82=B9=E3=81=99=E3=82=8B=E3=81=A8?= =?UTF-8?q?=E3=82=B5=E3=83=BC=E3=83=90=E3=83=BC=E3=82=A8=E3=83=A9=E3=83=BC?= =?UTF-8?q?=E3=81=8C=E7=99=BA=E7=94=9F=E3=81=99=E3=82=8B=E5=95=8F=E9=A1=8C?= =?UTF-8?q?=E3=82=92=E4=BF=AE=E6=AD=A3=20(#13884)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../backend/src/server/web/ClientServerService.ts | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index f35ec8ba31..ab03489c0d 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -466,7 +466,9 @@ export class ClientServerService { }; // Atom - fastify.get<{ Params: { user: string; } }>('/@:user.atom', async (request, reply) => { + fastify.get<{ Params: { user?: string; } }>('/@:user.atom', async (request, reply) => { + if (request.params.user == null) return await renderBase(reply); + const feed = await getFeed(request.params.user); if (feed) { @@ -479,7 +481,9 @@ export class ClientServerService { }); // RSS - fastify.get<{ Params: { user: string; } }>('/@:user.rss', async (request, reply) => { + fastify.get<{ Params: { user?: string; } }>('/@:user.rss', async (request, reply) => { + if (request.params.user == null) return await renderBase(reply); + const feed = await getFeed(request.params.user); if (feed) { @@ -492,7 +496,9 @@ export class ClientServerService { }); // JSON - fastify.get<{ Params: { user: string; } }>('/@:user.json', async (request, reply) => { + fastify.get<{ Params: { user?: string; } }>('/@:user.json', async (request, reply) => { + if (request.params.user == null) return await renderBase(reply); + const feed = await getFeed(request.params.user); if (feed) { From 1b81ca45636db21166753e0aa00d91ab23e46ac5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Mon, 27 May 2024 17:16:47 +0900 Subject: [PATCH 071/154] =?UTF-8?q?enhance(frontend):=20=E3=80=8C=E8=A6=8B?= =?UTF-8?q?=E3=81=9F=E3=81=93=E3=81=A8=E3=81=AE=E3=81=82=E3=82=8B=E3=83=AA?= =?UTF-8?q?=E3=83=8E=E3=83=BC=E3=83=88=E3=82=92=E7=9C=81=E7=95=A5=E3=81=97?= =?UTF-8?q?=E3=81=A6=E8=A1=A8=E7=A4=BA=E3=80=8D=E3=81=AE=E5=90=8D=E7=A7=B0?= =?UTF-8?q?=E3=82=92=E5=A4=89=E6=9B=B4=20(#13883)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * enhance(frontend): 「見たことのあるリノートを省略して表示」の名称を変更 * ひとつだけcaptionが入ってるやつが真ん中にいると不格好だったので場所変更 --- locales/index.d.ts | 6 +++++- locales/ja-JP.yml | 3 ++- packages/frontend/src/pages/settings/general.vue | 5 ++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/locales/index.d.ts b/locales/index.d.ts index 18d8eee18f..eb7e297aa3 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -4113,9 +4113,13 @@ export interface Locale extends ILocale { */ "thisPostMayBeAnnoyingIgnore": string; /** - * 見たことのあるリノートを省略して表示 + * リノートのスマート省略 */ "collapseRenotes": string; + /** + * リアクションやリノートをしたことがあるノートをたたんで表示します。 + */ + "collapseRenotesDescription": string; /** * サーバー内部エラー */ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 8b1738aebe..ebaf16745c 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -1024,7 +1024,8 @@ thisPostMayBeAnnoying: "この投稿は迷惑になる可能性があります thisPostMayBeAnnoyingHome: "ホームに投稿" thisPostMayBeAnnoyingCancel: "やめる" thisPostMayBeAnnoyingIgnore: "このまま投稿" -collapseRenotes: "見たことのあるリノートを省略して表示" +collapseRenotes: "リノートのスマート省略" +collapseRenotesDescription: "リアクションやリノートをしたことがあるノートをたたんで表示します。" internalServerError: "サーバー内部エラー" internalServerErrorDescription: "サーバー内部で予期しないエラーが発生しました。" copyErrorInfo: "エラー情報をコピー" diff --git a/packages/frontend/src/pages/settings/general.vue b/packages/frontend/src/pages/settings/general.vue index 55d514ddf9..cfc63f2a08 100644 --- a/packages/frontend/src/pages/settings/general.vue +++ b/packages/frontend/src/pages/settings/general.vue @@ -50,9 +50,12 @@ SPDX-License-Identifier: AGPL-3.0-only
+ + + + {{ i18n.ts.showNoteActionsOnlyHover }} {{ i18n.ts.showClipButtonInNoteFooter }} - {{ i18n.ts.collapseRenotes }} {{ i18n.ts.enableAdvancedMfm }} {{ i18n.ts.enableAnimatedMfm }} {{ i18n.ts.enableQuickAddMfmFunction }} From 805a11aadbbc0f0a32531fd86443de514df74466 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Mon, 27 May 2024 17:18:12 +0900 Subject: [PATCH 072/154] =?UTF-8?q?enhance(backend):=20=E3=83=97=E3=83=AD?= =?UTF-8?q?=E3=83=95=E3=82=A3=E3=83=BC=E3=83=AB=E3=81=AE=E3=83=AA=E3=83=B3?= =?UTF-8?q?=E3=82=AF=E6=A4=9C=E8=A8=BC=E3=81=ABtry-catch=E3=82=92=E8=BF=BD?= =?UTF-8?q?=E5=8A=A0=20(#13882)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * enhance(backend): プロフィールのリンク検証にtry-catchを追加 * :v: --- .../src/server/api/endpoints/i/update.ts | 36 +++++++++++-------- 1 file changed, 21 insertions(+), 15 deletions(-) diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index 84a1931a3d..a8e702f328 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -498,26 +498,32 @@ export default class extends Endpoint { // eslint- private async verifyLink(url: string, user: MiLocalUser) { if (!safeForSql(url)) return; - const html = await this.httpRequestService.getHtml(url); + try { + const html = await this.httpRequestService.getHtml(url); - const { window } = new JSDOM(html); - const doc = window.document; + const { window } = new JSDOM(html); + const doc = window.document; - const myLink = `${this.config.url}/@${user.username}`; + const myLink = `${this.config.url}/@${user.username}`; - const aEls = Array.from(doc.getElementsByTagName('a')); - const linkEls = Array.from(doc.getElementsByTagName('link')); + const aEls = Array.from(doc.getElementsByTagName('a')); + const linkEls = Array.from(doc.getElementsByTagName('link')); - const includesMyLink = aEls.some(a => a.href === myLink); - const includesRelMeLinks = [...aEls, ...linkEls].some(link => link.rel === 'me' && link.href === myLink); + const includesMyLink = aEls.some(a => a.href === myLink); + const includesRelMeLinks = [...aEls, ...linkEls].some(link => link.rel === 'me' && link.href === myLink); - if (includesMyLink || includesRelMeLinks) { - await this.userProfilesRepository.createQueryBuilder('profile').update() - .where('userId = :userId', { userId: user.id }) - .set({ - verifiedLinks: () => `array_append("verifiedLinks", '${url}')`, // ここでSQLインジェクションされそうなのでとりあえず safeForSql で弾いている - }) - .execute(); + if (includesMyLink || includesRelMeLinks) { + await this.userProfilesRepository.createQueryBuilder('profile').update() + .where('userId = :userId', { userId: user.id }) + .set({ + verifiedLinks: () => `array_append("verifiedLinks", '${url}')`, // ここでSQLインジェクションされそうなのでとりあえず safeForSql で弾いている + }) + .execute(); + } + + window.close(); + } catch (err) { + // なにもしない } } } From d013e4516d7afb6ed4362467f69df2d79b9f0f9f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Mon, 27 May 2024 17:19:09 +0900 Subject: [PATCH 073/154] =?UTF-8?q?enhance(frontend):=20=E3=81=8A=E6=B0=97?= =?UTF-8?q?=E3=81=AB=E5=85=A5=E3=82=8A=E3=83=81=E3=83=A3=E3=83=B3=E3=83=8D?= =?UTF-8?q?=E3=83=AB=E3=82=92=E3=82=AD=E3=83=A3=E3=83=83=E3=82=B7=E3=83=A5?= =?UTF-8?q?=E3=81=99=E3=82=8B=E3=82=88=E3=81=86=E3=81=AB=20(#13881)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/frontend/src/cache.ts | 1 + packages/frontend/src/pages/channel.vue | 3 +++ packages/frontend/src/pages/timeline.vue | 6 ++---- packages/frontend/src/scripts/get-note-menu.ts | 6 ++---- packages/frontend/src/ui/deck/channel-column.vue | 13 ++++++------- 5 files changed, 14 insertions(+), 15 deletions(-) diff --git a/packages/frontend/src/cache.ts b/packages/frontend/src/cache.ts index b286528de6..bfe8fbe0e4 100644 --- a/packages/frontend/src/cache.ts +++ b/packages/frontend/src/cache.ts @@ -11,3 +11,4 @@ export const clipsCache = new Cache(1000 * 60 * 30, () export const rolesCache = new Cache(1000 * 60 * 30, () => misskeyApi('admin/roles/list')); export const userListsCache = new Cache(1000 * 60 * 30, () => misskeyApi('users/lists/list')); export const antennasCache = new Cache(1000 * 60 * 30, () => misskeyApi('antennas/list')); +export const favoritedChannelsCache = new Cache(1000 * 60 * 30, () => misskeyApi('channels/my-favorites', { limit: 100 })); diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue index 611ae6feca..a895df76e8 100644 --- a/packages/frontend/src/pages/channel.vue +++ b/packages/frontend/src/pages/channel.vue @@ -83,6 +83,7 @@ import { definePageMetadata } from '@/scripts/page-metadata.js'; import { deviceKind } from '@/scripts/device-kind.js'; import MkNotes from '@/components/MkNotes.vue'; import { url } from '@/config.js'; +import { favoritedChannelsCache } from '@/cache.js'; import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; import { defaultStore } from '@/store.js'; @@ -153,6 +154,7 @@ function favorite() { channelId: channel.value.id, }).then(() => { favorited.value = true; + favoritedChannelsCache.delete(); }); } @@ -168,6 +170,7 @@ async function unfavorite() { channelId: channel.value.id, }).then(() => { favorited.value = false; + favoritedChannelsCache.delete(); }); } diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue index 48dfc1fd44..98744c6318 100644 --- a/packages/frontend/src/pages/timeline.vue +++ b/packages/frontend/src/pages/timeline.vue @@ -48,7 +48,7 @@ import { i18n } from '@/i18n.js'; import { instance } from '@/instance.js'; import { $i } from '@/account.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; -import { antennasCache, userListsCache } from '@/cache.js'; +import { antennasCache, userListsCache, favoritedChannelsCache } from '@/cache.js'; import { deviceKind } from '@/scripts/device-kind.js'; import { deepMerge } from '@/scripts/merge.js'; import { MenuItem } from '@/types/menu.js'; @@ -173,9 +173,7 @@ async function chooseAntenna(ev: MouseEvent): Promise { } async function chooseChannel(ev: MouseEvent): Promise { - const channels = await misskeyApi('channels/my-favorites', { - limit: 100, - }); + const channels = await favoritedChannelsCache.fetch(); const items: MenuItem[] = [ ...channels.map(channel => { const lastReadedAt = miLocalStorage.getItemAsJson(`channelLastReadedAt:${channel.id}`) ?? null; diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/scripts/get-note-menu.ts index e7c9a848e0..71ad299f50 100644 --- a/packages/frontend/src/scripts/get-note-menu.ts +++ b/packages/frontend/src/scripts/get-note-menu.ts @@ -16,7 +16,7 @@ import { url } from '@/config.js'; import { defaultStore, noteActions } from '@/store.js'; import { miLocalStorage } from '@/local-storage.js'; import { getUserMenu } from '@/scripts/get-user-menu.js'; -import { clipsCache } from '@/cache.js'; +import { clipsCache, favoritedChannelsCache } from '@/cache.js'; import { MenuItem } from '@/types/menu.js'; import MkRippleEffect from '@/components/MkRippleEffect.vue'; import { isSupportShare } from '@/scripts/navigator.js'; @@ -603,9 +603,7 @@ export function getRenoteMenu(props: { icon: 'ti ti-repeat', text: appearNote.channel ? i18n.ts.renoteToOtherChannel : i18n.ts.renoteToChannel, children: async () => { - const channels = await misskeyApi('channels/my-favorites', { - limit: 30, - }); + const channels = await favoritedChannelsCache.fetch(); return channels.filter((channel) => { if (!appearNote.channelId) return true; return channel.id !== appearNote.channelId; diff --git a/packages/frontend/src/ui/deck/channel-column.vue b/packages/frontend/src/ui/deck/channel-column.vue index bd3b059497..28c741bba2 100644 --- a/packages/frontend/src/ui/deck/channel-column.vue +++ b/packages/frontend/src/ui/deck/channel-column.vue @@ -26,6 +26,7 @@ import { updateColumn, Column } from './deck-store.js'; import MkTimeline from '@/components/MkTimeline.vue'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os.js'; +import { favoritedChannelsCache } from '@/cache.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; @@ -42,20 +43,18 @@ if (props.column.channelId == null) { } async function setChannel() { - const channels = await misskeyApi('channels/my-favorites', { - limit: 100, - }); - const { canceled, result: channel } = await os.select({ + const channels = await favoritedChannelsCache.fetch(); + const { canceled, result: chosenChannel } = await os.select({ title: i18n.ts.selectChannel, items: channels.map(x => ({ value: x, text: x.name, })), default: props.column.channelId, }); - if (canceled) return; + if (canceled || chosenChannel == null) return; updateColumn(props.column.id, { - channelId: channel.id, - name: channel.name, + channelId: chosenChannel.id, + name: chosenChannel.name, }); } From 6af9492ea5492c02a11302afe7c6a6e83c00de1b Mon Sep 17 00:00:00 2001 From: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com> Date: Mon, 27 May 2024 17:21:05 +0900 Subject: [PATCH 074/154] Quick action implement (#13878) * enhance(frontend): quick action for file admin-lookup * docs(changelog): update changelog * enhance(frontend): quick action for general admin-lookup, remove unimplemented note, instance admin-lookup * docs(changelog): update changelog * chore: fix lint --- CHANGELOG.md | 2 ++ packages/frontend/src/pages/admin/files.vue | 27 ++----------------- packages/frontend/src/pages/admin/index.vue | 21 ++++++--------- packages/frontend/src/pages/admin/users.vue | 2 +- .../{lookup-user.ts => admin-lookup.ts} | 23 ++++++++++++++++ 5 files changed, 36 insertions(+), 39 deletions(-) rename packages/frontend/src/scripts/{lookup-user.ts => admin-lookup.ts} (72%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e23fa8f72..d23f512e3d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,8 @@ - Enhance: AiScriptを0.18.0にバージョンアップ - Enhance: 通常のノートでも、お気に入りに登録したチャンネルにリノートできるように - Enhance: 長いテキストをペーストした際にテキストファイルとして添付するかどうかを選択できるように +- Enhance: コントロールパネルのクイックアクションからファイルを照会できるように +- Enhance: コントロールパネルのクイックアクションから通常の照会を行えるように - Fix: 一部のページ内リンクが正しく動作しない問題を修正 - Fix: 周年の実績が閏年を考慮しない問題を修正 - Fix: ローカルURLのプレビューポップアップが左上に表示される diff --git a/packages/frontend/src/pages/admin/files.vue b/packages/frontend/src/pages/admin/files.vue index 3fe021e771..5132b85c64 100644 --- a/packages/frontend/src/pages/admin/files.vue +++ b/packages/frontend/src/pages/admin/files.vue @@ -42,7 +42,7 @@ import MkInput from '@/components/MkInput.vue'; import MkSelect from '@/components/MkSelect.vue'; import MkFileListForAdmin from '@/components/MkFileListForAdmin.vue'; import * as os from '@/os.js'; -import { misskeyApi } from '@/scripts/misskey-api.js'; +import { lookupFile } from '@/scripts/admin-lookup.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; @@ -73,33 +73,10 @@ function clear() { }); } -function show(file) { - os.pageWindow(`/admin/file/${file.id}`); -} - -async function find() { - const { canceled, result: q } = await os.inputText({ - title: i18n.ts.fileIdOrUrl, - minLength: 1, - }); - if (canceled) return; - - misskeyApi('admin/drive/show-file', q.startsWith('http://') || q.startsWith('https://') ? { url: q.trim() } : { fileId: q.trim() }).then(file => { - show(file); - }).catch(err => { - if (err.code === 'NO_SUCH_FILE') { - os.alert({ - type: 'error', - text: i18n.ts.notFound, - }); - } - }); -} - const headerActions = computed(() => [{ text: i18n.ts.lookup, icon: 'ti ti-search', - handler: find, + handler: lookupFile, }, { text: i18n.ts.clearCachedFiles, icon: 'ti ti-trash', diff --git a/packages/frontend/src/pages/admin/index.vue b/packages/frontend/src/pages/admin/index.vue index d4a41c66cc..eef1c8afa9 100644 --- a/packages/frontend/src/pages/admin/index.vue +++ b/packages/frontend/src/pages/admin/index.vue @@ -33,9 +33,10 @@ import { i18n } from '@/i18n.js'; import MkSuperMenu from '@/components/MkSuperMenu.vue'; import MkInfo from '@/components/MkInfo.vue'; import { instance } from '@/instance.js'; +import { lookup } from '@/scripts/lookup.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; -import { lookupUser, lookupUserByEmail } from '@/scripts/lookup-user.js'; +import { lookupUser, lookupUserByEmail, lookupFile } from '@/scripts/admin-lookup.js'; import { PageMetadata, definePageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js'; import { useRouter } from '@/router/supplier.js'; @@ -82,7 +83,7 @@ const menuDef = computed(() => [{ type: 'button', icon: 'ti ti-search', text: i18n.ts.lookup, - action: lookup, + action: adminLookup, }, ...(instance.disableRegistration ? [{ type: 'button', icon: 'ti ti-user-plus', @@ -282,7 +283,7 @@ function invite() { }); } -function lookup(ev: MouseEvent) { +function adminLookup(ev: MouseEvent) { os.popupMenu([{ text: i18n.ts.user, icon: 'ti ti-user', @@ -295,23 +296,17 @@ function lookup(ev: MouseEvent) { action: () => { lookupUserByEmail(); }, - }, { - text: i18n.ts.note, - icon: 'ti ti-pencil', - action: () => { - alert('TODO'); - }, }, { text: i18n.ts.file, icon: 'ti ti-cloud', action: () => { - alert('TODO'); + lookupFile(); }, }, { - text: i18n.ts.instance, - icon: 'ti ti-planet', + text: i18n.ts.lookup, + icon: 'ti ti-world-search', action: () => { - alert('TODO'); + lookup(); }, }], ev.currentTarget ?? ev.target); } diff --git a/packages/frontend/src/pages/admin/users.vue b/packages/frontend/src/pages/admin/users.vue index 06317760d2..7d87b97a36 100644 --- a/packages/frontend/src/pages/admin/users.vue +++ b/packages/frontend/src/pages/admin/users.vue @@ -63,7 +63,7 @@ import MkInput from '@/components/MkInput.vue'; import MkSelect from '@/components/MkSelect.vue'; import MkPagination from '@/components/MkPagination.vue'; import * as os from '@/os.js'; -import { lookupUser } from '@/scripts/lookup-user.js'; +import { lookupUser } from '@/scripts/admin-lookup.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import MkUserCardMini from '@/components/MkUserCardMini.vue'; diff --git a/packages/frontend/src/scripts/lookup-user.ts b/packages/frontend/src/scripts/admin-lookup.ts similarity index 72% rename from packages/frontend/src/scripts/lookup-user.ts rename to packages/frontend/src/scripts/admin-lookup.ts index efc9132e75..1b57b853c9 100644 --- a/packages/frontend/src/scripts/lookup-user.ts +++ b/packages/frontend/src/scripts/admin-lookup.ts @@ -63,3 +63,26 @@ export async function lookupUserByEmail() { } } } + +export async function lookupFile() { + const { canceled, result: q } = await os.inputText({ + title: i18n.ts.fileIdOrUrl, + minLength: 1, + }); + if (canceled) return; + + const show = (file) => { + os.pageWindow(`/admin/file/${file.id}`); + }; + + misskeyApi('admin/drive/show-file', q.startsWith('http://') || q.startsWith('https://') ? { url: q.trim() } : { fileId: q.trim() }).then(file => { + show(file); + }).catch(err => { + if (err.code === 'NO_SUCH_FILE') { + os.alert({ + type: 'error', + text: i18n.ts.notFound, + }); + } + }); +} From 140df4b5e050f1c2b55e08f9c5b511588b0370d2 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 27 May 2024 08:27:39 +0000 Subject: [PATCH 075/154] Bump version to 2024.5.0-beta.3 --- package.json | 2 +- packages/misskey-js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index b9ac4fc2a1..22e5217ea2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "2024.5.0-beta.2", + "version": "2024.5.0-beta.3", "codename": "nasubi", "repository": { "type": "git", diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json index 80ae84796a..d72004862c 100644 --- a/packages/misskey-js/package.json +++ b/packages/misskey-js/package.json @@ -1,7 +1,7 @@ { "type": "module", "name": "misskey-js", - "version": "2024.5.0-beta.2", + "version": "2024.5.0-beta.3", "description": "Misskey SDK for JavaScript", "main": "./built/index.js", "types": "./built/index.d.ts", From e50107792c870098ac78a64d8a92e69d5f11893a Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Mon, 27 May 2024 08:37:07 +0000 Subject: [PATCH 076/154] Bump version to 2024.5.0-beta.4 --- package.json | 2 +- packages/misskey-js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 22e5217ea2..ca3883b804 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "misskey", - "version": "2024.5.0-beta.3", + "version": "2024.5.0-beta.4", "codename": "nasubi", "repository": { "type": "git", diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json index d72004862c..bad0142899 100644 --- a/packages/misskey-js/package.json +++ b/packages/misskey-js/package.json @@ -1,7 +1,7 @@ { "type": "module", "name": "misskey-js", - "version": "2024.5.0-beta.3", + "version": "2024.5.0-beta.4", "description": "Misskey SDK for JavaScript", "main": "./built/index.js", "types": "./built/index.d.ts", From 28e0e20879d2b2834b5f3f47fdf8663afa8a07f8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=81=8B=E3=81=A3=E3=81=93=E3=81=8B=E3=82=8A?= <67428053+kakkokari-gtyih@users.noreply.github.com> Date: Mon, 27 May 2024 19:22:46 +0900 Subject: [PATCH 077/154] [ci skip] Delete .github/FUNDING.yml use misskey-dev/.github repository --- .github/FUNDING.yml | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml deleted file mode 100644 index d42b58abc0..0000000000 --- a/.github/FUNDING.yml +++ /dev/null @@ -1,4 +0,0 @@ -# These are supported funding model platforms - -github: [misskey-dev] -patreon: syuilo From cf2256cf4162f0f58fea3afbe08d9805451a9efc Mon Sep 17 00:00:00 2001 From: zyoshoka <107108195+zyoshoka@users.noreply.github.com> Date: Mon, 27 May 2024 20:11:39 +0900 Subject: [PATCH 078/154] fix: CHANGELOG not reflecting correctly (#13888) * fix: CHANGELOG not reflecting correctly * Update .github/workflows/release-edit-with-push.yml Co-authored-by: anatawa12 --------- Co-authored-by: anatawa12 --- .github/workflows/release-edit-with-push.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release-edit-with-push.yml b/.github/workflows/release-edit-with-push.yml index 944b98eb7c..890cb047bd 100644 --- a/.github/workflows/release-edit-with-push.yml +++ b/.github/workflows/release-edit-with-push.yml @@ -37,4 +37,7 @@ jobs: # PRのnotesを更新 - name: Update PR run: | - gh pr edit ${{ steps.get_pr.outputs.pr_number }} --body "${{ steps.changelog.outputs.changelog }}" + gh pr edit "$PR_NUMBER" --body "$CHANGELOG" + env: + CHANGELOG: ${{ steps.changelog.outputs.changelog }} + PR_NUMBER: ${{ steps.get_pr.outputs.pr_number }} From a7a8dc4dbbab075cdee140f468fd7e3559cde475 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Mon, 27 May 2024 20:12:25 +0900 Subject: [PATCH 079/154] =?UTF-8?q?=E3=82=82=E3=81=A8=E3=82=82=E3=81=A8?= =?UTF-8?q?=E3=82=BB=E3=83=B3=E3=82=B7=E3=83=86=E3=82=A3=E3=83=96=E3=81=A7?= =?UTF-8?q?=E3=81=AF=E3=81=AA=E3=81=84=E3=81=A8=E9=80=A3=E5=90=88=E3=81=95?= =?UTF-8?q?=E3=82=8C=E3=81=A6=E3=81=84=E3=81=9F=E3=83=95=E3=82=A1=E3=82=A4?= =?UTF-8?q?=E3=83=AB=E3=81=8C=E3=82=BB=E3=83=B3=E3=82=B7=E3=83=86=E3=82=A3?= =?UTF-8?q?=E3=83=96=E3=81=A8=E3=81=97=E3=81=A6=E9=80=A3=E5=90=88=E3=81=95?= =?UTF-8?q?=E3=82=8C=E3=81=9F=E5=A0=B4=E5=90=88=E3=81=AB=E3=82=BB=E3=83=B3?= =?UTF-8?q?=E3=82=B7=E3=83=86=E3=82=A3=E3=83=96=E3=81=A8=E3=81=97=E3=81=A6?= =?UTF-8?q?=E3=81=9D=E3=81=AE=E3=83=95=E3=82=A1=E3=82=A4=E3=83=AB=E3=82=92?= =?UTF-8?q?=E6=89=B1=E3=81=86=E3=82=88=E3=81=86=E3=81=AB=20(#13879)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(backend): mark an file as sensitive if the file was newly federated as sensitive * docs(changelog): もともとセンシティブではないと連合されていたファイルがセンシティブとして連合された場合にセンシティブとしてそのファイルを扱うように * fix: change way to update federated image * Update packages/backend/src/core/DriveService.ts Co-authored-by: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com> * update isSensitive of existing record object --------- Co-authored-by: Sayamame-beans <61457993+Sayamame-beans@users.noreply.github.com> --- CHANGELOG.md | 2 ++ packages/backend/src/core/DriveService.ts | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d23f512e3d..f8463f8cbf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -96,6 +96,8 @@ - Fix: `/i/notifications`に `includeTypes`か`excludeTypes`を指定しているとき、通知が存在するのに空配列を返すことがある問題を修正 - Fix: 複数idを指定する`users/show`が関係ないユーザを返すことがある問題を修正 - Fix: `/tags` と `/user-tags` が検索エンジンにインデックスされないように +- Fix: もともとセンシティブではないと連合されていたファイルがセンシティブとして連合された場合にセンシティブとしてそのファイルを扱うように + - センシティブとして連合したファイルは非センシティブとして連合されてもセンシティブとして扱われます ## 2024.3.1 diff --git a/packages/backend/src/core/DriveService.ts b/packages/backend/src/core/DriveService.ts index 1bc1df1dda..63fa26f69d 100644 --- a/packages/backend/src/core/DriveService.ts +++ b/packages/backend/src/core/DriveService.ts @@ -504,6 +504,12 @@ export class DriveService { if (much) { this.registerLogger.info(`file with same hash is found: ${much.id}`); + if (sensitive && !much.isSensitive) { + // The file is federated as sensitive for this time, but was federated as non-sensitive before. + // Therefore, update the file to sensitive. + await this.driveFilesRepository.update({ id: much.id }, { isSensitive: true }); + much.isSensitive = true; + } return much; } } From d7982e471c11d0656fa1266b2e4747ca5179647d Mon Sep 17 00:00:00 2001 From: syuilo <4439005+syuilo@users.noreply.github.com> Date: Mon, 27 May 2024 20:24:15 +0900 Subject: [PATCH 080/154] New Crowdin updates (#13860) * New translations ja-jp.yml (Chinese Traditional) * New translations ja-jp.yml (Chinese Simplified) * New translations ja-jp.yml (Spanish) * New translations ja-jp.yml (Italian) * New translations ja-jp.yml (Japanese, Kansai) * New translations ja-jp.yml (Indonesian) * New translations ja-jp.yml (French) * New translations ja-jp.yml (Czech) * New translations ja-jp.yml (German) * New translations ja-jp.yml (Korean) * New translations ja-jp.yml (Polish) * New translations ja-jp.yml (Portuguese) * New translations ja-jp.yml (Vietnamese) * New translations ja-jp.yml (Romanian) * New translations ja-jp.yml (Arabic) * New translations ja-jp.yml (Catalan) * New translations ja-jp.yml (Dutch) * New translations ja-jp.yml (Norwegian) * New translations ja-jp.yml (Russian) * New translations ja-jp.yml (Slovak) * New translations ja-jp.yml (Swedish) * New translations ja-jp.yml (Turkish) * New translations ja-jp.yml (Ukrainian) * New translations ja-jp.yml (Chinese Traditional) * New translations ja-jp.yml (English) * New translations ja-jp.yml (Bengali) * New translations ja-jp.yml (Thai) * New translations ja-jp.yml (Uzbek) * New translations ja-jp.yml (Lao) * New translations ja-jp.yml (Korean (Gyeongsang)) * New translations ja-jp.yml (Chinese Simplified) * New translations ja-jp.yml (Spanish) * New translations ja-jp.yml (Italian) * New translations ja-jp.yml (Japanese, Kansai) * New translations ja-jp.yml (Indonesian) * New translations ja-jp.yml (Korean) * New translations ja-jp.yml (Catalan) * New translations ja-jp.yml (Chinese Traditional) * New translations ja-jp.yml (English) * New translations ja-jp.yml (Thai) * New translations ja-jp.yml (Chinese Simplified) * New translations ja-jp.yml (Chinese Simplified) * New translations ja-jp.yml (Chinese Traditional) * New translations ja-jp.yml (Chinese Traditional) * New translations ja-jp.yml (Chinese Traditional) * New translations ja-jp.yml (English) * New translations ja-jp.yml (Chinese Traditional) * New translations ja-jp.yml (Danish) * New translations ja-jp.yml (Chinese Simplified) --- locales/ar-SA.yml | 2 + locales/bn-BD.yml | 4 ++ locales/ca-ES.yml | 5 +- locales/cs-CZ.yml | 4 ++ locales/da-DK.yml | 2 + locales/de-DE.yml | 4 ++ locales/en-US.yml | 14 +++++- locales/es-ES.yml | 5 +- locales/fr-FR.yml | 4 ++ locales/id-ID.yml | 14 +++++- locales/it-IT.yml | 5 +- locales/ja-KS.yml | 5 +- locales/ko-GS.yml | 4 ++ locales/ko-KR.yml | 5 +- locales/lo-LA.yml | 4 ++ locales/nl-NL.yml | 4 ++ locales/no-NO.yml | 2 + locales/pl-PL.yml | 4 ++ locales/pt-PT.yml | 4 ++ locales/ro-RO.yml | 4 ++ locales/ru-RU.yml | 4 ++ locales/sk-SK.yml | 4 ++ locales/sv-SE.yml | 4 ++ locales/th-TH.yml | 5 +- locales/tr-TR.yml | 4 ++ locales/uk-UA.yml | 4 ++ locales/uz-UZ.yml | 4 ++ locales/vi-VN.yml | 4 ++ locales/zh-CN.yml | 14 +++++- locales/zh-TW.yml | 124 +++++++++++++++++++++++++--------------------- 30 files changed, 205 insertions(+), 65 deletions(-) diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml index 88707fe111..955d672c1d 100644 --- a/locales/ar-SA.yml +++ b/locales/ar-SA.yml @@ -1016,6 +1016,8 @@ sourceCode: "الشفرة المصدرية" flip: "اقلب" lastNDays: "آخر {n} أيام" surrender: "ألغِ" +_delivery: + stop: "مُعلّق" _initialAccountSetting: accountCreated: "نجح إنشاء حسابك!" letsStartAccountSetup: "إذا كنت جديدًا لنعدّ حسابك الشخصي." diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml index dc5d315aed..abcf07da83 100644 --- a/locales/bn-BD.yml +++ b/locales/bn-BD.yml @@ -857,6 +857,10 @@ replies: "জবাব" renotes: "রিনোট" sourceCode: "সোর্স কোড" flip: "উল্টান" +_delivery: + stop: "স্থগিত করা হয়েছে" + _type: + none: "প্রকাশ করা হচ্ছে" _role: priority: "অগ্রাধিকার" _priority: diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml index d035555c73..0345ee0326 100644 --- a/locales/ca-ES.yml +++ b/locales/ca-ES.yml @@ -1224,6 +1224,10 @@ gameRetry: "Torna a provar" notUsePleaseLeaveBlank: "Si no voleu usar-ho, deixeu-ho en blanc" useTotp: "Usa una contrasenya d'un sol ús" useBackupCode: "Usa un codi de recuperació" +_delivery: + stop: "Suspés" + _type: + none: "S'està publicant" _bubbleGame: howToPlay: "Com es juga" _howToPlay: @@ -2001,7 +2005,6 @@ _permissions: "read:admin:server-info": "Veure informació del servidor" "read:admin:show-moderation-log": "Veure registre de moderació " "read:admin:show-user": "Veure informació privada de l'usuari " - "read:admin:show-users": "Veure informació privada de l'usuari " "write:admin:suspend-user": "Suspendre usuari" "write:admin:unset-user-avatar": "Esborrar avatar d'usuari " "write:admin:unset-user-banner": "Esborrar bàner de l'usuari " diff --git a/locales/cs-CZ.yml b/locales/cs-CZ.yml index cff533976e..c8a0b0cb28 100644 --- a/locales/cs-CZ.yml +++ b/locales/cs-CZ.yml @@ -1099,6 +1099,10 @@ sourceCode: "Zdrojový kód" flip: "Otočit" lastNDays: "Posledních {n} dnů" surrender: "Zrušit" +_delivery: + stop: "Suspendováno" + _type: + none: "Publikuji" _initialAccountSetting: accountCreated: "Váš účet byl úspěšně vytvořen!" letsStartAccountSetup: "Pro začátek si nastavte svůj profil." diff --git a/locales/da-DK.yml b/locales/da-DK.yml index 08c15ed092..5eb7a5a5f4 100644 --- a/locales/da-DK.yml +++ b/locales/da-DK.yml @@ -1,2 +1,4 @@ --- _lang_: "Dansk" +headlineMisskey: "" +introMisskey: "ようこそ!Misskeyは、オープンソースの分散型マイクロブログサービスです。\n「ノート」を作成して、いま起こっていることを共有したり、あなたについて皆に発信しよう📡\n「リアクション」機能で、皆のノートに素早く反応を追加することもできます👍\n新しい世界を探検しよう🚀" diff --git a/locales/de-DE.yml b/locales/de-DE.yml index 3e1c40512e..9e42e01252 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -1185,6 +1185,10 @@ addMfmFunction: "MFM hinzufügen" sfx: "Soundeffekte" lastNDays: "Letzten {n} Tage" surrender: "Abbrechen" +_delivery: + stop: "Gesperrt" + _type: + none: "Wird veröffentlicht" _announcement: forExistingUsers: "Nur für existierende Nutzer" forExistingUsersDescription: "Ist diese Option aktiviert, wird diese Ankündigung nur Nutzern angezeigt, die zum Zeitpunkt der Ankündigung bereits registriert sind. Ist sie deaktiviert, wird sie auch Nutzern, die sich nach dessen Veröffentlichung registrieren, angezeigt." diff --git a/locales/en-US.yml b/locales/en-US.yml index 10e9fd778e..c20a1ac7d8 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -108,11 +108,14 @@ enterEmoji: "Enter an emoji" renote: "Renote" unrenote: "Remove renote" renoted: "Renoted." +renotedToX: "Renote from {name} users。" cantRenote: "This post can't be renoted." cantReRenote: "A renote can't be renoted." quote: "Quote" inChannelRenote: "Channel-only Renote" inChannelQuote: "Channel-only Quote" +renoteToChannel: "Renote to channel" +renoteToOtherChannel: "Renote to other channel" pinnedNote: "Pinned note" pinned: "Pin to profile" you: "You" @@ -468,6 +471,7 @@ retype: "Enter again" noteOf: "Note by {user}" quoteAttached: "Quote" quoteQuestion: "Append as quote?" +attachAsFileQuestion: "The text in clipboard is long. Would you want to attach it as text file?" noMessagesYet: "No messages yet" newMessageExists: "There are new messages" onlyOneFileCanBeAttached: "You can only attach one file to a message" @@ -1235,6 +1239,15 @@ keepOriginalFilenameDescription: "If you turn off this setting, files names will noDescription: "There is not the explanation" alwaysConfirmFollow: "Always confirm when following" inquiry: "Contact" +_delivery: + status: "Delivery status" + stop: "Suspended" + resume: "Delivery resume" + _type: + none: "Publishing" + manuallySuspended: "Manually suspended" + goneSuspended: "Server is suspended due to server deletion" + autoSuspendedForNotResponding: "Server is suspended due to no responding" _bubbleGame: howToPlay: "How to play" hold: "Hold" @@ -2032,7 +2045,6 @@ _permissions: "read:admin:server-info": "View server info" "read:admin:show-moderation-log": "View moderation log" "read:admin:show-user": "View private user info" - "read:admin:show-users": "View private user info" "write:admin:suspend-user": "Suspend user" "write:admin:unset-user-avatar": "Remove user avatar" "write:admin:unset-user-banner": "Remove user banner" diff --git a/locales/es-ES.yml b/locales/es-ES.yml index 2e05364c31..5c8249ded5 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -1233,6 +1233,10 @@ useNativeUIForVideoAudioPlayer: "Usar la interfaz del navegador cuando se reprod keepOriginalFilename: "Mantener el nombre original del archivo" noDescription: "No hay descripción" alwaysConfirmFollow: "Confirmar siempre cuando se sigue a alguien" +_delivery: + stop: "Suspendido" + _type: + none: "Publicando" _bubbleGame: howToPlay: "Cómo jugar" hold: "Mantener" @@ -2029,7 +2033,6 @@ _permissions: "read:admin:server-info": "Ver información del servidor" "read:admin:show-moderation-log": "Ver log de moderación" "read:admin:show-user": "Ver información privada de usuario" - "read:admin:show-users": "Ver información privada de usuario" "write:admin:suspend-user": "Suspender cuentas de usuario" "write:admin:unset-user-avatar": "Quitar avatares de usuario" "write:admin:unset-user-banner": "Quitar banner de usuarios" diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index 58a11a5cc4..8d66c3d375 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -1224,6 +1224,10 @@ enableHorizontalSwipe: "Glisser pour changer d'onglet" loading: "Chargement en cours" surrender: "Annuler" gameRetry: "Réessayer" +_delivery: + stop: "Suspendu·e" + _type: + none: "Publié" _bubbleGame: howToPlay: "Comment jouer" hold: "Réserver" diff --git a/locales/id-ID.yml b/locales/id-ID.yml index f8e645d63b..7f509afa50 100644 --- a/locales/id-ID.yml +++ b/locales/id-ID.yml @@ -108,11 +108,14 @@ enterEmoji: "Masukkan emoji" renote: "Renote" unrenote: "Hapus renote" renoted: "Telah direnote" +renotedToX: "{name} telah merenote" cantRenote: "Postingan ini tidak dapat direnote" cantReRenote: "Renote tidak dapat direnote" quote: "Kutip" inChannelRenote: "Hanya renote dalam kanal" inChannelQuote: "Hanya kutip dalam kanal" +renoteToChannel: "Renote ke kanal" +renoteToOtherChannel: "Renote ke kanal lainnya" pinnedNote: "Catatan yang disematkan" pinned: "Sematkan ke profil" you: "Kamu" @@ -468,6 +471,7 @@ retype: "Masukkan ulang" noteOf: "Catatan milik {user}" quoteAttached: "Dikutip" quoteQuestion: "Apakah kamu ingin menambahkan kutipan?" +attachAsFileQuestion: "Teks dalam papan klip terlalu panjang. Apakah kamu ingin melampirkannya sebagai berkas teks?" noMessagesYet: "Tidak ada pesan" newMessageExists: "Kamu mendapatkan pesan baru" onlyOneFileCanBeAttached: "Kamu hanya dapat melampirkan satu berkas ke dalam pesan" @@ -1235,6 +1239,15 @@ keepOriginalFilenameDescription: "Apabila pengaturan ini dimatikan, nama berkas noDescription: "Tidak ada deskripsi" alwaysConfirmFollow: "Selalu konfirmasi ketika mengikuti" inquiry: "Hubungi kami" +_delivery: + status: "Status pengiriman" + stop: "Ditangguhkan" + resume: "Lanjutkan pengiriman" + _type: + none: "Sedang menyiarkan langsung" + manuallySuspended: "Ditangguhkan manual" + goneSuspended: "Sedang ditangguhkan untuk penghapusan peladen" + autoSuspendedForNotResponding: "Sedang ditangguhkan karena peladen tidak menjawab" _bubbleGame: howToPlay: "Cara bermain" hold: "Tahan" @@ -2032,7 +2045,6 @@ _permissions: "read:admin:server-info": "Lihat informasi peladen" "read:admin:show-moderation-log": "Lihat log moderasi" "read:admin:show-user": "Lihat informasi pengguna privat" - "read:admin:show-users": "Lihat informasi pengguna privat" "write:admin:suspend-user": "Tangguhkan pengguna" "write:admin:unset-user-avatar": "Hapus avatar pengguna" "write:admin:unset-user-banner": "Hapus banner pengguna" diff --git a/locales/it-IT.yml b/locales/it-IT.yml index 0a250a2e28..1d12a62cca 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -1233,6 +1233,10 @@ useNativeUIForVideoAudioPlayer: "Riprodurre audio/video usando le funzionalità keepOriginalFilename: "Mantieni il nome file originale" keepOriginalFilenameDescription: "Disattivandola, i file verranno caricati usando nomi casuali." noDescription: "Manca la descrizione" +_delivery: + stop: "Sospensione" + _type: + none: "Pubblicazione" _bubbleGame: howToPlay: "Come giocare" hold: "Tieni" @@ -2025,7 +2029,6 @@ _permissions: "read:admin:server-info": "Vedere le informazioni sul server" "read:admin:show-moderation-log": "Vedere lo storico di moderazione" "read:admin:show-user": "Vedere le informazioni private degli account utente" - "read:admin:show-users": "Vedere le informazioni private degli account utente" "write:admin:suspend-user": "Sospendere i profili" "write:admin:unset-user-avatar": "Rimuovere la foto profilo dai profili" "write:admin:unset-user-banner": "Rimuovere l'immagine testata dai profili" diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml index e6a23a34d7..7a33968e9e 100644 --- a/locales/ja-KS.yml +++ b/locales/ja-KS.yml @@ -1235,6 +1235,10 @@ keepOriginalFilenameDescription: "この設定をオフにすると、アップ noDescription: "説明文はあらへんで" alwaysConfirmFollow: "フォローの際常に確認する" inquiry: "問い合わせ" +_delivery: + stop: "配信せぇへん" + _type: + none: "配信しとる" _bubbleGame: howToPlay: "遊び方" hold: "ホールド" @@ -2032,7 +2036,6 @@ _permissions: "read:admin:server-info": "サーバーの情報見る" "read:admin:show-moderation-log": "モデレーションログ見る" "read:admin:show-user": "ユーザーのプライベートな情報見る" - "read:admin:show-users": "ユーザーのプライベートな情報見る" "write:admin:suspend-user": "ユーザーを凍結" "write:admin:unset-user-avatar": "ユーザーのアバターを削除" "write:admin:unset-user-banner": "ユーザーのバナーを削除" diff --git a/locales/ko-GS.yml b/locales/ko-GS.yml index c80a4d3997..9466aff01f 100644 --- a/locales/ko-GS.yml +++ b/locales/ko-GS.yml @@ -649,6 +649,10 @@ replies: "답하기" renotes: "리노트" attach: "옇기" surrender: "아이예" +_delivery: + stop: "고만 보내예" + _type: + none: "보내고 잇어예" _initialAccountSetting: startTutorial: "길라잡이 하기" _initialTutorial: diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index fc3a64acab..294a5a1520 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -1230,6 +1230,10 @@ useTotp: "일회용 비밀번호 사용" useBackupCode: "백업 코드 사용" launchApp: "앱 실행" useNativeUIForVideoAudioPlayer: "브라우저 UI에서 미디어 재생" +_delivery: + stop: "정지됨" + _type: + none: "배포 중" _bubbleGame: howToPlay: "설명" hold: "홀드" @@ -2021,7 +2025,6 @@ _permissions: "read:admin:server-info": "서버 정보 보기" "read:admin:show-moderation-log": "조정 기록 보기" "read:admin:show-user": "사용자 개인정보 보기" - "read:admin:show-users": "사용자 개인정보 보기" "write:admin:suspend-user": "사용자 정지하기" "write:admin:unset-user-avatar": "사용자 아바타 삭제하기" "write:admin:unset-user-banner": "사용자 배너 삭제하기" diff --git a/locales/lo-LA.yml b/locales/lo-LA.yml index fa4b3b6f9a..087bac3745 100644 --- a/locales/lo-LA.yml +++ b/locales/lo-LA.yml @@ -395,6 +395,10 @@ searchByGoogle: "ຄົ້ນຫາ" file: "ໄຟລ໌" replies: "ຕອບ​ໄປ​ທີ" renotes: "Renote" +_delivery: + stop: "ໂຈະ" + _type: + none: "ການ​ພິມ​ເຜີຍ​ແຜ່" _role: _priority: middle: "ປານກາງ" diff --git a/locales/nl-NL.yml b/locales/nl-NL.yml index e33b978bc8..eb48cf72da 100644 --- a/locales/nl-NL.yml +++ b/locales/nl-NL.yml @@ -429,6 +429,10 @@ loggedInAsBot: "Momenteel als bot ingelogd" icon: "Avatar" replies: "Antwoord" renotes: "Herdelen" +_delivery: + stop: "Opgeschort" + _type: + none: "Publiceren" _email: _follow: title: "volgde jou" diff --git a/locales/no-NO.yml b/locales/no-NO.yml index 475f93267b..2b4c9b7776 100644 --- a/locales/no-NO.yml +++ b/locales/no-NO.yml @@ -464,6 +464,8 @@ icon: "Avatar" replies: "Svar" renotes: "Renote" surrender: "Avbryt" +_delivery: + stop: "Suspendert" _initialAccountSetting: theseSettingsCanEditLater: "Du kan endre disse innstillingene senere." _achievements: diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml index 2183aa3022..9d75f7a9d7 100644 --- a/locales/pl-PL.yml +++ b/locales/pl-PL.yml @@ -1023,6 +1023,10 @@ flip: "Odwróć" lastNDays: "W ciągu ostatnich {n} dni" surrender: "Odrzuć" gameRetry: "Spróbuj ponownie" +_delivery: + stop: "Zawieszono" + _type: + none: "Publikowanie" _bubbleGame: _score: score: "Wynik" diff --git a/locales/pt-PT.yml b/locales/pt-PT.yml index e00f5750dd..cfc576b6e1 100644 --- a/locales/pt-PT.yml +++ b/locales/pt-PT.yml @@ -1012,6 +1012,10 @@ keepScreenOn: "Manter a tela do dispositivo sempre ligada" flip: "Inversão" lastNDays: "Últimos {n} dias" surrender: "Cancelar" +_delivery: + stop: "Suspenso" + _type: + none: "Publicando" _initialAccountSetting: followUsers: "Siga usuários que lhe interessam para criar a sua linha do tempo." _serverSettings: diff --git a/locales/ro-RO.yml b/locales/ro-RO.yml index 695eb2501f..328d34405e 100644 --- a/locales/ro-RO.yml +++ b/locales/ro-RO.yml @@ -651,6 +651,10 @@ show: "Arată" icon: "Avatar" replies: "Răspunde" renotes: "Re-notează" +_delivery: + stop: "Suspendat" + _type: + none: "Publicare" _role: _priority: middle: "Mediu" diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml index 66e032f16f..71f5cad601 100644 --- a/locales/ru-RU.yml +++ b/locales/ru-RU.yml @@ -1099,6 +1099,10 @@ flip: "Переворот" code: "Код" lastNDays: "Последние {n} сут" surrender: "Этот пост не может быть отменен." +_delivery: + stop: "Заморожено" + _type: + none: "Публикация" _initialAccountSetting: accountCreated: "Аккаунт успешно создан!" letsStartAccountSetup: "Давайте настроим вашу учётную запись." diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml index 0978701e55..52f6bf142c 100644 --- a/locales/sk-SK.yml +++ b/locales/sk-SK.yml @@ -922,6 +922,10 @@ renotes: "Preposlať" sourceCode: "Zdrojový kód" flip: "Preklopiť" lastNDays: "Posledných {n} dní" +_delivery: + stop: "Zmrazené" + _type: + none: "Zverejňovanie" _role: priority: "Priorita" _priority: diff --git a/locales/sv-SE.yml b/locales/sv-SE.yml index 62bc71a13d..089dc3949f 100644 --- a/locales/sv-SE.yml +++ b/locales/sv-SE.yml @@ -488,6 +488,10 @@ dataSaver: "Databesparing" icon: "Profilbild" replies: "Svara" renotes: "Omnotera" +_delivery: + stop: "Suspenderad" + _type: + none: "Publiceras" _achievements: _types: _open3windows: diff --git a/locales/th-TH.yml b/locales/th-TH.yml index 020b954854..ab09ac4d5a 100644 --- a/locales/th-TH.yml +++ b/locales/th-TH.yml @@ -1235,6 +1235,10 @@ keepOriginalFilenameDescription: "หากปิดการตั้งค่ noDescription: "ไม่มีข้อความอธิบาย" alwaysConfirmFollow: "แสดงข้อความยืนยันเมื่อกดติดตาม" inquiry: "ติดต่อเรา" +_delivery: + stop: "ถูกระงับ" + _type: + none: "กำลังเผยแพร่" _bubbleGame: howToPlay: "วิธีเล่น" hold: "หยุดชั่วคราว" @@ -2032,7 +2036,6 @@ _permissions: "read:admin:server-info": "ดูข้อมูลเซิร์ฟเวอร์" "read:admin:show-moderation-log": "ดูปูมการแก้ไข" "read:admin:show-user": "ดูข้อมูลส่วนตัวของผู้ใช้" - "read:admin:show-users": "ดูข้อมูลส่วนตัวของผู้ใช้" "write:admin:suspend-user": "ระงับผู้ใช้" "write:admin:unset-user-avatar": "ลบอวตารผู้ใช้" "write:admin:unset-user-banner": "ลบแบนเนอร์ผู้ใช้" diff --git a/locales/tr-TR.yml b/locales/tr-TR.yml index 0793592d34..cf6729a81d 100644 --- a/locales/tr-TR.yml +++ b/locales/tr-TR.yml @@ -378,6 +378,10 @@ addMemo: "Kısa not ekle" icon: "Avatar" replies: "yanıt" renotes: "vazgeçme" +_delivery: + stop: "Askıya alınmış" + _type: + none: "Paylaşım" _accountDelete: started: "Silme işlemi başlatıldı" _email: diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml index 0ce5dc1202..661ecf19d7 100644 --- a/locales/uk-UA.yml +++ b/locales/uk-UA.yml @@ -914,6 +914,10 @@ renotes: "Поширити" sourceCode: "Вихідний код" flip: "Перевернути" lastNDays: "Останні {n} днів" +_delivery: + stop: "Призупинено" + _type: + none: "Публікація" _achievements: earnedAt: "Відкрито" _types: diff --git a/locales/uz-UZ.yml b/locales/uz-UZ.yml index 809e785492..4a930626f4 100644 --- a/locales/uz-UZ.yml +++ b/locales/uz-UZ.yml @@ -846,6 +846,10 @@ icon: "Avatar" replies: "Javob berish" renotes: "Qayta qayd etish" flip: "Teskari" +_delivery: + stop: "To'xtatilgan" + _type: + none: "Yuborilmoqda" _achievements: _types: _viewInstanceChart: diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml index d9c21d29ad..acc2e0c6a9 100644 --- a/locales/vi-VN.yml +++ b/locales/vi-VN.yml @@ -1118,6 +1118,10 @@ pullDownToRefresh: "Kéo xuống để làm mới" cwNotationRequired: "Nếu \"Ẩn nội dung\" được bật thì cần phải có chú thích." lastNDays: "{n} ngày trước" surrender: "Từ chối" +_delivery: + stop: "Đã vô hiệu hóa" + _type: + none: "Đang đăng" _announcement: forExistingUsers: "Chỉ những người dùng đã tồn tại" forExistingUsersDescription: "Nếu được bật, thông báo này sẽ chỉ hiển thị với những người dùng đã tồn tại vào lúc thông báo được tạo. Nếu tắt đi, những tài khoản mới đăng ký sau khi thông báo được đăng lên cũng sẽ thấy nó." diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index 17164dfe98..3e500f8642 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -471,6 +471,7 @@ retype: "重新输入" noteOf: "{user} 的帖子" quoteAttached: "已引用" quoteQuestion: "是否引用此链接内容?" +attachAsFileQuestion: "剪贴板内的文字过长。要转换为文本文件并添加吗?" noMessagesYet: "现在没有新的聊天" newMessageExists: "新信息" onlyOneFileCanBeAttached: "只能添加一个附件" @@ -1024,6 +1025,7 @@ thisPostMayBeAnnoyingHome: "发到首页" thisPostMayBeAnnoyingCancel: "取消" thisPostMayBeAnnoyingIgnore: "就这样发布" collapseRenotes: "省略显示已经看过的转发内容" +collapseRenotesDescription: "将回应过或转贴过的贴子折叠表示。" internalServerError: "内部服务器错误" internalServerErrorDescription: "内部服务器发生了预期外的错误" copyErrorInfo: "复制错误信息" @@ -1238,6 +1240,15 @@ keepOriginalFilenameDescription: "若关闭此设置,上传文件时文件名 noDescription: "没有描述" alwaysConfirmFollow: "总是确认关注" inquiry: "联系我们" +_delivery: + status: "投递状态" + stop: "停止投递" + resume: "继续投递" + _type: + none: "投递中" + manuallySuspended: "手动停止中" + goneSuspended: "因服务器被删除而停止" + autoSuspendedForNotResponding: "因服务器无应答而停止" _bubbleGame: howToPlay: "游戏说明" hold: "抓住" @@ -1696,8 +1707,10 @@ _role: roleAssignedTo: "已分配给手动角色" isLocal: "是本地用户" isRemote: "是远程用户" + isCat: "猫猫用户" isBot: "机器人用户" isSuspended: "停用的用户" + isLocked: "锁推用户" isExplorable: "启用“使账号可见”的用户" createdLessThan: "账户创建时间少于" createdMoreThan: "账户创建时间超过" @@ -2032,7 +2045,6 @@ _permissions: "read:admin:server-info": "查看服务器信息" "read:admin:show-moderation-log": "查看管理日志" "read:admin:show-user": "查看用户的非公开信息" - "read:admin:show-users": "查看用户的非公开信息" "write:admin:suspend-user": "冻结用户" "write:admin:unset-user-avatar": "删除用户头像" "write:admin:unset-user-banner": "删除用户横幅" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index 8cde13052f..fed7b642dc 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -108,11 +108,14 @@ enterEmoji: "輸入表情符號" renote: "轉發" unrenote: "取消轉發" renoted: "轉發成功。" +renotedToX: "轉發給 {name} 了。" cantRenote: "無法轉發此貼文。" cantReRenote: "無法轉發之前已經轉發過的內容。" quote: "引用" inChannelRenote: "在頻道內轉發" inChannelQuote: "在頻道內引用" +renoteToChannel: "轉發至頻道" +renoteToOtherChannel: "轉發至其他頻道" pinnedNote: "已置頂的貼文" pinned: "置頂" you: "您" @@ -169,7 +172,7 @@ cacheRemoteSensitiveFilesDescription: "若停用這個設定,則不會快取 flagAsBot: "此使用者是機器人" flagAsBotDescription: "如果本帳戶是由程式控制,請啟用此選項。啟用後,會作為標示幫助其他開發者防止機器人之間產生無限互動的行為,並會調整 Misskey 內部系統將本帳戶識別為機器人。" flagAsCat: "此帳戶是一隻貓,喵~~~!!!" -flagAsCatDescription: "如果想將本帳戶標示為一隻貓,請開啟此標示" +flagAsCatDescription: "喵喵喵??" flagShowTimelineReplies: "在時間軸上顯示貼文的回覆" flagShowTimelineRepliesDescription: "啟用後,時間軸除了顯示使用者的貼文以外,還會顯示使用者對其他貼文的回覆。" autoAcceptFollowed: "自動允許來自追隨中使用者的追隨請求" @@ -366,7 +369,7 @@ enableRegistration: "開放新使用者註冊" invite: "邀請" driveCapacityPerLocalAccount: "每個本地使用者的雲端硬碟容量" driveCapacityPerRemoteAccount: "每個非本地用戶的雲端空間大小" -inMb: "以Mbps為單位" +inMb: "以 MB 為單位" bannerUrl: "橫幅圖片URL" backgroundImageUrl: "背景圖片的來源網址 " basicInfo: "基本資訊" @@ -378,12 +381,12 @@ pinnedClipId: "置頂的摘錄ID" pinnedNotes: "已置頂的貼文" hcaptcha: "hCaptcha" enableHcaptcha: "啟用 hCaptcha" -hcaptchaSiteKey: "網站金鑰" -hcaptchaSecretKey: "金鑰" +hcaptchaSiteKey: "hcaptchaSiteKey" +hcaptchaSecretKey: "hcaptchaSecretKey" mcaptcha: "mCaptcha" enableMcaptcha: "啟用 mCaptcha" mcaptchaSiteKey: "網站金鑰" -mcaptchaSecretKey: "金鑰" +mcaptchaSecretKey: "私密金鑰" mcaptchaInstanceUrl: "mCaptcha 的實例網址" recaptcha: "reCAPTCHA" enableRecaptcha: "啟用 reCAPTCHA" @@ -391,8 +394,8 @@ recaptchaSiteKey: "網站金鑰" recaptchaSecretKey: "金鑰" turnstile: "Turnstile" enableTurnstile: "啟用 Turnstile" -turnstileSiteKey: "網站金鑰" -turnstileSecretKey: "金鑰" +turnstileSiteKey: "turnstileSiteKey" +turnstileSecretKey: "turnstileSecretKey" avoidMultiCaptchaConfirm: "使用多種驗證方式可能會造成干擾,您要關閉其他驗證方式嗎?您可以按「取消」保留多種驗證方式。" antennas: "天線" manageAntennas: "管理天線" @@ -464,10 +467,11 @@ title: "標題" text: "文字" enable: "啟用" next: "下一步" -retype: "再次輸入" +retype: "重新輸入" noteOf: "{user}的貼文" quoteAttached: "引用" quoteQuestion: "是否要引用?" +attachAsFileQuestion: "剪貼簿的文字較長。請問是否要改成附加檔案呢?" noMessagesYet: "沒有訊息" newMessageExists: "有新的訊息" onlyOneFileCanBeAttached: "只能加入一個附件" @@ -602,7 +606,7 @@ addItem: "新增項目" rearrange: "排序方式" relays: "中繼器" addRelay: "新增中繼器" -inboxUrl: "收件夾URL" +inboxUrl: "收件夾 URL" addedRelays: "已加入的中繼器" serviceworkerInfo: "如要使用推播通知,需要啟用此選項並設定金鑰。" deletedNote: "已刪除的貼文" @@ -791,7 +795,7 @@ newVersionOfClientAvailable: "新版本的客戶端可用。" usageAmount: "使用量" capacity: "容量" inUse: "已使用" -editCode: "編輯代碼" +editCode: "編輯程式碼" apply: "套用" receiveAnnouncementFromInstance: "接收來自伺服器的通知" emailNotification: "郵件通知" @@ -1062,7 +1066,7 @@ enableChartsForFederatedInstances: "生成遠端伺服器的圖表" showClipButtonInNoteFooter: "新增摘錄按鈕至貼文" reactionsDisplaySize: "反應的顯示尺寸" limitWidthOfReaction: "限制反應的最大寬度,並縮小顯示尺寸。" -noteIdOrUrl: "貼文ID或URL" +noteIdOrUrl: "貼文 ID 或 URL" video: "影片" videos: "影片" audio: "音效" @@ -1077,7 +1081,7 @@ addMemo: "新增備註" editMemo: "編輯備註" reactionsList: "反應列表" renotesList: "轉發貼文列表" -notificationDisplay: "通知的顯示" +notificationDisplay: "通知" leftTop: "左上" rightTop: "右上" leftBottom: "左下" @@ -1179,15 +1183,15 @@ repositoryUrlOrTarballRequired: "如果儲存庫不是公開的,則必須提 feedback: "意見回饋" feedbackUrl: "意見回饋 URL" impressum: "營運者資訊" -impressumUrl: "營運者資訊網址" +impressumUrl: "營運者資訊 URL" impressumDescription: "在德國與部份地區必須要明確顯示營運者資訊。" privacyPolicy: "隱私政策" -privacyPolicyUrl: "隱私政策網址" +privacyPolicyUrl: "隱私政策 URL" tosAndPrivacyPolicy: "服務條款和隱私政策" avatarDecorations: "頭像裝飾" attach: "裝上" detach: "取下" -detachAll: "移除所有裝飾" +detachAll: "全部移除" angle: "角度" flip: "翻轉" showAvatarDecorations: "顯示頭像裝飾" @@ -1205,7 +1209,7 @@ remainingN: "剩餘:{n}" overwriteContentConfirm: "確定要覆蓋目前的內容嗎?" seasonalScreenEffect: "隨季節變換畫面的呈現" decorate: "設置頭像裝飾" -addMfmFunction: "插入MFM功能語法" +addMfmFunction: "插入 MFM 功能語法" enableQuickAddMfmFunction: "顯示高級 MFM 選擇器" bubbleGame: "氣泡遊戲" sfx: "音效" @@ -1225,16 +1229,25 @@ enableHorizontalSwipe: "滑動切換時間軸" loading: "載入中" surrender: "退出" gameRetry: "再試一次" -notUsePleaseLeaveBlank: "如不使用,請留空" +notUsePleaseLeaveBlank: "如果不使用的話請留白" useTotp: "使用一次性密碼" useBackupCode: "使用備用驗證碼" -launchApp: "啟動 App" +launchApp: "啟動 APP" useNativeUIForVideoAudioPlayer: "使用瀏覽器的 UI 播放影片與音訊" keepOriginalFilename: "保留原始檔名" keepOriginalFilenameDescription: "如果關閉此設置,上傳時檔案名稱會自動替換為隨機字串。" noDescription: "沒有說明文字" alwaysConfirmFollow: "點擊追隨時總是顯示確認訊息" inquiry: "聯絡我們" +_delivery: + status: "傳送狀態" + stop: "已凍結" + resume: "繼續傳送" + _type: + none: "直播中" + manuallySuspended: "手動暫停中" + goneSuspended: "因為伺服器刪除所以暫停中" + autoSuspendedForNotResponding: "因為伺服器沒有回應所以暫停中" _bubbleGame: howToPlay: "玩法說明" hold: "保留" @@ -1243,7 +1256,7 @@ _bubbleGame: scoreYen: "賺取的金額" highScore: "最高分" maxChain: "最大結合數" - yen: "{yen} 日圓" + yen: "{yen}円" estimatedQty: "{qty}個" scoreSweets: "飯糰 {onigiriQtyWithUnit}" _howToPlay: @@ -1271,7 +1284,7 @@ _initialAccountSetting: privacySetting: "隱私設定" theseSettingsCanEditLater: "這裡的設定可以在之後變更。" youCanEditMoreSettingsInSettingsPageLater: "除此之外,還可以在「設定」頁面進行各種設定。之後請確認看看。" - followUsers: "為了構築時間軸,試著追蹤您感興趣的使用者吧。" + followUsers: "為了構築時間軸,試著追隨您感興趣的使用者吧。" pushNotificationDescription: "啟用推送通知,就可以在設備上接收{name}的通知。" initialAccountSettingCompleted: "初始設定完成了!" haveFun: "盡情享受{name}吧!" @@ -1326,7 +1339,7 @@ _initialTutorial: title: "隱藏內容(CW)" description: "將顯示「註釋」中寫入的內容而不是本文。按一下「顯示內容」以顯示本文。" _exampleNote: - cw: "美食恐怖主義注意" + cw: "注意消夜文" note: "我吃了一個巧克力甜甜圈🍩😋" useCases: "伺服器的服務條款可能會規範特定的貼文需要使用隱藏內容,除此之外也會用在隱藏劇情洩漏與敏感內容的貼文。" _howToMakeAttachmentsSensitive: @@ -1351,7 +1364,7 @@ _serverRules: _serverSettings: iconUrl: "圖示的 URL" appIconDescription: "指定顯示 {host} 為應用程式時的圖示。" - appIconUsageExample: "例如:漸進式網路應用程式(PWA)、於手機桌面新增書籤" + appIconUsageExample: "例如:PWA 或是在手機桌面作為書籤等" appIconStyleRecommendation: "因為可能會裁剪成圓形或圓角,所以建議用單色填滿邊框及背景。" appIconResolutionMustBe: "解析度必須為 {resolution}。" manifestJsonOverride: "覆寫 manifest.json" @@ -1559,7 +1572,7 @@ _achievements: _postedAt0min0sec: title: "報時" description: "在零分零秒發佈貼文" - flavor: "啵、啵、啵、嗶ーー" + flavor: "啵.啵.啵.嗶ー" _selfQuote: title: "自我引用" description: "引用了自己的貼文" @@ -1694,8 +1707,8 @@ _role: roleAssignedTo: "手動指派角色完成" isLocal: "本地使用者" isRemote: "遠端使用者" - isCat: "使用者是貓" - isBot: "使用者是機器人" + isCat: "貓使用者" + isBot: "機器人使用者" isSuspended: "被停權的使用者" isLocked: "上鎖的使用者" isExplorable: "開啟了「使您的帳戶更容易被找到」功能的使用者" @@ -1857,7 +1870,7 @@ _theme: invalid: "佈景主題格式錯誤" make: "製作佈景主題" base: "基於" - addConstant: "添加常數" + addConstant: "新增常數" constant: "常數" defaultValue: "預設值" color: "顏色" @@ -1932,22 +1945,22 @@ _soundSettings: _ago: future: "未來" justNow: "剛剛" - secondsAgo: "{n} 秒前" - minutesAgo: "{n} 分鐘前 " - hoursAgo: "{n} 小時前" - daysAgo: "{n} 天前" - weeksAgo: "{n} 週前" - monthsAgo: "{n} 個月前" - yearsAgo: "{n} 年前" + secondsAgo: "{n}秒前" + minutesAgo: "{n}分鐘前" + hoursAgo: "{n}小時前" + daysAgo: "{n}天前" + weeksAgo: "{n}周前" + monthsAgo: "{n}個月前" + yearsAgo: "{n}年前" invalid: "無" _timeIn: - seconds: "{n} 秒後" - minutes: "{n} 分後" - hours: "{n} 小時後" - days: "{n} 日後" - weeks: "{n} 週後" - months: "{n} 個月後" - years: "{n} 年後" + seconds: "{n}秒後" + minutes: "{n}分鐘後" + hours: "{n}小時後" + days: "{n}天後" + weeks: "{n}周後" + months: "{n}個月後" + years: "{n}年後" _time: second: "秒" minute: "分鐘" @@ -2032,7 +2045,6 @@ _permissions: "read:admin:server-info": "查看伺服器的資訊" "read:admin:show-moderation-log": "查看審查紀錄" "read:admin:show-user": "查看使用者的私密資訊" - "read:admin:show-users": "查看使用者的私密資訊" "write:admin:suspend-user": "凍結使用者" "write:admin:unset-user-avatar": "刪除使用者的頭像" "write:admin:unset-user-banner": "刪除使用者的橫幅" @@ -2085,13 +2097,13 @@ _antennaSources: userList: "來自特定清單中的貼文" userBlacklist: "除指定使用者外的所有貼文" _weekday: - sunday: "週日" - monday: "週一" - tuesday: "週二" - wednesday: "週三" - thursday: "週四" - friday: "週五" - saturday: "週六" + sunday: "星期天" + monday: "星期一" + tuesday: "星期二" + wednesday: "星期三" + thursday: "星期四" + friday: "星期五" + saturday: "星期六" _widgets: profile: "個人檔案" instanceInfo: "伺服器資訊" @@ -2140,7 +2152,7 @@ _poll: deadlineDate: "截止日期" deadlineTime: "小時" duration: "時長" - votesCount: "{n} 票" + votesCount: "{n}票" totalVotes: "合計 {n} 票" vote: "投票" showResult: "顯示結果" @@ -2173,7 +2185,7 @@ _postForm: e: "寫些什麼吧……" f: "靜待發文……" _profile: - name: "名稱" + name: "名字" username: "使用者名稱" description: "關於我" youCanIncludeHashtags: "你也可以在「關於我」中加上 #tag" @@ -2231,10 +2243,10 @@ _timelines: _play: new: "新增 Play" edit: "編輯 Play" - created: "已新增Play " - updated: "已更新Play " + created: "已新增 Play " + updated: "已更新 Play " deleted: "已刪除 Play" - pageSetting: "Play設定" + pageSetting: "Play 設定" editThisPage: "編輯此 Play" viewSource: "檢視原始碼" my: "自己的 Play" @@ -2247,7 +2259,7 @@ _play: _pages: newPage: "建立頁面" editPage: "編輯頁面" - readPage: "正檢視原始碼" + readPage: "正在檢視原始碼" created: "頁面已建立" updated: "頁面已更新" deleted: "頁面已被刪除" @@ -2274,7 +2286,7 @@ _pages: hideTitleWhenPinned: "被置頂於個人資料時隱藏頁面標題" font: "字型" fontSerif: "襯線體" - fontSansSerif: "無襯線體" + fontSansSerif: "黑體" eyeCatchingImageSet: "設定封面影像" eyeCatchingImageRemove: "刪除封面影像" chooseBlock: "新增方塊" @@ -2384,7 +2396,7 @@ _drivecleaner: orderByCreatedAtAsc: "按新增日期降序排列" _webhookSettings: createWebhook: "建立 Webhook" - name: "名稱" + name: "名字" secret: "密鑰" events: "何時運行 Webhook" active: "已啟用" From 4579be0f5401001bcfc27c4d56133cc910f3f581 Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Mon, 27 May 2024 20:54:53 +0900 Subject: [PATCH 081/154] =?UTF-8?q?=E6=96=B0=E7=9D=80=E3=83=8E=E3=83=BC?= =?UTF-8?q?=E3=83=88=E3=82=92=E3=82=B5=E3=82=A6=E3=83=B3=E3=83=89=E3=81=A7?= =?UTF-8?q?=E9=80=9A=E7=9F=A5=E3=81=99=E3=82=8B=E6=A9=9F=E8=83=BD=E3=82=92?= =?UTF-8?q?deck=20UI=E3=81=AB=E8=BF=BD=E5=8A=A0=20(#13867)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(deck-ui): implement note notification * chore: remove notify in antenna * docs(changelog): 新着ノートをサウンドで通知する機能をdeck UIに追加 * fix: type error in test * lint: key order * fix: remove notify column * test: remove test for notify * chore: make sound selectable * fix: add license header * fix: add license header again * Unnecessary await Co-authored-by: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com> * ファイルを選択してください -> ファイルが選択されていません * fix: i18n忘れ * fix: i18n忘れ * pleaseSelectFile > fileNotSelected --------- Co-authored-by: syuilo <4439005+syuilo@users.noreply.github.com> Co-authored-by: かっこかり <67428053+kakkokari-gtyih@users.noreply.github.com> --- CHANGELOG.md | 1 + locales/index.d.ts | 8 ++ locales/ja-JP.yml | 2 + .../1716450883149-RemoveAntennaNotify.js | 16 +++ .../src/core/entities/AntennaEntityService.ts | 1 - packages/backend/src/models/Antenna.ts | 3 - .../backend/src/models/json-schema/antenna.ts | 4 - .../ExportAntennasProcessorService.ts | 1 - .../ImportAntennasProcessorService.ts | 4 +- .../server/api/endpoints/antennas/create.ts | 4 +- .../server/api/endpoints/antennas/update.ts | 2 - packages/backend/test/e2e/antennas.ts | 4 - packages/backend/test/e2e/move.ts | 2 - .../src/components/MkFormDialog.file.vue | 71 ++++++++++++ .../frontend/src/components/MkFormDialog.vue | 12 +- packages/frontend/src/os.ts | 2 +- .../frontend/src/pages/my-antennas/editor.vue | 3 - packages/frontend/src/scripts/form.ts | 30 +++-- .../frontend/src/ui/deck/antenna-column.vue | 24 +++- .../frontend/src/ui/deck/channel-column.vue | 23 +++- packages/frontend/src/ui/deck/deck-store.ts | 2 + packages/frontend/src/ui/deck/list-column.vue | 22 +++- .../src/ui/deck/role-timeline-column.vue | 23 +++- packages/frontend/src/ui/deck/tl-column.vue | 20 +++- .../src/ui/deck/tl-note-notification.ts | 107 ++++++++++++++++++ packages/misskey-js/src/autogen/types.ts | 3 - 26 files changed, 341 insertions(+), 53 deletions(-) create mode 100644 packages/backend/migration/1716450883149-RemoveAntennaNotify.js create mode 100644 packages/frontend/src/components/MkFormDialog.file.vue create mode 100644 packages/frontend/src/ui/deck/tl-note-notification.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index f8463f8cbf..0a70fc7a8a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -49,6 +49,7 @@ - Enhance: AiScriptを0.18.0にバージョンアップ - Enhance: 通常のノートでも、お気に入りに登録したチャンネルにリノートできるように - Enhance: 長いテキストをペーストした際にテキストファイルとして添付するかどうかを選択できるように +- Enhance: 新着ノートをサウンドで通知する機能をdeck UIに追加しました - Enhance: コントロールパネルのクイックアクションからファイルを照会できるように - Enhance: コントロールパネルのクイックアクションから通常の照会を行えるように - Fix: 一部のページ内リンクが正しく動作しない問題を修正 diff --git a/locales/index.d.ts b/locales/index.d.ts index eb7e297aa3..d4ded0bb5b 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1280,6 +1280,10 @@ export interface Locale extends ILocale { * フォルダーを選択 */ "selectFolders": string; + /** + * ファイルが選択されていません + */ + "fileNotSelected": string; /** * ファイル名を変更 */ @@ -9143,6 +9147,10 @@ export interface Locale extends ILocale { * カラムを追加 */ "addColumn": string; + /** + * 新着ノート通知の設定 + */ + "newNoteNotificationSettings": string; /** * カラムの設定 */ diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index ebaf16745c..d7ceb971af 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -316,6 +316,7 @@ selectFile: "ファイルを選択" selectFiles: "ファイルを選択" selectFolder: "フォルダーを選択" selectFolders: "フォルダーを選択" +fileNotSelected: "ファイルが選択されていません" renameFile: "ファイル名を変更" folderName: "フォルダー名" createFolder: "フォルダーを作成" @@ -2420,6 +2421,7 @@ _deck: alwaysShowMainColumn: "常にメインカラムを表示" columnAlign: "カラムの寄せ" addColumn: "カラムを追加" + newNoteNotificationSettings: "新着ノート通知の設定" configureColumn: "カラムの設定" swapLeft: "左に移動" swapRight: "右に移動" diff --git a/packages/backend/migration/1716450883149-RemoveAntennaNotify.js b/packages/backend/migration/1716450883149-RemoveAntennaNotify.js new file mode 100644 index 0000000000..b5a2441855 --- /dev/null +++ b/packages/backend/migration/1716450883149-RemoveAntennaNotify.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class RemoveAntennaNotify1716450883149 { + name = 'RemoveAntennaNotify1716450883149' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "notify"`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "antenna" ADD "notify" boolean NOT NULL`); + } +} diff --git a/packages/backend/src/core/entities/AntennaEntityService.ts b/packages/backend/src/core/entities/AntennaEntityService.ts index 3ec8efa6bf..4a17a3d80f 100644 --- a/packages/backend/src/core/entities/AntennaEntityService.ts +++ b/packages/backend/src/core/entities/AntennaEntityService.ts @@ -38,7 +38,6 @@ export class AntennaEntityService { users: antenna.users, caseSensitive: antenna.caseSensitive, localOnly: antenna.localOnly, - notify: antenna.notify, excludeBots: antenna.excludeBots, withReplies: antenna.withReplies, withFile: antenna.withFile, diff --git a/packages/backend/src/models/Antenna.ts b/packages/backend/src/models/Antenna.ts index f5e819059e..33e6f48189 100644 --- a/packages/backend/src/models/Antenna.ts +++ b/packages/backend/src/models/Antenna.ts @@ -90,9 +90,6 @@ export class MiAntenna { }) public expression: string | null; - @Column('boolean') - public notify: boolean; - @Index() @Column('boolean', { default: true, diff --git a/packages/backend/src/models/json-schema/antenna.ts b/packages/backend/src/models/json-schema/antenna.ts index 78cf6d3ba2..c4ac358fa6 100644 --- a/packages/backend/src/models/json-schema/antenna.ts +++ b/packages/backend/src/models/json-schema/antenna.ts @@ -72,10 +72,6 @@ export const packedAntennaSchema = { optional: false, nullable: false, default: false, }, - notify: { - type: 'boolean', - optional: false, nullable: false, - }, excludeBots: { type: 'boolean', optional: false, nullable: false, diff --git a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts index 1d8e90f367..88c4ea29c0 100644 --- a/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts +++ b/packages/backend/src/queue/processors/ExportAntennasProcessorService.ts @@ -84,7 +84,6 @@ export class ExportAntennasProcessorService { excludeBots: antenna.excludeBots, withReplies: antenna.withReplies, withFile: antenna.withFile, - notify: antenna.notify, })); if (antennas.length - 1 !== index) { write(', '); diff --git a/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts b/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts index ff1c04de06..e5b7c5ac52 100644 --- a/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts +++ b/packages/backend/src/queue/processors/ImportAntennasProcessorService.ts @@ -47,9 +47,8 @@ const validate = new Ajv().compile({ excludeBots: { type: 'boolean' }, withReplies: { type: 'boolean' }, withFile: { type: 'boolean' }, - notify: { type: 'boolean' }, }, - required: ['name', 'src', 'keywords', 'excludeKeywords', 'users', 'caseSensitive', 'withReplies', 'withFile', 'notify'], + required: ['name', 'src', 'keywords', 'excludeKeywords', 'users', 'caseSensitive', 'withReplies', 'withFile'], }); @Injectable() @@ -92,7 +91,6 @@ export class ImportAntennasProcessorService { excludeBots: antenna.excludeBots, withReplies: antenna.withReplies, withFile: antenna.withFile, - notify: antenna.notify, }).then(x => this.antennasRepository.findOneByOrFail(x.identifiers[0])); this.logger.succ('Antenna created: ' + result.id); this.globalEventService.publishInternalEvent('antennaCreated', result); diff --git a/packages/backend/src/server/api/endpoints/antennas/create.ts b/packages/backend/src/server/api/endpoints/antennas/create.ts index 57c8eb4958..6b7bacb054 100644 --- a/packages/backend/src/server/api/endpoints/antennas/create.ts +++ b/packages/backend/src/server/api/endpoints/antennas/create.ts @@ -67,9 +67,8 @@ export const paramDef = { excludeBots: { type: 'boolean' }, withReplies: { type: 'boolean' }, withFile: { type: 'boolean' }, - notify: { type: 'boolean' }, }, - required: ['name', 'src', 'keywords', 'excludeKeywords', 'users', 'caseSensitive', 'withReplies', 'withFile', 'notify'], + required: ['name', 'src', 'keywords', 'excludeKeywords', 'users', 'caseSensitive', 'withReplies', 'withFile'], } as const; @Injectable() @@ -128,7 +127,6 @@ export default class extends Endpoint { // eslint- excludeBots: ps.excludeBots, withReplies: ps.withReplies, withFile: ps.withFile, - notify: ps.notify, }).then(x => this.antennasRepository.findOneByOrFail(x.identifiers[0])); this.globalEventService.publishInternalEvent('antennaCreated', antenna); diff --git a/packages/backend/src/server/api/endpoints/antennas/update.ts b/packages/backend/src/server/api/endpoints/antennas/update.ts index e6720aacf8..0c30bca9e0 100644 --- a/packages/backend/src/server/api/endpoints/antennas/update.ts +++ b/packages/backend/src/server/api/endpoints/antennas/update.ts @@ -66,7 +66,6 @@ export const paramDef = { excludeBots: { type: 'boolean' }, withReplies: { type: 'boolean' }, withFile: { type: 'boolean' }, - notify: { type: 'boolean' }, }, required: ['antennaId'], } as const; @@ -124,7 +123,6 @@ export default class extends Endpoint { // eslint- excludeBots: ps.excludeBots, withReplies: ps.withReplies, withFile: ps.withFile, - notify: ps.notify, isActive: true, lastUsedAt: new Date(), }); diff --git a/packages/backend/test/e2e/antennas.ts b/packages/backend/test/e2e/antennas.ts index cf5c7dd130..4f78cc999d 100644 --- a/packages/backend/test/e2e/antennas.ts +++ b/packages/backend/test/e2e/antennas.ts @@ -38,7 +38,6 @@ describe('アンテナ', () => { excludeKeywords: [['']], keywords: [['keyword']], name: 'test', - notify: false, src: 'all' as const, userListId: null, users: [''], @@ -151,7 +150,6 @@ describe('アンテナ', () => { isActive: true, keywords: [['keyword']], name: 'test', - notify: false, src: 'all', userListId: null, users: [''], @@ -219,8 +217,6 @@ describe('アンテナ', () => { { parameters: () => ({ withReplies: true }) }, { parameters: () => ({ withFile: false }) }, { parameters: () => ({ withFile: true }) }, - { parameters: () => ({ notify: false }) }, - { parameters: () => ({ notify: true }) }, ]; test.each(antennaParamPattern)('を作成できること($#)', async ({ parameters }) => { const response = await successfulApiCall({ diff --git a/packages/backend/test/e2e/move.ts b/packages/backend/test/e2e/move.ts index 4e5306da97..35050130dc 100644 --- a/packages/backend/test/e2e/move.ts +++ b/packages/backend/test/e2e/move.ts @@ -191,7 +191,6 @@ describe('Account Move', () => { localOnly: false, withReplies: false, withFile: false, - notify: false, }, alice); antennaId = antenna.body.id; @@ -435,7 +434,6 @@ describe('Account Move', () => { localOnly: false, withReplies: false, withFile: false, - notify: false, }, alice); assert.strictEqual(res.status, 403); diff --git a/packages/frontend/src/components/MkFormDialog.file.vue b/packages/frontend/src/components/MkFormDialog.file.vue new file mode 100644 index 0000000000..9360594236 --- /dev/null +++ b/packages/frontend/src/components/MkFormDialog.file.vue @@ -0,0 +1,71 @@ + + + + + + + diff --git a/packages/frontend/src/components/MkFormDialog.vue b/packages/frontend/src/components/MkFormDialog.vue index deedc5badb..124f114111 100644 --- a/packages/frontend/src/components/MkFormDialog.vue +++ b/packages/frontend/src/components/MkFormDialog.vue @@ -21,8 +21,9 @@ SPDX-License-Identifier: AGPL-3.0-only
- - + diff --git a/packages/frontend/src/ui/deck/deck-store.ts b/packages/frontend/src/ui/deck/deck-store.ts index 70b55e8172..bb3c04cd5c 100644 --- a/packages/frontend/src/ui/deck/deck-store.ts +++ b/packages/frontend/src/ui/deck/deck-store.ts @@ -9,6 +9,7 @@ import { notificationTypes } from 'misskey-js'; import { Storage } from '@/pizzax.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { deepClone } from '@/scripts/clone.js'; +import { SoundStore } from '@/store.js'; type ColumnWidget = { name: string; @@ -33,6 +34,7 @@ export type Column = { withRenotes?: boolean; withReplies?: boolean; onlyFiles?: boolean; + soundSetting: SoundStore; }; export const deckStore = markRaw(new Storage('deck', { diff --git a/packages/frontend/src/ui/deck/list-column.vue b/packages/frontend/src/ui/deck/list-column.vue index 70ea54326f..5369112494 100644 --- a/packages/frontend/src/ui/deck/list-column.vue +++ b/packages/frontend/src/ui/deck/list-column.vue @@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only {{ column.name }} - + @@ -21,6 +21,10 @@ import MkTimeline from '@/components/MkTimeline.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; +import { MenuItem } from '@/types/menu.js'; +import { SoundStore } from '@/store.js'; +import { soundSettingsButton } from '@/ui/deck/tl-note-notification.js'; +import * as sound from '@/scripts/sound.js'; const props = defineProps<{ column: Column; @@ -29,6 +33,7 @@ const props = defineProps<{ const timeline = shallowRef>(); const withRenotes = ref(props.column.withRenotes ?? true); +const soundSetting = ref(props.column.soundSetting ?? { type: null, volume: 1 }); if (props.column.listId == null) { setList(); @@ -40,6 +45,10 @@ watch(withRenotes, v => { }); }); +watch(soundSetting, v => { + updateColumn(props.column.id, { soundSetting: v }); +}); + async function setList() { const lists = await misskeyApi('users/lists/list'); const { canceled, result: list } = await os.select({ @@ -59,7 +68,11 @@ function editList() { os.pageWindow('my/lists/' + props.column.listId); } -const menu = [ +function onNote() { + sound.playMisskeySfxFile(soundSetting.value); +} + +const menu: MenuItem[] = [ { icon: 'ti ti-pencil', text: i18n.ts.selectList, @@ -75,5 +88,10 @@ const menu = [ text: i18n.ts.showRenotes, ref: withRenotes, }, + { + icon: 'ti ti-bell', + text: i18n.ts._deck.newNoteNotificationSettings, + action: () => soundSettingsButton(soundSetting), + }, ]; diff --git a/packages/frontend/src/ui/deck/role-timeline-column.vue b/packages/frontend/src/ui/deck/role-timeline-column.vue index eae2ee13f3..32ab7527b4 100644 --- a/packages/frontend/src/ui/deck/role-timeline-column.vue +++ b/packages/frontend/src/ui/deck/role-timeline-column.vue @@ -9,18 +9,22 @@ SPDX-License-Identifier: AGPL-3.0-only {{ column.name }} - + diff --git a/packages/frontend/src/components/MkSwitch.vue b/packages/frontend/src/components/MkSwitch.vue index a19b45448b..721ac357f4 100644 --- a/packages/frontend/src/components/MkSwitch.vue +++ b/packages/frontend/src/components/MkSwitch.vue @@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only @keydown.enter="toggle" > - + @@ -34,16 +34,19 @@ const props = defineProps<{ modelValue: boolean | Ref; disabled?: boolean; helpText?: string; + noBody?: boolean; }>(); const emit = defineEmits<{ (ev: 'update:modelValue', v: boolean): void; + (ev: 'change', v: boolean): void; }>(); const checked = toRefs(props).modelValue; const toggle = () => { if (props.disabled) return; emit('update:modelValue', !checked.value); + emit('change', !checked.value); }; diff --git a/packages/frontend/src/components/MkSystemWebhookEditor.impl.ts b/packages/frontend/src/components/MkSystemWebhookEditor.impl.ts new file mode 100644 index 0000000000..1222d3261d --- /dev/null +++ b/packages/frontend/src/components/MkSystemWebhookEditor.impl.ts @@ -0,0 +1,45 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { defineAsyncComponent } from 'vue'; +import * as os from '@/os.js'; + +export type SystemWebhookEventType = 'abuseReport' | 'abuseReportResolved'; + +export type MkSystemWebhookEditorProps = { + mode: 'create' | 'edit'; + id?: string; + requiredEvents?: SystemWebhookEventType[]; +}; + +export type MkSystemWebhookResult = { + id?: string; + isActive: boolean; + name: string; + on: SystemWebhookEventType[]; + url: string; + secret: string; +}; + +export async function showSystemWebhookEditorDialog(props: MkSystemWebhookEditorProps): Promise { + const { dispose, result } = await new Promise<{ dispose: () => void, result: MkSystemWebhookResult | null }>(async resolve => { + const res = await os.popup( + defineAsyncComponent(() => import('@/components/MkSystemWebhookEditor.vue')), + props, + { + submitted: (ev: MkSystemWebhookResult) => { + resolve({ dispose: res.dispose, result: ev }); + }, + closed: () => { + resolve({ dispose: res.dispose, result: null }); + }, + }, + ); + }); + + dispose(); + + return result; +} diff --git a/packages/frontend/src/components/MkSystemWebhookEditor.vue b/packages/frontend/src/components/MkSystemWebhookEditor.vue new file mode 100644 index 0000000000..007d841f00 --- /dev/null +++ b/packages/frontend/src/components/MkSystemWebhookEditor.vue @@ -0,0 +1,217 @@ + + + + + + + diff --git a/packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue new file mode 100644 index 0000000000..ffe9c620d6 --- /dev/null +++ b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue @@ -0,0 +1,307 @@ + + + + + + + diff --git a/packages/frontend/src/pages/admin/abuse-report/notification-recipient.item.vue b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.item.vue new file mode 100644 index 0000000000..0b86808faf --- /dev/null +++ b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.item.vue @@ -0,0 +1,114 @@ + + + + + + + diff --git a/packages/frontend/src/pages/admin/abuse-report/notification-recipient.vue b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.vue new file mode 100644 index 0000000000..a52f8eb7af --- /dev/null +++ b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.vue @@ -0,0 +1,176 @@ + + + + + + + diff --git a/packages/frontend/src/pages/admin/abuses.vue b/packages/frontend/src/pages/admin/abuses.vue index d2f4a4b531..9a9fa472a5 100644 --- a/packages/frontend/src/pages/admin/abuses.vue +++ b/packages/frontend/src/pages/admin/abuses.vue @@ -7,30 +7,33 @@ SPDX-License-Identifier: AGPL-3.0-only -
-
-
-
- - - - - - - - - - - - - - - - - - -
- - - - -
-
+ + +
@@ -60,6 +61,7 @@ import MkPagination from '@/components/MkPagination.vue'; import XAbuseReport from '@/components/MkAbuseReport.vue'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; +import MkButton from '@/components/MkButton.vue'; const reports = shallowRef>(); @@ -80,7 +82,7 @@ const pagination = { }; function resolved(reportId) { - reports.value.removeItem(reportId); + reports.value?.removeItem(reportId); } const headerActions = computed(() => []); @@ -92,3 +94,26 @@ definePageMetadata(() => ({ icon: 'ti ti-exclamation-circle', })); + + diff --git a/packages/frontend/src/pages/admin/index.vue b/packages/frontend/src/pages/admin/index.vue index 794feae202..292f10da1a 100644 --- a/packages/frontend/src/pages/admin/index.vue +++ b/packages/frontend/src/pages/admin/index.vue @@ -214,6 +214,11 @@ const menuDef = computed(() => [{ text: i18n.ts.externalServices, to: '/admin/external-services', active: currentPage.value?.route.name === 'external-services', + }, { + icon: 'ti ti-webhook', + text: 'Webhook', + to: '/admin/system-webhook', + active: currentPage.value?.route.name === 'system-webhook', }, { icon: 'ti ti-adjustments', text: i18n.ts.other, diff --git a/packages/frontend/src/pages/admin/modlog.ModLog.vue b/packages/frontend/src/pages/admin/modlog.ModLog.vue index e33c882721..91f1c7c5e6 100644 --- a/packages/frontend/src/pages/admin/modlog.ModLog.vue +++ b/packages/frontend/src/pages/admin/modlog.ModLog.vue @@ -8,9 +8,35 @@ SPDX-License-Identifier: AGPL-3.0-only + +
raw diff --git a/packages/frontend/src/pages/admin/system-webhook.item.vue b/packages/frontend/src/pages/admin/system-webhook.item.vue new file mode 100644 index 0000000000..0c07122af3 --- /dev/null +++ b/packages/frontend/src/pages/admin/system-webhook.item.vue @@ -0,0 +1,117 @@ + + + + + + + diff --git a/packages/frontend/src/pages/admin/system-webhook.vue b/packages/frontend/src/pages/admin/system-webhook.vue new file mode 100644 index 0000000000..7a40eec944 --- /dev/null +++ b/packages/frontend/src/pages/admin/system-webhook.vue @@ -0,0 +1,96 @@ + + + + + + + diff --git a/packages/frontend/src/router/definition.ts b/packages/frontend/src/router/definition.ts index c12ae0fa57..8a443f627b 100644 --- a/packages/frontend/src/router/definition.ts +++ b/packages/frontend/src/router/definition.ts @@ -471,6 +471,14 @@ const routes: RouteDef[] = [{ path: '/invites', name: 'invites', component: page(() => import('@/pages/admin/invites.vue')), + }, { + path: '/abuse-report-notification-recipient', + name: 'abuse-report-notification-recipient', + component: page(() => import('@/pages/admin/abuse-report/notification-recipient.vue')), + }, { + path: '/system-webhook', + name: 'system-webhook', + component: page(() => import('@/pages/admin/system-webhook.vue')), }, { path: '/', component: page(() => import('@/pages/_empty_.vue')), diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index 6ff711cabb..bea89f2a7c 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -6,6 +6,11 @@ import { EventEmitter } from 'eventemitter3'; +// Warning: (ae-forgotten-export) The symbol "components" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +type AbuseReportNotificationRecipient = components['schemas']['AbuseReportNotificationRecipient']; + // @public (undocumented) export type Acct = { username: string; @@ -21,13 +26,38 @@ declare namespace acct { } export { acct } -// Warning: (ae-forgotten-export) The symbol "components" needs to be exported by the entry point index.d.ts -// // @public (undocumented) type Ad = components['schemas']['Ad']; // Warning: (ae-forgotten-export) The symbol "operations" needs to be exported by the entry point index.d.ts // +// @public (undocumented) +type AdminAbuseReportNotificationRecipientCreateRequest = operations['admin___abuse-report___notification-recipient___create']['requestBody']['content']['application/json']; + +// @public (undocumented) +type AdminAbuseReportNotificationRecipientCreateResponse = operations['admin___abuse-report___notification-recipient___create']['responses']['200']['content']['application/json']; + +// @public (undocumented) +type AdminAbuseReportNotificationRecipientDeleteRequest = operations['admin___abuse-report___notification-recipient___delete']['requestBody']['content']['application/json']; + +// @public (undocumented) +type AdminAbuseReportNotificationRecipientListRequest = operations['admin___abuse-report___notification-recipient___list']['requestBody']['content']['application/json']; + +// @public (undocumented) +type AdminAbuseReportNotificationRecipientListResponse = operations['admin___abuse-report___notification-recipient___list']['responses']['200']['content']['application/json']; + +// @public (undocumented) +type AdminAbuseReportNotificationRecipientShowRequest = operations['admin___abuse-report___notification-recipient___show']['requestBody']['content']['application/json']; + +// @public (undocumented) +type AdminAbuseReportNotificationRecipientShowResponse = operations['admin___abuse-report___notification-recipient___show']['responses']['200']['content']['application/json']; + +// @public (undocumented) +type AdminAbuseReportNotificationRecipientUpdateRequest = operations['admin___abuse-report___notification-recipient___update']['requestBody']['content']['application/json']; + +// @public (undocumented) +type AdminAbuseReportNotificationRecipientUpdateResponse = operations['admin___abuse-report___notification-recipient___update']['responses']['200']['content']['application/json']; + // @public (undocumented) type AdminAbuseUserReportsRequest = operations['admin___abuse-user-reports']['requestBody']['content']['application/json']; @@ -307,6 +337,33 @@ type AdminShowUsersResponse = operations['admin___show-users']['responses']['200 // @public (undocumented) type AdminSuspendUserRequest = operations['admin___suspend-user']['requestBody']['content']['application/json']; +// @public (undocumented) +type AdminSystemWebhookCreateRequest = operations['admin___system-webhook___create']['requestBody']['content']['application/json']; + +// @public (undocumented) +type AdminSystemWebhookCreateResponse = operations['admin___system-webhook___create']['responses']['200']['content']['application/json']; + +// @public (undocumented) +type AdminSystemWebhookDeleteRequest = operations['admin___system-webhook___delete']['requestBody']['content']['application/json']; + +// @public (undocumented) +type AdminSystemWebhookListRequest = operations['admin___system-webhook___list']['requestBody']['content']['application/json']; + +// @public (undocumented) +type AdminSystemWebhookListResponse = operations['admin___system-webhook___list']['responses']['200']['content']['application/json']; + +// @public (undocumented) +type AdminSystemWebhookShowRequest = operations['admin___system-webhook___show']['requestBody']['content']['application/json']; + +// @public (undocumented) +type AdminSystemWebhookShowResponse = operations['admin___system-webhook___show']['responses']['200']['content']['application/json']; + +// @public (undocumented) +type AdminSystemWebhookUpdateRequest = operations['admin___system-webhook___update']['requestBody']['content']['application/json']; + +// @public (undocumented) +type AdminSystemWebhookUpdateResponse = operations['admin___system-webhook___update']['responses']['200']['content']['application/json']; + // @public (undocumented) type AdminUnsetUserAvatarRequest = operations['admin___unset-user-avatar']['requestBody']['content']['application/json']; @@ -1133,6 +1190,15 @@ declare namespace entities { AdminMetaResponse, AdminAbuseUserReportsRequest, AdminAbuseUserReportsResponse, + AdminAbuseReportNotificationRecipientListRequest, + AdminAbuseReportNotificationRecipientListResponse, + AdminAbuseReportNotificationRecipientShowRequest, + AdminAbuseReportNotificationRecipientShowResponse, + AdminAbuseReportNotificationRecipientCreateRequest, + AdminAbuseReportNotificationRecipientCreateResponse, + AdminAbuseReportNotificationRecipientUpdateRequest, + AdminAbuseReportNotificationRecipientUpdateResponse, + AdminAbuseReportNotificationRecipientDeleteRequest, AdminAccountsCreateRequest, AdminAccountsCreateResponse, AdminAccountsDeleteRequest, @@ -1228,6 +1294,15 @@ declare namespace entities { AdminRolesUpdateDefaultPoliciesRequest, AdminRolesUsersRequest, AdminRolesUsersResponse, + AdminSystemWebhookCreateRequest, + AdminSystemWebhookCreateResponse, + AdminSystemWebhookDeleteRequest, + AdminSystemWebhookListRequest, + AdminSystemWebhookListResponse, + AdminSystemWebhookShowRequest, + AdminSystemWebhookShowResponse, + AdminSystemWebhookUpdateRequest, + AdminSystemWebhookUpdateResponse, AnnouncementsRequest, AnnouncementsResponse, AnnouncementsShowRequest, @@ -1733,7 +1808,9 @@ declare namespace entities { ReversiGameDetailed, MetaLite, MetaDetailedOnly, - MetaDetailed + MetaDetailed, + SystemWebhook, + AbuseReportNotificationRecipient } } export { entities } @@ -2380,8 +2457,23 @@ type ModerationLog = { type: 'unsetUserAvatar'; info: ModerationLogPayloads['unsetUserAvatar']; } | { - type: 'unsetUserBanner'; - info: ModerationLogPayloads['unsetUserBanner']; + type: 'createSystemWebhook'; + info: ModerationLogPayloads['createSystemWebhook']; +} | { + type: 'updateSystemWebhook'; + info: ModerationLogPayloads['updateSystemWebhook']; +} | { + type: 'deleteSystemWebhook'; + info: ModerationLogPayloads['deleteSystemWebhook']; +} | { + type: 'createAbuseReportNotificationRecipient'; + info: ModerationLogPayloads['createAbuseReportNotificationRecipient']; +} | { + type: 'updateAbuseReportNotificationRecipient'; + info: ModerationLogPayloads['updateAbuseReportNotificationRecipient']; +} | { + type: 'deleteAbuseReportNotificationRecipient'; + info: ModerationLogPayloads['deleteAbuseReportNotificationRecipient']; }); // @public (undocumented) @@ -2921,6 +3013,9 @@ type SwUpdateRegistrationRequest = operations['sw___update-registration']['reque // @public (undocumented) type SwUpdateRegistrationResponse = operations['sw___update-registration']['responses']['200']['content']['application/json']; +// @public (undocumented) +type SystemWebhook = components['schemas']['SystemWebhook']; + // @public (undocumented) type TestRequest = operations['test']['requestBody']['content']['application/json']; diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts index 181f7274b7..e799d4a0c5 100644 --- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts +++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts @@ -25,6 +25,66 @@ declare module '../api.js' { credential?: string | null, ): Promise>; + /** + * 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**: *read:admin:abuse-report:notification-recipient* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * 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**: *read:admin:abuse-report:notification-recipient* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * 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:admin:abuse-report:notification-recipient* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * 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:admin:abuse-report:notification-recipient* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * 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:admin:abuse-report:notification-recipient* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + /** * No description provided. * @@ -840,6 +900,66 @@ declare module '../api.js' { credential?: string | null, ): Promise>; + /** + * 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:admin:system-webhook* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * 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:admin:system-webhook* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * 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:admin:system-webhook* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * 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:admin:system-webhook* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + + /** + * 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:admin:system-webhook* + */ + request( + endpoint: E, + params: P, + credential?: string | null, + ): Promise>; + /** * No description provided. * diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts index ab3baf1670..20c8509d4c 100644 --- a/packages/misskey-js/src/autogen/endpoint.ts +++ b/packages/misskey-js/src/autogen/endpoint.ts @@ -4,6 +4,15 @@ import type { AdminMetaResponse, AdminAbuseUserReportsRequest, AdminAbuseUserReportsResponse, + AdminAbuseReportNotificationRecipientListRequest, + AdminAbuseReportNotificationRecipientListResponse, + AdminAbuseReportNotificationRecipientShowRequest, + AdminAbuseReportNotificationRecipientShowResponse, + AdminAbuseReportNotificationRecipientCreateRequest, + AdminAbuseReportNotificationRecipientCreateResponse, + AdminAbuseReportNotificationRecipientUpdateRequest, + AdminAbuseReportNotificationRecipientUpdateResponse, + AdminAbuseReportNotificationRecipientDeleteRequest, AdminAccountsCreateRequest, AdminAccountsCreateResponse, AdminAccountsDeleteRequest, @@ -99,6 +108,15 @@ import type { AdminRolesUpdateDefaultPoliciesRequest, AdminRolesUsersRequest, AdminRolesUsersResponse, + AdminSystemWebhookCreateRequest, + AdminSystemWebhookCreateResponse, + AdminSystemWebhookDeleteRequest, + AdminSystemWebhookListRequest, + AdminSystemWebhookListResponse, + AdminSystemWebhookShowRequest, + AdminSystemWebhookShowResponse, + AdminSystemWebhookUpdateRequest, + AdminSystemWebhookUpdateResponse, AnnouncementsRequest, AnnouncementsResponse, AnnouncementsShowRequest, @@ -558,6 +576,11 @@ import type { export type Endpoints = { 'admin/meta': { req: EmptyRequest; res: AdminMetaResponse }; 'admin/abuse-user-reports': { req: AdminAbuseUserReportsRequest; res: AdminAbuseUserReportsResponse }; + 'admin/abuse-report/notification-recipient/list': { req: AdminAbuseReportNotificationRecipientListRequest; res: AdminAbuseReportNotificationRecipientListResponse }; + 'admin/abuse-report/notification-recipient/show': { req: AdminAbuseReportNotificationRecipientShowRequest; res: AdminAbuseReportNotificationRecipientShowResponse }; + 'admin/abuse-report/notification-recipient/create': { req: AdminAbuseReportNotificationRecipientCreateRequest; res: AdminAbuseReportNotificationRecipientCreateResponse }; + 'admin/abuse-report/notification-recipient/update': { req: AdminAbuseReportNotificationRecipientUpdateRequest; res: AdminAbuseReportNotificationRecipientUpdateResponse }; + 'admin/abuse-report/notification-recipient/delete': { req: AdminAbuseReportNotificationRecipientDeleteRequest; res: EmptyResponse }; 'admin/accounts/create': { req: AdminAccountsCreateRequest; res: AdminAccountsCreateResponse }; 'admin/accounts/delete': { req: AdminAccountsDeleteRequest; res: EmptyResponse }; 'admin/accounts/find-by-email': { req: AdminAccountsFindByEmailRequest; res: AdminAccountsFindByEmailResponse }; @@ -632,6 +655,11 @@ export type Endpoints = { 'admin/roles/unassign': { req: AdminRolesUnassignRequest; res: EmptyResponse }; 'admin/roles/update-default-policies': { req: AdminRolesUpdateDefaultPoliciesRequest; res: EmptyResponse }; 'admin/roles/users': { req: AdminRolesUsersRequest; res: AdminRolesUsersResponse }; + 'admin/system-webhook/create': { req: AdminSystemWebhookCreateRequest; res: AdminSystemWebhookCreateResponse }; + 'admin/system-webhook/delete': { req: AdminSystemWebhookDeleteRequest; res: EmptyResponse }; + 'admin/system-webhook/list': { req: AdminSystemWebhookListRequest; res: AdminSystemWebhookListResponse }; + 'admin/system-webhook/show': { req: AdminSystemWebhookShowRequest; res: AdminSystemWebhookShowResponse }; + 'admin/system-webhook/update': { req: AdminSystemWebhookUpdateRequest; res: AdminSystemWebhookUpdateResponse }; 'announcements': { req: AnnouncementsRequest; res: AnnouncementsResponse }; 'announcements/show': { req: AnnouncementsShowRequest; res: AnnouncementsShowResponse }; 'antennas/create': { req: AntennasCreateRequest; res: AntennasCreateResponse }; diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts index 02ca932d8a..357b5e9eaf 100644 --- a/packages/misskey-js/src/autogen/entities.ts +++ b/packages/misskey-js/src/autogen/entities.ts @@ -7,6 +7,15 @@ export type EmptyResponse = Record | undefined; export type AdminMetaResponse = operations['admin___meta']['responses']['200']['content']['application/json']; export type AdminAbuseUserReportsRequest = operations['admin___abuse-user-reports']['requestBody']['content']['application/json']; export type AdminAbuseUserReportsResponse = operations['admin___abuse-user-reports']['responses']['200']['content']['application/json']; +export type AdminAbuseReportNotificationRecipientListRequest = operations['admin___abuse-report___notification-recipient___list']['requestBody']['content']['application/json']; +export type AdminAbuseReportNotificationRecipientListResponse = operations['admin___abuse-report___notification-recipient___list']['responses']['200']['content']['application/json']; +export type AdminAbuseReportNotificationRecipientShowRequest = operations['admin___abuse-report___notification-recipient___show']['requestBody']['content']['application/json']; +export type AdminAbuseReportNotificationRecipientShowResponse = operations['admin___abuse-report___notification-recipient___show']['responses']['200']['content']['application/json']; +export type AdminAbuseReportNotificationRecipientCreateRequest = operations['admin___abuse-report___notification-recipient___create']['requestBody']['content']['application/json']; +export type AdminAbuseReportNotificationRecipientCreateResponse = operations['admin___abuse-report___notification-recipient___create']['responses']['200']['content']['application/json']; +export type AdminAbuseReportNotificationRecipientUpdateRequest = operations['admin___abuse-report___notification-recipient___update']['requestBody']['content']['application/json']; +export type AdminAbuseReportNotificationRecipientUpdateResponse = operations['admin___abuse-report___notification-recipient___update']['responses']['200']['content']['application/json']; +export type AdminAbuseReportNotificationRecipientDeleteRequest = operations['admin___abuse-report___notification-recipient___delete']['requestBody']['content']['application/json']; export type AdminAccountsCreateRequest = operations['admin___accounts___create']['requestBody']['content']['application/json']; export type AdminAccountsCreateResponse = operations['admin___accounts___create']['responses']['200']['content']['application/json']; export type AdminAccountsDeleteRequest = operations['admin___accounts___delete']['requestBody']['content']['application/json']; @@ -102,6 +111,15 @@ export type AdminRolesUnassignRequest = operations['admin___roles___unassign'][' export type AdminRolesUpdateDefaultPoliciesRequest = operations['admin___roles___update-default-policies']['requestBody']['content']['application/json']; export type AdminRolesUsersRequest = operations['admin___roles___users']['requestBody']['content']['application/json']; export type AdminRolesUsersResponse = operations['admin___roles___users']['responses']['200']['content']['application/json']; +export type AdminSystemWebhookCreateRequest = operations['admin___system-webhook___create']['requestBody']['content']['application/json']; +export type AdminSystemWebhookCreateResponse = operations['admin___system-webhook___create']['responses']['200']['content']['application/json']; +export type AdminSystemWebhookDeleteRequest = operations['admin___system-webhook___delete']['requestBody']['content']['application/json']; +export type AdminSystemWebhookListRequest = operations['admin___system-webhook___list']['requestBody']['content']['application/json']; +export type AdminSystemWebhookListResponse = operations['admin___system-webhook___list']['responses']['200']['content']['application/json']; +export type AdminSystemWebhookShowRequest = operations['admin___system-webhook___show']['requestBody']['content']['application/json']; +export type AdminSystemWebhookShowResponse = operations['admin___system-webhook___show']['responses']['200']['content']['application/json']; +export type AdminSystemWebhookUpdateRequest = operations['admin___system-webhook___update']['requestBody']['content']['application/json']; +export type AdminSystemWebhookUpdateResponse = operations['admin___system-webhook___update']['responses']['200']['content']['application/json']; export type AnnouncementsRequest = operations['announcements']['requestBody']['content']['application/json']; export type AnnouncementsResponse = operations['announcements']['responses']['200']['content']['application/json']; export type AnnouncementsShowRequest = operations['announcements___show']['requestBody']['content']['application/json']; diff --git a/packages/misskey-js/src/autogen/models.ts b/packages/misskey-js/src/autogen/models.ts index a6e5fbe689..04574849d4 100644 --- a/packages/misskey-js/src/autogen/models.ts +++ b/packages/misskey-js/src/autogen/models.ts @@ -51,3 +51,5 @@ export type ReversiGameDetailed = components['schemas']['ReversiGameDetailed']; export type MetaLite = components['schemas']['MetaLite']; export type MetaDetailedOnly = components['schemas']['MetaDetailedOnly']; export type MetaDetailed = components['schemas']['MetaDetailed']; +export type SystemWebhook = components['schemas']['SystemWebhook']; +export type AbuseReportNotificationRecipient = components['schemas']['AbuseReportNotificationRecipient']; diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index 2c80676f3e..bdcc1dfd77 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -30,6 +30,56 @@ export type paths = { */ post: operations['admin___abuse-user-reports']; }; + '/admin/abuse-report/notification-recipient/list': { + /** + * admin/abuse-report/notification-recipient/list + * @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**: *read:admin:abuse-report:notification-recipient* + */ + post: operations['admin___abuse-report___notification-recipient___list']; + }; + '/admin/abuse-report/notification-recipient/show': { + /** + * admin/abuse-report/notification-recipient/show + * @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**: *read:admin:abuse-report:notification-recipient* + */ + post: operations['admin___abuse-report___notification-recipient___show']; + }; + '/admin/abuse-report/notification-recipient/create': { + /** + * admin/abuse-report/notification-recipient/create + * @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:admin:abuse-report:notification-recipient* + */ + post: operations['admin___abuse-report___notification-recipient___create']; + }; + '/admin/abuse-report/notification-recipient/update': { + /** + * admin/abuse-report/notification-recipient/update + * @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:admin:abuse-report:notification-recipient* + */ + post: operations['admin___abuse-report___notification-recipient___update']; + }; + '/admin/abuse-report/notification-recipient/delete': { + /** + * admin/abuse-report/notification-recipient/delete + * @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:admin:abuse-report:notification-recipient* + */ + post: operations['admin___abuse-report___notification-recipient___delete']; + }; '/admin/accounts/create': { /** * admin/accounts/create @@ -697,6 +747,56 @@ export type paths = { */ post: operations['admin___roles___users']; }; + '/admin/system-webhook/create': { + /** + * admin/system-webhook/create + * @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:admin:system-webhook* + */ + post: operations['admin___system-webhook___create']; + }; + '/admin/system-webhook/delete': { + /** + * admin/system-webhook/delete + * @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:admin:system-webhook* + */ + post: operations['admin___system-webhook___delete']; + }; + '/admin/system-webhook/list': { + /** + * admin/system-webhook/list + * @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:admin:system-webhook* + */ + post: operations['admin___system-webhook___list']; + }; + '/admin/system-webhook/show': { + /** + * admin/system-webhook/show + * @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:admin:system-webhook* + */ + post: operations['admin___system-webhook___show']; + }; + '/admin/system-webhook/update': { + /** + * admin/system-webhook/update + * @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:admin:system-webhook* + */ + post: operations['admin___system-webhook___update']; + }; '/announcements': { /** * announcements @@ -4859,6 +4959,32 @@ export type components = { cacheRemoteSensitiveFiles: boolean; }; MetaDetailed: components['schemas']['MetaLite'] & components['schemas']['MetaDetailedOnly']; + SystemWebhook: { + id: string; + isActive: boolean; + /** Format: date-time */ + updatedAt: string; + /** Format: date-time */ + latestSentAt: string | null; + latestStatus: number | null; + name: string; + on: ('abuseReport' | 'abuseReportResolved')[]; + url: string; + secret: string; + }; + AbuseReportNotificationRecipient: { + id: string; + isActive: boolean; + /** Format: date-time */ + updatedAt: string; + name: string; + /** @enum {string} */ + method: 'email' | 'webhook'; + userId?: string; + user?: components['schemas']['UserLite']; + systemWebhookId?: string; + systemWebhook?: components['schemas']['SystemWebhook']; + }; }; responses: never; parameters: never; @@ -5126,17 +5252,17 @@ export type operations = { }; }; /** - * admin/accounts/create + * admin/abuse-report/notification-recipient/list * @description No description provided. * - * **Credential required**: *No* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *read:admin:abuse-report:notification-recipient* */ - admin___accounts___create: { + 'admin___abuse-report___notification-recipient___list': { requestBody: { content: { 'application/json': { - username: string; - password: string; + method?: ('email' | 'webhook')[]; }; }; }; @@ -5144,7 +5270,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['MeDetailed']; + 'application/json': components['schemas']['AbuseReportNotificationRecipient'][]; }; }; /** @description Client error */ @@ -5180,24 +5306,27 @@ export type operations = { }; }; /** - * admin/accounts/delete + * admin/abuse-report/notification-recipient/show * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:account* + * **Internal Endpoint**: This endpoint is an API for the misskey mainframe and is not intended for use by third parties. + * **Credential required**: *Yes* / **Permission**: *read:admin:abuse-report:notification-recipient* */ - admin___accounts___delete: { + 'admin___abuse-report___notification-recipient___show': { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - userId: string; + id: string; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['AbuseReportNotificationRecipient']; + }; }; /** @description Client error */ 400: { @@ -5232,16 +5361,24 @@ export type operations = { }; }; /** - * admin/accounts/find-by-email + * admin/abuse-report/notification-recipient/create * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:admin:account* + * **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:admin:abuse-report:notification-recipient* */ - 'admin___accounts___find-by-email': { + 'admin___abuse-report___notification-recipient___create': { requestBody: { content: { 'application/json': { - email: string; + isActive: boolean; + name: string; + /** @enum {string} */ + method: 'email' | 'webhook'; + /** Format: misskey:id */ + userId?: string; + /** Format: misskey:id */ + systemWebhookId?: string; }; }; }; @@ -5249,7 +5386,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['UserDetailedNotMe']; + 'application/json': components['schemas']['AbuseReportNotificationRecipient']; }; }; /** @description Client error */ @@ -5285,24 +5422,26 @@ export type operations = { }; }; /** - * admin/ad/create + * admin/abuse-report/notification-recipient/update * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:ad* + * **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:admin:abuse-report:notification-recipient* */ - admin___ad___create: { + 'admin___abuse-report___notification-recipient___update': { requestBody: { content: { 'application/json': { - url: string; - memo: string; - place: string; - priority: string; - ratio: number; - expiresAt: number; - startsAt: number; - imageUrl: string; - dayOfWeek: number; + /** Format: misskey:id */ + id: string; + isActive: boolean; + name: string; + /** @enum {string} */ + method: 'email' | 'webhook'; + /** Format: misskey:id */ + userId?: string; + /** Format: misskey:id */ + systemWebhookId?: string; }; }; }; @@ -5310,7 +5449,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['Ad']; + 'application/json': components['schemas']['AbuseReportNotificationRecipient']; }; }; /** @description Client error */ @@ -5346,12 +5485,13 @@ export type operations = { }; }; /** - * admin/ad/delete + * admin/abuse-report/notification-recipient/delete * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:ad* + * **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:admin:abuse-report:notification-recipient* */ - admin___ad___delete: { + 'admin___abuse-report___notification-recipient___delete': { requestBody: { content: { 'application/json': { @@ -5398,23 +5538,17 @@ export type operations = { }; }; /** - * admin/ad/list + * admin/accounts/create * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:admin:ad* + * **Credential required**: *No* */ - admin___ad___list: { + admin___accounts___create: { requestBody: { content: { 'application/json': { - /** @default 10 */ - limit?: number; - /** Format: misskey:id */ - sinceId?: string; - /** Format: misskey:id */ - untilId?: string; - /** @default null */ - publishing?: boolean | null; + username: string; + password: string; }; }; }; @@ -5422,7 +5556,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': components['schemas']['Ad'][]; + 'application/json': components['schemas']['MeDetailed']; }; }; /** @description Client error */ @@ -5458,26 +5592,17 @@ export type operations = { }; }; /** - * admin/ad/update + * admin/accounts/delete * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:ad* + * **Credential required**: *Yes* / **Permission**: *write:admin:account* */ - admin___ad___update: { + admin___accounts___delete: { requestBody: { content: { 'application/json': { /** Format: misskey:id */ - id: string; - memo: string; - url: string; - imageUrl: string; - place: string; - priority: string; - ratio: number; - expiresAt: number; - startsAt: number; - dayOfWeek: number; + userId: string; }; }; }; @@ -5519,39 +5644,16 @@ export type operations = { }; }; /** - * admin/announcements/create + * admin/accounts/find-by-email * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:announcements* + * **Credential required**: *Yes* / **Permission**: *read:admin:account* */ - admin___announcements___create: { + 'admin___accounts___find-by-email': { requestBody: { content: { 'application/json': { - title: string; - text: string; - imageUrl: string | null; - /** - * @default info - * @enum {string} - */ - icon?: 'info' | 'warning' | 'error' | 'success'; - /** - * @default normal - * @enum {string} - */ - display?: 'normal' | 'banner' | 'dialog'; - /** @default false */ - forExistingUsers?: boolean; - /** @default false */ - silence?: boolean; - /** @default false */ - needConfirmationToRead?: boolean; - /** - * Format: misskey:id - * @default null - */ - userId?: string | null; + email: string; }; }; }; @@ -5559,20 +5661,7 @@ export type operations = { /** @description OK (with results) */ 200: { content: { - 'application/json': { - /** - * Format: id - * @example xxxxxxxxxx - */ - id: string; - /** Format: date-time */ - createdAt: string; - /** Format: date-time */ - updatedAt: string | null; - title: string; - text: string; - imageUrl: string | null; - }; + 'application/json': components['schemas']['UserDetailedNotMe']; }; }; /** @description Client error */ @@ -5608,24 +5697,33 @@ export type operations = { }; }; /** - * admin/announcements/delete + * admin/ad/create * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *write:admin:announcements* + * **Credential required**: *Yes* / **Permission**: *write:admin:ad* */ - admin___announcements___delete: { + admin___ad___create: { requestBody: { content: { 'application/json': { - /** Format: misskey:id */ - id: string; + url: string; + memo: string; + place: string; + priority: string; + ratio: number; + expiresAt: number; + startsAt: number; + imageUrl: string; + dayOfWeek: number; }; }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['Ad']; + }; }; /** @description Client error */ 400: { @@ -5660,32 +5758,346 @@ export type operations = { }; }; /** - * admin/announcements/list + * admin/ad/delete * @description No description provided. * - * **Credential required**: *Yes* / **Permission**: *read:admin:announcements* + * **Credential required**: *Yes* / **Permission**: *write:admin:ad* */ - admin___announcements___list: { + admin___ad___delete: { requestBody: { content: { 'application/json': { - /** @default 10 */ - limit?: number; - /** Format: misskey:id */ - sinceId?: string; - /** Format: misskey:id */ - untilId?: string; /** Format: misskey:id */ - userId?: string | null; + id: string; }; }; }; responses: { - /** @description OK (with results) */ - 200: { - content: { - 'application/json': ({ - /** + /** @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/ad/list + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:admin:ad* + */ + admin___ad___list: { + requestBody: { + content: { + 'application/json': { + /** @default 10 */ + limit?: number; + /** Format: misskey:id */ + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; + /** @default null */ + publishing?: boolean | null; + }; + }; + }; + responses: { + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['Ad'][]; + }; + }; + /** @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/ad/update + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:admin:ad* + */ + admin___ad___update: { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + id: string; + memo: string; + url: string; + imageUrl: string; + place: string; + priority: string; + ratio: number; + expiresAt: number; + startsAt: number; + dayOfWeek: number; + }; + }; + }; + 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/create + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:admin:announcements* + */ + admin___announcements___create: { + requestBody: { + content: { + 'application/json': { + title: string; + text: string; + imageUrl: string | null; + /** + * @default info + * @enum {string} + */ + icon?: 'info' | 'warning' | 'error' | 'success'; + /** + * @default normal + * @enum {string} + */ + display?: 'normal' | 'banner' | 'dialog'; + /** @default false */ + forExistingUsers?: boolean; + /** @default false */ + silence?: boolean; + /** @default false */ + needConfirmationToRead?: boolean; + /** + * Format: misskey:id + * @default null + */ + userId?: string | null; + }; + }; + }; + responses: { + /** @description OK (with results) */ + 200: { + content: { + 'application/json': { + /** + * Format: id + * @example xxxxxxxxxx + */ + id: string; + /** Format: date-time */ + createdAt: string; + /** Format: date-time */ + updatedAt: string | null; + title: string; + text: string; + imageUrl: string | null; + }; + }; + }; + /** @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/delete + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:admin:announcements* + */ + admin___announcements___delete: { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + id: 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/list + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *read:admin:announcements* + */ + admin___announcements___list: { + requestBody: { + content: { + 'application/json': { + /** @default 10 */ + limit?: number; + /** Format: misskey:id */ + sinceId?: string; + /** Format: misskey:id */ + untilId?: string; + /** Format: misskey:id */ + userId?: string | null; + }; + }; + }; + responses: { + /** @description OK (with results) */ + 200: { + content: { + 'application/json': ({ + /** * Format: id * @example xxxxxxxxxx */ @@ -9615,6 +10027,287 @@ export type operations = { }; }; }; + /** + * admin/system-webhook/create + * @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:admin:system-webhook* + */ + 'admin___system-webhook___create': { + requestBody: { + content: { + 'application/json': { + isActive: boolean; + name: string; + on: ('abuseReport' | 'abuseReportResolved')[]; + url: string; + secret: string; + }; + }; + }; + responses: { + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['SystemWebhook']; + }; + }; + /** @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/system-webhook/delete + * @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:admin:system-webhook* + */ + 'admin___system-webhook___delete': { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + id: 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/system-webhook/list + * @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:admin:system-webhook* + */ + 'admin___system-webhook___list': { + requestBody: { + content: { + 'application/json': { + isActive?: boolean; + on?: ('abuseReport' | 'abuseReportResolved')[]; + }; + }; + }; + responses: { + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['SystemWebhook'][]; + }; + }; + /** @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/system-webhook/show + * @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:admin:system-webhook* + */ + 'admin___system-webhook___show': { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + id: string; + }; + }; + }; + responses: { + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['SystemWebhook']; + }; + }; + /** @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/system-webhook/update + * @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:admin:system-webhook* + */ + 'admin___system-webhook___update': { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + id: string; + isActive: boolean; + name: string; + on: ('abuseReport' | 'abuseReportResolved')[]; + url: string; + secret: string; + }; + }; + }; + responses: { + /** @description OK (with results) */ + 200: { + content: { + 'application/json': components['schemas']['SystemWebhook']; + }; + }; + /** @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']; + }; + }; + }; + }; /** * announcements * @description No description provided. diff --git a/packages/misskey-js/src/consts.ts b/packages/misskey-js/src/consts.ts index fd6ef4d68d..03b9069290 100644 --- a/packages/misskey-js/src/consts.ts +++ b/packages/misskey-js/src/consts.ts @@ -325,4 +325,30 @@ export type ModerationLogPayloads = { userHost: string | null; fileId: string; }; + createSystemWebhook: { + systemWebhookId: string; + webhook: any; + }; + updateSystemWebhook: { + systemWebhookId: string; + before: any; + after: any; + }; + deleteSystemWebhook: { + systemWebhookId: string; + webhook: any; + }; + createAbuseReportNotificationRecipient: { + recipientId: string; + recipient: any; + }; + updateAbuseReportNotificationRecipient: { + recipientId: string; + before: any; + after: any; + }; + deleteAbuseReportNotificationRecipient: { + recipientId: string; + recipient: any; + }; }; diff --git a/packages/misskey-js/src/entities.ts b/packages/misskey-js/src/entities.ts index 35503d6d6f..7a84cb6a1a 100644 --- a/packages/misskey-js/src/entities.ts +++ b/packages/misskey-js/src/entities.ts @@ -132,8 +132,23 @@ export type ModerationLog = { type: 'unsetUserAvatar'; info: ModerationLogPayloads['unsetUserAvatar']; } | { - type: 'unsetUserBanner'; - info: ModerationLogPayloads['unsetUserBanner']; + type: 'createSystemWebhook'; + info: ModerationLogPayloads['createSystemWebhook']; +} | { + type: 'updateSystemWebhook'; + info: ModerationLogPayloads['updateSystemWebhook']; +} | { + type: 'deleteSystemWebhook'; + info: ModerationLogPayloads['deleteSystemWebhook']; +} | { + type: 'createAbuseReportNotificationRecipient'; + info: ModerationLogPayloads['createAbuseReportNotificationRecipient']; +} | { + type: 'updateAbuseReportNotificationRecipient'; + info: ModerationLogPayloads['updateAbuseReportNotificationRecipient']; +} | { + type: 'deleteAbuseReportNotificationRecipient'; + info: ModerationLogPayloads['deleteAbuseReportNotificationRecipient']; }); export type ServerStats = { From 9849aab40283cbde2184e74d4795aec8ef8ccba3 Mon Sep 17 00:00:00 2001 From: zyoshoka <107108195+zyoshoka@users.noreply.github.com> Date: Sat, 8 Jun 2024 18:00:54 +0900 Subject: [PATCH 141/154] test(#10336): add `components/MkC.*` stories (#13830) * test(storybook): add `components/MkC.*` stories * test(storybook): add some tests * test: add sleep * test: comment-out flaky test * test(storybook): add test for `MkChannelFollowButton` * chore(storybook): tweak sleep duration in `MkChannelFollowButton` story test * fix(chromatic): add delay to `MkChannelList` * chore: replace `mswDecorator` with `mswLoader` * fix(storybook): tweak some parameters * chore: serve static files * fix(chromatic): add delay to `MkCwButton` * chore: delete logging for debug * fix: add right click in `MkContextMenu` play * refactor: remove unused imports --- packages/frontend/.storybook/fakes.ts | 60 +++++++++ packages/frontend/.storybook/generate.tsx | 2 +- packages/frontend/.storybook/main.ts | 1 + packages/frontend/.storybook/preview.ts | 4 +- packages/frontend/package.json | 2 + .../MkChannelFollowButton.stories.impl.ts | 77 ++++++++++++ .../src/components/MkChannelFollowButton.vue | 5 +- .../components/MkChannelList.stories.impl.ts | 65 ++++++++++ .../MkChannelPreview.stories.impl.ts | 43 +++++++ .../src/components/MkChart.stories.impl.ts | 117 ++++++++++++++++++ packages/frontend/src/components/MkChart.vue | 90 ++++++++------ .../components/MkChartLegend.stories.impl.ts | 7 ++ .../components/MkChartTooltip.stories.impl.ts | 7 ++ .../components/MkClickerGame.stories.impl.ts | 79 ++++++++++++ .../frontend/src/components/MkClickerGame.vue | 2 +- .../components/MkClipPreview.stories.impl.ts | 43 +++++++ .../components/MkCode.core.stories.impl.ts | 7 ++ .../src/components/MkCode.stories.impl.ts | 44 +++++++ .../components/MkCodeEditor.stories.impl.ts | 62 ++++++++++ .../components/MkCodeInline.stories.impl.ts | 37 ++++++ .../components/MkColorInput.stories.impl.ts | 50 ++++++++ .../components/MkContainer.stories.impl.ts | 7 ++ .../components/MkContextMenu.stories.impl.ts | 58 +++++++++ .../MkCropperDialog.stories.impl.ts | 75 +++++++++++ ...kCustomEmojiDetailedDialog.stories.impl.ts | 38 ++++++ .../src/components/MkCwButton.stories.impl.ts | 89 +++++++++++++ packages/frontend/src/scripts/test-utils.ts | 10 ++ pnpm-lock.yaml | 88 +++++++------ 28 files changed, 1083 insertions(+), 86 deletions(-) create mode 100644 packages/frontend/src/components/MkChannelFollowButton.stories.impl.ts create mode 100644 packages/frontend/src/components/MkChannelList.stories.impl.ts create mode 100644 packages/frontend/src/components/MkChannelPreview.stories.impl.ts create mode 100644 packages/frontend/src/components/MkChart.stories.impl.ts create mode 100644 packages/frontend/src/components/MkChartLegend.stories.impl.ts create mode 100644 packages/frontend/src/components/MkChartTooltip.stories.impl.ts create mode 100644 packages/frontend/src/components/MkClickerGame.stories.impl.ts create mode 100644 packages/frontend/src/components/MkClipPreview.stories.impl.ts create mode 100644 packages/frontend/src/components/MkCode.core.stories.impl.ts create mode 100644 packages/frontend/src/components/MkCode.stories.impl.ts create mode 100644 packages/frontend/src/components/MkCodeEditor.stories.impl.ts create mode 100644 packages/frontend/src/components/MkCodeInline.stories.impl.ts create mode 100644 packages/frontend/src/components/MkColorInput.stories.impl.ts create mode 100644 packages/frontend/src/components/MkContainer.stories.impl.ts create mode 100644 packages/frontend/src/components/MkContextMenu.stories.impl.ts create mode 100644 packages/frontend/src/components/MkCropperDialog.stories.impl.ts create mode 100644 packages/frontend/src/components/MkCustomEmojiDetailedDialog.stories.impl.ts create mode 100644 packages/frontend/src/components/MkCwButton.stories.impl.ts diff --git a/packages/frontend/.storybook/fakes.ts b/packages/frontend/.storybook/fakes.ts index 3a24ccb248..fdb155261b 100644 --- a/packages/frontend/.storybook/fakes.ts +++ b/packages/frontend/.storybook/fakes.ts @@ -22,6 +22,66 @@ export function abuseUserReport() { }; } +export function channel(id = 'somechannelid', name = 'Some Channel', bannerUrl: string | null = 'https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/fedi.jpg?raw=true'): entities.Channel { + return { + id, + createdAt: '2016-12-28T22:49:51.000Z', + lastNotedAt: '2016-12-28T22:49:51.000Z', + name, + description: null, + userId: null, + bannerUrl, + pinnedNoteIds: [], + color: '#000', + isArchived: false, + usersCount: 1, + notesCount: 1, + isSensitive: false, + allowRenoteToExternal: false, + }; +} + +export function clip(id = 'someclipid', name = 'Some Clip'): entities.Clip { + return { + id, + createdAt: '2016-12-28T22:49:51.000Z', + lastClippedAt: null, + userId: 'someuserid', + user: { + id: 'someuserid', + name: 'Misskey User', + username: 'miskist', + host: 'misskey-hub.net', + avatarUrl: 'https://github.com/misskey-dev/misskey/blob/master/packages/frontend/assets/about-icon.png?raw=true', + avatarBlurhash: 'eQFRshof5NWBRi},juayfPju53WB?0ofs;s*a{ofjuay^SoMEJR%ay', + avatarDecorations: [], + emojis: {}, + badgeRoles: [], + onlineStatus: 'unknown', + }, + notesCount: undefined, + name, + description: 'Some clip description', + isPublic: false, + favoritedCount: 0, + }; +} + +export function emojiDetailed(id = 'someemojiid', name = 'some_emoji'): entities.EmojiDetailed { + return { + id, + aliases: ['alias1', 'alias2'], + name, + category: 'emojiCategory', + host: null, + url: '/client-assets/about-icon.png', + license: null, + isSensitive: false, + localOnly: false, + roleIdsThatCanBeUsedThisEmojiAsReaction: ['roleId1', 'roleId2'], + }; +} + export function galleryPost(isSensitive = false) { return { id: 'somepostid', diff --git a/packages/frontend/.storybook/generate.tsx b/packages/frontend/.storybook/generate.tsx index d74c83a500..d21eea9d17 100644 --- a/packages/frontend/.storybook/generate.tsx +++ b/packages/frontend/.storybook/generate.tsx @@ -397,7 +397,7 @@ function toStories(component: string): Promise { const globs = await Promise.all([ glob('src/components/global/Mk*.vue'), glob('src/components/global/RouterView.vue'), - glob('src/components/Mk{A,B}*.vue'), + glob('src/components/Mk[A-C]*.vue'), glob('src/components/MkDigitalClock.vue'), glob('src/components/MkGalleryPostPreview.vue'), glob('src/components/MkSignupServerRules.vue'), diff --git a/packages/frontend/.storybook/main.ts b/packages/frontend/.storybook/main.ts index d3822942cd..9f318cf449 100644 --- a/packages/frontend/.storybook/main.ts +++ b/packages/frontend/.storybook/main.ts @@ -15,6 +15,7 @@ const _dirname = fileURLToPath(new URL('.', import.meta.url)); const config = { stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], + staticDirs: [{ from: '../assets', to: '/client-assets' }], addons: [ getAbsolutePath('@storybook/addon-essentials'), getAbsolutePath('@storybook/addon-interactions'), diff --git a/packages/frontend/.storybook/preview.ts b/packages/frontend/.storybook/preview.ts index 982a2979ac..73ee007fb8 100644 --- a/packages/frontend/.storybook/preview.ts +++ b/packages/frontend/.storybook/preview.ts @@ -7,7 +7,7 @@ import { FORCE_REMOUNT } from '@storybook/core-events'; import { addons } from '@storybook/preview-api'; import { type Preview, setup } from '@storybook/vue3'; import isChromatic from 'chromatic/isChromatic'; -import { initialize, mswDecorator } from 'msw-storybook-addon'; +import { initialize, mswLoader } from 'msw-storybook-addon'; import { userDetailed } from './fakes.js'; import locale from './locale.js'; import { commonHandlers, onUnhandledRequest } from './mocks.js'; @@ -122,7 +122,6 @@ const preview = { } return story; }, - mswDecorator, (Story, context) => { return { setup() { @@ -137,6 +136,7 @@ const preview = { }; }, ], + loaders: [mswLoader], parameters: { controls: { exclude: /^__/, diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 56b824c0c5..66940a1601 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -104,6 +104,7 @@ "@types/node": "20.12.7", "@types/punycode": "2.1.4", "@types/sanitize-html": "2.11.0", + "@types/seedrandom": "3.0.8", "@types/throttle-debounce": "5.0.2", "@types/tinycolor2": "1.4.6", "@types/uuid": "9.0.8", @@ -128,6 +129,7 @@ "prettier": "3.2.5", "react": "18.3.1", "react-dom": "18.3.1", + "seedrandom": "3.0.5", "start-server-and-test": "2.0.3", "storybook": "8.0.9", "storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme", diff --git a/packages/frontend/src/components/MkChannelFollowButton.stories.impl.ts b/packages/frontend/src/components/MkChannelFollowButton.stories.impl.ts new file mode 100644 index 0000000000..b99620da22 --- /dev/null +++ b/packages/frontend/src/components/MkChannelFollowButton.stories.impl.ts @@ -0,0 +1,77 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +/* eslint-disable @typescript-eslint/explicit-function-return-type */ +/* eslint-disable import/no-default-export */ +import { StoryObj } from '@storybook/vue3'; +import { HttpResponse, http } from 'msw'; +import { action } from '@storybook/addon-actions'; +import { expect, userEvent, within } from '@storybook/test'; +import { channel } from '../../.storybook/fakes.js'; +import { commonHandlers } from '../../.storybook/mocks.js'; +import MkChannelFollowButton from './MkChannelFollowButton.vue'; +import { semaphore } from '@/scripts/test-utils.js'; +import { i18n } from '@/i18n.js'; + +function sleep(ms: number) { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +const s = semaphore(); +export const Default = { + render(args) { + return { + components: { + MkChannelFollowButton, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + }, + template: '', + }; + }, + args: { + channel: channel(), + full: true, + }, + async play({ canvasElement }) { + await s.acquire(); + await sleep(1000); + const canvas = within(canvasElement); + const buttonElement = canvas.getByRole('button'); + await expect(buttonElement).toHaveTextContent(i18n.ts.follow); + await userEvent.click(buttonElement); + await sleep(1000); + await expect(buttonElement).toHaveTextContent(i18n.ts.unfollow); + await sleep(100); + await userEvent.click(buttonElement); + s.release(); + }, + parameters: { + layout: 'centered', + msw: { + handlers: [ + ...commonHandlers, + http.post('/api/channels/follow', async ({ request }) => { + action('POST /api/channels/follow')(await request.json()); + return HttpResponse.json({}); + }), + http.post('/api/channels/unfollow', async ({ request }) => { + action('POST /api/channels/unfollow')(await request.json()); + return HttpResponse.json({}); + }), + ], + }, + }, +} satisfies StoryObj; diff --git a/packages/frontend/src/components/MkChannelFollowButton.vue b/packages/frontend/src/components/MkChannelFollowButton.vue index 6b1b380e41..841d37a568 100644 --- a/packages/frontend/src/components/MkChannelFollowButton.vue +++ b/packages/frontend/src/components/MkChannelFollowButton.vue @@ -26,17 +26,18 @@ SPDX-License-Identifier: AGPL-3.0-only diff --git a/packages/frontend/src/components/MkNoteSub.vue b/packages/frontend/src/components/MkNoteSub.vue index 0a5a245774..b6469bcbef 100644 --- a/packages/frontend/src/components/MkNoteSub.vue +++ b/packages/frontend/src/components/MkNoteSub.vue @@ -88,12 +88,12 @@ if (props.detail) { } function noteClick(ev: MouseEvent) { - if (!expandOnNoteClick || window.getSelection().toString() !== '' || defaultStore.state.expandOnNoteClickBehavior === 'doubleClick') ev.stopPropagation(); + if (!expandOnNoteClick || window.getSelection()?.toString() !== '' || defaultStore.state.expandOnNoteClickBehavior === 'doubleClick') ev.stopPropagation(); else router.push(notePage(props.note)); } function noteDblClick(ev: MouseEvent) { - if (!expandOnNoteClick || window.getSelection().toString() !== '' || defaultStore.state.expandOnNoteClickBehavior === 'click') ev.stopPropagation(); + if (!expandOnNoteClick || window.getSelection()?.toString() !== '' || defaultStore.state.expandOnNoteClickBehavior === 'click') ev.stopPropagation(); else router.push(notePage(props.note)); } diff --git a/packages/frontend/src/ui/classic.vue b/packages/frontend/src/ui/classic.vue index b833e9f6be..d134f1b0fa 100644 --- a/packages/frontend/src/ui/classic.vue +++ b/packages/frontend/src/ui/classic.vue @@ -112,7 +112,7 @@ function onContextmenu(ev: MouseEvent) { }; if (isLink(ev.target)) return; if (['INPUT', 'TEXTAREA', 'IMG', 'VIDEO', 'CANVAS'].includes(ev.target.tagName) || ev.target.attributes['contenteditable']) return; - if (window.getSelection().toString() !== '') return; + if (window.getSelection()?.toString() !== '') return; const path = mainRouter.getCurrentPath(); os.contextMenu([{ type: 'label', From 72e5c8ddb3254421a1d0bde6b48003d875caf983 Mon Sep 17 00:00:00 2001 From: NoriDev Date: Wed, 12 Jun 2024 11:47:38 +0900 Subject: [PATCH 146/154] =?UTF-8?q?enhance(frontend):=20=EB=85=B8=ED=8A=B8?= =?UTF-8?q?=20=EC=9E=91=EC=84=B1=20=ED=8F=BC=EC=97=90=EC=84=9C=20'?= =?UTF-8?q?=EA=B3=B5=EA=B0=9C=20=EB=B2=94=EC=9C=84=20=EA=B8=B0=EC=96=B5?= =?UTF-8?q?=ED=95=98=EA=B8=B0'=20=EC=84=A4=EC=A0=95=EC=9D=84=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=ED=95=A0=20=EC=88=98=20=EC=9E=88=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG_CHERRYPICK.md | 1 + .../frontend/src/components/MkVisibilityPicker.vue | 12 +++++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/CHANGELOG_CHERRYPICK.md b/CHANGELOG_CHERRYPICK.md index 690bcb3815..e8bef66676 100644 --- a/CHANGELOG_CHERRYPICK.md +++ b/CHANGELOG_CHERRYPICK.md @@ -29,6 +29,7 @@ Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2024xx](CHANGE Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2024xx](CHANGELOG.md#2024xx) 문서를 참고하십시오. ### Client +- Enhance: 노트 작성 폼에서 '공개 범위 기억하기' 설정을 변경할 수 있음 - Fix: 타임라인 노트의 리액션 뷰어에 리모트 서버의 커스텀 이모지가 표시되지 않음 - Fix: '리노트 공개 범위 지정' 옵션이 `없음`으로 설정된 경우 리노트를 할 수 없음 diff --git a/packages/frontend/src/components/MkVisibilityPicker.vue b/packages/frontend/src/components/MkVisibilityPicker.vue index 95ad80959c..8af5d455bc 100644 --- a/packages/frontend/src/components/MkVisibilityPicker.vue +++ b/packages/frontend/src/components/MkVisibilityPicker.vue @@ -37,14 +37,22 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts._visibility.specifiedDescription }}
+ + + +
+ {{ i18n.ts.rememberNoteVisibility }} +
From 3e146b423aa3578540a56813d6c9085e3542625e Mon Sep 17 00:00:00 2001 From: NoriDev Date: Wed, 12 Jun 2024 12:15:26 +0900 Subject: [PATCH 149/154] =?UTF-8?q?fix(frontend):=20=EC=95=84=EB=B0=94?= =?UTF-8?q?=ED=83=80=20=EC=9E=A5=EC=8B=9D=EC=9D=84=20=EC=84=A4=EC=A0=95?= =?UTF-8?q?=ED=95=A0=20=EB=95=8C=20=EC=84=A4=EC=A0=95=20=ED=8C=9D=EC=97=85?= =?UTF-8?q?=EC=9D=B4=20=EB=91=90=20=EB=B2=88=20=ED=91=9C=EC=8B=9C=EB=90=A0?= =?UTF-8?q?=20=EC=88=98=20=EC=9E=88=EC=9D=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG_CHERRYPICK.md | 3 ++- .../src/pages/settings/avatar-decoration.decoration.vue | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG_CHERRYPICK.md b/CHANGELOG_CHERRYPICK.md index e8bef66676..3b74d5985c 100644 --- a/CHANGELOG_CHERRYPICK.md +++ b/CHANGELOG_CHERRYPICK.md @@ -31,7 +31,8 @@ Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2024xx](CHANGE ### Client - Enhance: 노트 작성 폼에서 '공개 범위 기억하기' 설정을 변경할 수 있음 - Fix: 타임라인 노트의 리액션 뷰어에 리모트 서버의 커스텀 이모지가 표시되지 않음 -- Fix: '리노트 공개 범위 지정' 옵션이 `없음`으로 설정된 경우 리노트를 할 수 없음 +- Fix: '리노트 공개 범위 지정' 옵션이 `없음`으로 설정된 경우 리노트를 할 수 없음 +- Fix: 아바타 장식을 설정할 때 설정 팝업이 두 번 표시될 수 있음 ### Server - Feat: 리모트 유저의 아바타 장식을 여러 개 불러올 수 있음([yunochi/misskey@696787b3](https://github.com/yunochi/misskey/commit/696787b38bac31e7586899a5a59611a6fe50b9a1), [yunochi/misskey@4a5fcfe4](https://github.com/yunochi/misskey/commit/4a5fcfe43880f08380541caa6b7593b90306d103)) diff --git a/packages/frontend/src/pages/settings/avatar-decoration.decoration.vue b/packages/frontend/src/pages/settings/avatar-decoration.decoration.vue index e8be612cb7..45101003f6 100644 --- a/packages/frontend/src/pages/settings/avatar-decoration.decoration.vue +++ b/packages/frontend/src/pages/settings/avatar-decoration.decoration.vue @@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only @click="emit('click')" >
{{ decoration.name }}
- +
From 7aec22eb2e9070c119853d3347397544e66256d6 Mon Sep 17 00:00:00 2001 From: NoriDev Date: Wed, 12 Jun 2024 12:35:46 +0900 Subject: [PATCH 150/154] lint --- packages/backend/src/models/RepositoryModule.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend/src/models/RepositoryModule.ts b/packages/backend/src/models/RepositoryModule.ts index 09c146e31c..325645cbd6 100644 --- a/packages/backend/src/models/RepositoryModule.ts +++ b/packages/backend/src/models/RepositoryModule.ts @@ -3,7 +3,6 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import type { Provider } from '@nestjs/common'; import { Module } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; import { @@ -83,8 +82,9 @@ import { MiUserProfile, MiUserPublickey, MiUserSecurityKey, - MiWebhook + MiWebhook, } from './_.js'; +import type { Provider } from '@nestjs/common'; import type { DataSource } from 'typeorm'; const $usersRepository: Provider = { From adb8ec02a19dc8a49de94e18f6fb2ffb42fb576e Mon Sep 17 00:00:00 2001 From: NoriDev Date: Wed, 12 Jun 2024 12:36:04 +0900 Subject: [PATCH 151/154] 4.9.0-beta.2 --- package.json | 2 +- packages/cherrypick-js/package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 3e1f4d7768..69ef7b0303 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cherrypick", - "version": "4.9.0-beta.1", + "version": "4.9.0-beta.2", "basedMisskeyVersion": "2024.5.0", "codename": "nasubi", "repository": { diff --git a/packages/cherrypick-js/package.json b/packages/cherrypick-js/package.json index 74cf4bcf9f..5c0c9d9677 100644 --- a/packages/cherrypick-js/package.json +++ b/packages/cherrypick-js/package.json @@ -1,7 +1,7 @@ { "type": "module", "name": "cherrypick-js", - "version": "4.9.0-beta.1", + "version": "4.9.0-beta.2", "basedMisskeyVersion": "2024.5.0", "description": "CherryPick SDK for JavaScript", "license": "MIT", From cd95a6e9c9ec0661892d824f520a48416e96adef Mon Sep 17 00:00:00 2001 From: anatawa12 Date: Thu, 18 Jul 2024 14:23:47 +0900 Subject: [PATCH 152/154] fix: remove unreleased section (#14246) --- CHANGELOG.md | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d4ef23d27..9f78ba677d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,15 +1,3 @@ -## Unreleased - -### General -- - -### Client -- - -### Server -- - - ## 2024.5.0 ### Note From c8626912a0983a3e1e0ce9db02b0cf7ad03269f6 Mon Sep 17 00:00:00 2001 From: NoriDev Date: Sat, 27 Jul 2024 15:41:30 +0900 Subject: [PATCH 153/154] =?UTF-8?q?fix(frontend):=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EB=B8=94=EB=A1=9D=EC=9D=98=20=ED=95=98=EC=9D=B4=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=ED=8A=B8=EA=B0=80=20=EC=8B=A4=EC=A0=9C=20=EC=9C=84?= =?UTF-8?q?=EC=B9=98=EC=99=80=20=EB=8B=A4=EB=A5=B4=EA=B2=8C=20=ED=91=9C?= =?UTF-8?q?=EC=8B=9C=EB=90=A0=20=EC=88=98=20=EC=9E=88=EC=9D=8C=20(kokonect?= =?UTF-8?q?-link/cherrypick#475)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG_CHERRYPICK.md | 1 + packages/frontend/src/components/MkCode.core.vue | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG_CHERRYPICK.md b/CHANGELOG_CHERRYPICK.md index 3b74d5985c..dcd1eee0c4 100644 --- a/CHANGELOG_CHERRYPICK.md +++ b/CHANGELOG_CHERRYPICK.md @@ -33,6 +33,7 @@ Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2024xx](CHANGE - Fix: 타임라인 노트의 리액션 뷰어에 리모트 서버의 커스텀 이모지가 표시되지 않음 - Fix: '리노트 공개 범위 지정' 옵션이 `없음`으로 설정된 경우 리노트를 할 수 없음 - Fix: 아바타 장식을 설정할 때 설정 팝업이 두 번 표시될 수 있음 +- Fix: 코드 블록의 하이라이트가 실제 위치와 다르게 표시될 수 있음 (kokonect-link/cherrypick#475) ### Server - Feat: 리모트 유저의 아바타 장식을 여러 개 불러올 수 있음([yunochi/misskey@696787b3](https://github.com/yunochi/misskey/commit/696787b38bac31e7586899a5a59611a6fe50b9a1), [yunochi/misskey@4a5fcfe4](https://github.com/yunochi/misskey/commit/4a5fcfe43880f08380541caa6b7593b90306d103)) diff --git a/packages/frontend/src/components/MkCode.core.vue b/packages/frontend/src/components/MkCode.core.vue index 171df8c8d1..52d3374ece 100644 --- a/packages/frontend/src/components/MkCode.core.vue +++ b/packages/frontend/src/components/MkCode.core.vue @@ -90,7 +90,7 @@ watch(() => props.lang, (to) => { & pre, & code { - font-family: "JetBrains Mono", "Pretendard JP", Pretendard, Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; + font-family: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace; } } From 745e00666e8814f01a9c8f61bd57c406024a6b58 Mon Sep 17 00:00:00 2001 From: NoriDev Date: Sat, 27 Jul 2024 15:49:12 +0900 Subject: [PATCH 154/154] 4.9.0 --- CHANGELOG_CHERRYPICK.md | 8 ++++---- package.json | 2 +- packages/cherrypick-js/package.json | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/CHANGELOG_CHERRYPICK.md b/CHANGELOG_CHERRYPICK.md index dcd1eee0c4..424e23f1ea 100644 --- a/CHANGELOG_CHERRYPICK.md +++ b/CHANGELOG_CHERRYPICK.md @@ -23,10 +23,10 @@ Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2024xx](CHANGE # 릴리즈 노트 이 문서는 CherryPick의 변경 사항만 포함합니다. -## 4.x.x -출시일: unreleased
-기반 Misskey 버전: 2024.x.x
-Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#2024xx](CHANGELOG.md#2024xx) 문서를 참고하십시오. +## 4.9.0 +출시일: 2024/7/27
+기반 Misskey 버전: 2024.5.0
+Misskey의 전체 변경 사항을 확인하려면, [CHANGELOG.md#202450](CHANGELOG.md#202450) 문서를 참고하십시오. ### Client - Enhance: 노트 작성 폼에서 '공개 범위 기억하기' 설정을 변경할 수 있음 diff --git a/package.json b/package.json index 69ef7b0303..bb6c2ce1c7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cherrypick", - "version": "4.9.0-beta.2", + "version": "4.9.0", "basedMisskeyVersion": "2024.5.0", "codename": "nasubi", "repository": { diff --git a/packages/cherrypick-js/package.json b/packages/cherrypick-js/package.json index 5c0c9d9677..fc3d1efcb7 100644 --- a/packages/cherrypick-js/package.json +++ b/packages/cherrypick-js/package.json @@ -1,7 +1,7 @@ { "type": "module", "name": "cherrypick-js", - "version": "4.9.0-beta.2", + "version": "4.9.0", "basedMisskeyVersion": "2024.5.0", "description": "CherryPick SDK for JavaScript", "license": "MIT",