summaryrefslogtreecommitdiff
path: root/packages/backend/src
diff options
context:
space:
mode:
authordakkar <dakkar@thenautilus.net>2024-04-25 11:31:35 +0100
committerdakkar <dakkar@thenautilus.net>2024-04-25 11:44:24 +0100
commit4fe8a260817d30385cddecad91a4e84c15889666 (patch)
tree70ef7d0215767befa923b41b53cff67a5130eda2 /packages/backend/src
parentMerge branch 'develop' into future-2024-04-10-post (diff)
parentfeat: improve emoji endpoint (#13742) (diff)
downloadsharkey-4fe8a260817d30385cddecad91a4e84c15889666.tar.gz
sharkey-4fe8a260817d30385cddecad91a4e84c15889666.tar.bz2
sharkey-4fe8a260817d30385cddecad91a4e84c15889666.zip
Merge remote-tracking branch 'misskey/develop' into future-2024-04-25
Diffstat (limited to 'packages/backend/src')
-rw-r--r--packages/backend/src/core/FanoutTimelineEndpointService.ts8
-rw-r--r--packages/backend/src/core/MfmService.ts5
-rw-r--r--packages/backend/src/core/NoteCreateService.ts23
-rw-r--r--packages/backend/src/core/NoteDeleteService.ts4
-rw-r--r--packages/backend/src/core/RoleService.ts34
-rw-r--r--packages/backend/src/core/entities/ClipEntityService.ts6
-rw-r--r--packages/backend/src/misc/is-pure-renote.ts15
-rw-r--r--packages/backend/src/misc/is-quote.ts12
-rw-r--r--packages/backend/src/misc/is-renote.ts67
-rw-r--r--packages/backend/src/misc/json-schema.ts2
-rw-r--r--packages/backend/src/models/Role.ts85
-rw-r--r--packages/backend/src/models/json-schema/clip.ts4
-rw-r--r--packages/backend/src/models/json-schema/role.ts17
-rw-r--r--packages/backend/src/server/ActivityPubServerService.ts4
-rw-r--r--packages/backend/src/server/FileServerService.ts7
-rw-r--r--packages/backend/src/server/ServerService.ts14
-rw-r--r--packages/backend/src/server/api/endpoints/i/update-email.ts10
-rw-r--r--packages/backend/src/server/api/endpoints/notes/create.ts6
-rw-r--r--packages/backend/src/server/api/stream/channel.ts22
-rw-r--r--packages/backend/src/server/api/stream/channels/antenna.ts8
-rw-r--r--packages/backend/src/server/api/stream/channels/channel.ts11
-rw-r--r--packages/backend/src/server/api/stream/channels/global-timeline.ts28
-rw-r--r--packages/backend/src/server/api/stream/channels/hashtag.ts11
-rw-r--r--packages/backend/src/server/api/stream/channels/home-timeline.ts21
-rw-r--r--packages/backend/src/server/api/stream/channels/hybrid-timeline.ts19
-rw-r--r--packages/backend/src/server/api/stream/channels/local-timeline.ts17
-rw-r--r--packages/backend/src/server/api/stream/channels/role-timeline.ts9
-rw-r--r--packages/backend/src/server/api/stream/channels/user-list.ts17
28 files changed, 319 insertions, 167 deletions
diff --git a/packages/backend/src/core/FanoutTimelineEndpointService.ts b/packages/backend/src/core/FanoutTimelineEndpointService.ts
index 6aa63d7d55..2f4d98fab4 100644
--- a/packages/backend/src/core/FanoutTimelineEndpointService.ts
+++ b/packages/backend/src/core/FanoutTimelineEndpointService.ts
@@ -13,7 +13,7 @@ import type { NotesRepository } from '@/models/_.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { FanoutTimelineName, FanoutTimelineService } from '@/core/FanoutTimelineService.js';
import { isUserRelated } from '@/misc/is-user-related.js';
-import { isPureRenote } from '@/misc/is-pure-renote.js';
+import { isQuote, isRenote } from '@/misc/is-renote.js';
import { CacheService } from '@/core/CacheService.js';
import { isReply } from '@/misc/is-reply.js';
import { isInstanceMuted } from '@/misc/is-instance-muted.js';
@@ -101,7 +101,7 @@ export class FanoutTimelineEndpointService {
if (ps.excludePureRenotes) {
const parentFilter = filter;
- filter = (note) => !isPureRenote(note) && parentFilter(note);
+ filter = (note) => (!isRenote(note) || isQuote(note)) && parentFilter(note);
}
if (ps.me) {
@@ -122,9 +122,7 @@ export class FanoutTimelineEndpointService {
filter = (note) => {
if (isUserRelated(note, userIdsWhoBlockingMe, ps.ignoreAuthorFromBlock)) return false;
if (isUserRelated(note, userIdsWhoMeMuting, ps.ignoreAuthorFromMute)) return false;
- if (note.mentions.some(mention => userIdsWhoMeMuting.has(mention))) return false;
- if (isPureRenote(note) && note.renote && note.renote.mentions.some(mention => userIdsWhoMeMuting.has(mention))) return false;
- if (isPureRenote(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/core/MfmService.ts b/packages/backend/src/core/MfmService.ts
index b7c9064cef..79e810ce4e 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(/<br\s?\/?>\r?\n/gi, '\n');
+ const normalizedHashtagNames = hashtagNames == null ? undefined : new Set<string>(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 '))) {
diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts
index 631d7074bd..9b6d4d2901 100644
--- a/packages/backend/src/core/NoteCreateService.ts
+++ b/packages/backend/src/core/NoteCreateService.ts
@@ -495,7 +495,7 @@ export class NoteCreateService implements OnApplicationShutdown {
}
// Check blocking
- if (data.renote && !this.isQuote(data)) {
+ if (this.isRenote(data) && !this.isQuote(data)) {
if (data.renote.userHost === null) {
if (data.renote.userId !== user.id) {
const blocked = await this.userBlockingService.checkBlocked(data.renote.userId, user.id);
@@ -855,7 +855,7 @@ export class NoteCreateService implements OnApplicationShutdown {
}
// If it is renote
- if (data.renote) {
+ if (this.isRenote(data)) {
const type = this.isQuote(data) ? 'quote' : 'renote';
// Notify
@@ -1055,9 +1055,20 @@ export class NoteCreateService implements OnApplicationShutdown {
}
@bindThis
- private isQuote(note: Option): note is Option & { renote: MiNote } {
- // sync with misc/is-quote.ts
- return !!note.renote && (!!note.text || !!note.cw || (!!note.files && !!note.files.length) || !!note.poll);
+ private isRenote(note: Option): note is Option & { renote: MiNote } {
+ return note.renote != null;
+ }
+
+ @bindThis
+ private isQuote(note: Option & { renote: MiNote }): note is Option & { renote: MiNote } & (
+ { text: string } | { cw: string } | { reply: MiNote } | { poll: IPoll } | { files: MiDriveFile[] }
+ ) {
+ // NOTE: SYNC WITH misc/is-quote.ts
+ return note.text != null ||
+ note.reply != null ||
+ note.cw != null ||
+ note.poll != null ||
+ (note.files != null && note.files.length > 0);
}
@bindThis
@@ -1133,7 +1144,7 @@ export class NoteCreateService implements OnApplicationShutdown {
private async renderNoteOrRenoteActivity(data: Option, note: MiNote) {
if (data.localOnly) return null;
- const content = data.renote && !this.isQuote(data)
+ const content = this.isRenote(data) && !this.isQuote(data)
? this.apRendererService.renderAnnounce(data.renote.uri ? data.renote.uri : `${this.config.url}/notes/${data.renote.id}`, note)
: this.apRendererService.renderCreate(await this.apRendererService.renderNote(note, false), note);
diff --git a/packages/backend/src/core/NoteDeleteService.ts b/packages/backend/src/core/NoteDeleteService.ts
index 471ade92c7..af65750a01 100644
--- a/packages/backend/src/core/NoteDeleteService.ts
+++ b/packages/backend/src/core/NoteDeleteService.ts
@@ -24,7 +24,7 @@ import { bindThis } from '@/decorators.js';
import { MetaService } from '@/core/MetaService.js';
import { SearchService } from '@/core/SearchService.js';
import { ModerationLogService } from '@/core/ModerationLogService.js';
-import { isPureRenote } from '@/misc/is-pure-renote.js';
+import { isQuote, isRenote } from '@/misc/is-renote.js';
@Injectable()
export class NoteDeleteService {
@@ -86,7 +86,7 @@ export class NoteDeleteService {
let renote: MiNote | null = null;
// if deleted note is renote
- if (isPureRenote(note)) {
+ if (isRenote(note) && !isQuote(note)) {
renote = await this.notesRepository.findOneBy({
id: note.renoteId,
});
diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts
index 2059a6e784..f5a753afc7 100644
--- a/packages/backend/src/core/RoleService.ts
+++ b/packages/backend/src/core/RoleService.ts
@@ -209,45 +209,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/core/entities/ClipEntityService.ts b/packages/backend/src/core/entities/ClipEntityService.ts
index 26fcd6714d..ce49c3458c 100644
--- a/packages/backend/src/core/entities/ClipEntityService.ts
+++ b/packages/backend/src/core/entities/ClipEntityService.ts
@@ -5,7 +5,7 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
-import type { ClipFavoritesRepository, ClipsRepository, MiUser } from '@/models/_.js';
+import type { ClipNotesRepository, ClipFavoritesRepository, ClipsRepository, MiUser } from '@/models/_.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/json-schema.js';
import type { } from '@/models/Blocking.js';
@@ -20,6 +20,9 @@ export class ClipEntityService {
@Inject(DI.clipsRepository)
private clipsRepository: ClipsRepository,
+ @Inject(DI.clipNotesRepository)
+ private clipNotesRepository: ClipNotesRepository,
+
@Inject(DI.clipFavoritesRepository)
private clipFavoritesRepository: ClipFavoritesRepository,
@@ -47,6 +50,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,
});
}
diff --git a/packages/backend/src/misc/is-pure-renote.ts b/packages/backend/src/misc/is-pure-renote.ts
deleted file mode 100644
index f9c2243a06..0000000000
--- a/packages/backend/src/misc/is-pure-renote.ts
+++ /dev/null
@@ -1,15 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-import type { MiNote } from '@/models/Note.js';
-
-export function isPureRenote(note: MiNote): note is MiNote & { renoteId: NonNullable<MiNote['renoteId']> } {
- if (!note.renoteId) return false;
-
- if (note.text) return false; // it's quoted with text
- if (note.fileIds.length !== 0) return false; // it's quoted with files
- if (note.hasPoll) return false; // it's quoted with poll
- return true;
-}
diff --git a/packages/backend/src/misc/is-quote.ts b/packages/backend/src/misc/is-quote.ts
deleted file mode 100644
index 75b29f63f4..0000000000
--- a/packages/backend/src/misc/is-quote.ts
+++ /dev/null
@@ -1,12 +0,0 @@
-/*
- * SPDX-FileCopyrightText: syuilo and misskey-project
- * SPDX-License-Identifier: AGPL-3.0-only
- */
-
-import type { MiNote } from '@/models/Note.js';
-
-// eslint-disable-next-line import/no-default-export
-export default function(note: MiNote): boolean {
- // sync with NoteCreateService.isQuote
- return note.renoteId != null && (note.text != null || note.hasPoll || (note.fileIds != null && note.fileIds.length > 0));
-}
diff --git a/packages/backend/src/misc/is-renote.ts b/packages/backend/src/misc/is-renote.ts
new file mode 100644
index 0000000000..48f821806c
--- /dev/null
+++ b/packages/backend/src/misc/is-renote.ts
@@ -0,0 +1,67 @@
+/*
+ * SPDX-FileCopyrightText: syuilo and misskey-project
+ * SPDX-License-Identifier: AGPL-3.0-only
+ */
+
+import type { MiNote } from '@/models/Note.js';
+import type { Packed } from '@/misc/json-schema.js';
+
+type Renote =
+ MiNote & {
+ renoteId: NonNullable<MiNote['renoteId']>
+ };
+
+type Quote =
+ Renote & ({
+ text: NonNullable<MiNote['text']>
+ } | {
+ cw: NonNullable<MiNote['cw']>
+ } | {
+ replyId: NonNullable<MiNote['replyId']>
+ reply: NonNullable<MiNote['reply']>
+ } | {
+ hasPoll: true
+ });
+
+export function isRenote(note: MiNote): note is Renote {
+ return note.renoteId != null;
+}
+
+export function isQuote(note: Renote): note is Quote {
+ // NOTE: SYNC WITH NoteCreateService.isQuote
+ return note.text != null ||
+ note.cw != null ||
+ note.replyId != null ||
+ note.hasPoll ||
+ note.fileIds.length > 0;
+}
+
+type PackedRenote =
+ Packed<'Note'> & {
+ renoteId: NonNullable<Packed<'Note'>['renoteId']>
+ };
+
+type PackedQuote =
+ PackedRenote & ({
+ text: NonNullable<Packed<'Note'>['text']>
+ } | {
+ cw: NonNullable<Packed<'Note'>['cw']>
+ } | {
+ replyId: NonNullable<Packed<'Note'>['replyId']>
+ } | {
+ poll: NonNullable<Packed<'Note'>['poll']>
+ } | {
+ fileIds: NonNullable<Packed<'Note'>['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/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/clip.ts b/packages/backend/src/models/json-schema/clip.ts
index ca4886c978..c4e7055cd8 100644
--- a/packages/backend/src/models/json-schema/clip.ts
+++ b/packages/backend/src/models/json-schema/clip.ts
@@ -52,5 +52,9 @@ export const packedClipSchema = {
type: 'boolean',
optional: true, nullable: false,
},
+ notesCount: {
+ type: 'integer',
+ optional: true, nullable: false,
+ },
},
} as const;
diff --git a/packages/backend/src/models/json-schema/role.ts b/packages/backend/src/models/json-schema/role.ts
index 7eba1d5443..08580d22de 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: {
@@ -136,6 +150,9 @@ export const packedRoleCondFormulaValueSchema = {
ref: 'RoleCondFormulaValueIsLocalOrRemote',
},
{
+ ref: 'RoleCondFormulaValueUserSettingBooleanSchema',
+ },
+ {
ref: 'RoleCondFormulaValueAssignedRole',
},
{
diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts
index 64bce07a98..398ad54f6a 100644
--- a/packages/backend/src/server/ActivityPubServerService.ts
+++ b/packages/backend/src/server/ActivityPubServerService.ts
@@ -33,7 +33,7 @@ import { UtilityService } from '@/core/UtilityService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js';
import { IActivity } from '@/core/activitypub/type.js';
-import { isPureRenote } from '@/misc/is-pure-renote.js';
+import { isQuote, isRenote } from '@/misc/is-renote.js';
import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions, FastifyBodyParser } from 'fastify';
import type { FindOptionsWhere } from 'typeorm';
import type Logger from '@/logger.js';
@@ -107,7 +107,7 @@ export class ActivityPubServerService {
*/
@bindThis
private async packActivity(note: MiNote): Promise<any> {
- if (isPureRenote(note)) {
+ if (isRenote(note) && !isQuote(note)) {
const renote = await this.notesRepository.findOneByOrFail({ id: note.renoteId });
return this.apRendererService.renderAnnounce(renote.uri ? renote.uri : `${this.config.url}/notes/${renote.id}`, note);
}
diff --git a/packages/backend/src/server/FileServerService.ts b/packages/backend/src/server/FileServerService.ts
index 3cb353f2b3..e0b187f3cf 100644
--- a/packages/backend/src/server/FileServerService.ts
+++ b/packages/backend/src/server/FileServerService.ts
@@ -212,6 +212,8 @@ export class FileServerService {
}
reply.header('Content-Type', FILE_TYPE_BROWSERSAFE.includes(image.type) ? image.type : 'application/octet-stream');
+ reply.header('Content-Length', file.file.size);
+ reply.header('Cache-Control', 'max-age=31536000, immutable');
reply.header('Content-Disposition',
contentDisposition(
'inline',
@@ -254,6 +256,7 @@ export class FileServerService {
return fs.createReadStream(file.path);
} else {
reply.header('Content-Type', FILE_TYPE_BROWSERSAFE.includes(file.file.type) ? file.file.type : 'application/octet-stream');
+ reply.header('Content-Length', file.file.size);
reply.header('Cache-Control', 'max-age=31536000, immutable');
reply.header('Content-Disposition', contentDisposition('inline', file.filename));
@@ -528,9 +531,7 @@ export class FileServerService {
if (!file.storedInternal) {
if (!(file.isLink && file.uri)) return '204';
const result = await this.downloadAndDetectTypeFromUrl(file.uri);
- if (!file.size) {
- file.size = (await fs.promises.stat(result.path)).size;
- }
+ file.size = (await fs.promises.stat(result.path)).size; // DB file.sizeは正確とは限らないので
return {
...result,
url: file.uri,
diff --git a/packages/backend/src/server/ServerService.ts b/packages/backend/src/server/ServerService.ts
index 5a456e09ad..6adf8d9f9f 100644
--- a/packages/backend/src/server/ServerService.ts
+++ b/packages/backend/src/server/ServerService.ts
@@ -122,12 +122,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,
});
diff --git a/packages/backend/src/server/api/endpoints/i/update-email.ts b/packages/backend/src/server/api/endpoints/i/update-email.ts
index 08a8301bd1..7332026d84 100644
--- a/packages/backend/src/server/api/endpoints/i/update-email.ts
+++ b/packages/backend/src/server/api/endpoints/i/update-email.ts
@@ -16,6 +16,7 @@ import { DI } from '@/di-symbols.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { L_CHARS, secureRndstr } from '@/misc/secure-rndstr.js';
import { UserAuthService } from '@/core/UserAuthService.js';
+import { MetaService } from '@/core/MetaService.js';
import { ApiError } from '../../error.js';
export const meta = {
@@ -40,6 +41,12 @@ export const meta = {
code: 'UNAVAILABLE',
id: 'a2defefb-f220-8849-0af6-17f816099323',
},
+
+ emailRequired: {
+ message: 'Email address is required.',
+ code: 'EMAIL_REQUIRED',
+ id: '324c7a88-59f2-492f-903f-89134f93e47e',
+ },
},
res: {
@@ -67,6 +74,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
@Inject(DI.userProfilesRepository)
private userProfilesRepository: UserProfilesRepository,
+ private metaService: MetaService,
private userEntityService: UserEntityService,
private emailService: EmailService,
private userAuthService: UserAuthService,
@@ -98,6 +106,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (!res.available) {
throw new ApiError(meta.errors.unavailable);
}
+ } else if ((await this.metaService.fetch()).emailRequiredForSignup) {
+ throw new ApiError(meta.errors.emailRequired);
}
await this.userProfilesRepository.update(me.id, {
diff --git a/packages/backend/src/server/api/endpoints/notes/create.ts b/packages/backend/src/server/api/endpoints/notes/create.ts
index 95ebda2f21..296698522d 100644
--- a/packages/backend/src/server/api/endpoints/notes/create.ts
+++ b/packages/backend/src/server/api/endpoints/notes/create.ts
@@ -16,7 +16,7 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { NoteCreateService } from '@/core/NoteCreateService.js';
import { DI } from '@/di-symbols.js';
-import { isPureRenote } from '@/misc/is-pure-renote.js';
+import { isQuote, isRenote } from '@/misc/is-renote.js';
import { MetaService } from '@/core/MetaService.js';
import { UtilityService } from '@/core/UtilityService.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
@@ -286,7 +286,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (renote == null) {
throw new ApiError(meta.errors.noSuchRenoteTarget);
- } else if (isPureRenote(renote)) {
+ } else if (isRenote(renote) && !isQuote(renote)) {
throw new ApiError(meta.errors.cannotReRenote);
}
@@ -332,7 +332,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
if (reply == null) {
throw new ApiError(meta.errors.noSuchReplyTarget);
- } else if (isPureRenote(reply)) {
+ } else if (isRenote(reply) && !isQuote(reply)) {
throw new ApiError(meta.errors.cannotReplyToPureRenote);
} else if (!await this.noteEntityService.isVisibleForMe(reply, me.id)) {
throw new ApiError(meta.errors.cannotReplyToInvisibleNote);
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<string>(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 fc25724782..0a894147a2 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 {
@@ -55,31 +53,13 @@ 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 (isRenotePacked(note) && !isQuotePacked(note) && !this.withRenotes) return;
if (note.user.isSilenced && !this.following[note.userId] && note.userId !== this.user!.id) 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<string>(this.userProfile?.mutedInstances ?? [])) && !this.following[note.userId]) return;
-
- // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
- if (isUserRelated(note, this.userIdsWhoMeMuting)) return;
- // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
- if (isUserRelated(note, this.userIdsWhoBlockingMe)) return;
-
- if (note.renote && !note.text && note.renote.mentions?.some(mention => this.userIdsWhoMeMuting.has(mention))) return;
- if (note.mentions?.some(mention => this.userIdsWhoMeMuting.has(mention))) 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 0a4852ee8d..84ff241469 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<string>(this.userProfile!.mutedInstances)) && !this.following[note.userId]) return;
-
if (note.visibility === 'followers') {
if (!isMe && !Object.hasOwn(this.following, note.userId)) return;
} else if (note.visibility === 'specified') {
@@ -74,7 +69,7 @@ class HomeTimelineChannel extends Channel {
if (note.user.isSilenced && !this.following[note.userId] && note.userId !== this.user!.id) return;
// 純粋なリノート(引用リノートでないリノート)の場合
- 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;
@@ -83,17 +78,9 @@ class HomeTimelineChannel extends Channel {
}
}
- // 流れてきたNoteがミュートしているユーザーが関わるものだったら無視する
- if (isUserRelated(note, this.userIdsWhoMeMuting)) return;
- // 流れてきたNoteがブロックされているユーザーが関わるものだったら無視する
- if (isUserRelated(note, this.userIdsWhoBlockingMe)) return;
-
- if (note.renote && !note.text && note.renote.mentions?.some(mention => this.userIdsWhoMeMuting.has(mention))) return;
- if (note.mentions?.some(mention => this.userIdsWhoMeMuting.has(mention))) 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 02786e9e16..b83d3ec817 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 {
@@ -74,8 +72,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<string>(this.userProfile!.mutedInstances)) && !this.following[note.userId]) return;
+ if (this.isNoteMutedOrBlocked(note)) return;
if (note.reply) {
const reply = note.reply;
@@ -90,17 +87,7 @@ class HybridTimelineChannel extends Channel {
if (note.user.isSilenced && !this.following[note.userId] && note.userId !== this.user!.id) return;
- 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 && note.renote.mentions?.some(mention => this.userIdsWhoMeMuting.has(mention))) return;
- if (note.mentions?.some(mention => this.userIdsWhoMeMuting.has(mention))) 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 71b5675402..48cc76c497 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 {
@@ -66,19 +65,11 @@ class LocalTimelineChannel extends Channel {
if (note.user.isSilenced && !this.following[note.userId] && note.userId !== this.user!.id) 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 && note.renote.mentions?.some(mention => this.userIdsWhoMeMuting.has(mention))) return;
- if (note.mentions?.some(mention => this.userIdsWhoMeMuting.has(mention))) 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);