diff --git a/CHANGELOG.md b/CHANGELOG.md index 35351333f0..4a25d14253 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ### General - Feat: 通報を受けた際、または解決した際に、予め登録した宛先に通知を飛ばせるように(mail or webhook) #13705 - Fix: 配信停止したインスタンス一覧が見れなくなる問題を修正 +- Fix: Dockerコンテナの立ち上げ時に`pnpm`のインストールで固まることがある問題 ### Client - Feat: ノート単体・ユーザーのノート・クリップのノートの埋め込み機能 @@ -10,6 +11,7 @@ - Fix: `/about#federation` ページなどで各インスタンスのチャートが表示されなくなっていた問題を修正 - Fix: ユーザーページの追加情報のラベルを投稿者のサーバーの絵文字で表示する (#13968) - Fix: リバーシの対局を正しく共有できないことがある問題を修正 +- Fix: コントロールパネルでベースロールのポリシーを編集してもUI上では変更が反映されない問題を修正 ### Server - チャート生成時にinstance.suspentionStateに置き換えられたinstance.isSuspendedが参照されてしまう問題を修正 @@ -24,6 +26,9 @@ - Enhance: エンドポイント`admin/ad/update`の必須項目を`id`のみに - Fix: notRespondingSinceが実装される前に不通になったインスタンスが自動的に配信停止にならない (#14059) - Fix: FTT有効時、タイムライン用エンドポイントで`sinceId`にキャッシュ内最古のものより古いものを指定した場合に正しく結果が返ってこない問題を修正 +- Fix: 自分以外のクリップ内のノート個数が見えることがあるのを修正 +- Fix: 空文字列のリアクションはフォールバックされるように +- Fix: リノートにリアクションできないように ## 2024.5.0 diff --git a/Dockerfile b/Dockerfile index 9fc2d611cd..d6ca6b8cdf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -82,6 +82,10 @@ RUN apt-get update \ USER misskey WORKDIR /misskey +# add package.json to add pnpm +COPY --chown=misskey:misskey ./package.json ./package.json +RUN corepack install + COPY --chown=misskey:misskey --from=target-builder /misskey/node_modules ./node_modules COPY --chown=misskey:misskey --from=target-builder /misskey/packages/backend/node_modules ./packages/backend/node_modules COPY --chown=misskey:misskey --from=target-builder /misskey/packages/misskey-js/node_modules ./packages/misskey-js/node_modules diff --git a/locales/index.d.ts b/locales/index.d.ts index ea4e0dd0df..b1817f04c7 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -9765,7 +9765,7 @@ export interface Locale extends ILocale { "_dataSaver": { "_media": { /** - * メディアの読み込み + * メディアの読み込みを無効化 */ "title": string; /** @@ -9775,7 +9775,7 @@ export interface Locale extends ILocale { }; "_avatar": { /** - * アイコン画像 + * アイコン画像のアニメーションを無効化 */ "title": string; /** @@ -9785,7 +9785,7 @@ export interface Locale extends ILocale { }; "_urlPreview": { /** - * URLプレビューのサムネイル + * URLプレビューのサムネイルを非表示 */ "title": string; /** @@ -9795,7 +9795,7 @@ export interface Locale extends ILocale { }; "_code": { /** - * コードハイライト + * コードハイライトを非表示 */ "title": string; /** diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 53716a4935..bc4b23bb51 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -2600,16 +2600,16 @@ _externalResourceInstaller: _dataSaver: _media: - title: "メディアの読み込み" + title: "メディアの読み込みを無効化" description: "画像・動画が自動で読み込まれるのを防止します。隠れている画像・動画はタップすると読み込まれます。" _avatar: - title: "アイコン画像" + title: "アイコン画像のアニメーションを無効化" description: "アイコン画像のアニメーションが停止します。アニメーション画像は通常の画像よりファイルサイズが大きいことがあるので、データ通信量をさらに削減できます。" _urlPreview: - title: "URLプレビューのサムネイル" + title: "URLプレビューのサムネイルを非表示" description: "URLプレビューのサムネイル画像が読み込まれなくなります。" _code: - title: "コードハイライト" + title: "コードハイライトを非表示" description: "MFMなどでコードハイライト記法が使われている場合、タップするまで読み込まれなくなります。コードハイライトではハイライトする言語ごとにその定義ファイルを読み込む必要がありますが、それらが自動で読み込まれなくなるため、通信量の削減が見込めます。" _hemisphere: diff --git a/packages/backend/package.json b/packages/backend/package.json index 15134b1ca8..0467ab0bee 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -207,7 +207,6 @@ "@types/mime-types": "2.1.4", "@types/ms": "0.7.34", "@types/node": "20.12.7", - "@types/node-fetch": "3.0.3", "@types/nodemailer": "6.4.15", "@types/oauth": "0.9.4", "@types/oauth2orize": "1.11.5", diff --git a/packages/backend/src/core/ReactionService.ts b/packages/backend/src/core/ReactionService.ts index cb0b079df0..64c7b2ed03 100644 --- a/packages/backend/src/core/ReactionService.ts +++ b/packages/backend/src/core/ReactionService.ts @@ -29,6 +29,7 @@ import { CustomEmojiService } from '@/core/CustomEmojiService.js'; import { RoleService } from '@/core/RoleService.js'; import { FeaturedService } from '@/core/FeaturedService.js'; import { trackPromise } from '@/misc/promise-tracker.js'; +import { isQuote, isRenote } from '@/misc/is-renote.js'; const FALLBACK = '\u2764'; const PER_NOTE_REACTION_USER_PAIR_CACHE_MAX = 16; @@ -117,11 +118,16 @@ export class ReactionService { throw new IdentifiableError('68e9d2d1-48bf-42c2-b90a-b20e09fd3d48', 'Note not accessible for you.'); } + // Check if note is Renote + if (isRenote(note) && !isQuote(note)) { + throw new IdentifiableError('12c35529-3c79-4327-b1cc-e2cf63a71925', 'You cannot react to Renote.'); + } + let reaction = _reaction ?? FALLBACK; if (note.reactionAcceptance === 'likeOnly' || ((note.reactionAcceptance === 'likeOnlyForRemote' || note.reactionAcceptance === 'nonSensitiveOnlyForLocalLikeOnlyForRemote') && (user.host != null))) { reaction = '\u2764'; - } else if (_reaction) { + } else if (_reaction != null) { const custom = reaction.match(isCustomEmojiRegexp); if (custom) { const reacterHost = this.utilityService.toPunyNullable(user.host); diff --git a/packages/backend/src/core/entities/ClipEntityService.ts b/packages/backend/src/core/entities/ClipEntityService.ts index 3855a28436..d915645906 100644 --- a/packages/backend/src/core/entities/ClipEntityService.ts +++ b/packages/backend/src/core/entities/ClipEntityService.ts @@ -53,7 +53,7 @@ export class ClipEntityService { isPublic: clip.isPublic, favoritedCount: await this.clipFavoritesRepository.countBy({ clipId: clip.id }), isFavorited: meId ? await this.clipFavoritesRepository.exists({ where: { clipId: clip.id, userId: meId } }) : undefined, - notesCount: meId ? await this.clipNotesRepository.countBy({ clipId: clip.id }) : undefined, + notesCount: (meId === clip.userId) ? await this.clipNotesRepository.countBy({ clipId: clip.id }) : undefined, }); } diff --git a/packages/backend/src/misc/prelude/array.ts b/packages/backend/src/misc/prelude/array.ts index dbfe1fff18..f741a0c913 100644 --- a/packages/backend/src/misc/prelude/array.ts +++ b/packages/backend/src/misc/prelude/array.ts @@ -65,44 +65,6 @@ export function maximum(xs: number[]): number { return Math.max(...xs); } -/** - * Splits an array based on the equivalence relation. - * The concatenation of the result is equal to the argument. - */ -export function groupBy(f: EndoRelation, xs: T[]): T[][] { - const groups = [] as T[][]; - for (const x of xs) { - const lastGroup = groups.at(-1); - if (lastGroup !== undefined && f(lastGroup[0], x)) { - lastGroup.push(x); - } else { - groups.push([x]); - } - } - return groups; -} - -/** - * Splits an array based on the equivalence relation induced by the function. - * The concatenation of the result is equal to the argument. - */ -export function groupOn(f: (x: T) => S, xs: T[]): T[][] { - return groupBy((a, b) => f(a) === f(b), xs); -} - -export function groupByX(collections: T[], keySelector: (x: T) => string) { - return collections.reduce((obj: Record, item: T) => { - const key = keySelector(item); - if (!Object.prototype.hasOwnProperty.call(obj, key)) { - obj[key] = []; - } - - obj[key].push(item); - - return obj; - }, {}); -} - /** * Compare two arrays by lexicographical order */ diff --git a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts index b9899608bf..0f0dcca605 100644 --- a/packages/backend/src/server/api/endpoints/notes/reactions/create.ts +++ b/packages/backend/src/server/api/endpoints/notes/reactions/create.ts @@ -36,6 +36,12 @@ export const meta = { code: 'YOU_HAVE_BEEN_BLOCKED', id: '20ef5475-9f38-4e4c-bd33-de6d979498ec', }, + + cannotReactToRenote: { + message: 'You cannot react to Renote.', + code: 'CANNOT_REACT_TO_RENOTE', + id: 'eaccdc08-ddef-43fe-908f-d108faad57f5', + }, }, } as const; @@ -62,6 +68,7 @@ export default class extends Endpoint { // eslint- await this.reactionService.create(me, note, ps.reaction).catch(err => { if (err.id === '51c42bb4-931a-456b-bff7-e5a8a70dd298') throw new ApiError(meta.errors.alreadyReacted); if (err.id === 'e70412a4-7197-4726-8e74-f3e0deb92aa7') throw new ApiError(meta.errors.youHaveBeenBlocked); + if (err.id === '12c35529-3c79-4327-b1cc-e2cf63a71925') throw new ApiError(meta.errors.cannotReactToRenote); throw err; }); return; diff --git a/packages/backend/test/e2e/endpoints.ts b/packages/backend/test/e2e/endpoints.ts index bc89dc37f4..de5e8ba95e 100644 --- a/packages/backend/test/e2e/endpoints.ts +++ b/packages/backend/test/e2e/endpoints.ts @@ -266,6 +266,67 @@ describe('Endpoints', () => { assert.strictEqual(res.status, 400); }); + test('リノートにリアクションできない', async () => { + const bobNote = await post(bob, { text: 'hi' }); + const bobRenote = await post(bob, { renoteId: bobNote.id }); + + const res = await api('notes/reactions/create', { + noteId: bobRenote.id, + reaction: '🚀', + }, alice); + + assert.strictEqual(res.status, 400); + assert.strictEqual(res.body.error.code, 'CANNOT_REACT_TO_RENOTE'); + }); + + test('引用にリアクションできる', async () => { + const bobNote = await post(bob, { text: 'hi' }); + const bobRenote = await post(bob, { text: 'hi again', renoteId: bobNote.id }); + + const res = await api('notes/reactions/create', { + noteId: bobRenote.id, + reaction: '🚀', + }, alice); + + assert.strictEqual(res.status, 204); + }); + + test('空文字列のリアクションは\u2764にフォールバックされる', async () => { + const bobNote = await post(bob, { text: 'hi' }); + + const res = await api('notes/reactions/create', { + noteId: bobNote.id, + reaction: '', + }, alice); + + assert.strictEqual(res.status, 204); + + const reaction = await api('notes/reactions', { + noteId: bobNote.id, + }); + + assert.strictEqual(reaction.body.length, 1); + assert.strictEqual(reaction.body[0].type, '\u2764'); + }); + + test('絵文字ではない文字列のリアクションは\u2764にフォールバックされる', async () => { + const bobNote = await post(bob, { text: 'hi' }); + + const res = await api('notes/reactions/create', { + noteId: bobNote.id, + reaction: 'Hello!', + }, alice); + + assert.strictEqual(res.status, 204); + + const reaction = await api('notes/reactions', { + noteId: bobNote.id, + }); + + assert.strictEqual(reaction.body.length, 1); + assert.strictEqual(reaction.body[0].type, '\u2764'); + }); + test('空のパラメータで怒られる', async () => { // @ts-expect-error param must not be empty const res = await api('notes/reactions/create', {}, alice); diff --git a/packages/frontend/src/filters/user.ts b/packages/frontend/src/filters/user.ts index b713d41789..a87766764d 100644 --- a/packages/frontend/src/filters/user.ts +++ b/packages/frontend/src/filters/user.ts @@ -6,7 +6,7 @@ import * as Misskey from 'misskey-js'; import { url } from '@/config.js'; -export const acct = (user: misskey.Acct) => { +export const acct = (user: Misskey.Acct) => { return Misskey.acct.toString(user); }; @@ -14,6 +14,6 @@ export const userName = (user: Misskey.entities.User) => { return user.name || user.username; }; -export const userPage = (user: misskey.Acct, path?, absolute = false) => { +export const userPage = (user: Misskey.Acct, path?: string, absolute = false) => { return `${absolute ? url : ''}/@${acct(user)}${(path ? `/${path}` : '')}`; }; diff --git a/packages/frontend/src/pages/admin/roles.vue b/packages/frontend/src/pages/admin/roles.vue index 9753d9f6cb..50323e3de5 100644 --- a/packages/frontend/src/pages/admin/roles.vue +++ b/packages/frontend/src/pages/admin/roles.vue @@ -243,7 +243,7 @@ import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; -import { instance } from '@/instance.js'; +import { instance, fetchInstance } from '@/instance.js'; import MkFoldableSection from '@/components/MkFoldableSection.vue'; import { ROLE_POLICIES } from '@/const.js'; import { useRouter } from '@/router/supplier.js'; @@ -267,6 +267,7 @@ async function updateBaseRole() { await os.apiWithDialog('admin/roles/update-default-policies', { policies, }); + fetchInstance(true); } function create() { diff --git a/packages/frontend/src/scripts/array.ts b/packages/frontend/src/scripts/array.ts index b3d76e149f..f2feb29dfc 100644 --- a/packages/frontend/src/scripts/array.ts +++ b/packages/frontend/src/scripts/array.ts @@ -77,44 +77,6 @@ export function maximum(xs: number[]): number { return Math.max(...xs); } -/** - * Splits an array based on the equivalence relation. - * The concatenation of the result is equal to the argument. - */ -export function groupBy(f: EndoRelation, xs: T[]): T[][] { - const groups = [] as T[][]; - for (const x of xs) { - const lastGroup = groups.at(-1); - if (lastGroup !== undefined && f(lastGroup[0], x)) { - lastGroup.push(x); - } else { - groups.push([x]); - } - } - return groups; -} - -/** - * Splits an array based on the equivalence relation induced by the function. - * The concatenation of the result is equal to the argument. - */ -export function groupOn(f: (x: T) => S, xs: T[]): T[][] { - return groupBy((a, b) => f(a) === f(b), xs); -} - -export function groupByX(collections: T[], keySelector: (x: T) => string) { - return collections.reduce((obj: Record, item: T) => { - const key = keySelector(item); - if (typeof obj[key] === 'undefined') { - obj[key] = []; - } - - obj[key].push(item); - - return obj; - }, {}); -} - /** * Compare two arrays by lexicographical order */ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1281f7eefe..09df15853b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -586,9 +586,6 @@ importers: '@types/node': specifier: 20.12.7 version: 20.12.7 - '@types/node-fetch': - specifier: 3.0.3 - version: 3.0.3 '@types/nodemailer': specifier: 6.4.15 version: 6.4.15 @@ -4625,10 +4622,6 @@ packages: '@types/node-fetch@2.6.4': resolution: {integrity: sha512-1ZX9fcN4Rvkvgv4E6PAY5WXUFWFcRWxZa3EW83UjycOB9ljJCedb2CupIP4RZMEwF/M3eTcCihbBRgwtGbg5Rg==} - '@types/node-fetch@3.0.3': - resolution: {integrity: sha512-HhggYPH5N+AQe/OmN6fmhKmRRt2XuNJow+R3pQwJxOOF9GuwM7O2mheyGeIrs5MOIeNjDEdgdoyHBOrFeJBR3g==} - deprecated: This is a stub types definition. node-fetch provides its own type definitions, so you do not need this installed. - '@types/node@18.17.15': resolution: {integrity: sha512-2yrWpBk32tvV/JAd3HNHWuZn/VDN1P+72hWirHnvsvTGSqbANi+kSeuQR9yAHnbvaBvHDsoTdXV0Fe+iRtHLKA==} @@ -16025,10 +16018,6 @@ snapshots: '@types/node': 20.12.7 form-data: 3.0.1 - '@types/node-fetch@3.0.3': - dependencies: - node-fetch: 3.3.2 - '@types/node@18.17.15': {} '@types/node@20.11.5':