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: チャンネルミュートの実装 #14105

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

Conversation

samunohito
Copy link
Member

@samunohito samunohito commented Jun 30, 2024

※でかいので本線投入のタイミングは計りたい

fix: #10649

What

チャンネルミュートの機能を実装しました。

  • エンドポイントの追加
    • channels/mute/create
    • channels/mute/delete
    • channels/mute/list
  • 各TLのフィルタ時にチャンネルのミュート有無を考慮するように
    • HTL
      • ミュート中チャンネルのノートが流れてこなくなる
      • リノート含め/フォロー中のチャンネルも同様
    • LTL
      • ミュート中チャンネルのノートがチャンネル外にリノートされた場合でも流れてこなくなる
    • STL
      • LTL + HTL
    • リストTL
      • ミュート中チャンネルのノートがチャンネル外にリノートされた場合でも流れてこなくなる
    • ロールTL
      • ミュート中チャンネルのノートが流れてこなくなる
    • ユーザTL
      • そのユーザの投稿からミュート中チャンネルの投稿が省かれる
    • チャンネルTL
      • ミュート中でもそのチャンネル自体のTLは通常通り閲覧可能
      • ただし、ミュート中チャンネルの投稿を別チャンネルにリノートした場合はミュートされて見えなくなる

Why

fix: #10649

Additional info (optional)

  • unittestに新設したServiceのテストを追加
  • e2etestに各TL向けのテストを追加
  • pnpm migrate / revertによるエラーが無いことを確認

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

@samunohito samunohito marked this pull request as draft June 30, 2024 13:22
@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 Jun 30, 2024
@samunohito samunohito changed the title 【】feat: チャンネルミュートの実装 [wip] feat: チャンネルミュートの実装 Jun 30, 2024
Copy link
Contributor

github-actions bot commented Jun 30, 2024

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

差分はこちら
--- base
+++ head
@@ -23161,6 +23161,494 @@
         }
       }
     },
+    "/channels/mute/create": {
+      "post": {
+        "operationId": "channels___mute___create",
+        "summary": "channels/mute/create",
+        "description": "No description provided.\n\n**Credential required**: *Yes* / **Permission**: *write:channels*",
+        "externalDocs": {
+          "description": "Source code",
+          "url": "https://github.com/misskey-dev/misskey/blob/develop/packages/backend/src/server/api/endpoints/channels/mute/create.ts"
+        },
+        "tags": [
+          "channels"
+        ],
+        "security": [
+          {
+            "bearerAuth": []
+          }
+        ],
+        "requestBody": {
+          "required": true,
+          "content": {
+            "application/json": {
+              "schema": {
+                "type": "object",
+                "properties": {
+                  "channelId": {
+                    "type": "string",
+                    "format": "misskey:id"
+                  },
+                  "expiresAt": {
+                    "type": [
+                      "integer",
+                      "null"
+                    ],
+                    "description": "A Unix Epoch timestamp that must lie in the future. `null` means an indefinite mute."
+                  }
+                },
+                "required": [
+                  "channelId"
+                ]
+              }
+            }
+          }
+        },
+        "responses": {
+          "204": {
+            "description": "OK (without any results)"
+          },
+          "400": {
+            "description": "Client error",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Error"
+                },
+                "examples": {
+                  "NO_SUCH_CHANNEL": {
+                    "value": {
+                      "error": {
+                        "message": "No such Channel.",
+                        "code": "NO_SUCH_CHANNEL",
+                        "id": "7174361e-d58f-31d6-2e7c-6fb830786a3f"
+                      }
+                    }
+                  },
+                  "ALREADY_MUTING_CHANNEL": {
+                    "value": {
+                      "error": {
+                        "message": "You are already muting that user.",
+                        "code": "ALREADY_MUTING_CHANNEL",
+                        "id": "5a251978-769a-da44-3e89-3931e43bb592"
+                      }
+                    }
+                  },
+                  "EXPIRES_AT_IS_PAST": {
+                    "value": {
+                      "error": {
+                        "message": "Cannot set past date to \"expiresAt\".",
+                        "code": "EXPIRES_AT_IS_PAST",
+                        "id": "42b32236-df2c-a45f-fdbf-def67268f749"
+                      }
+                    }
+                  },
+                  "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/mute/delete": {
+      "post": {
+        "operationId": "channels___mute___delete",
+        "summary": "channels/mute/delete",
+        "description": "No description provided.\n\n**Credential required**: *Yes* / **Permission**: *write:channels*",
+        "externalDocs": {
+          "description": "Source code",
+          "url": "https://github.com/misskey-dev/misskey/blob/develop/packages/backend/src/server/api/endpoints/channels/mute/delete.ts"
+        },
+        "tags": [
+          "channels"
+        ],
+        "security": [
+          {
+            "bearerAuth": []
+          }
+        ],
+        "requestBody": {
+          "required": true,
+          "content": {
+            "application/json": {
+              "schema": {
+                "type": "object",
+                "properties": {
+                  "channelId": {
+                    "type": "string",
+                    "format": "misskey:id"
+                  }
+                },
+                "required": [
+                  "channelId"
+                ]
+              }
+            }
+          }
+        },
+        "responses": {
+          "204": {
+            "description": "OK (without any results)"
+          },
+          "400": {
+            "description": "Client error",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "$ref": "#/components/schemas/Error"
+                },
+                "examples": {
+                  "NO_SUCH_CHANNEL": {
+                    "value": {
+                      "error": {
+                        "message": "No such Channel.",
+                        "code": "NO_SUCH_CHANNEL",
+                        "id": "e7998769-6e94-d9c2-6b8f-94a527314aba"
+                      }
+                    }
+                  },
+                  "NOT_MUTING_CHANNEL": {
+                    "value": {
+                      "error": {
+                        "message": "You are not muting that channel.",
+                        "code": "NOT_MUTING_CHANNEL",
+                        "id": "14d55962-6ea8-d990-1333-d6bef78dc2ab"
+                      }
+                    }
+                  },
+                  "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/mute/list": {
+      "post": {
+        "operationId": "channels___mute___list",
+        "summary": "channels/mute/list",
+        "description": "No description provided.\n\n**Credential required**: *Yes* / **Permission**: *read:channels*",
+        "externalDocs": {
+          "description": "Source code",
+          "url": "https://github.com/misskey-dev/misskey/blob/develop/packages/backend/src/server/api/endpoints/channels/mute/list.ts"
+        },
+        "tags": [
+          "channels"
+        ],
+        "security": [
+          {
+            "bearerAuth": []
+          }
+        ],
+        "responses": {
+          "200": {
+            "description": "OK (with results)",
+            "content": {
+              "application/json": {
+                "schema": {
+                  "type": "array",
+                  "items": {
+                    "type": "object",
+                    "$ref": "#/components/schemas/Channel"
+                  }
+                }
+              }
+            }
+          },
+          "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"
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          }
+        }
+      }
+    },
     "/charts/active-users": {
       "get": {
         "operationId": "charts___active-users",
@@ -80734,6 +81222,9 @@
           "isFavorited": {
             "type": "boolean"
           },
+          "isMuting": {
+            "type": "boolean"
+          },
           "pinnedNotes": {
             "type": "array",
             "items": {

Get diff files from Workflow Page

Copy link

codecov bot commented Jun 30, 2024

Codecov Report

Attention: Patch coverage is 59.42549% with 452 lines in your changes missing coverage. Please review.

Project coverage is 41.77%. Comparing base (a3d236c) to head (c17aa8e).
Report is 449 commits behind head on develop.

Files with missing lines Patch % Lines
.../backend/src/core/entities/ChannelEntityService.ts 10.60% 118 Missing ⚠️
packages/frontend/src/pages/channel.vue 0.00% 75 Missing ⚠️
...d/src/server/api/endpoints/channels/mute/create.ts 62.22% 34 Missing ⚠️
...backend/src/server/api/endpoints/notes/timeline.ts 9.37% 29 Missing ⚠️
...d/src/server/api/endpoints/channels/mute/delete.ts 61.64% 28 Missing ⚠️
...es/backend/src/server/api/endpoints/users/notes.ts 3.84% 25 Missing ⚠️
...kend/src/server/api/endpoints/channels/timeline.ts 10.00% 18 Missing ⚠️
.../src/server/api/endpoints/notes/hybrid-timeline.ts 15.00% 17 Missing ⚠️
...backend/src/server/api/endpoints/antennas/notes.ts 11.11% 16 Missing ⚠️
...d/src/server/api/endpoints/notes/local-timeline.ts 5.88% 16 Missing ⚠️
... and 9 more
Additional details and impacted files
@@             Coverage Diff             @@
##           develop   #14105      +/-   ##
===========================================
+ Coverage    41.74%   41.77%   +0.02%     
===========================================
  Files         1549     1573      +24     
  Lines       196555   204533    +7978     
  Branches      2767     3753     +986     
===========================================
+ Hits         82055    85441    +3386     
- Misses      113939   118487    +4548     
- Partials       561      605      +44     

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


🚨 Try these New Features:

@samunohito
Copy link
Member Author

image
チャンネルのミュート・アンミュートはこちらから。
分かりにくい位置になってしまいましたが、今の画面構成だとここに置くのが限界だと思い…

ユーザのフォロー画面のように出来ればいいのかもしれませんが、あまり派手に変えるとチャンネルミュートの本筋から逸れてしまうのでひとまずこの形式で実装しました。

@samunohito samunohito changed the title [wip] feat: チャンネルミュートの実装 feat: チャンネルミュートの実装 Jul 7, 2024
@samunohito samunohito marked this pull request as ready for review July 7, 2024 02:38
@samunohito
Copy link
Member Author

ready for review

@@ -10,6 +10,7 @@ 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 { isChannelRelated } from '@/misc/is-channel-related.js';
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
Member Author

Choose a reason for hiding this comment

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

fix b6e22f3

@@ -8,6 +8,7 @@ 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 { isChannelRelated } from '@/misc/is-channel-related.js';
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
Member Author

Choose a reason for hiding this comment

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

fix b6e22f3

# Conflicts:
#	CHANGELOG.md
#	packages/backend/src/core/CoreModule.ts
#	packages/backend/src/server/api/endpoints/channels/timeline.ts
#	packages/backend/src/server/api/endpoints/notes/hybrid-timeline.ts
#	packages/backend/src/server/api/endpoints/notes/local-timeline.ts
#	packages/backend/src/server/api/endpoints/notes/timeline.ts
#	packages/backend/src/server/api/endpoints/notes/user-list-timeline.ts
#	packages/backend/src/server/api/endpoints/users/notes.ts
#	packages/backend/src/server/api/stream/Connection.ts
#	packages/backend/src/server/api/stream/channel.ts
#	packages/backend/src/server/api/stream/channels/channel.ts
#	packages/backend/src/server/api/stream/channels/home-timeline.ts
#	packages/backend/src/server/api/stream/channels/hybrid-timeline.ts
#	packages/backend/test/e2e/timelines.ts
# Conflicts:
#	CHANGELOG.md
#	packages/backend/src/core/CoreModule.ts
@syuilo
Copy link
Member

syuilo commented Oct 19, 2024

マージするか

@@ -89,7 +121,8 @@ export class ChannelEntityService {

...(me ? {
isFollowing,
isFavorited,
isFavorite,
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
Member Author

Choose a reason for hiding this comment

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

fix
4827fbd

@@ -98,6 +104,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-
useDbFallback: true,
redisTimelines: [`channelTimeline:${channel.id}`],
excludePureRenotes: false,
noteFilter: note => {
// 共通機能を使うと見ているチャンネルそのものもミュートしてしまうので閲覧中のチャンネル以外を除く形にする
Copy link
Member

Choose a reason for hiding this comment

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

実装の一貫性的にはexcludeAuthorFromMuteみたいな実装の葉法がユーザーミュートと一貫性が取れて良いような茎もする

Copy link
Member Author

Choose a reason for hiding this comment

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

これは…どういう意味でしょう…?

Copy link
Member

Choose a reason for hiding this comment

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

"ignoreAuthorFromMuteみたいな実装"の間違いですね。

ユーザミュートにignoreAuthorFromMuteがあるようにignoreAuthorChannelFromMuteのようなフラグを持ったほうが一貫性がある実装になるかなと思った感じです

] = await Promise.all([
this.cacheService.userMutingsCache.fetch(ps.me.id),
this.cacheService.renoteMutingsCache.fetch(ps.me.id),
this.cacheService.userBlockedCache.fetch(ps.me.id),
this.cacheService.userProfileCache.fetch(me.id).then(p => new Set(p.mutedInstances)),
ps.excludeMutedChannels ? this.channelMutingService.mutingChannelsCache.fetch(me.id) : Promise.resolve(new Set<string>()),
Copy link
Member

@anatawa12 anatawa12 Oct 19, 2024

Choose a reason for hiding this comment

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

excludeの方針にするならexcludeMutedChannelsなくなると思いますが、このまま行くならfalse by defaultになってるけど除外するのがデフォルトの実装にするべきだと思う。ミューとしてるのに流れてくるってのを今後の実装などで誘発するので

Copy link
Member Author

Choose a reason for hiding this comment

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

excludeよりもincludeのほうが自然ぽいので変更しました

d95543d

@samunohito samunohito removed this from the v2024.11.1? milestone Nov 23, 2024
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.

チャンネルをミュート
4 participants