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

Feat: 特定ユーザーからのリアクションをブロックする機能の追加 #14992

Open
wants to merge 17 commits into
base: develop
Choose a base branch
from

Conversation

sakuhanight
Copy link
Contributor

@sakuhanight sakuhanight commented Nov 18, 2024

  • Blockingエンティティの変更(isReactionBlock列の追加)
  • blocking-reaction-userエンドポイントの追加
  • 関連するフロントエンドの追加
    Migrationがあります。

What

特定ユーザーからのリアクションをブロックする

Why

脈絡がなかったりよくわからん絵文字でリアクションをする人からリアクションを受け取りたくないが、ブロックまではする必要がないことがある。
Fix #14987

Additional info (optional)

以下PRの再提出です。
#14986

Checklist

  • Read the contribution guide
  • Test working in a local environment
  • (If needed) Add story of storybook
  • (If needed) Update CHANGELOG.md
  • (If possible) Add tests

@github-actions github-actions bot added packages/frontend Client side specific issue/PR packages/backend Server side specific issue/PR packages/misskey-js packages/backend:test labels Nov 18, 2024
Copy link

codecov bot commented Nov 18, 2024

Codecov Report

Attention: Patch coverage is 55.97381% with 269 lines in your changes missing coverage. Please review.

Project coverage is 40.69%. Comparing base (0df6c79) to head (6236c93).
Report is 7 commits behind head on develop.

Files with missing lines Patch % Lines
packages/backend/src/core/UserBlockingService.ts 13.40% 84 Missing ⚠️
...ackages/frontend/src/pages/settings/mute-block.vue 0.00% 48 Missing ⚠️
...ver/api/endpoints/blocking-reaction-user/delete.ts 58.03% 47 Missing ⚠️
...ver/api/endpoints/blocking-reaction-user/create.ts 58.55% 46 Missing ⚠️
...erver/api/endpoints/blocking-reaction-user/list.ts 68.25% 20 Missing ⚠️
packages/frontend/src/scripts/get-user-menu.ts 0.00% 14 Missing ⚠️
packages/backend/src/core/QueryService.ts 20.00% 4 Missing ⚠️
packages/backend/src/core/ReactionService.ts 0.00% 2 Missing ⚠️
.../backend/src/server/api/endpoints/blocking/list.ts 33.33% 2 Missing ⚠️
...ackend/src/server/api/endpoints/blocking/create.ts 66.66% 1 Missing ⚠️
... and 1 more
Additional details and impacted files
@@             Coverage Diff             @@
##           develop   #14992      +/-   ##
===========================================
+ Coverage    39.97%   40.69%   +0.71%     
===========================================
  Files         1561     1568       +7     
  Lines       197313   203731    +6418     
  Branches      3622     3377     -245     
===========================================
+ Hits         78878    82903    +4025     
- Misses      117829   120218    +2389     
- Partials       606      610       +4     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.


🚨 Try these New Features:

Copy link
Contributor

github-actions bot commented Nov 18, 2024

このPRによるapi.jsonの差分

差分はこちら
--- base
+++ head
@@ -20952,6 +20952,580 @@
         }
       }
     },
+    "/blocking-reaction-user/create": {
+      "post": {
+        "operationId": "blocking-reaction-user___create",
+        "summary": "blocking-reaction-user/create",
+        "description": "No description provided.\n\n**Credential required**: *Yes* / **Permission**: *write:blocks*",
+        "externalDocs": {
+          "description": "Source code",
+          "url": "https://github.com/misskey-dev/misskey/blob/develop/packages/backend/src/server/api/endpoints/blocking-reaction-user/create.ts"
+        },
+        "tags": [
+          "account"
+        ],
+        "security": [
+          {
+            "bearerAuth": []
+          }
+        ],
+        "requestBody": {
+          "required": true,
+          "content": {
+            "application/json": {
+              "schema": {
+                "type": "object",
+                "properties": {
+                  "userId": {
+                    "type": "string",
+                    "format": "misskey:id"
+                  }
+                },
+                "required": [
+                  "userId"
+                ]
+              }
+            }
+          }
+        },
+        "responses": {
+          "200": {
+            "description": "OK (with results)",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "type": "object",
+                  "$ref": "#/components/schemas/UserDetailedNotMe"
+                }
+              }
+            }
+          },
+          "400": {
+            "description": "Client error",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Error"
+                },
+                "examples": {
+                  "NO_SUCH_USER": {
+                    "value": {
+                      "error": {
+                        "message": "No such user.",
+                        "code": "NO_SUCH_USER",
+                        "id": "7cc4f851-e2f1-4621-9633-ec9e1d00c01e"
+                      }
+                    }
+                  },
+                  "BLOCKEE_IS_YOURSELF": {
+                    "value": {
+                      "error": {
+                        "message": "Blockee is yourself.",
+                        "code": "BLOCKEE_IS_YOURSELF",
+                        "id": "88b19138-f28d-42c0-8499-6a31bbd0fdc6"
+                      }
+                    }
+                  },
+                  "ALREADY_BLOCKING": {
+                    "value": {
+                      "error": {
+                        "message": "You are already blocking that user.",
+                        "code": "ALREADY_BLOCKING",
+                        "id": "787fed64-acb9-464a-82eb-afbd745b9614"
+                      }
+                    }
+                  },
+                  "INVALID_PARAM": {
+                    "value": {
+                      "error": {
+                        "message": "Invalid param.",
+                        "code": "INVALID_PARAM",
+                        "id": "3d81ceae-475f-4600-b2a8-2bc116157532"
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          },
+          "401": {
+            "description": "Authentication error",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Error"
+                },
+                "examples": {
+                  "CREDENTIAL_REQUIRED": {
+                    "value": {
+                      "error": {
+                        "message": "Credential required.",
+                        "code": "CREDENTIAL_REQUIRED",
+                        "id": "1384574d-a912-4b81-8601-c7b1c4085df1"
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          },
+          "403": {
+            "description": "Forbidden error",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Error"
+                },
+                "examples": {
+                  "AUTHENTICATION_FAILED": {
+                    "value": {
+                      "error": {
+                        "message": "Authentication failed. Please ensure your token is correct.",
+                        "code": "AUTHENTICATION_FAILED",
+                        "id": "b0a7f5f8-dc2f-4171-b91f-de88ad238e14"
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          },
+          "418": {
+            "description": "I'm Ai",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Error"
+                },
+                "examples": {
+                  "I_AM_AI": {
+                    "value": {
+                      "error": {
+                        "message": "You sent a request to Ai-chan, Misskey's showgirl, instead of the server.",
+                        "code": "I_AM_AI",
+                        "id": "60c46cd1-f23a-46b1-bebe-5d2b73951a84"
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          },
+          "429": {
+            "description": "To many requests",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Error"
+                },
+                "examples": {
+                  "RATE_LIMIT_EXCEEDED": {
+                    "value": {
+                      "error": {
+                        "message": "Rate limit exceeded. Please try again later.",
+                        "code": "RATE_LIMIT_EXCEEDED",
+                        "id": "d5826d14-3982-4d2e-8011-b9e9f02499ef"
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          },
+          "500": {
+            "description": "Internal server error",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Error"
+                },
+                "examples": {
+                  "INTERNAL_ERROR": {
+                    "value": {
+                      "error": {
+                        "message": "Internal error occurred. Please contact us if the error persists.",
+                        "code": "INTERNAL_ERROR",
+                        "id": "5d37dbcb-891e-41ca-a3d6-e690c97775ac"
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/blocking-reaction-user/delete": {
+      "post": {
+        "operationId": "blocking-reaction-user___delete",
+        "summary": "blocking-reaction-user/delete",
+        "description": "No description provided.\n\n**Credential required**: *Yes* / **Permission**: *write:blocks*",
+        "externalDocs": {
+          "description": "Source code",
+          "url": "https://github.com/misskey-dev/misskey/blob/develop/packages/backend/src/server/api/endpoints/blocking-reaction-user/delete.ts"
+        },
+        "tags": [
+          "account"
+        ],
+        "security": [
+          {
+            "bearerAuth": []
+          }
+        ],
+        "requestBody": {
+          "required": true,
+          "content": {
+            "application/json": {
+              "schema": {
+                "type": "object",
+                "properties": {
+                  "userId": {
+                    "type": "string",
+                    "format": "misskey:id"
+                  }
+                },
+                "required": [
+                  "userId"
+                ]
+              }
+            }
+          }
+        },
+        "responses": {
+          "200": {
+            "description": "OK (with results)",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "type": "object",
+                  "$ref": "#/components/schemas/UserDetailedNotMe"
+                }
+              }
+            }
+          },
+          "400": {
+            "description": "Client error",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Error"
+                },
+                "examples": {
+                  "NO_SUCH_USER": {
+                    "value": {
+                      "error": {
+                        "message": "No such user.",
+                        "code": "NO_SUCH_USER",
+                        "id": "8621d8bf-c358-4303-a066-5ea78610eb3f"
+                      }
+                    }
+                  },
+                  "BLOCKEE_IS_YOURSELF": {
+                    "value": {
+                      "error": {
+                        "message": "Blockee is yourself.",
+                        "code": "BLOCKEE_IS_YOURSELF",
+                        "id": "06f6fac6-524b-473c-a354-e97a40ae6eac"
+                      }
+                    }
+                  },
+                  "NOT_BLOCKING": {
+                    "value": {
+                      "error": {
+                        "message": "You are not blocking that user.",
+                        "code": "NOT_BLOCKING",
+                        "id": "291b2efa-60c6-45c0-9f6a-045c8f9b02cd"
+                      }
+                    }
+                  },
+                  "INVALID_PARAM": {
+                    "value": {
+                      "error": {
+                        "message": "Invalid param.",
+                        "code": "INVALID_PARAM",
+                        "id": "3d81ceae-475f-4600-b2a8-2bc116157532"
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          },
+          "401": {
+            "description": "Authentication error",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Error"
+                },
+                "examples": {
+                  "CREDENTIAL_REQUIRED": {
+                    "value": {
+                      "error": {
+                        "message": "Credential required.",
+                        "code": "CREDENTIAL_REQUIRED",
+                        "id": "1384574d-a912-4b81-8601-c7b1c4085df1"
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          },
+          "403": {
+            "description": "Forbidden error",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Error"
+                },
+                "examples": {
+                  "AUTHENTICATION_FAILED": {
+                    "value": {
+                      "error": {
+                        "message": "Authentication failed. Please ensure your token is correct.",
+                        "code": "AUTHENTICATION_FAILED",
+                        "id": "b0a7f5f8-dc2f-4171-b91f-de88ad238e14"
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          },
+          "418": {
+            "description": "I'm Ai",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Error"
+                },
+                "examples": {
+                  "I_AM_AI": {
+                    "value": {
+                      "error": {
+                        "message": "You sent a request to Ai-chan, Misskey's showgirl, instead of the server.",
+                        "code": "I_AM_AI",
+                        "id": "60c46cd1-f23a-46b1-bebe-5d2b73951a84"
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          },
+          "429": {
+            "description": "To many requests",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Error"
+                },
+                "examples": {
+                  "RATE_LIMIT_EXCEEDED": {
+                    "value": {
+                      "error": {
+                        "message": "Rate limit exceeded. Please try again later.",
+                        "code": "RATE_LIMIT_EXCEEDED",
+                        "id": "d5826d14-3982-4d2e-8011-b9e9f02499ef"
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          },
+          "500": {
+            "description": "Internal server error",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Error"
+                },
+                "examples": {
+                  "INTERNAL_ERROR": {
+                    "value": {
+                      "error": {
+                        "message": "Internal error occurred. Please contact us if the error persists.",
+                        "code": "INTERNAL_ERROR",
+                        "id": "5d37dbcb-891e-41ca-a3d6-e690c97775ac"
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    },
+    "/blocking-reaction-user/list": {
+      "post": {
+        "operationId": "blocking-reaction-user___list",
+        "summary": "blocking-reaction-user/list",
+        "description": "No description provided.\n\n**Credential required**: *Yes* / **Permission**: *read:blocks*",
+        "externalDocs": {
+          "description": "Source code",
+          "url": "https://github.com/misskey-dev/misskey/blob/develop/packages/backend/src/server/api/endpoints/blocking-reaction-user/list.ts"
+        },
+        "tags": [
+          "account"
+        ],
+        "security": [
+          {
+            "bearerAuth": []
+          }
+        ],
+        "requestBody": {
+          "required": true,
+          "content": {
+            "application/json": {
+              "schema": {
+                "type": "object",
+                "properties": {
+                  "limit": {
+                    "type": "integer",
+                    "minimum": 1,
+                    "maximum": 100,
+                    "default": 30
+                  },
+                  "sinceId": {
+                    "type": "string",
+                    "format": "misskey:id"
+                  },
+                  "untilId": {
+                    "type": "string",
+                    "format": "misskey:id"
+                  }
+                }
+              }
+            }
+          }
+        },
+        "responses": {
+          "200": {
+            "description": "OK (with results)",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "type": "array",
+                  "items": {
+                    "type": "object",
+                    "$ref": "#/components/schemas/Blocking"
+                  }
+                }
+              }
+            }
+          },
+          "400": {
+            "description": "Client error",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Error"
+                },
+                "examples": {
+                  "INVALID_PARAM": {
+                    "value": {
+                      "error": {
+                        "message": "Invalid param.",
+                        "code": "INVALID_PARAM",
+                        "id": "3d81ceae-475f-4600-b2a8-2bc116157532"
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          },
+          "401": {
+            "description": "Authentication error",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Error"
+                },
+                "examples": {
+                  "CREDENTIAL_REQUIRED": {
+                    "value": {
+                      "error": {
+                        "message": "Credential required.",
+                        "code": "CREDENTIAL_REQUIRED",
+                        "id": "1384574d-a912-4b81-8601-c7b1c4085df1"
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          },
+          "403": {
+            "description": "Forbidden error",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Error"
+                },
+                "examples": {
+                  "AUTHENTICATION_FAILED": {
+                    "value": {
+                      "error": {
+                        "message": "Authentication failed. Please ensure your token is correct.",
+                        "code": "AUTHENTICATION_FAILED",
+                        "id": "b0a7f5f8-dc2f-4171-b91f-de88ad238e14"
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          },
+          "418": {
+            "description": "I'm Ai",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Error"
+                },
+                "examples": {
+                  "I_AM_AI": {
+                    "value": {
+                      "error": {
+                        "message": "You sent a request to Ai-chan, Misskey's showgirl, instead of the server.",
+                        "code": "I_AM_AI",
+                        "id": "60c46cd1-f23a-46b1-bebe-5d2b73951a84"
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          },
+          "500": {
+            "description": "Internal server error",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Error"
+                },
+                "examples": {
+                  "INTERNAL_ERROR": {
+                    "value": {
+                      "error": {
+                        "message": "Internal error occurred. Please contact us if the error persists.",
+                        "code": "INTERNAL_ERROR",
+                        "id": "5d37dbcb-891e-41ca-a3d6-e690c97775ac"
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    },
     "/channels/create": {
       "post": {
         "operationId": "channels___create",
@@ -77751,6 +78325,12 @@
           "isBlocked": {
             "type": "boolean"
           },
+          "isReactionBlocking": {
+            "type": "boolean"
+          },
+          "isReactionBlocked": {
+            "type": "boolean"
+          },
           "isMuted": {
             "type": "boolean"
           },
@@ -80325,13 +80905,21 @@
           "blockee": {
             "type": "object",
             "$ref": "#/components/schemas/UserDetailedNotMe"
+          },
+          "blockType": {
+            "type": "string",
+            "enum": [
+              "user",
+              "reaction"
+            ]
           }
         },
         "required": [
           "id",
           "createdAt",
           "blockeeId",
-          "blockee"
+          "blockee",
+          "blockType"
         ]
       },
       "Hashtag": {

Get diff files from Workflow Page

不要なonModuleInit Imprementsを除去
Comment on lines 6 to 20
export class AddBlockingReactionUser1731898598469 {
name = 'AddBlockingReactionUser1731898598469'

async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "blocking" ADD "isReactionBlock" boolean NOT NULL DEFAULT false`);
await queryRunner.query(`COMMENT ON COLUMN "blocking"."isReactionBlock" IS 'Whether the blockee is a reaction block.'`);
await queryRunner.query(`CREATE INDEX "IDX_7b0698c38d27a5554bed4858bd" ON "blocking" ("isReactionBlock") `);
}

async down(queryRunner) {
await queryRunner.query(`DELETE FROM blocking WHERE "isReactionBlock" = 'true'`); // blockingテーブルのisReactionBlockカラムがtrueの行を削除する
await queryRunner.query(`DROP INDEX "IDX_7b0698c38d27a5554bed4858bd"`);
await queryRunner.query(`ALTER TABLE "blocking" DROP COLUMN "isReactionBlock"`);
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.


この変更は、blocking.blockeeIdblocking.blockerIdの組み合わせが2つ以上存在しうることを許容する前提のように見受けられますが、相違ございませんか?(通常のブロックで1レコード、リアクションブロックで1レコードずつ)

もし上記の状態を期待するのであれば…やり方を変える必要があると考えます。
以下の画像の通り、blocking.blockeeIdblocking.blockerIdのユニークインデックスが既に存在しているので重複する組み合わせは登録できないようになっています。
image

なので…blockingの1レコードでやりくりする必要があるかと思います。
 

isReactionBlockよりもblockTypeとしてenumを保持するようにすると後から手を入れやすいかも…?

this.cacheService.userBlockingCache.refresh(blocker.id);
this.cacheService.userBlockedCache.refresh(blockee.id);
this.cacheService.userReactionBlockedCache.refresh(blocker.id);
this.cacheService.userReactionBlockedCache.refresh(blockee.id);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

このServiceは通常のブロック向けかと思いますが、ここでリアクションブロック向けの機能を更新しているのは特殊な理由がありますか…?


this.globalEventService.publishInternalEvent('blockingDeleted', {
this.globalEventService.publishInternalEvent('blockingReactionDeleted', {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

こちらも、配信するイベントが変わっています

if (this.userEntityService.isLocalUser(blocker) && this.userEntityService.isRemoteUser(blockee)) {
const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderBlock(blocking), blocker));
this.queueService.deliver(blocker, content, blockee.inbox, false);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ActivityPub向けにブロックイベントの配信をする処理なのですが、こちらも意図を確認させてください

Comment on lines 92 to 104
this.cacheService.userBlockingCache.refresh(blocker.id);
this.cacheService.userBlockedCache.refresh(blockee.id);

this.globalEventService.publishInternalEvent('blockingDeleted', {
blockerId: blocker.id,
blockeeId: blockee.id,
});

// deliver if remote bloking
if (this.userEntityService.isLocalUser(blocker) && this.userEntityService.isRemoteUser(blockee)) {
const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderBlock(blocking), blocker));
this.queueService.deliver(blocker, content, blockee.inbox, false);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

もしかして通常のブロック側と逆になっています…?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

すみません、完全に全て逆になっていたようです。修正しました。

@samunohito
Copy link
Member

あと、「blockingテーブルに該当レコードが在るか無いか」でのみ判断している個所にも条件追加などの対応が必要かと思います。以下はその一例です。

@bindThis
public generateBlockedUserQuery(q: SelectQueryBuilder<any>, me: { id: MiUser['id'] }): void {
const blockingQuery = this.blockingsRepository.createQueryBuilder('blocking')
.select('blocking.blockerId')
.where('blocking.blockeeId = :blockeeId', { blockeeId: me.id });
// 投稿の作者にブロックされていない かつ
// 投稿の返信先の作者にブロックされていない かつ
// 投稿の引用元の作者にブロックされていない
q
.andWhere(`note.userId NOT IN (${ blockingQuery.getQuery() })`)
.andWhere(new Brackets(qb => {
qb
.where('note.replyUserId IS NULL')
.orWhere(`note.replyUserId NOT IN (${ blockingQuery.getQuery() })`);
}))
.andWhere(new Brackets(qb => {
qb
.where('note.renoteUserId IS NULL')
.orWhere(`note.renoteUserId NOT IN (${ blockingQuery.getQuery() })`);
}));
q.setParameters(blockingQuery.getParameters());
}

※Redisの代わり(FTT有効時のフォールバックやFTT無効時)にDBから各種タイムラインのノートを取得するときに呼ばれるクエリです

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
packages/backend:test packages/backend Server side specific issue/PR packages/frontend Client side specific issue/PR packages/misskey-js
Projects
None yet
Development

Successfully merging this pull request may close these issues.

リアクションだけブロック機能
3 participants