From 2e80cebc11bfbab0af141e4318d91b7c57588b3f Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 26 Oct 2021 01:10:27 +0900 Subject: [PATCH 01/24] Remove CircleCI configuration --- .github/CODEOWNERS | 1 - README.md | 1 - 2 files changed, 2 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 2a41c12c7..aa888f4da 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,6 +1,5 @@ # PATH OWNERS /.autogen/ @acid-chicken -/.circleci/ @syuilo @acid-chicken /.config/ @syuilo @AyaMorisawa @mei23 @acid-chicken @rinsuki # /.config/mongo_initdb_example.js @khws4v1 /.github/ @syuilo @AyaMorisawa @acid-chicken diff --git a/README.md b/README.md index ce0aa0941..a60f71cac 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,6 @@
-[![CircleCI](https://img.shields.io/circleci/project/github/misskey-dev/misskey.svg?style=for-the-badge&logo=circleci)](https://circleci.com/gh/misskey-dev/misskey) [![Dependencies](https://img.shields.io/david/misskey-dev/misskey.svg?style=for-the-badge&logo=npm)](https://david-dm.org/misskey-dev/misskey) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=for-the-badge&logo=github)](http://makeapullrequest.com) [![Awesome Humane Tech](https://raw.githubusercontent.com/humanetech-community/awesome-humane-tech/main/humane-tech-badge.svg?sanitize=true)](https://github.com/humanetech-community/awesome-humane-tech) From 9c74c5c5dc19ee60b9d2cee228fbb8199e38b775 Mon Sep 17 00:00:00 2001 From: syuilo Date: Tue, 26 Oct 2021 01:11:07 +0900 Subject: [PATCH 02/24] =?UTF-8?q?=E8=A6=81=E3=82=89=E3=81=AA=E3=81=95?= =?UTF-8?q?=E3=81=9D=E3=81=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/CODEOWNERS | 37 ------------------------------------- 1 file changed, 37 deletions(-) delete mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index aa888f4da..000000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1,37 +0,0 @@ -# PATH OWNERS -/.autogen/ @acid-chicken -/.config/ @syuilo @AyaMorisawa @mei23 @acid-chicken @rinsuki -# /.config/mongo_initdb_example.js @khws4v1 -/.github/ @syuilo @AyaMorisawa @acid-chicken -/.vscode/ @acid-chicken -/assets/ @syuilo # @tamaina -/docs/ @syuilo -/docs/*.en.md @AyaMorisawa # @skid9000 -# /docs/*.fr.md @BoFFire -# /docs/docker.*.md @khws4v1 -/locales/ @syuilo -/src/ @syuilo @AyaMorisawa @mei23 @acid-chicken @rinsuki -# /src/crypto_key.cc @akihikodaki -# /src/crypto_key.d.ts @akihikodaki -/.dockerignore @syuilo # @khws4v1 -/.editorconfig @syuilo @AyaMorisawa -/.eslintrc @syuilo -/.gitattributes @syuilo -/.gitignore @syuilo -/.npmrc @syuilo -/.vsls.json @AyaMorisawa -/CHANGELOG.md @syuilo -/CODE_OF_CONDUCT.md @syuilo -/CONTRIBUTING.md @syuilo -/Dockerfile @syuilo @AyaMorisawa @acid-chicken # @khws4v1 -/LICENSE @syuilo -/README.md @syuilo @AyaMorisawa @acid-chicken # @nikhiljha -# /binding.gyp @akihikodaki -/crowdin.yml @syuilo -# /docker-compose.yml @khws4v1 -/gulpfile.ts @syuilo @AyaMorisawa -/jsconfig.json @syuilo @AyaMorisawa -/package.json @syuilo @AyaMorisawa -/tsconfig.json @syuilo @AyaMorisawa -/tslint.json @syuilo @AyaMorisawa -/webpack.config.ts @syuilo @AyaMorisawa From c2ae160d2326ab2854224cb98811d8fb47e95231 Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 27 Oct 2021 23:42:09 +0900 Subject: [PATCH 03/24] refactor --- src/client/components/google.vue | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/client/components/google.vue b/src/client/components/google.vue index 6d8ae0b5b..be724f038 100644 --- a/src/client/components/google.vue +++ b/src/client/components/google.vue @@ -10,7 +10,12 @@ import { defineComponent } from 'vue'; import * as os from '@client/os'; export default defineComponent({ - props: ['q'], + props: { + q: { + type: String, + required: true, + } + }, data() { return { query: null, @@ -21,10 +26,7 @@ export default defineComponent({ }, methods: { search() { - const engine = this.$store.state.webSearchEngine || - 'https://www.google.com/search?q={{query}}'; - const url = engine.replace('{{query}}', this.query) - window.open(url, '_blank'); + window.open(`https://www.google.com/search?q=${this.query}`, '_blank'); } } }); From 21ece52a9fa38594745bda54d5d9bc4cd6262a78 Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 28 Oct 2021 00:57:49 +0900 Subject: [PATCH 04/24] fix test --- src/client/ui/_common_/sidebar.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/ui/_common_/sidebar.vue b/src/client/ui/_common_/sidebar.vue index ece80e60d..cd78b6ae4 100644 --- a/src/client/ui/_common_/sidebar.vue +++ b/src/client/ui/_common_/sidebar.vue @@ -35,7 +35,7 @@ {{ $ts.settings }} -
From d35b02fa236ce825e7d96a8d85d710dccc62bdb8 Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 28 Oct 2021 01:16:13 +0900 Subject: [PATCH 05/24] Improve CI --- .github/workflows/lint.yml | 21 ++++++++++ .github/workflows/{nodejs.yml => test.yml} | 46 +++++++++++++++++----- package.json | 3 +- 3 files changed, 60 insertions(+), 10 deletions(-) create mode 100644 .github/workflows/lint.yml rename .github/workflows/{nodejs.yml => test.yml} (55%) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 000000000..0b3bbc186 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,21 @@ +name: Lint + +on: + push: + branches: + - master + - develop + pull_request: + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + submodules: true + - uses: actions/setup-node@v1 + with: + node-version: 12.x + - run: yarn install + - run: yarn lint diff --git a/.github/workflows/nodejs.yml b/.github/workflows/test.yml similarity index 55% rename from .github/workflows/nodejs.yml rename to .github/workflows/test.yml index a91572ad7..045d20980 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/test.yml @@ -1,4 +1,5 @@ -name: Node.js CI +name: Test + on: push: branches: @@ -7,12 +8,12 @@ on: pull_request: jobs: - build_and_test: + mocha: runs-on: ubuntu-latest strategy: matrix: - node-version: [14.x, 16.x] + node-version: [16.x] services: postgres: @@ -44,16 +45,43 @@ jobs: - name: Build run: yarn build - name: Test - run: yarn test + run: yarn mocha - lint: + e2e: runs-on: ubuntu-latest + + strategy: + matrix: + node-version: [16.x] + + services: + postgres: + image: postgres:12.2-alpine + ports: + - 54312:5432 + env: + POSTGRES_DB: test-misskey + POSTGRES_HOST_AUTH_METHOD: trust + redis: + image: redis:4.0-alpine + ports: + - 56312:6379 + steps: - uses: actions/checkout@v2 with: submodules: true - - uses: actions/setup-node@v1 + - name: Use Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v1 with: - node-version: 12.x - - run: yarn install - - run: yarn lint + node-version: ${{ matrix.node-version }} + - name: Install dependencies + run: yarn install + - name: Check yarn.lock + run: git diff --exit-code yarn.lock + - name: Copy Configure + run: cp test/test.yml .config + - name: Build + run: yarn build + - name: Test + run: yarn e2e diff --git a/package.json b/package.json index f0bdd0049..1da71f4d8 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,8 @@ "cy:open": "cypress open", "cy:run": "cypress run", "e2e": "start-server-and-test start:test http://localhost cy:run", - "test": "cross-env TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha", + "mocha": "cross-env TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha", + "test": "npm run mocha", "format": "gulp format" }, "resolutions": { From d964c5f27f0aa37f0d5703b1bda928da699111c6 Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 28 Oct 2021 01:24:55 +0900 Subject: [PATCH 06/24] remove circleci configuration --- .circleci/config.yml | 49 ------------------------- .circleci/misskey/default.yml | 12 ------ {.circleci => .github}/misskey/test.yml | 0 test/test.yml | 6 +-- 4 files changed, 3 insertions(+), 64 deletions(-) delete mode 100644 .circleci/config.yml delete mode 100644 .circleci/misskey/default.yml rename {.circleci => .github}/misskey/test.yml (100%) diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index a29b31a04..000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,49 +0,0 @@ -version: 2.1 - -executors: - docker: - working_directory: /tmp/workspace - docker: - - image: docker:latest - -jobs: - docker: - parameters: - with_deploy: - type: boolean - default: false - executor: docker - steps: - - checkout - - setup_remote_docker: - version: 19.03.13 - - run: - name: Build - command: | - docker build -t misskey/misskey . - - when: - condition: <> - steps: - - run: - name: Deploy - command: | - if [ "$DOCKERHUB_USERNAME$DOCKERHUB_PASSWORD" ] - then - apk update && apk add jq - docker tag misskey/misskey misskey/misskey:$(cat package.json | jq -r .version) - docker login -u $DOCKERHUB_USERNAME -p $DOCKERHUB_PASSWORD - docker push -a misskey/misskey - else - echo -e '\033[0;33mAborted deploying to Docker Hub\033[0;39m' - fi - -workflows: - version: 2 - docker: - jobs: - - docker: - name: auto-build - with_deploy: true - filters: - branches: - only: master diff --git a/.circleci/misskey/default.yml b/.circleci/misskey/default.yml deleted file mode 100644 index ae18a841b..000000000 --- a/.circleci/misskey/default.yml +++ /dev/null @@ -1,12 +0,0 @@ -url: 'http://misskey.local' -port: 8080 -db: - host: localhost - port: 5432 - db: test-misskey - user: postgres - pass: '' -redis: - host: localhost - port: 6379 -id: aid diff --git a/.circleci/misskey/test.yml b/.github/misskey/test.yml similarity index 100% rename from .circleci/misskey/test.yml rename to .github/misskey/test.yml diff --git a/test/test.yml b/test/test.yml index 2d3094653..ae18a841b 100644 --- a/test/test.yml +++ b/test/test.yml @@ -1,12 +1,12 @@ url: 'http://misskey.local' -port: 61812 +port: 8080 db: host: localhost - port: 54312 + port: 5432 db: test-misskey user: postgres pass: '' redis: host: localhost - port: 56312 + port: 6379 id: aid From 877bd52ed7820bef49aecfcbe6cad40afd07d43d Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 28 Oct 2021 01:34:31 +0900 Subject: [PATCH 07/24] Update test.yml --- test/test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/test.yml b/test/test.yml index ae18a841b..2d3094653 100644 --- a/test/test.yml +++ b/test/test.yml @@ -1,12 +1,12 @@ url: 'http://misskey.local' -port: 8080 +port: 61812 db: host: localhost - port: 5432 + port: 54312 db: test-misskey user: postgres pass: '' redis: host: localhost - port: 6379 + port: 56312 id: aid From 46010187c385dac0e9c433dbb478bbaf0a83a56e Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 28 Oct 2021 16:06:06 +0900 Subject: [PATCH 08/24] fix e2e test --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 1da71f4d8..94cdb1a6c 100644 --- a/package.json +++ b/package.json @@ -28,7 +28,7 @@ "lint": "tslint 'src/**/*.ts'", "cy:open": "cypress open", "cy:run": "cypress run", - "e2e": "start-server-and-test start:test http://localhost cy:run", + "e2e": "start-server-and-test start:test http://localhost:61812 cy:run", "mocha": "cross-env TS_NODE_FILES=true TS_NODE_TRANSPILE_ONLY=true TS_NODE_PROJECT=\"./test/tsconfig.json\" mocha", "test": "npm run mocha", "format": "gulp format" From ddd931a0a379aedb68a0b7031ed9ca11dd69d372 Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 28 Oct 2021 17:12:57 +0900 Subject: [PATCH 09/24] fix e2e test --- cypress.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress.json b/cypress.json index f2c02689e..e858e480b 100644 --- a/cypress.json +++ b/cypress.json @@ -1,3 +1,3 @@ { - "baseUrl": "http://localhost" + "baseUrl": "http://localhost:61812" } From fa0814f9393b3a15869df8d780e91bc6f03d112e Mon Sep 17 00:00:00 2001 From: syuilo Date: Thu, 28 Oct 2021 22:00:38 +0900 Subject: [PATCH 10/24] fix e2e test --- cypress/integration/basic.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cypress/integration/basic.js b/cypress/integration/basic.js index 182f70ff6..a754f41b9 100644 --- a/cypress/integration/basic.js +++ b/cypress/integration/basic.js @@ -128,7 +128,8 @@ describe('After user signup', () => { cy.get('[data-cy-signin-username] input').type('alice'); cy.get('[data-cy-signin-password] input').type('alice1234{enter}'); - cy.contains('アカウントが凍結されています'); + // TODO: cypressにブラウザの言語指定できる機能が実装され次第英語のみテストするようにする + cy.contains(/アカウントが凍結されています|This account has been suspended due to/gi); }); }); From f47a564819500d801c76fe327fec276b4bfa9989 Mon Sep 17 00:00:00 2001 From: MeiMei <30769358+mei23@users.noreply.github.com> Date: Sun, 31 Oct 2021 15:18:46 +0900 Subject: [PATCH 11/24] fix: Fix #7895 (#7937) * Fix #7895 * CHANGELOG --- CHANGELOG.md | 7 +++++++ src/remote/activitypub/renderer/delete.ts | 3 ++- src/remote/activitypub/renderer/undo.ts | 3 ++- src/remote/activitypub/renderer/update.ts | 3 ++- 4 files changed, 13 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 13dccdc38..bf62565b8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ --> +## 12.x.x (unreleased) + +### Improvements + +### Bugfixes +- リレー向けのActivityが一部実装で除外されてしまうことがあるのを修正 + ## 12.94.1 (2021/10/25) ### Improvements diff --git a/src/remote/activitypub/renderer/delete.ts b/src/remote/activitypub/renderer/delete.ts index 83b27fa86..176a6f7e2 100644 --- a/src/remote/activitypub/renderer/delete.ts +++ b/src/remote/activitypub/renderer/delete.ts @@ -4,5 +4,6 @@ import { User } from '@/models/entities/user'; export default (object: any, user: { id: User['id']; host: null }) => ({ type: 'Delete', actor: `${config.url}/users/${user.id}`, - object + object, + published: new Date().toISOString(), }); diff --git a/src/remote/activitypub/renderer/undo.ts b/src/remote/activitypub/renderer/undo.ts index f9082ffdf..14115b788 100644 --- a/src/remote/activitypub/renderer/undo.ts +++ b/src/remote/activitypub/renderer/undo.ts @@ -7,6 +7,7 @@ export default (object: any, user: { id: User['id'] }) => { return { type: 'Undo', actor: `${config.url}/users/${user.id}`, - object + object, + published: new Date().toISOString(), }; }; diff --git a/src/remote/activitypub/renderer/update.ts b/src/remote/activitypub/renderer/update.ts index d9a8149af..8bb415d11 100644 --- a/src/remote/activitypub/renderer/update.ts +++ b/src/remote/activitypub/renderer/update.ts @@ -7,7 +7,8 @@ export default (object: any, user: { id: User['id'] }) => { actor: `${config.url}/users/${user.id}`, type: 'Update', to: [ 'https://www.w3.org/ns/activitystreams#Public' ], - object + object, + published: new Date().toISOString(), } as any; return activity; From fc65190ef7b687650018cccfee2219bf00827f70 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 31 Oct 2021 15:30:22 +0900 Subject: [PATCH 12/24] feat: thread mute (#7930) * feat: thread mute * chore: fix comment * fix test * fix * refactor --- CHANGELOG.md | 1 + locales/ja-JP.yml | 2 + migration/1635500777168-note-thread-mute.ts | 26 +++++ src/client/components/note-detailed.vue | 15 +++ src/client/components/note.vue | 15 +++ src/db/postgre.ts | 2 + src/models/entities/note-thread-muting.ts | 33 ++++++ src/models/entities/note.ts | 6 + src/models/index.ts | 2 + .../generate-muted-note-thread-query.ts | 17 +++ src/server/api/endpoints/notes/mentions.ts | 2 + src/server/api/endpoints/notes/state.ts | 28 +++-- .../endpoints/notes/thread-muting/create.ts | 54 +++++++++ .../endpoints/notes/thread-muting/delete.ts | 40 +++++++ src/services/note/create.ts | 29 ++++- src/services/note/unread.ts | 11 +- test/thread-mute.ts | 103 ++++++++++++++++++ test/utils.ts | 3 +- 18 files changed, 375 insertions(+), 14 deletions(-) create mode 100644 migration/1635500777168-note-thread-mute.ts create mode 100644 src/models/entities/note-thread-muting.ts create mode 100644 src/server/api/common/generate-muted-note-thread-query.ts create mode 100644 src/server/api/endpoints/notes/thread-muting/create.ts create mode 100644 src/server/api/endpoints/notes/thread-muting/delete.ts create mode 100644 test/thread-mute.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index bf62565b8..ae652c310 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ ## 12.x.x (unreleased) ### Improvements +- スレッドミュート機能 ### Bugfixes - リレー向けのActivityが一部実装で除外されてしまうことがあるのを修正 diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index dbb0bf166..1326369f8 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -800,6 +800,8 @@ manageAccounts: "アカウントを管理" makeReactionsPublic: "リアクション一覧を公開する" makeReactionsPublicDescription: "あなたがしたリアクション一覧を誰でも見れるようにします。" classic: "クラシック" +muteThread: "スレッドをミュート" +unmuteThread: "スレッドのミュートを解除" _signup: almostThere: "ほとんど完了です" diff --git a/migration/1635500777168-note-thread-mute.ts b/migration/1635500777168-note-thread-mute.ts new file mode 100644 index 000000000..aed10d18d --- /dev/null +++ b/migration/1635500777168-note-thread-mute.ts @@ -0,0 +1,26 @@ +import {MigrationInterface, QueryRunner} from "typeorm"; + +export class noteThreadMute1635500777168 implements MigrationInterface { + name = 'noteThreadMute1635500777168' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE "note_thread_muting" ("id" character varying(32) NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL, "userId" character varying(32) NOT NULL, "threadId" character varying(256) NOT NULL, CONSTRAINT "PK_ec5936d94d1a0369646d12a3a47" PRIMARY KEY ("id"))`); + await queryRunner.query(`CREATE INDEX "IDX_29c11c7deb06615076f8c95b80" ON "note_thread_muting" ("userId") `); + await queryRunner.query(`CREATE INDEX "IDX_c426394644267453e76f036926" ON "note_thread_muting" ("threadId") `); + await queryRunner.query(`CREATE UNIQUE INDEX "IDX_ae7aab18a2641d3e5f25e0c4ea" ON "note_thread_muting" ("userId", "threadId") `); + await queryRunner.query(`ALTER TABLE "note" ADD "threadId" character varying(256)`); + await queryRunner.query(`CREATE INDEX "IDX_d4ebdef929896d6dc4a3c5bb48" ON "note" ("threadId") `); + await queryRunner.query(`ALTER TABLE "note_thread_muting" ADD CONSTRAINT "FK_29c11c7deb06615076f8c95b80a" FOREIGN KEY ("userId") REFERENCES "user"("id") ON DELETE CASCADE ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "note_thread_muting" DROP CONSTRAINT "FK_29c11c7deb06615076f8c95b80a"`); + await queryRunner.query(`DROP INDEX "public"."IDX_d4ebdef929896d6dc4a3c5bb48"`); + await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "threadId"`); + await queryRunner.query(`DROP INDEX "public"."IDX_ae7aab18a2641d3e5f25e0c4ea"`); + await queryRunner.query(`DROP INDEX "public"."IDX_c426394644267453e76f036926"`); + await queryRunner.query(`DROP INDEX "public"."IDX_29c11c7deb06615076f8c95b80"`); + await queryRunner.query(`DROP TABLE "note_thread_muting"`); + } + +} diff --git a/src/client/components/note-detailed.vue b/src/client/components/note-detailed.vue index 40b0a68c5..568a2360d 100644 --- a/src/client/components/note-detailed.vue +++ b/src/client/components/note-detailed.vue @@ -601,6 +601,12 @@ export default defineComponent({ }); }, + toggleThreadMute(mute: boolean) { + os.apiWithDialog(mute ? 'notes/thread-muting/create' : 'notes/thread-muting/delete', { + noteId: this.appearNote.id + }); + }, + getMenu() { let menu; if (this.$i) { @@ -657,6 +663,15 @@ export default defineComponent({ text: this.$ts.watch, action: () => this.toggleWatch(true) }) : undefined, + statePromise.then(state => state.isMutedThread ? { + icon: 'fas fa-comment-slash', + text: this.$ts.unmuteThread, + action: () => this.toggleThreadMute(false) + } : { + icon: 'fas fa-comment-slash', + text: this.$ts.muteThread, + action: () => this.toggleThreadMute(true) + }), this.appearNote.userId == this.$i.id ? (this.$i.pinnedNoteIds || []).includes(this.appearNote.id) ? { icon: 'fas fa-thumbtack', text: this.$ts.unpin, diff --git a/src/client/components/note.vue b/src/client/components/note.vue index 91a3e3b87..681e819a2 100644 --- a/src/client/components/note.vue +++ b/src/client/components/note.vue @@ -576,6 +576,12 @@ export default defineComponent({ }); }, + toggleThreadMute(mute: boolean) { + os.apiWithDialog(mute ? 'notes/thread-muting/create' : 'notes/thread-muting/delete', { + noteId: this.appearNote.id + }); + }, + getMenu() { let menu; if (this.$i) { @@ -632,6 +638,15 @@ export default defineComponent({ text: this.$ts.watch, action: () => this.toggleWatch(true) }) : undefined, + statePromise.then(state => state.isMutedThread ? { + icon: 'fas fa-comment-slash', + text: this.$ts.unmuteThread, + action: () => this.toggleThreadMute(false) + } : { + icon: 'fas fa-comment-slash', + text: this.$ts.muteThread, + action: () => this.toggleThreadMute(true) + }), this.appearNote.userId == this.$i.id ? (this.$i.pinnedNoteIds || []).includes(this.appearNote.id) ? { icon: 'fas fa-thumbtack', text: this.$ts.unpin, diff --git a/src/db/postgre.ts b/src/db/postgre.ts index 4f4047b61..f52c2ab72 100644 --- a/src/db/postgre.ts +++ b/src/db/postgre.ts @@ -17,6 +17,7 @@ import { PollVote } from '@/models/entities/poll-vote'; import { Note } from '@/models/entities/note'; import { NoteReaction } from '@/models/entities/note-reaction'; import { NoteWatching } from '@/models/entities/note-watching'; +import { NoteThreadMuting } from '@/models/entities/note-thread-muting'; import { NoteUnread } from '@/models/entities/note-unread'; import { Notification } from '@/models/entities/notification'; import { Meta } from '@/models/entities/meta'; @@ -138,6 +139,7 @@ export const entities = [ NoteFavorite, NoteReaction, NoteWatching, + NoteThreadMuting, NoteUnread, Page, PageLike, diff --git a/src/models/entities/note-thread-muting.ts b/src/models/entities/note-thread-muting.ts new file mode 100644 index 000000000..b438522a4 --- /dev/null +++ b/src/models/entities/note-thread-muting.ts @@ -0,0 +1,33 @@ +import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typeorm'; +import { User } from './user'; +import { Note } from './note'; +import { id } from '../id'; + +@Entity() +@Index(['userId', 'threadId'], { unique: true }) +export class NoteThreadMuting { + @PrimaryColumn(id()) + public id: string; + + @Column('timestamp with time zone', { + }) + public createdAt: Date; + + @Index() + @Column({ + ...id(), + }) + public userId: User['id']; + + @ManyToOne(type => User, { + onDelete: 'CASCADE' + }) + @JoinColumn() + public user: User | null; + + @Index() + @Column('varchar', { + length: 256, + }) + public threadId: string; +} diff --git a/src/models/entities/note.ts b/src/models/entities/note.ts index 9a8553263..4a5411f93 100644 --- a/src/models/entities/note.ts +++ b/src/models/entities/note.ts @@ -47,6 +47,12 @@ export class Note { @JoinColumn() public renote: Note | null; + @Index() + @Column('varchar', { + length: 256, nullable: true + }) + public threadId: string | null; + @Column('varchar', { length: 8192, nullable: true }) diff --git a/src/models/index.ts b/src/models/index.ts index 4c6f19eaf..7154cca55 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -7,6 +7,7 @@ import { PollVote } from './entities/poll-vote'; import { Meta } from './entities/meta'; import { SwSubscription } from './entities/sw-subscription'; import { NoteWatching } from './entities/note-watching'; +import { NoteThreadMuting } from './entities/note-thread-muting'; import { NoteUnread } from './entities/note-unread'; import { RegistrationTicket } from './entities/registration-tickets'; import { UserRepository } from './repositories/user'; @@ -69,6 +70,7 @@ export const Apps = getCustomRepository(AppRepository); export const Notes = getCustomRepository(NoteRepository); export const NoteFavorites = getCustomRepository(NoteFavoriteRepository); export const NoteWatchings = getRepository(NoteWatching); +export const NoteThreadMutings = getRepository(NoteThreadMuting); export const NoteReactions = getCustomRepository(NoteReactionRepository); export const NoteUnreads = getRepository(NoteUnread); export const Polls = getRepository(Poll); diff --git a/src/server/api/common/generate-muted-note-thread-query.ts b/src/server/api/common/generate-muted-note-thread-query.ts new file mode 100644 index 000000000..7e2cbd498 --- /dev/null +++ b/src/server/api/common/generate-muted-note-thread-query.ts @@ -0,0 +1,17 @@ +import { User } from '@/models/entities/user'; +import { NoteThreadMutings } from '@/models/index'; +import { Brackets, SelectQueryBuilder } from 'typeorm'; + +export function generateMutedNoteThreadQuery(q: SelectQueryBuilder, me: { id: User['id'] }) { + const mutedQuery = NoteThreadMutings.createQueryBuilder('threadMuted') + .select('threadMuted.threadId') + .where('threadMuted.userId = :userId', { userId: me.id }); + + q.andWhere(`note.id NOT IN (${ mutedQuery.getQuery() })`); + q.andWhere(new Brackets(qb => { qb + .where(`note.threadId IS NULL`) + .orWhere(`note.threadId NOT IN (${ mutedQuery.getQuery() })`); + })); + + q.setParameters(mutedQuery.getParameters()); +} diff --git a/src/server/api/endpoints/notes/mentions.ts b/src/server/api/endpoints/notes/mentions.ts index 74f7911bf..ffaebd6c9 100644 --- a/src/server/api/endpoints/notes/mentions.ts +++ b/src/server/api/endpoints/notes/mentions.ts @@ -8,6 +8,7 @@ import { generateMutedUserQuery } from '../../common/generate-muted-user-query'; import { makePaginationQuery } from '../../common/make-pagination-query'; import { Brackets } from 'typeorm'; import { generateBlockedUserQuery } from '../../common/generate-block-query'; +import { generateMutedNoteThreadQuery } from '../../common/generate-muted-note-thread-query'; export const meta = { tags: ['notes'], @@ -67,6 +68,7 @@ export default define(meta, async (ps, user) => { generateVisibilityQuery(query, user); generateMutedUserQuery(query, user); + generateMutedNoteThreadQuery(query, user); generateBlockedUserQuery(query, user); if (ps.visibility) { diff --git a/src/server/api/endpoints/notes/state.ts b/src/server/api/endpoints/notes/state.ts index 489902435..b3913a5e7 100644 --- a/src/server/api/endpoints/notes/state.ts +++ b/src/server/api/endpoints/notes/state.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import { ID } from '@/misc/cafy-id'; import define from '../../define'; -import { NoteFavorites, NoteWatchings } from '@/models/index'; +import { NoteFavorites, Notes, NoteThreadMutings, NoteWatchings } from '@/models/index'; export const meta = { tags: ['notes'], @@ -25,31 +25,45 @@ export const meta = { isWatching: { type: 'boolean' as const, optional: false as const, nullable: false as const - } + }, + isMutedThread: { + type: 'boolean' as const, + optional: false as const, nullable: false as const + }, } } }; export default define(meta, async (ps, user) => { - const [favorite, watching] = await Promise.all([ + const note = await Notes.findOneOrFail(ps.noteId); + + const [favorite, watching, threadMuting] = await Promise.all([ NoteFavorites.count({ where: { userId: user.id, - noteId: ps.noteId + noteId: note.id, }, take: 1 }), NoteWatchings.count({ where: { userId: user.id, - noteId: ps.noteId + noteId: note.id, }, take: 1 - }) + }), + NoteThreadMutings.count({ + where: { + userId: user.id, + threadId: note.threadId || note.id, + }, + take: 1 + }), ]); return { isFavorited: favorite !== 0, - isWatching: watching !== 0 + isWatching: watching !== 0, + isMutedThread: threadMuting !== 0, }; }); diff --git a/src/server/api/endpoints/notes/thread-muting/create.ts b/src/server/api/endpoints/notes/thread-muting/create.ts new file mode 100644 index 000000000..2010d5433 --- /dev/null +++ b/src/server/api/endpoints/notes/thread-muting/create.ts @@ -0,0 +1,54 @@ +import $ from 'cafy'; +import { ID } from '@/misc/cafy-id'; +import define from '../../../define'; +import { getNote } from '../../../common/getters'; +import { ApiError } from '../../../error'; +import { Notes, NoteThreadMutings } from '@/models'; +import { genId } from '@/misc/gen-id'; +import readNote from '@/services/note/read'; + +export const meta = { + tags: ['notes'], + + requireCredential: true as const, + + kind: 'write:account', + + params: { + noteId: { + validator: $.type(ID), + } + }, + + errors: { + noSuchNote: { + message: 'No such note.', + code: 'NO_SUCH_NOTE', + id: '5ff67ada-ed3b-2e71-8e87-a1a421e177d2' + } + } +}; + +export default define(meta, async (ps, user) => { + const note = await getNote(ps.noteId).catch(e => { + if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw e; + }); + + const mutedNotes = await Notes.find({ + where: [{ + id: note.threadId || note.id, + }, { + threadId: note.threadId || note.id, + }], + }); + + await readNote(user.id, mutedNotes); + + await NoteThreadMutings.insert({ + id: genId(), + createdAt: new Date(), + threadId: note.threadId || note.id, + userId: user.id, + }); +}); diff --git a/src/server/api/endpoints/notes/thread-muting/delete.ts b/src/server/api/endpoints/notes/thread-muting/delete.ts new file mode 100644 index 000000000..05d569187 --- /dev/null +++ b/src/server/api/endpoints/notes/thread-muting/delete.ts @@ -0,0 +1,40 @@ +import $ from 'cafy'; +import { ID } from '@/misc/cafy-id'; +import define from '../../../define'; +import { getNote } from '../../../common/getters'; +import { ApiError } from '../../../error'; +import { NoteThreadMutings } from '@/models'; + +export const meta = { + tags: ['notes'], + + requireCredential: true as const, + + kind: 'write:account', + + params: { + noteId: { + validator: $.type(ID), + } + }, + + errors: { + noSuchNote: { + message: 'No such note.', + code: 'NO_SUCH_NOTE', + id: 'bddd57ac-ceb3-b29d-4334-86ea5fae481a' + } + } +}; + +export default define(meta, async (ps, user) => { + const note = await getNote(ps.noteId).catch(e => { + if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); + throw e; + }); + + await NoteThreadMutings.delete({ + threadId: note.threadId || note.id, + userId: user.id, + }); +}); diff --git a/src/services/note/create.ts b/src/services/note/create.ts index 8c996bdba..69d854ab1 100644 --- a/src/services/note/create.ts +++ b/src/services/note/create.ts @@ -10,13 +10,13 @@ import { resolveUser } from '@/remote/resolve-user'; import config from '@/config/index'; import { updateHashtags } from '../update-hashtag'; import { concat } from '@/prelude/array'; -import insertNoteUnread from './unread'; +import { insertNoteUnread } from '@/services/note/unread'; import { registerOrFetchInstanceDoc } from '../register-or-fetch-instance-doc'; import { extractMentions } from '@/misc/extract-mentions'; import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm'; import { extractHashtags } from '@/misc/extract-hashtags'; import { Note, IMentionedRemoteUsers } from '@/models/entities/note'; -import { Mutings, Users, NoteWatchings, Notes, Instances, UserProfiles, Antennas, Followings, MutedNotes, Channels, ChannelFollowings, Blockings } from '@/models/index'; +import { Mutings, Users, NoteWatchings, Notes, Instances, UserProfiles, Antennas, Followings, MutedNotes, Channels, ChannelFollowings, Blockings, NoteThreadMutings } from '@/models/index'; import { DriveFile } from '@/models/entities/drive-file'; import { App } from '@/models/entities/app'; import { Not, getConnection, In } from 'typeorm'; @@ -344,8 +344,15 @@ export default async (user: { id: User['id']; username: User['username']; host: // 通知 if (data.reply.userHost === null) { - nm.push(data.reply.userId, 'reply'); - publishMainStream(data.reply.userId, 'reply', noteObj); + const threadMuted = await NoteThreadMutings.findOne({ + userId: data.reply.userId, + threadId: data.reply.threadId || data.reply.id, + }); + + if (!threadMuted) { + nm.push(data.reply.userId, 'reply'); + publishMainStream(data.reply.userId, 'reply', noteObj); + } } } @@ -459,6 +466,11 @@ async function insertNote(user: { id: User['id']; host: User['host']; }, data: O replyId: data.reply ? data.reply.id : null, renoteId: data.renote ? data.renote.id : null, channelId: data.channel ? data.channel.id : null, + threadId: data.reply + ? data.reply.threadId + ? data.reply.threadId + : data.reply.id + : null, name: data.name, text: data.text, hasPoll: data.poll != null, @@ -581,6 +593,15 @@ async function notifyToWatchersOfReplyee(reply: Note, user: { id: User['id']; }, async function createMentionedEvents(mentionedUsers: User[], note: Note, nm: NotificationManager) { for (const u of mentionedUsers.filter(u => Users.isLocalUser(u))) { + const threadMuted = await NoteThreadMutings.findOne({ + userId: u.id, + threadId: note.threadId || note.id, + }); + + if (threadMuted) { + continue; + } + const detailPackedNote = await Notes.pack(note, u, { detail: true }); diff --git a/src/services/note/unread.ts b/src/services/note/unread.ts index 4a9df6083..29d2b54af 100644 --- a/src/services/note/unread.ts +++ b/src/services/note/unread.ts @@ -1,10 +1,10 @@ import { Note } from '@/models/entities/note'; import { publishMainStream } from '@/services/stream'; import { User } from '@/models/entities/user'; -import { Mutings, NoteUnreads } from '@/models/index'; +import { Mutings, NoteThreadMutings, NoteUnreads } from '@/models/index'; import { genId } from '@/misc/gen-id'; -export default async function(userId: User['id'], note: Note, params: { +export async function insertNoteUnread(userId: User['id'], note: Note, params: { // NOTE: isSpecifiedがtrueならisMentionedは必ずfalse isSpecified: boolean; isMentioned: boolean; @@ -17,6 +17,13 @@ export default async function(userId: User['id'], note: Note, params: { if (mute.map(m => m.muteeId).includes(note.userId)) return; //#endregion + // スレッドミュート + const threadMute = await NoteThreadMutings.findOne({ + userId: userId, + threadId: note.threadId || note.id, + }); + if (threadMute) return; + const unread = { id: genId(), noteId: note.id, diff --git a/test/thread-mute.ts b/test/thread-mute.ts new file mode 100644 index 000000000..95601cd90 --- /dev/null +++ b/test/thread-mute.ts @@ -0,0 +1,103 @@ +process.env.NODE_ENV = 'test'; + +import * as assert from 'assert'; +import * as childProcess from 'child_process'; +import { async, signup, request, post, react, connectStream, startServer, shutdownServer } from './utils'; + +describe('Note thread mute', () => { + let p: childProcess.ChildProcess; + + let alice: any; + let bob: any; + let carol: any; + + before(async () => { + p = await startServer(); + alice = await signup({ username: 'alice' }); + bob = await signup({ username: 'bob' }); + carol = await signup({ username: 'carol' }); + }); + + after(async () => { + await shutdownServer(p); + }); + + it('notes/mentions にミュートしているスレッドの投稿が含まれない', async(async () => { + const bobNote = await post(bob, { text: '@alice @carol root note' }); + const aliceReply = await post(alice, { replyId: bobNote.id, text: '@bob @carol child note' }); + + await request('/notes/thread-muting/create', { noteId: bobNote.id }, alice); + + const carolReply = await post(carol, { replyId: bobNote.id, text: '@bob @alice child note' }); + const carolReplyWithoutMention = await post(carol, { replyId: aliceReply.id, text: 'child note' }); + + const res = await request('/notes/mentions', {}, alice); + + assert.strictEqual(res.status, 200); + assert.strictEqual(Array.isArray(res.body), true); + assert.strictEqual(res.body.some((note: any) => note.id === bobNote.id), false); + assert.strictEqual(res.body.some((note: any) => note.id === carolReply.id), false); + assert.strictEqual(res.body.some((note: any) => note.id === carolReplyWithoutMention.id), false); + })); + + it('ミュートしているスレッドからメンションされても、hasUnreadMentions が true にならない', async(async () => { + // 状態リセット + await request('/i/read-all-unread-notes', {}, alice); + + const bobNote = await post(bob, { text: '@alice @carol root note' }); + + await request('/notes/thread-muting/create', { noteId: bobNote.id }, alice); + + const carolReply = await post(carol, { replyId: bobNote.id, text: '@bob @alice child note' }); + + const res = await request('/i', {}, alice); + + assert.strictEqual(res.status, 200); + assert.strictEqual(res.body.hasUnreadMentions, false); + })); + + it('ミュートしているスレッドからメンションされても、ストリームに unreadMention イベントが流れてこない', () => new Promise(async done => { + // 状態リセット + await request('/i/read-all-unread-notes', {}, alice); + + const bobNote = await post(bob, { text: '@alice @carol root note' }); + + await request('/notes/thread-muting/create', { noteId: bobNote.id }, alice); + + let fired = false; + + const ws = await connectStream(alice, 'main', async ({ type, body }) => { + if (type === 'unreadMention') { + if (body === bobNote.id) return; + fired = true; + } + }); + + const carolReply = await post(carol, { replyId: bobNote.id, text: '@bob @alice child note' }); + + setTimeout(() => { + assert.strictEqual(fired, false); + ws.close(); + done(); + }, 5000); + })); + + it('i/notifications にミュートしているスレッドの通知が含まれない', async(async () => { + const bobNote = await post(bob, { text: '@alice @carol root note' }); + const aliceReply = await post(alice, { replyId: bobNote.id, text: '@bob @carol child note' }); + + await request('/notes/thread-muting/create', { noteId: bobNote.id }, alice); + + const carolReply = await post(carol, { replyId: bobNote.id, text: '@bob @alice child note' }); + const carolReplyWithoutMention = await post(carol, { replyId: aliceReply.id, text: 'child note' }); + + const res = await request('/i/notifications', {}, alice); + + assert.strictEqual(res.status, 200); + assert.strictEqual(Array.isArray(res.body), true); + assert.strictEqual(res.body.some((notification: any) => notification.note.id === carolReply.id), false); + assert.strictEqual(res.body.some((notification: any) => notification.note.id === carolReplyWithoutMention.id), false); + + // NOTE: bobの投稿はスレッドミュート前に行われたため通知に含まれていてもよい + })); +}); diff --git a/test/utils.ts b/test/utils.ts index 1a0c54463..54bcf65ab 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -1,5 +1,6 @@ import * as fs from 'fs'; import * as WebSocket from 'ws'; +import * as misskey from 'misskey-js'; import fetch from 'node-fetch'; const FormData = require('form-data'); import * as childProcess from 'child_process'; @@ -52,7 +53,7 @@ export const signup = async (params?: any): Promise => { return res.body; }; -export const post = async (user: any, params?: any): Promise => { +export const post = async (user: any, params?: misskey.Endpoints['notes/create']['req']): Promise => { const q = Object.assign({ text: 'test' }, params); From 303c5abfb42939011dd1bd48f5516a4ec2f2ac1d Mon Sep 17 00:00:00 2001 From: tamaina Date: Sun, 31 Oct 2021 16:01:50 +0900 Subject: [PATCH 13/24] =?UTF-8?q?feat:=20=E3=82=AF=E3=83=A9=E3=82=A4?= =?UTF-8?q?=E3=82=A2=E3=83=B3=E3=83=88=E3=81=A7=E3=83=AD=E3=82=B0=E3=82=A4?= =?UTF-8?q?=E3=83=B3=E3=81=99=E3=82=8B=E3=82=A2=E3=82=AB=E3=82=A6=E3=83=B3?= =?UTF-8?q?=E3=83=88id=E3=82=92=E6=8C=87=E5=AE=9A=E3=81=99=E3=82=8B?= =?UTF-8?q?=E3=82=AF=E3=82=A8=E3=83=AA(loginId=3D:userId)=20(#7929)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: ログインするアカウントのIDをクエリ文字列で指定する機能 * await? * rename --- src/client/init.ts | 21 +++++++++++++++++++++ src/client/scripts/login-id.ts | 11 +++++++++++ 2 files changed, 32 insertions(+) create mode 100644 src/client/scripts/login-id.ts diff --git a/src/client/init.ts b/src/client/init.ts index 123d4020e..654e17639 100644 --- a/src/client/init.ts +++ b/src/client/init.ts @@ -37,6 +37,8 @@ import { isMobile } from '@client/scripts/is-mobile'; import { initializeSw } from '@client/scripts/initialize-sw'; import { reloadChannel } from '@client/scripts/unison-reload'; import { reactionPicker } from '@client/scripts/reaction-picker'; +import { getUrlWithoutLoginId } from '@client/scripts/login-id'; +import { getAccountFromId } from '@client/scripts/get-account-from-id'; console.info(`Misskey v${version}`); @@ -116,6 +118,25 @@ const html = document.documentElement; html.setAttribute('lang', lang); //#endregion +//#region loginId +const params = new URLSearchParams(location.search); +const loginId = params.get('loginId'); + +if (loginId) { + const target = getUrlWithoutLoginId(location.href); + + if (!$i || $i.id !== loginId) { + const account = await getAccountFromId(loginId); + if (account) { + await login(account.token, target); + } + } + + history.replaceState({ misskey: 'loginId' }, '', target); +} + +//#endregion + //#region Fetch user if ($i && $i.token) { if (_DEV_) { diff --git a/src/client/scripts/login-id.ts b/src/client/scripts/login-id.ts new file mode 100644 index 000000000..0f9c6be4a --- /dev/null +++ b/src/client/scripts/login-id.ts @@ -0,0 +1,11 @@ +export function getUrlWithLoginId(url: string, loginId: string) { + const u = new URL(url, origin); + u.searchParams.append('loginId', loginId); + return u.toString(); +} + +export function getUrlWithoutLoginId(url: string) { + const u = new URL(url); + u.searchParams.delete('loginId'); + return u.toString(); +} From 9236a8fd6c32e6b33ad53f3d82c177fe5060d352 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E3=82=B7=E3=82=A2=E3=83=8E=E3=83=B3?= Date: Sun, 31 Oct 2021 16:55:25 +0900 Subject: [PATCH 14/24] =?UTF-8?q?=E3=83=A6=E3=83=BC=E3=82=B6=E3=83=BC?= =?UTF-8?q?=E6=83=85=E5=A0=B1=E3=81=AEhasUnreadChannel=E3=81=8C=E5=B8=B8?= =?UTF-8?q?=E3=81=ABfalse=E3=81=AB=E3=81=AA=E3=81=A3=E3=81=A6=E3=81=97?= =?UTF-8?q?=E3=81=BE=E3=81=A3=E3=81=A6=E3=81=84=E3=82=8B=E3=81=AE=E3=82=92?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20(#7938)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/models/repositories/user.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/repositories/user.ts b/src/models/repositories/user.ts index 72cefbaac..9598e8719 100644 --- a/src/models/repositories/user.ts +++ b/src/models/repositories/user.ts @@ -112,7 +112,7 @@ export class UserRepository extends Repository { const unread = channels.length > 0 ? await NoteUnreads.findOne({ userId: userId, - noteChannelId: In(channels.map(x => x.id)), + noteChannelId: In(channels.map(x => x.followeeId)), }) : null; return unread != null; From e2556189decf5d6e6c9923a0c81c6214749b40b6 Mon Sep 17 00:00:00 2001 From: MeiMei <30769358+mei23@users.noreply.github.com> Date: Sun, 31 Oct 2021 18:01:16 +0900 Subject: [PATCH 15/24] =?UTF-8?q?fix:=20=E5=89=8A=E9=99=A4=E3=81=97?= =?UTF-8?q?=E3=81=9F=E3=83=8E=E3=83=BC=E3=83=88=E3=82=84=E3=83=A6=E3=83=BC?= =?UTF-8?q?=E3=82=B6=E3=83=BC=E3=81=8C=E3=83=AA=E3=83=A2=E3=83=BC=E3=83=88?= =?UTF-8?q?=E3=81=8B=E3=82=89=E5=8F=82=E7=85=A7=E3=81=95=E3=82=8C=E3=82=8B?= =?UTF-8?q?=E3=81=A8=E5=BE=A9=E6=B4=BB=E3=81=99=E3=82=8B=E3=81=93=E3=81=A8?= =?UTF-8?q?=E3=81=8C=E3=81=82=E3=82=8B=E3=81=AE=E3=82=92=E4=BF=AE=E6=AD=A3?= =?UTF-8?q?=20(#7918)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix #7557 * CHANGELOG * Fix user * CHANGELOG * Tune CHANGELOG * Tune CHANGELOG * resolver * Remove check * Remove import * CHANGELOG * Tune Co-authored-by: syuilo --- CHANGELOG.md | 1 + src/remote/activitypub/models/note.ts | 4 ++++ src/remote/activitypub/models/person.ts | 5 +++++ 3 files changed, 10 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae652c310..260d15af1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ ### Bugfixes - リレー向けのActivityが一部実装で除外されてしまうことがあるのを修正 +- 削除したノートやユーザーがリモートから参照されると復活することがあるのを修正 ## 12.94.1 (2021/10/25) diff --git a/src/remote/activitypub/models/note.ts b/src/remote/activitypub/models/note.ts index cf68f3005..492dc0524 100644 --- a/src/remote/activitypub/models/note.ts +++ b/src/remote/activitypub/models/note.ts @@ -288,6 +288,10 @@ export async function resolveNote(value: string | IObject, resolver?: Resolver): } //#endregion + if (uri.startsWith(config.url)) { + throw new StatusError('cannot resolve local note', 400, 'cannot resolve local note'); + } + // リモートサーバーからフェッチしてきて登録 // ここでuriの代わりに添付されてきたNote Objectが指定されていると、サーバーフェッチを経ずにノートが生成されるが // 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。 diff --git a/src/remote/activitypub/models/person.ts b/src/remote/activitypub/models/person.ts index 84b2f0c51..eb8c00a10 100644 --- a/src/remote/activitypub/models/person.ts +++ b/src/remote/activitypub/models/person.ts @@ -29,6 +29,7 @@ import { toArray } from '@/prelude/array'; import { fetchInstanceMetadata } from '@/services/fetch-instance-metadata'; import { normalizeForSearch } from '@/misc/normalize-for-search'; import { truncate } from '@/misc/truncate'; +import { StatusError } from '@/misc/fetch'; const logger = apLogger; @@ -116,6 +117,10 @@ export async function fetchPerson(uri: string, resolver?: Resolver): Promise { if (typeof uri !== 'string') throw new Error('uri is not string'); + if (uri.startsWith(config.url)) { + throw new StatusError('cannot resolve local user', 400, 'cannot resolve local user'); + } + if (resolver == null) resolver = new Resolver(); const object = await resolver.resolve(uri) as any; From 15cd56361289590e09f68e8161fdb2dce8e6b9e5 Mon Sep 17 00:00:00 2001 From: Johann150 Date: Sun, 31 Oct 2021 11:12:19 +0100 Subject: [PATCH 16/24] stop context menu handling for videos (#7927) --- src/client/components/media-video.vue | 1 + 1 file changed, 1 insertion(+) diff --git a/src/client/components/media-video.vue b/src/client/components/media-video.vue index 44367ee99..4d4a55165 100644 --- a/src/client/components/media-video.vue +++ b/src/client/components/media-video.vue @@ -11,6 +11,7 @@ :title="video.name" preload="none" controls + @contextmenu.stop > Date: Sun, 31 Oct 2021 19:19:28 +0900 Subject: [PATCH 17/24] chore(client): Fix #7923 Close #7924 --- src/client/components/forgot-password.vue | 25 +++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/src/client/components/forgot-password.vue b/src/client/components/forgot-password.vue index cb2380f48..7fcf9aa72 100644 --- a/src/client/components/forgot-password.vue +++ b/src/client/components/forgot-password.vue @@ -7,21 +7,21 @@ > -
-
- + +
+ - + - {{ $ts.send }} + {{ $ts.send }}
-
+
{{ $ts._forgotPassword.ifNoEmail }}
@@ -69,3 +69,16 @@ export default defineComponent({ } }); + + From baf3d8f3eef57f4032b2a047349d180353263d28 Mon Sep 17 00:00:00 2001 From: okpierre <1679025+okpierre@users.noreply.github.com> Date: Sun, 31 Oct 2021 06:22:19 -0400 Subject: [PATCH 18/24] Update emojis.vue (#7915) --- src/client/pages/admin/emojis.vue | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/client/pages/admin/emojis.vue b/src/client/pages/admin/emojis.vue index 80e0e00ba..b26553080 100644 --- a/src/client/pages/admin/emojis.vue +++ b/src/client/pages/admin/emojis.vue @@ -168,6 +168,10 @@ export default defineComponent({