Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Develop #14474

Closed
wants to merge 85 commits into from
Closed

Develop #14474

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
b1ca25d
[feat] mfm cropを追加
sweshelo Aug 21, 2023
1cf0459
Merge pull request #1 from sweshelo/master
sweshelo Sep 12, 2023
0c7fee0
Merge branch 'misskey-dev:develop' into develop
sweshelo Sep 12, 2023
9ad7045
Merge branch 'misskey-dev:master' into master
sweshelo Sep 12, 2023
18921a9
[add] mfmのタグにcropを追記
sweshelo Sep 26, 2023
d68fb7b
[change] チャンネルへの投稿でも可視性はリクエスト内容に準拠するように変更
sweshelo Sep 26, 2023
7e21f71
Merge branch 'develop' into master
sweshelo Sep 26, 2023
ca978f0
Merge pull request #2 from sweshelo/master
sweshelo Sep 26, 2023
27a3ccc
Merge branch 'misskey-dev:master' into master
sweshelo Sep 26, 2023
d9c3fdb
Merge branch 'misskey-dev:master' into master
sweshelo Sep 29, 2023
a74177a
[change] cwをinputからtextareaに変更
sweshelo Oct 4, 2023
636bb65
Merge branch 'misskey-dev:master' into master
sweshelo Nov 20, 2023
4231cca
Fix conflicts
sweshelo Feb 2, 2024
2b56110
[feat] スパム対策
sweshelo Feb 17, 2024
95f6cdb
[feat] スパム対策 - 単一メンションに対策
sweshelo Feb 17, 2024
cd7c08c
[feat] DiscordWebHook - 現状WebHookを使っているユーザがいないので不完全の状態で放置
sweshelo Feb 17, 2024
bf4a06c
Merge branch 'master' of github.com:misskey-dev/misskey
sweshelo Feb 17, 2024
681fb98
Merge branch 'misskey-dev:master' into master
sweshelo Feb 17, 2024
f249ee8
Merge branch 'master' of github.com:sweshelo/misskey
sweshelo Feb 17, 2024
7e21fdf
[feat] SPAM対策関連のログを見やすく
sweshelo Feb 19, 2024
3528696
[fix] ログ整形 漏れ修正
sweshelo Feb 19, 2024
0434933
feat: 外部サーバの絵文字を即座にインポートしてリアクション可能に
sweshelo Mar 15, 2024
5a7868e
feat: 同一ノートに複数回のリアクションを可能に
sweshelo Mar 15, 2024
b885063
fix: hintがない場合に不正なmyReactionsを返却するのを修正
sweshelo Mar 15, 2024
69d2525
fix: **front only** リアクション削除時に削除するリアクション名を渡すように
sweshelo Mar 15, 2024
b3da07c
fix: backend - リアクション削除時に削除するリアクション名を渡すように
sweshelo Mar 15, 2024
8e8809a
fix: リアクション削除でreactionが指定された場合は必ずそれを見る
sweshelo Mar 15, 2024
eed7163
fix: カスタム絵文字はreactionが不正な文字列になる場合があるのを考慮
sweshelo Mar 15, 2024
d8df1e8
fix: myReactionsが存在しない場合にrenderされなくなるのを修正
sweshelo Mar 15, 2024
9e83d32
Merge pull request #5 from sweshelo/dev/remote-emoji
sweshelo Mar 15, 2024
7c48496
Merge pull request #6 from sweshelo/develop
sweshelo Mar 15, 2024
b468053
SPAM対策とwebpで読めない問題 暫定対処
sweshelo Mar 15, 2024
507e9e3
Merge branch 'master' of github.com:sweshelo/misskey
sweshelo Mar 15, 2024
d50832a
fix: myReactionsが存在しない場合に相乗りが不可なのを修正
sweshelo Mar 15, 2024
d9fb7d1
resolve conflict
sweshelo Mar 15, 2024
afe8fc6
Merge pull request #7 from sweshelo/develop
sweshelo Mar 15, 2024
19d564e
fix: 定数の再定義エラー修正
sweshelo Mar 15, 2024
bb59cc5
feat: Publicなリストは他のユーザもTL表示可能に
sweshelo Mar 15, 2024
61258fc
feat: nayizeを強制的に無効化
sweshelo Mar 15, 2024
b3de1c0
autogen types
sweshelo Mar 16, 2024
70e9368
chore: eslint fix
sweshelo Mar 16, 2024
d9f537b
Merge pull request #8 from sweshelo/develop
sweshelo Mar 16, 2024
39460c2
feat: 外部のサーバでも最終更新時刻からオンライン状況を推定
sweshelo Mar 18, 2024
8f8adec
delete: SPAMチェックを一部削除
sweshelo Mar 18, 2024
3606b50
fix: stringが期待される箇所でstring[]を渡されているのを修正
sweshelo Mar 18, 2024
5d07733
fix: WebHookでcontentがunknownなのに暫定対応
sweshelo Mar 18, 2024
7396ddc
fix: importエラー
sweshelo Mar 18, 2024
7864f53
chore: version
sweshelo Mar 18, 2024
3b91db8
chore: locale denyaizeキーを追加
sweshelo Mar 18, 2024
046c060
chore: ESLint
sweshelo Mar 18, 2024
ac36f21
Merge pull request #9 from sweshelo/develop
sweshelo Mar 18, 2024
4ab0106
fix: 意図しないサーバから絵文字をインポートすることがあるのを修正
sweshelo Mar 18, 2024
b953702
feat: Nodeinfoに最大リアクション数を追加
emtkmkk Mar 18, 2024
325002a
chore: locales
sweshelo Mar 18, 2024
9f8c905
feat: 招待関連の強化
sweshelo Mar 25, 2024
c952dd3
Merge pull request #10 from emtkmkk/patch-1
sweshelo Mar 25, 2024
e09e47d
chore: ESLint
sweshelo Mar 25, 2024
cbddfc8
Merge pull request #11 from sweshelo/feat/invite
sweshelo Mar 25, 2024
5ba960d
Merge pull request #12 from sweshelo/develop
sweshelo Mar 25, 2024
3197435
Merge branch 'develop' of github.com:sweshelo/misskey into HEAD
sweshelo Mar 25, 2024
723f214
version 2024.3.1-enma-1
sweshelo Mar 25, 2024
33520b7
Merge pull request #13 from sweshelo/main
sweshelo Mar 26, 2024
b6225e1
update: testを実装に即した内容に
sweshelo Mar 28, 2024
651e9b4
feat: 絵文字のインポートだけを行うモードを追加
sweshelo Mar 29, 2024
2802b6a
feat: ロケールのキーを追加
sweshelo Mar 29, 2024
ba509ba
fix: ESLint
sweshelo Mar 31, 2024
ced08ff
Merge pull request #14 from sweshelo/feat/emoji-import
sweshelo Mar 31, 2024
8cc3f65
version 2024.3.1-enma-2
sweshelo Mar 31, 2024
bcc3b61
Merge branch 'master' into develop
sweshelo Mar 31, 2024
ed4347a
Merge pull request #15 from sweshelo/develop
sweshelo Mar 31, 2024
fa2ecc4
feat: 表示上のURLが実際のURLと異なる際に警告
sweshelo Apr 2, 2024
ae87e9b
feat: 絵文字の人数をアイコンにした
rassi0429 Apr 2, 2024
134842d
fix: initial api get
rassi0429 Apr 2, 2024
f9e85b2
chore: アイコンをもうちょい小さく
rassi0429 Apr 2, 2024
d2a617c
fix: ESLint
sweshelo Apr 3, 2024
7bc65c1
fix: ESLint
sweshelo Apr 3, 2024
f8b8dc8
Merge pull request #16 from sweshelo/feat/url-domain-check
sweshelo Apr 3, 2024
6b159e8
Merge pull request #17 from sweshelo/feat/reaction-user-icon-from-res…
sweshelo Apr 3, 2024
0a77af3
chore: version enma-3
sweshelo Apr 3, 2024
2b9b937
Merge pull request #18 from sweshelo/develop
sweshelo Apr 3, 2024
2752883
add: Locales
sweshelo Apr 3, 2024
63fa753
Merge pull request #19 from sweshelo/feat/url-domain-check
sweshelo Apr 3, 2024
e17bd55
Merge pull request #20 from sweshelo/master
sweshelo Apr 3, 2024
e2aa28e
マージ テストは通った 未テスト
sweshelo Aug 28, 2024
5622f83
Merge branch 'develop' of github.com:sweshelo/misskey into develop
sweshelo Aug 28, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions locales/en-US.yml
Original file line number Diff line number Diff line change
Expand Up @@ -753,6 +753,7 @@ noCrawleDescription: "Ask search engines to not index your profile page, notes,
lockedAccountInfo: "Unless you set your note visiblity to \"Followers only\", your notes will be visible to anyone, even if you require followers to be manually approved."
alwaysMarkSensitive: "Mark as sensitive by default"
loadRawImages: "Load original images instead of showing thumbnails"
denyaize: "Forcefully disable the nyaize (cat-marking) feature"
disableShowingAnimatedImages: "Don't play animated images"
highlightSensitiveMedia: "Highlight sensitive media"
verificationEmailSent: "A verification email has been sent. Please follow the included link to complete verification."
Expand Down
24 changes: 24 additions & 0 deletions locales/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1236,6 +1236,14 @@ export interface Locale extends ILocale {
* リモートユーザーのため、情報が不完全です。
*/
"remoteUserCaution": string;
/**
* 表示されているURLと実際のURLが異なります。フィッシング詐欺などに注意してください。
*/
"phishingCaution": string;
/**
* 表示上と実際のURLが異なります。
*/
"shortPhishingCaution": string;
/**
* アクティビティ
*/
Expand Down Expand Up @@ -3028,6 +3036,10 @@ export interface Locale extends ILocale {
* 添付画像のサムネイルをオリジナル画質にする
*/
"loadRawImages": string;
/**
* 猫化(nyaize)を強制的に無効にする
*/
"denyaize": string;
/**
* アニメーション画像を再生しない
*/
Expand Down Expand Up @@ -5008,6 +5020,18 @@ export interface Locale extends ILocale {
* リトライ
*/
"gameRetry": string;
/**
* QRコード
*/
"qrcode": string;
/**
* このQRコードをスキャンすると、新規登録時の招待コードが自動で入力されます。
*/
"qrcodeToInvite": string;
/**
* 指定された絵文字は既に存在します。
*/
"emojiAlreadyExists": string;
/**
* 使用しない場合は空欄にしてください
*/
Expand Down
6 changes: 6 additions & 0 deletions locales/ja-JP.yml
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,8 @@ termsOfService: "利用規約"
start: "始める"
home: "ホーム"
remoteUserCaution: "リモートユーザーのため、情報が不完全です。"
phishingCaution: "表示されているURLと実際のURLが異なります。フィッシング詐欺などに注意してください。"
shortPhishingCaution: "表示上と実際のURLが異なります。"
activity: "アクティビティ"
images: "画像"
image: "画像"
Expand Down Expand Up @@ -753,6 +755,7 @@ noCrawleDescription: "外部の検索エンジンにあなたのユーザーペ
lockedAccountInfo: "フォローを承認制にしても、ノートの公開範囲を「フォロワー」にしない限り、誰でもあなたのノートを見ることができます。"
alwaysMarkSensitive: "デフォルトでメディアをセンシティブ設定にする"
loadRawImages: "添付画像のサムネイルをオリジナル画質にする"
denyaize: "猫化(nyaize)を強制的に無効にする"
disableShowingAnimatedImages: "アニメーション画像を再生しない"
highlightSensitiveMedia: "メディアがセンシティブであることを分かりやすく表示"
verificationEmailSent: "確認のメールを送信しました。メールに記載されたリンクにアクセスして、設定を完了してください。"
Expand Down Expand Up @@ -1248,6 +1251,9 @@ enableHorizontalSwipe: "スワイプしてタブを切り替える"
loading: "読み込み中"
surrender: "やめる"
gameRetry: "リトライ"
qrcode: "QRコード"
qrcodeToInvite: "このQRコードをスキャンすると、新規登録時の招待コードが自動で入力されます。"
emojiAlreadyExists: "指定された絵文字は既に存在します。"
notUsePleaseLeaveBlank: "使用しない場合は空欄にしてください"
useTotp: "ワンタイムパスワードを使う"
useBackupCode: "バックアップコードを使う"
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "misskey",
"version": "2024.8.0",
"version": "2024.8.0-enma",
"codename": "nasubi",
"repository": {
"type": "git",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export class UpdateNoteReactionsUniqueConstraint1710499309897 {
name = 'UpdateNoteReactionsUniqueConstraint1710499309897'

async up(queryRunner){
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_note_reaction_user_note_reaction" ON "note_reaction" ("userId", "noteId", "reaction")`);
await queryRunner.query(`DROP INDEX "IDX_ad0c221b25672daf2df320a817"`);
}

async down(queryRunner){
await queryRunner.query(`DROP INDEX "IDX_note_reaction_user_note_reaction"`);
await queryRunner.query(`CREATE UNIQUE INDEX "IDX_ad0c221b25672daf2df320a817" ON "note_reaction" ("userId", "noteId")`); }
}

3 changes: 0 additions & 3 deletions packages/backend/src/core/NoteCreateService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,9 +247,6 @@ export class NoteCreateService implements OnApplicationShutdown {
if (data.createdAt == null) data.createdAt = new Date();
if (data.visibility == null) data.visibility = 'public';
if (data.localOnly == null) data.localOnly = false;
if (data.channel != null) data.visibility = 'public';
if (data.channel != null) data.visibleUsers = [];
if (data.channel != null) data.localOnly = true;

const meta = await this.metaService.fetch();

Expand Down
44 changes: 26 additions & 18 deletions packages/backend/src/core/ReactionService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -179,19 +179,14 @@ export class ReactionService {
await this.noteReactionsRepository.insert(record);
} catch (e) {
if (isDuplicateKeyValueError(e)) {
const exists = await this.noteReactionsRepository.findOneByOrFail({
const exists = await this.noteReactionsRepository.findOneBy({
noteId: note.id,
userId: user.id,
reaction,
});

if (exists.reaction !== reaction) {
// 別のリアクションがすでにされていたら置き換える
await this.delete(user, note);
await this.noteReactionsRepository.insert(record);
} else {
// 同じリアクションがすでにされていたらエラー
throw new IdentifiableError('51c42bb4-931a-456b-bff7-e5a8a70dd298');
}
// 同じリアクションがすでにされていたらエラー
if (exists) throw new IdentifiableError('51c42bb4-931a-456b-bff7-e5a8a70dd298');
} else {
throw e;
}
Expand Down Expand Up @@ -286,42 +281,55 @@ export class ReactionService {
}

@bindThis
public async delete(user: { id: MiUser['id']; host: MiUser['host']; isBot: MiUser['isBot']; }, note: MiNote) {
public async delete(user: { id: MiUser['id']; host: MiUser['host']; isBot: MiUser['isBot']; }, note: MiNote, reaction?: string) {
// if already unreacted
const exist = await this.noteReactionsRepository.findOneBy({
noteId: note.id,
userId: user.id,
const exist = await this.noteReactionsRepository.find({
where: {
noteId: note.id,
userId: user.id,
reaction
}
});

if (exist == null) {
throw new IdentifiableError('60527ec9-b4cb-4a88-a6bd-32d3ad26817d', 'not reacted');
}

if (exist.length > 1 && !reaction) {
throw new IdentifiableError('b21ffbbf-037c-bfc2-e29a-3edd6174a36b', 'multi reacted');
}

const target = exist[0];

if (!target) {
throw new IdentifiableError('60527ec9-b4cb-4a88-a6bd-32d3ad26817d', 'not reacted');
}

// Delete reaction
const result = await this.noteReactionsRepository.delete(exist.id);
const result = await this.noteReactionsRepository.delete(target.id);

if (result.affected !== 1) {
throw new IdentifiableError('60527ec9-b4cb-4a88-a6bd-32d3ad26817d', 'not reacted');
}

// Decrement reactions count
const sql = `jsonb_set("reactions", '{${exist.reaction}}', (COALESCE("reactions"->>'${exist.reaction}', '0')::int - 1)::text::jsonb)`;
const sql = `jsonb_set("reactions", '{${target.reaction}}', (COALESCE("reactions"->>'${target.reaction}', '0')::int - 1)::text::jsonb)`;
await this.notesRepository.createQueryBuilder().update()
.set({
reactions: () => sql,
reactionAndUserPairCache: () => `array_remove("reactionAndUserPairCache", '${user.id}/${exist.reaction}')`,
reactionAndUserPairCache: () => `array_remove("reactionAndUserPairCache", '${user.id}/${target.reaction}')`,
})
.where('id = :id', { id: note.id })
.execute();

this.globalEventService.publishNoteStream(note.id, 'unreacted', {
reaction: this.decodeReaction(exist.reaction).reaction,
reaction: this.decodeReaction(target.reaction).reaction,
userId: user.id,
});

//#region 配信
if (this.userEntityService.isLocalUser(user) && !note.localOnly) {
const content = this.apRendererService.addContext(this.apRendererService.renderUndo(await this.apRendererService.renderLike(exist, note), user));
const content = this.apRendererService.addContext(this.apRendererService.renderUndo(await this.apRendererService.renderLike(target, note), user));
const dm = this.apDeliverManagerService.createDeliverManager(user, content);
if (note.userHost !== null) {
const reactee = await this.usersRepository.findOneBy({ id: note.userId });
Expand Down
15 changes: 14 additions & 1 deletion packages/backend/src/core/activitypub/models/ApNoteService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
import { forwardRef, Inject, Injectable } from '@nestjs/common';
import { In } from 'typeorm';
import { DI } from '@/di-symbols.js';
import type { PollsRepository, EmojisRepository } from '@/models/_.js';
import type { PollsRepository, EmojisRepository, UsersRepository } from '@/models/_.js';
import type { Config } from '@/config.js';
import type { MiRemoteUser } from '@/models/User.js';
import type { MiNote } from '@/models/Note.js';
Expand Down Expand Up @@ -52,6 +52,9 @@ export class ApNoteService {
@Inject(DI.emojisRepository)
private emojisRepository: EmojisRepository,

@Inject(DI.usersRepository)
private usersRepository: UsersRepository,

private idService: IdService,
private apMfmService: ApMfmService,
private apResolverService: ApResolverService,
Expand Down Expand Up @@ -207,6 +210,16 @@ export class ApNoteService {
}
}

// SPAM対策
const hasNoFF = actor.followersCount === 0 && actor.followingCount === 0;
if (actor.username === (actor.name ?? actor.username) && /^[a-z0-9]+$/.test(actor.username) && actor.username.length === 10 && apMentions.length > 0 && hasNoFF) {
this.logger.error('Suspected SPAM', {
account: `@${actor.username}@${actor.host}`,
note: `${note.id}`,
});
throw new StatusError('SUSPECTED_SPAM', 202, 'Violated SPAM policy.');
}

// 添付ファイル
const files: MiDriveFile[] = [];

Expand Down
47 changes: 27 additions & 20 deletions packages/backend/src/core/entities/NoteEntityService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,12 +170,12 @@ export class NoteEntityService implements OnModuleInit {

@bindThis
public async populateMyReaction(note: { id: MiNote['id']; reactions: MiNote['reactions']; reactionAndUserPairCache?: MiNote['reactionAndUserPairCache']; }, meId: MiUser['id'], _hint_?: {
myReactions: Map<MiNote['id'], string | null>;
myReactions: Map<MiNote['id'], string[]>;
}) {
if (_hint_?.myReactions) {
const reaction = _hint_.myReactions.get(note.id);
if (reaction) {
return this.reactionService.convertLegacyReaction(reaction);
return reaction.map(r => this.reactionService.convertLegacyReaction(r));
} else {
return undefined;
}
Expand All @@ -184,9 +184,9 @@ export class NoteEntityService implements OnModuleInit {
const reactionsCount = Object.values(note.reactions).reduce((a, b) => a + b, 0);
if (reactionsCount === 0) return undefined;
if (note.reactionAndUserPairCache && reactionsCount <= note.reactionAndUserPairCache.length) {
const pair = note.reactionAndUserPairCache.find(p => p.startsWith(meId));
const pair = note.reactionAndUserPairCache.filter(p => p.startsWith(meId));
if (pair) {
return this.reactionService.convertLegacyReaction(pair.split('/')[1]);
return pair.map(p => this.reactionService.convertLegacyReaction(p.split('/')[1]));
} else {
return undefined;
}
Expand All @@ -197,13 +197,15 @@ export class NoteEntityService implements OnModuleInit {
return undefined;
}

const reaction = await this.noteReactionsRepository.findOneBy({
userId: meId,
noteId: note.id,
const reaction = await this.noteReactionsRepository.find({
where: {
userId: meId,
noteId: note.id,
}
});

if (reaction) {
return this.reactionService.convertLegacyReaction(reaction.reaction);
return reaction.map(r => this.reactionService.convertLegacyReaction(r.reaction));
}

return undefined;
Expand Down Expand Up @@ -287,7 +289,7 @@ export class NoteEntityService implements OnModuleInit {
skipHide?: boolean;
withReactionAndUserPairCache?: boolean;
_hint_?: {
myReactions: Map<MiNote['id'], string | null>;
myReactions: Map<MiNote['id'], string[]>;
packedFiles: Map<MiNote['fileIds'][number], Packed<'DriveFile'> | null>;
packedUsers: Map<MiUser['id'], Packed<'UserLite'>>
};
Expand Down Expand Up @@ -321,6 +323,10 @@ export class NoteEntityService implements OnModuleInit {
const packedFiles = options?._hint_?.packedFiles;
const packedUsers = options?._hint_?.packedUsers;

const myReactions = (meId && Object.keys(note.reactions).length)
? await this.populateMyReaction(note, meId, options?._hint_)
: undefined;

const packed: Packed<'Note'> = await awaitAll({
id: note.id,
createdAt: this.idService.parse(note.id).date.toISOString(),
Expand Down Expand Up @@ -376,8 +382,9 @@ export class NoteEntityService implements OnModuleInit {

poll: note.hasPoll ? this.populatePoll(note, meId) : undefined,

...(meId && Object.keys(note.reactions).length > 0 ? {
myReaction: this.populateMyReaction(note, meId, options?._hint_),
...(myReactions && myReactions.length > 0 ? {
myReaction: myReactions[0],
myReactions,
} : {}),
} : {}),
});
Expand All @@ -401,7 +408,7 @@ export class NoteEntityService implements OnModuleInit {
if (notes.length === 0) return [];

const meId = me ? me.id : null;
const myReactionsMap = new Map<MiNote['id'], string | null>();
const myReactionsMap = new Map<MiNote['id'], string[]>();
if (meId) {
const idsNeedFetchMyReaction = new Set<MiNote['id']>();

Expand All @@ -412,26 +419,26 @@ export class NoteEntityService implements OnModuleInit {
if (note.renote && (note.text == null && note.fileIds.length === 0)) { // pure renote
const reactionsCount = Object.values(note.renote.reactions).reduce((a, b) => a + b, 0);
if (reactionsCount === 0) {
myReactionsMap.set(note.renote.id, null);
myReactionsMap.set(note.renote.id, []);
} else if (reactionsCount <= note.renote.reactionAndUserPairCache.length) {
const pair = note.renote.reactionAndUserPairCache.find(p => p.startsWith(meId));
myReactionsMap.set(note.renote.id, pair ? pair.split('/')[1] : null);
const pair = note.renote.reactionAndUserPairCache.filter(p => p.startsWith(meId));
myReactionsMap.set(note.renote.id, pair ? pair.map(p => p.split('/')[1]) : []);
} else {
idsNeedFetchMyReaction.add(note.renote.id);
}
} else {
if (note.id < oldId) {
const reactionsCount = Object.values(note.reactions).reduce((a, b) => a + b, 0);
if (reactionsCount === 0) {
myReactionsMap.set(note.id, null);
myReactionsMap.set(note.id, []);
} else if (reactionsCount <= note.reactionAndUserPairCache.length) {
const pair = note.reactionAndUserPairCache.find(p => p.startsWith(meId));
myReactionsMap.set(note.id, pair ? pair.split('/')[1] : null);
const pair = note.reactionAndUserPairCache.filter(p => p.startsWith(meId));
myReactionsMap.set(note.id, pair ? pair.map(p => p.split('/')[1]) : []);
} else {
idsNeedFetchMyReaction.add(note.id);
}
} else {
myReactionsMap.set(note.id, null);
myReactionsMap.set(note.id, []);
}
}
}
Expand All @@ -442,7 +449,7 @@ export class NoteEntityService implements OnModuleInit {
}) : [];

for (const id of idsNeedFetchMyReaction) {
myReactionsMap.set(id, myReactions.find(reaction => reaction.noteId === id)?.reaction ?? null);
myReactionsMap.set(id, myReactions.filter(reaction => reaction.noteId === id).map(r => r.reaction));
}
}

Expand Down
5 changes: 3 additions & 2 deletions packages/backend/src/core/entities/UserEntityService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -368,8 +368,9 @@ export class UserEntityService implements OnModuleInit {
@bindThis
public getOnlineStatus(user: MiUser): 'unknown' | 'online' | 'active' | 'offline' {
if (user.hideOnlineStatus) return 'unknown';
if (user.lastActiveDate == null) return 'unknown';
const elapsed = Date.now() - user.lastActiveDate.getTime();
const activeDate = user.lastActiveDate ?? user.updatedAt;
if (activeDate == null) return 'unknown';
const elapsed = Date.now() - activeDate.getTime();
return (
elapsed < USER_ONLINE_THRESHOLD ? 'online' :
elapsed < USER_ACTIVE_THRESHOLD ? 'active' :
Expand Down
2 changes: 1 addition & 1 deletion packages/backend/src/models/NoteReaction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { MiUser } from './User.js';
import { MiNote } from './Note.js';

@Entity('note_reaction')
@Index(['userId', 'noteId'], { unique: true })
@Index(['userId', 'noteId', 'reaction'], { unique: true })
export class MiNoteReaction {
@PrimaryColumn(id())
public id: string;
Expand Down
Loading
Loading