diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index aaa732e52c..0183d182d5 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -8,7 +8,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - node-version: [14.x, 16.x, 17.x, 18.x] + node-version: [14.x, 16.x, 18.x] python-version: [3.9] services: @@ -59,6 +59,7 @@ jobs: working-directory: ./e2e - name: Upload E2E ScreenShots + if: always() uses: actions/upload-artifact@v2 with: name: ScreenShots-${{ matrix.node-version }} diff --git a/.github/workflows/hub-docker-push.yml b/.github/workflows/hub-docker-push.yml new file mode 100644 index 0000000000..0a4dc4bce6 --- /dev/null +++ b/.github/workflows/hub-docker-push.yml @@ -0,0 +1,52 @@ +name: Publish DockerHub image + +on: +# release: +# types: [published] + workflow_dispatch: + inputs: + buildVersion: + description: 'input build version. ex: 11.37.1-rei0784-5.99.0' + required: true + default: '' + buildId: + description: 'input build id. ex: 1' + required: true + default: '' + confirm: + description: 'If run flow, input "run".' + required: true + default: '' + +jobs: + push_to_registry: + if: github.event.inputs.confirm == 'run' + name: Push Docker image to Docker Hub + runs-on: ubuntu-latest + + steps: + - name: Check out the repo + uses: actions/checkout@v2 + - name: Setup QEMU + uses: docker/setup-qemu-action@v1 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v1 + - name: Docker meta + id: meta + uses: docker/metadata-action@v3 + with: + images: sousuke0422/misskey + - name: Log in to Docker Hub + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Build and Push to Docker Hub + uses: docker/build-push-action@v2 + with: + context: . + platforms: linux/amd64,linux/arm64 + push: true +# tags: ${{ steps.meta.outputs.tags }} + tags: sousuke0422/misskey:latest,sousuke0422/misskey:11,sousuke0422/misskey:${{ github.event.inputs.buildVersion }}-${{ github.event.inputs.buildId }} + labels: ${{ steps.meta.outputs.labels }} diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml index 2f5fa85555..8bf0ff8ff2 100644 --- a/.github/workflows/nodejs.yml +++ b/.github/workflows/nodejs.yml @@ -9,20 +9,20 @@ jobs: strategy: matrix: - node-version: [14.x, 16.x, 17.x, 18.x] + node-version: [14.x, 16.x, 18.x] services: postgres: - image: postgres:10-alpine + image: postgres:11.2-alpine ports: - 54311:5432 env: POSTGRES_DB: test-misskey POSTGRES_HOST_AUTH_METHOD: trust redis: - image: redis:alpine + image: redis:4.0-alpine ports: - - 6379:6379 + - 56311:6379 steps: - uses: actions/checkout@v2 @@ -34,6 +34,7 @@ jobs: node-version: ${{ matrix.node-version }} cache: 'yarn' - run: node devtools/diag-environment.js + - run: sudo apt-get install -y ffmpeg - run: yarn install - run: yarn build - run: cp test/test.yml .config diff --git a/CHANGELOG.md b/CHANGELOG.md index 32eec285f6..5be17d3d6c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,25 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- 依存関係が更新されました。脆弱性が修正されている場合があります。 + - CVE-2022-29256 (sharp) + - CVE-2022-33987 (got) + +### Added + +- ~~Twemojiをインスタンスで配信~~ + - オプション化し、使用するcdnを変更 +- 引越し先をユーザーページに表示するように +- 投稿ページのURLで埋め込みプレイヤーを提供するように + +## Changed + +- emojilistを更新 +- クエリの最適化 +- dockerでnode@18を使用します + ## [11.37.1-rei0784-5.21.1] 2022-07-28 ### Fixed @@ -26,6 +45,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - 依存関係が更新されました。脆弱性が修正されている場合があります。 +- antenna, clip, listのパフォーマンス悪いのを修正 + - *ayuskeyではantenna, clipのapiのみを提供しています。* ## Changed diff --git a/Dockerfile b/Dockerfile index d234f6e068..0531d66a32 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,7 +1,5 @@ FROM node:18.6.0-alpine3.15 AS base -ENV NODE_ENV=production - WORKDIR /misskey FROM base AS builder @@ -21,9 +19,12 @@ RUN apk add --no-cache \ zlib-dev \ git -COPY package.json yarn.lock ./ -RUN yarn install COPY . ./ + +RUN yarn install + +ENV NODE_ENV=production + RUN yarn build FROM base AS runner diff --git a/README.md b/README.md index 8d94aec8cc..39f2fdcc3e 100644 --- a/README.md +++ b/README.md @@ -34,12 +34,24 @@ Fediverseの世界に漂うため、Misskeyをはじめとした他のソーシ * [TenCha](https://github.com/coke12103/TenCha) * isLadyをサポート -## ライブラリ +## ライブラリ(Ayuskey動作保証) +* [MiPA(Mi.py互換)](https://github.com/yupix/mipa) + * bot向けフレームワーク + * misskey(v12)対応 +* [MiPAC](https://github.com/yupix/mipac) + * [MiPA](https://github.com/yupix/mipa)の内部APIです。 + * MisskeyのAPI Wrapperであり、オブジェクト的にAPIを操作することができます + * misskey(v12)対応 * ~~[Mi.py](https://github.com/yupix/Mi.py)~~ - * **開発終了** + * **開発終了の為非推奨** * bot向けフレームワーク - * ayuskey動作保証、misskey(v12)対応 + * misskey(v12)対応 + * 今後新たにBOTを作成する場合は [MiPA](https://github.com/yupix/mipa) をご利用ください + +## ライブラリ(Ayuskey動作未保障) + +* [Misskey.py](https://github.com/YuzuRyo61/Misskey.py)
内部 diff --git a/chart/Chart.yaml b/chart/Chart.yaml new file mode 100644 index 0000000000..b37bd1fef4 --- /dev/null +++ b/chart/Chart.yaml @@ -0,0 +1,3 @@ +apiVersion: v2 +name: ayuskey +version: 0.0.0 diff --git a/chart/files/default.yml b/chart/files/default.yml new file mode 100644 index 0000000000..cd0b87b6f1 --- /dev/null +++ b/chart/files/default.yml @@ -0,0 +1,173 @@ +#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ +# Misskey configuration +#━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ + +# ┌─────┐ +#───┘ URL └───────────────────────────────────────────────────── + +# Final accessible URL seen by a user. +url: https://example.tld/ + +# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE +# URL SETTINGS AFTER THAT! + +# ┌───────────────┐ +#───┘ Port settings └─────────────────────────────────────────── + +# +----- https://example.tld/ ------------+ +# +------+ |+-------------+ +----------------+| +# | User | ---> || Proxy (443) | ---> | Misskey (3000) || +# +------+ |+-------------+ +----------------+| +# +---------------------------------------+ +# +# You need to setup reverse proxy. (eg. nginx) +# You do not define 'https' section. + +# Listen port +port: 3000 + +# ┌──────────────────────────┐ +#───┘ PostgreSQL configuration └──────────────────────────────── + +db: + #host: /var/run/postgresql #unixsocket + host: localhost + port: 5432 + + # Database name + db: misskey + + # Auth + user: example-misskey-user + pass: example-misskey-pass + + # Whether disable Caching queries + #disableCache: true + + # Extra Connection options + #extra: + # ssl: true + + # Use PGroonga + #pgroonga: false + +# ┌─────────────────────┐ +#───┘ Redis configuration └───────────────────────────────────── + +redis: + #path: /var/run/redis/redis-server.sock #unixsocket + host: localhost + port: 6379 + #pass: example-pass + #prefix: example-prefix + #db: 1 + +# ┌─────────────────────────────┐ +#───┘ Elasticsearch configuration └───────────────────────────── + +#elasticsearch: +# host: localhost +# port: 9200 +# ssl: false +# user: +# pass: + +# ┌─────────────────────┐ +#───┘ Sonic configuration └───────────────────────────────────── + +#sonic: +# host: localhost +# port: 1491 +# pass: example-pass + + +# ┌───────────────┐ +#───┘ ID generation └─────────────────────────────────────────── + +# You can select the ID generation method. +# You don't usually need to change this setting, but you can +# change it according to your preferences. + +# Available methods: +# aid ... Short, Millisecond accuracy. Not recommended. +# meid ... Similar to ObjectID, Millisecond accuracy +# ulid ... Millisecond accuracy +# objectid ... This is left for backward compatibility + +# Use meid or ulid + +# ONCE YOU HAVE STARTED THE INSTANCE, DO NOT CHANGE THE +# ID SETTINGS AFTER THAT! + +id: 'meid' + +# ┌─────────────────────┐ +#───┘ Instance configuration └───────────────────────────────────── + +# Disable Federation: (default: false) +#disableFederation: true + +# Disable URL Preview (default: false) +# disableUrlPreview: true + +# If enabled: +# The first account created is automatically marked as Admin. +autoAdmin: true + +# Number of worker processes +#clusterLimit: 1 + +# Job concurrency per worker +# deliverJobConcurrency: 128 +# inboxJobConcurrency: 16 + +# Job rate limiter +# deliverJobPerSec: 128 +# inboxJobPerSec: 16 + +# Job attempts +# deliverJobMaxAttempts: 12 +# inboxJobMaxAttempts: 8 + +# Syslog option +#syslog: +# host: localhost +# port: 514 + +# Media Proxy +#mediaProxy: https://example.com/proxy + +# Sign to ActivityPub GET request (default: true) +#signToActivityPubGet: true + +# Upload or download file size limits (bytes) +#maxFileSize: 262144000 + +# ┌─────────────────────┐ +#───┘ Network configuration └───────────────────────────────────── + +# IP address family used for outgoing request (ipv4, ipv6 or dual) +#outgoingAddressFamily: ipv4 + +# Proxy for HTTP/HTTPS +#proxy: http://127.0.0.1:3128 + +#proxyBypassHosts: [ +# 'example.com', +# '192.0.2.8' +#] + +#allowedPrivateNetworks: [ +# '127.0.0.1/32' +#] + +# Proxy for SMTP/SMTPS +#proxySmtp: http://127.0.0.1:3128 # use HTTP/1.1 CONNECT +#proxySmtp: socks4://127.0.0.1:1080 # use SOCKS4 +#proxySmtp: socks5://127.0.0.1:1080 # use SOCKS5 + +# ┌─────────────────────┐ +#───┘ Other configuration └───────────────────────────────────── + +# url-preview "Access-Control-Allow-Origin: *" (default: false) (for External FE) +#urlPreviewCors: true diff --git a/chart/templates/ConfigMap.yml b/chart/templates/ConfigMap.yml new file mode 100644 index 0000000000..da8dd188f8 --- /dev/null +++ b/chart/templates/ConfigMap.yml @@ -0,0 +1,7 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: {{ include "ayuskey.fullname" . }}-config-file +data: + default.yml: |- +{{ .Files.Get "files/default.yml"|indent 4 }} diff --git a/chart/templates/Deployment.yml b/chart/templates/Deployment.yml new file mode 100644 index 0000000000..83b17dab6c --- /dev/null +++ b/chart/templates/Deployment.yml @@ -0,0 +1,44 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "ayuskey.fullname" . }} + labels: + {{- include "ayuskey.labels" . | nindent 4 }} +spec: + selector: + matchLabels: + {{- include "ayuskey.selectorLabels" . | nindent 6 }} + replicas: 1 + template: + metadata: + labels: + {{- include "ayuskey.selectorLabels" . | nindent 8 }} + spec: + containers: + - name: ayuskey + image: okteto.dev/ayuskey:latest + volumeMounts: + - name: config-file + mountPath: /misskey/.config + readOnly: true + ports: + - containerPort: 3000 + - name: postgres + image: postgres:14-alpine + env: + - name: POSTGRES_USER + value: "example-misskey-user" + - name: POSTGRES_PASSWORD + value: "example-misskey-pass" + - name: POSTGRES_DB + value: "misskey" + ports: + - containerPort: 5432 + - name: redis + image: redis:alpine + ports: + - containerPort: 6379 + volumes: + - name: config-file + configMap: + name: {{ include "ayuskey.fullname" . }}-config-file diff --git a/chart/templates/Service.yml b/chart/templates/Service.yml new file mode 100644 index 0000000000..0271a6a463 --- /dev/null +++ b/chart/templates/Service.yml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "ayuskey.fullname" . }} + annotations: + dev.okteto.com/auto-ingress: "true" +spec: + type: ClusterIP + ports: + - port: 3000 + protocol: TCP + name: http + selector: + {{- include "ayuskey.selectorLabels" . | nindent 4 }} diff --git a/chart/templates/_helpers.tpl b/chart/templates/_helpers.tpl new file mode 100644 index 0000000000..c30a7b84cf --- /dev/null +++ b/chart/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "ayuskey.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "ayuskey.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "ayuskey.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "ayuskey.labels" -}} +helm.sh/chart: {{ include "ayuskey.chart" . }} +{{ include "ayuskey.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "ayuskey.selectorLabels" -}} +app.kubernetes.io/name: {{ include "ayuskey.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "ayuskey.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "ayuskey.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/e2e b/e2e index 222a824636..54fe5b54e2 160000 --- a/e2e +++ b/e2e @@ -1 +1 @@ -Subproject commit 222a8246361b6369bfbfd78e8da8f7f080c9b6eb +Subproject commit 54fe5b54e27bef2eb64d2d67bed571aec9e7a654 diff --git a/migration/1651405265025-join-avatar-banner.ts b/migration/1651405265025-join-avatar-banner.ts new file mode 100644 index 0000000000..095ae4e2a3 --- /dev/null +++ b/migration/1651405265025-join-avatar-banner.ts @@ -0,0 +1,20 @@ +import {MigrationInterface, QueryRunner} from "typeorm"; + +export class joinAvatarBanner1651405265025 implements MigrationInterface { + name = 'joinAvatarBanner1651405265025' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "avatarUrl"`); + await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "bannerUrl"`); + await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "avatarBlurhash"`); + await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "bannerBlurhash"`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "user" ADD "bannerBlurhash" character varying(128)`); + await queryRunner.query(`ALTER TABLE "user" ADD "avatarBlurhash" character varying(128)`); + await queryRunner.query(`ALTER TABLE "user" ADD "bannerUrl" character varying(512)`); + await queryRunner.query(`ALTER TABLE "user" ADD "avatarUrl" character varying(512)`); + } + +} diff --git a/migration/1652878170000-movedTo.ts b/migration/1652878170000-movedTo.ts new file mode 100644 index 0000000000..aab9571298 --- /dev/null +++ b/migration/1652878170000-movedTo.ts @@ -0,0 +1,15 @@ +import {MigrationInterface, QueryRunner} from "typeorm"; + +export class movedTo1652878170000 implements MigrationInterface { + name = 'movedTo1652878170000' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "user" ADD "movedToUserId" character varying(32)`); + await queryRunner.query(`ALTER TABLE "user" ADD CONSTRAINT "FK_81126443ddd4dc1291f2e764da7" FOREIGN KEY ("movedToUserId") REFERENCES "user"("id") ON DELETE SET NULL ON UPDATE NO ACTION`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "user" DROP CONSTRAINT "FK_81126443ddd4dc1291f2e764da7"`); + await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "movedToUserId"`); + } +} diff --git a/okteto-pipeline.yml b/okteto-pipeline.yml new file mode 100644 index 0000000000..0304ee8e10 --- /dev/null +++ b/okteto-pipeline.yml @@ -0,0 +1,3 @@ +deploy: + - okteto build -t okteto.dev/ayuskey:latest + - helm upgrade --install ayuskey chart diff --git a/package.json b/package.json index ea66393ee2..b7814414a0 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "yupix ", "syuilo " ], - "version": "11.37.1-rei0784-5.21.1", + "version": "11.37.1-rei0784-5.99.0", "codename": "malachite", "repository": { "type": "git", @@ -43,77 +43,16 @@ "dependencies": { "@ayuskey/summaly": "2.5.0-rei0784.3", "@ayuskey/xev": "2.0.1-rei0784.2", + "@discordapp/twemoji": "14.0.2", "@elastic/elasticsearch": "7.17.0", - "@fortawesome/fontawesome-svg-core": "6.1.1", + "@fortawesome/fontawesome-svg-core": "6.1.2", "@fortawesome/free-brands-svg-icons": "6.1.2", - "@fortawesome/free-regular-svg-icons": "6.1.1", + "@fortawesome/free-regular-svg-icons": "6.1.2", "@fortawesome/free-solid-svg-icons": "6.1.2", "@fortawesome/vue-fontawesome": "2.0.6", "@koa/cors": "3.3.0", "@koa/multer": "3.0.0", "@koa/router": "8.0.8", - "@types/animejs": "3.1.5", - "@types/bcryptjs": "2.4.2", - "@types/bull": "3.15.8", - "@types/cbor": "6.0.0", - "@types/dateformat": "3.0.1", - "@types/double-ended-queue": "2.1.1", - "@types/escape-regexp": "0.0.1", - "@types/fluent-ffmpeg": "2.1.20", - "@types/gulp": "4.0.9", - "@types/gulp-mocha": "0.0.33", - "@types/gulp-rename": "2.0.1", - "@types/gulp-replace": "1.1.0", - "@types/ioredis": "4.28.8", - "@types/is-url": "1.2.30", - "@types/js-yaml": "4.0.5", - "@types/jsdom": "16.2.14", - "@types/jsonld": "1.5.6", - "@types/katex": "0.14.0", - "@types/koa": "2.13.5", - "@types/koa-bodyparser": "4.3.7", - "@types/koa-cors": "0.0.2", - "@types/koa-favicon": "2.0.21", - "@types/koa-logger": "3.1.2", - "@types/koa-mount": "4.0.1", - "@types/koa-send": "4.1.3", - "@types/koa-views": "2.0.4", - "@types/koa__cors": "3.3.0", - "@types/koa__multer": "2.0.4", - "@types/koa__router": "8.0.11", - "@types/lolex": "5.1.2", - "@types/mocha": "9.1.1", - "@types/node": "16.11.29", - "@types/node-fetch": "2.6.1", - "@types/nodemailer": "6.4.4", - "@types/nprogress": "0.2.0", - "@types/oauth": "0.9.1", - "@types/parse5": "6.0.3", - "@types/parsimmon": "1.10.6", - "@types/portscanner": "2.1.1", - "@types/pug": "2.0.6", - "@types/qrcode": "1.4.2", - "@types/random-seed": "0.3.3", - "@types/ratelimiter": "3.4.3", - "@types/redis": "2.8.32", - "@types/rename": "1.0.4", - "@types/request-stats": "3.0.0", - "@types/rimraf": "3.0.2", - "@types/seedrandom": "3.0.2", - "@types/sharp": "0.29.5", - "@types/showdown": "2.0.0", - "@types/speakeasy": "2.0.7", - "@types/three": "0.134.0", - "@types/throttle-debounce": "4.0.0", - "@types/tinycolor2": "1.4.3", - "@types/tmp": "0.2.3", - "@types/uuid": "8.3.4", - "@types/web-push": "3.3.2", - "@types/webpack": "5.28.0", - "@types/webpack-stream": "3.2.12", - "@types/websocket": "1.0.5", - "@types/ws": "8.5.3", - "@typescript-eslint/parser": "5.31.0", "@vue/composition-api": "1.5.0", "abort-controller": "3.0.0", "animejs": "3.2.1", @@ -142,8 +81,6 @@ "dateformat": "4.6.3", "diskusage": "1.1.3", "double-ended-queue": "2.1.0-0", - "eslint": "8.14.0", - "eslint-plugin-vue": "9.3.0", "eventemitter3": "4.0.7", "feed": "4.2.2", "file-type": "16.5.3", @@ -226,7 +163,7 @@ "sass": "1.54.0", "sass-loader": "12.6.0", "seedrandom": "3.0.5", - "sharp": "0.29.3", + "sharp": "0.30.7", "showdown": "2.1.0", "showdown-highlightjs-extension": "0.1.2", "sonic-channel": "1.2.7", @@ -247,8 +184,6 @@ "ts-node": "10.7.0", "tsc-alias": "1.7.0", "tsconfig-paths": "3.14.1", - "tslint": "5.20.1", - "tslint-sonarts": "1.9.0", "typeorm": "0.2.44", "typescript": "4.6.3", "ulid": "2.3.0", @@ -281,7 +216,74 @@ }, "devDependencies": { "@redocly/openapi-core": "1.0.0-beta.105", + "@types/animejs": "3.1.5", + "@types/bcryptjs": "2.4.2", + "@types/bull": "3.15.8", + "@types/cbor": "6.0.0", + "@types/dateformat": "3.0.1", + "@types/double-ended-queue": "2.1.1", + "@types/escape-regexp": "0.0.1", + "@types/fluent-ffmpeg": "2.1.20", + "@types/gulp": "4.0.9", + "@types/gulp-mocha": "0.0.33", + "@types/gulp-rename": "2.0.1", + "@types/gulp-replace": "1.1.0", + "@types/ioredis": "4.28.8", + "@types/is-url": "1.2.30", + "@types/js-yaml": "4.0.5", + "@types/jsdom": "16.2.14", + "@types/jsonld": "1.5.6", + "@types/jsrsasign": "10.5.2", + "@types/katex": "0.14.0", + "@types/koa": "2.13.5", + "@types/koa-bodyparser": "4.3.7", + "@types/koa-cors": "0.0.2", + "@types/koa-favicon": "2.0.21", + "@types/koa-logger": "3.1.2", + "@types/koa-mount": "4.0.1", + "@types/koa-send": "4.1.3", + "@types/koa-views": "2.0.4", + "@types/koa__cors": "3.3.0", + "@types/koa__multer": "2.0.4", + "@types/koa__router": "8.0.11", + "@types/lolex": "5.1.2", + "@types/mocha": "9.1.1", + "@types/node": "16.11.29", + "@types/node-fetch": "2.6.1", + "@types/nodemailer": "6.4.4", + "@types/nprogress": "0.2.0", + "@types/oauth": "0.9.1", + "@types/parse5": "6.0.3", + "@types/parsimmon": "1.10.6", + "@types/portscanner": "2.1.1", + "@types/pug": "2.0.6", + "@types/qrcode": "1.4.2", + "@types/random-seed": "0.3.3", + "@types/ratelimiter": "3.4.3", + "@types/redis": "2.8.32", + "@types/rename": "1.0.4", + "@types/request-stats": "3.0.0", + "@types/rimraf": "3.0.2", + "@types/seedrandom": "3.0.2", + "@types/sharp": "0.29.5", + "@types/showdown": "2.0.0", + "@types/speakeasy": "2.0.7", + "@types/three": "0.134.0", + "@types/throttle-debounce": "4.0.0", + "@types/tinycolor2": "1.4.3", + "@types/tmp": "0.2.3", + "@types/uuid": "8.3.4", + "@types/web-push": "3.3.2", + "@types/webpack": "5.28.0", + "@types/webpack-stream": "3.2.12", + "@types/websocket": "1.0.5", + "@types/ws": "8.5.3", + "@typescript-eslint/parser": "5.31.0", + "eslint": "8.14.0", + "eslint-plugin-vue": "9.3.0", "npm-run-all": "4.1.5", + "tslint": "5.20.1", + "tslint-sonarts": "1.9.0", "webpack-dev-server": "4.8.1" } } diff --git a/src/@types/jsrsasign.d.ts b/src/@types/jsrsasign.d.ts deleted file mode 100644 index 55bebd9bfb..0000000000 --- a/src/@types/jsrsasign.d.ts +++ /dev/null @@ -1,803 +0,0 @@ -// Attention: Partial Type Definition - -declare module 'jsrsasign' { - //// HELPER TYPES - - /** - * Attention: The value might be changed by the function. - */ - type Mutable = T; - - /** - * Deprecated: The function might be deleted in future release. - */ - type Deprecated = T; - - //// COMMON TYPES - - /** - * byte number - */ - type ByteNumber = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255; - - /** - * hexadecimal string /[0-9A-F]/ - */ - type HexString = string; - - /** - * binary string /[01]/ - */ - type BinString = string; - - /** - * base64 string /[A-Za-z0-9+/]=+/ - */ - type Base64String = string; - - /** - * base64 URL encoded string /[A-Za-z0-9_-]/ - */ - type Base64URLString = string; - - /** - * time value (ex. "151231235959Z") - */ - type TimeValue = string; - - /** - * OID string (ex. '1.2.3.4.567') - */ - type OID = string; - - /** - * OID name - */ - type OIDName = string; - - /** - * PEM formatted string - */ - type PEM = string; - - //// ASN1 TYPES - - class ASN1Object { - public isModified: boolean; - - public hTLV: ASN1TLV; - - public hT: ASN1T; - - public hL: ASN1L; - - public hV: ASN1V; - - public getLengthHexFromValue(): HexString; - - public getEncodedHex(): ASN1TLV; - - public getValueHex(): ASN1V; - - public getFreshValueHex(): ASN1V; - } - - class DERAbstractStructured extends ASN1Object { - constructor(params?: Partial>); - - public setByASN1ObjectArray(asn1ObjectArray: ASN1Object[]): void; - - public appendASN1Object(asn1Object: ASN1Object): void; - } - - class DERSequence extends DERAbstractStructured { - constructor(params?: Partial>); - - public getFreshValueHex(): ASN1V; - } - - //// ASN1HEX TYPES - - /** - * ASN.1 DER encoded data (hexadecimal string) - */ - type ASN1S = HexString; - - /** - * index of something - */ - type Idx = ASN1S extends { [idx: string]: unknown } ? string : ASN1S extends { [idx: number]: unknown } ? number : never; - - /** - * byte length of something - */ - type ByteLength = T['length']; - - /** - * ASN.1 L(length) (hexadecimal string) - */ - type ASN1L = HexString; - - /** - * ASN.1 T(tag) (hexadecimal string) - */ - type ASN1T = HexString; - - /** - * ASN.1 V(value) (hexadecimal string) - */ - type ASN1V = HexString; - - /** - * ASN.1 TLV (hexadecimal string) - */ - type ASN1TLV = HexString; - - /** - * ASN.1 object string - */ - type ASN1ObjectString = string; - - /** - * nth - */ - type Nth = number; - - /** - * ASN.1 DER encoded OID value (hexadecimal string) - */ - type ASN1OIDV = HexString; - - class ASN1HEX { - public static getLblen(s: ASN1S, idx: Idx): ByteLength; - - public static getL(s: ASN1S, idx: Idx): ASN1L; - - public static getVblen(s: ASN1S, idx: Idx): ByteLength; - - public static getVidx(s: ASN1S, idx: Idx): Idx; - - public static getV(s: ASN1S, idx: Idx): ASN1V; - - public static getTLV(s: ASN1S, idx: Idx): ASN1TLV; - - public static getNextSiblingIdx(s: ASN1S, idx: Idx): Idx; - - public static getChildIdx(h: ASN1S, pos: Idx): Idx[]; - - public static getNthChildIdx(h: ASN1S, idx: Idx, nth: Nth): Idx; - - public static getIdxbyList(h: ASN1S, currentIndex: Idx, nthList: Mutable, checkingTag?: string): Idx>; - - public static getTLVbyList(h: ASN1S, currentIndex: Idx, nthList: Mutable, checkingTag?: string): ASN1TLV; - - public static getVbyList(h: ASN1S, currentIndex: Idx, nthList: Mutable, checkingTag?: string, removeUnusedbits?: boolean): ASN1V; - - public static hextooidstr(hex: ASN1OIDV): OID; - - public static dump(hexOrObj: ASN1S | ASN1Object, flags?: Record, idx?: Idx, indent?: string): string; - - public static isASN1HEX(hex: string): hex is HexString; - - public static oidname(oidDotOrHex: OID | ASN1OIDV): OIDName; - } - - //// BIG INTEGER TYPES (PARTIAL) - - class BigInteger { - constructor(a: null); - - constructor(a: number, b: SecureRandom); - - constructor(a: number, b: number, c: SecureRandom); - - constructor(a: unknown); - - constructor(a: string, b: number); - - public am(i: number, x: number, w: number, j: number, c: number, n: number): number; - - public DB: number; - - public DM: number; - - public DV: number; - - public FV: number; - - public F1: number; - - public F2: number; - - protected copyTo(r: Mutable): void; - - protected fromInt(x: number): void; - - protected fromString(s: string, b: number): void; - - protected clamp(): void; - - public toString(b: number): string; - - public negate(): BigInteger; - - public abs(): BigInteger; - - public compareTo(a: BigInteger): number; - - public bitLength(): number; - - protected dlShiftTo(n: number, r: Mutable): void; - - protected drShiftTo(n: number, r: Mutable): void; - - protected lShiftTo(n: number, r: Mutable): void; - - protected rShiftTo(n: number, r: Mutable): void; - - protected subTo(a: BigInteger, r: Mutable): void; - - protected multiplyTo(a: BigInteger, r: Mutable): void; - - protected squareTo(r: Mutable): void; - - protected divRemTo(m: BigInteger, q: Mutable, r: Mutable): void; - - public mod(a: BigInteger): BigInteger; - - protected invDigit(): number; - - protected isEven(): boolean; - - protected exp(e: number, z: Classic | Montgomery): BigInteger; - - public modPowInt(e: number, m: BigInteger): BigInteger; - - public static ZERO: BigInteger; - - public static ONE: BigInteger; - } - - class Classic { - constructor(m: BigInteger); - - public convert(x: BigInteger): BigInteger; - - public revert(x: BigInteger): BigInteger; - - public reduce(x: Mutable): void; - - public mulTo(x: BigInteger, r: Mutable): void; - - public sqrTo(x: BigInteger, y: BigInteger, r: Mutable): void; - } - - class Montgomery { - constructor(m: BigInteger); - - public convert(x: BigInteger): BigInteger; - - public revert(x: BigInteger): BigInteger; - - public reduce(x: Mutable): void; - - public mulTo(x: BigInteger, r: Mutable): void; - - public sqrTo(x: BigInteger, y: BigInteger, r: Mutable): void; - } - - //// KEYUTIL TYPES - - type DecryptAES = (dataHex: HexString, keyHex: HexString, ivHex: HexString) => HexString; - - type Decrypt3DES = (dataHex: HexString, keyHex: HexString, ivHex: HexString) => HexString; - - type DecryptDES = (dataHex: HexString, keyHex: HexString, ivHex: HexString) => HexString; - - type EncryptAES = (dataHex: HexString, keyHex: HexString, ivHex: HexString) => HexString; - - type Encrypt3DES = (dataHex: HexString, keyHex: HexString, ivHex: HexString) => HexString; - - type EncryptDES = (dataHex: HexString, keyHex: HexString, ivHex: HexString) => HexString; - - type AlgList = { - 'AES-256-CBC': { 'proc': DecryptAES; 'eproc': EncryptAES; keylen: 32; ivlen: 16; }; - 'AES-192-CBC': { 'proc': DecryptAES; 'eproc': EncryptAES; keylen: 24; ivlen: 16; }; - 'AES-128-CBC': { 'proc': DecryptAES; 'eproc': EncryptAES; keylen: 16; ivlen: 16; }; - 'DES-EDE3-CBC': { 'proc': Decrypt3DES; 'eproc': Encrypt3DES; keylen: 24; ivlen: 8; }; - 'DES-CBC': { 'proc': DecryptDES; 'eproc': EncryptDES; keylen: 8; ivlen: 8; }; - }; - - type AlgName = keyof AlgList; - - type PEMHeadAlgName = 'RSA' | 'EC' | 'DSA'; - - type GetKeyRSAParam = RSAKey | { - n: BigInteger; - e: number; - } | Record<'n' | 'e', HexString> | Record<'n' | 'e', HexString> & Record<'d' | 'p' | 'q' | 'dp' | 'dq' | 'co', HexString | null> | { - n: BigInteger; - e: number; - d: BigInteger; - } | { - kty: 'RSA'; - } & Record<'n' | 'e', Base64URLString> | { - kty: 'RSA'; - } & Record<'n' | 'e' | 'd' | 'p' | 'q' | 'dp' | 'dq' | 'qi', Base64URLString> | { - kty: 'RSA'; - } & Record<'n' | 'e' | 'd', Base64URLString>; - - type GetKeyECDSAParam = KJUR.crypto.ECDSA | { - curve: KJUR.crypto.CurveName; - xy: HexString; - } | { - curve: KJUR.crypto.CurveName; - d: HexString; - } | { - kty: 'EC'; - crv: KJUR.crypto.CurveName; - x: Base64URLString; - y: Base64URLString; - } | { - kty: 'EC'; - crv: KJUR.crypto.CurveName; - x: Base64URLString; - y: Base64URLString; - d: Base64URLString; - }; - - type GetKeyDSAParam = KJUR.crypto.DSA | Record<'p' | 'q' | 'g', BigInteger> & Record<'y', BigInteger | null> | Record<'p' | 'q' | 'g' | 'x', BigInteger> & Record<'y', BigInteger | null>; - - type GetKeyParam = GetKeyRSAParam | GetKeyECDSAParam | GetKeyDSAParam | string; - - class KEYUTIL { - public version: '1.0.0'; - - public parsePKCS5PEM(sPKCS5PEM: PEM): Partial> & (Record<'cipher' | 'ivsalt', string> | Record<'cipher' | 'ivsalt', undefined>); - - public getKeyAndUnusedIvByPasscodeAndIvsalt(algName: AlgName, passcode: string, ivsaltHex: HexString): Record<'keyhex' | 'ivhex', HexString>; - - public decryptKeyB64(privateKeyB64: Base64String, sharedKeyAlgName: AlgName, sharedKeyHex: HexString, ivsaltHex: HexString): Base64String; - - public getDecryptedKeyHex(sEncryptedPEM: PEM, passcode: string): HexString; - - public getEncryptedPKCS5PEMFromPrvKeyHex(pemHeadAlg: PEMHeadAlgName, hPrvKey: string, passcode: string, sharedKeyAlgName?: AlgName | null, ivsaltHex?: HexString | null): PEM; - - public parseHexOfEncryptedPKCS8(sHEX: HexString): { - ciphertext: ASN1V; - encryptionSchemeAlg: 'TripleDES'; - encryptionSchemeIV: ASN1V; - pbkdf2Salt: ASN1V; - pbkdf2Iter: number; - }; - - public getPBKDF2KeyHexFromParam(info: ReturnType, passcode: string): HexString; - - private _getPlainPKCS8HexFromEncryptedPKCS8PEM(pkcs8PEM: PEM, passcode: string): HexString; - - public getKeyFromEncryptedPKCS8PEM(prvKeyHex: HexString): ReturnType; - - public parsePlainPrivatePKCS8Hex(pkcs8PrvHex: HexString): { - algparam: ASN1V | null; - algoid: ASN1V; - keyidx: Idx; - }; - - public getKeyFromPlainPrivatePKCS8PEM(prvKeyHex: HexString): ReturnType; - - public getKeyFromPlainPrivatePKCS8Hex(prvKeyHex: HexString): RSAKey | KJUR.crypto.DSA | KJUR.crypto.ECDSA; - - private _getKeyFromPublicPKCS8Hex(h: HexString): RSAKey | KJUR.crypto.DSA | KJUR.crypto.ECDSA; - - public parsePublicRawRSAKeyHex(pubRawRSAHex: HexString): Record<'n' | 'e', ASN1V>; - - public parsePublicPKCS8Hex(pkcs8PubHex: HexString): { - algparam: ASN1V | Record<'p' | 'q' | 'g', ASN1V> | null; - algoid: ASN1V; - key: ASN1V; - }; - - public static getKey(param: GetKeyRSAParam): RSAKey; - - public static getKey(param: GetKeyECDSAParam): KJUR.crypto.ECDSA; - - public static getKey(param: GetKeyDSAParam): KJUR.crypto.DSA; - - public static getKey(param: string, passcode?: string, hextype?: string): RSAKey | KJUR.crypto.ECDSA | KJUR.crypto.DSA; - - public static generateKeypair(alg: 'RSA', keylen: number): Record<'prvKeyObj' | 'pubKeyObj', RSAKey>; - - public static generateKeypair(alg: 'EC', curve: KJUR.crypto.CurveName): Record<'prvKeyObj' | 'pubKeyObj', KJUR.crypto.ECDSA>; - - public static getPEM(keyObjOrHex: RSAKey | KJUR.crypto.ECDSA | KJUR.crypto.DSA, formatType?: 'PKCS1PRV' | 'PKCS5PRV' | 'PKCS8PRV', passwd?: string, encAlg?: 'DES-CBC' | 'DES-EDE3-CBC' | 'AES-128-CBC' | 'AES-192-CBC' | 'AES-256-CBC', hexType?: string, ivsaltHex?: HexString): object; // To Do - - public static getKeyFromCSRPEM(csrPEM: PEM): RSAKey | KJUR.crypto.ECDSA | KJUR.crypto.DSA; - - public static getKeyFromCSRHex(csrHex: HexString): RSAKey | KJUR.crypto.ECDSA | KJUR.crypto.DSA; - - public static parseCSRHex(csrHex: HexString): Record<'p8pubkeyhex', ASN1TLV>; - - public static getJWKFromKey(keyObj: RSAKey): { - kty: 'RSA'; - } & Record<'n' | 'e' | 'd' | 'p' | 'q' | 'dp' | 'dq' | 'qi', Base64URLString> | { - kty: 'RSA'; - } & Record<'n' | 'e', Base64URLString>; - - public static getJWKFromKey(keyObj: KJUR.crypto.ECDSA): { - kty: 'EC'; - crv: KJUR.crypto.CurveName; - x: Base64URLString; - y: Base64URLString; - d: Base64URLString; - } | { - kty: 'EC'; - crv: KJUR.crypto.CurveName; - x: Base64URLString; - y: Base64URLString; - }; - } - - //// KJUR NAMESPACE (PARTIAL) - - namespace KJUR { - namespace crypto { - type CurveName = 'secp128r1' | 'secp160k1' | 'secp160r1' | 'secp192k1' | 'secp192r1' | 'secp224r1' | 'secp256k1' | 'secp256r1' | 'secp384r1' | 'secp521r1'; - - class DSA { - public p: BigInteger | null; - - public q: BigInteger | null; - - public g: BigInteger | null; - - public y: BigInteger | null; - - public x: BigInteger | null; - - public type: 'DSA'; - - public isPrivate: boolean; - - public isPublic: boolean; - - public setPrivate(p: BigInteger, q: BigInteger, g: BigInteger, y: BigInteger | null, x: BigInteger): void; - - public setPrivateHex(hP: HexString, hQ: HexString, hG: HexString, hY: HexString | null, hX: HexString): void; - - public setPublic(p: BigInteger, q: BigInteger, g: BigInteger, y: BigInteger): void; - - public setPublicHex(hP: HexString, hQ: HexString, hG: HexString, hY: HexString): void; - - public signWithMessageHash(sHashHex: HexString): HexString; - - public verifyWithMessageHash(sHashHex: HexString, hSigVal: HexString): boolean; - - public parseASN1Signature(hSigVal: HexString): [BigInteger, BigInteger]; - - public readPKCS5PrvKeyHex(h: HexString): void; - - public readPKCS8PrvKeyHex(h: HexString): void; - - public readPKCS8PubKeyHex(h: HexString): void; - - public readCertPubKeyHex(h: HexString, nthPKI: number): void; - } - - class ECDSA { - constructor(params?: { - curve?: CurveName; - prv?: HexString; - pub?: HexString; - }); - - public p: BigInteger | null; - - public q: BigInteger | null; - - public g: BigInteger | null; - - public y: BigInteger | null; - - public x: BigInteger | null; - - public type: 'EC'; - - public isPrivate: boolean; - - public isPublic: boolean; - - public getBigRandom(limit: BigInteger): BigInteger; - - public setNamedCurve(curveName: CurveName): void; - - public setPrivateKeyHex(prvKeyHex: HexString): void; - - public setPublicKeyHex(pubKeyHex: HexString): void; - - public getPublicKeyXYHex(): Record<'x' | 'y', HexString>; - - public getShortNISTPCurveName(): 'P-256' | 'P-384' | null; - - public generateKeyPairHex(): Record<'ecprvhex' | 'ecpubhex', HexString>; - - public signWithMessageHash(hashHex: HexString): HexString; - - public signHex(hashHex: HexString, privHex: HexString): HexString; - - public verifyWithMessageHash(sHashHex: HexString, hSigVal: HexString): boolean; - - public parseASN1Signature(hSigVal: HexString): [BigInteger, BigInteger]; - - public readPKCS5PrvKeyHex(h: HexString): void; - - public readPKCS8PrvKeyHex(h: HexString): void; - - public readPKCS8PubKeyHex(h: HexString): void; - - public readCertPubKeyHex(h: HexString, nthPKI: number): void; - - public static parseSigHex(sigHex: HexString): Record<'r' | 's', BigInteger>; - - public static parseSigHexInHexRS(sigHex: HexString): Record<'r' | 's', ASN1V>; - - public static asn1SigToConcatSig(asn1Sig: HexString): HexString; - - public static concatSigToASN1Sig(concatSig: HexString): ASN1TLV; - - public static hexRSSigToASN1Sig(hR: HexString, hS: HexString): ASN1TLV; - - public static biRSSigToASN1Sig(biR: BigInteger, biS: BigInteger): ASN1TLV; - - public static getName(s: CurveName | HexString): 'secp256r1' | 'secp256k1' | 'secp384r1' | null; - } - - class Signature { - constructor(params?: ({ - alg: string; - prov?: string; - } | {}) & ({ - psssaltlen: number; - } | {}) & ({ - prvkeypem: PEM; - prvkeypas?: never; - } | {})); - - private _setAlgNames(): void; - - private _zeroPaddingOfSignature(hex: HexString, bitLength: number): HexString; - - public setAlgAndProvider(alg: string, prov: string): void; - - public init(key: GetKeyParam, pass?: string): void; - - public updateString(str: string): void; - - public updateHex(hex: HexString): void; - - public sign(): HexString; - - public signString(str: string): HexString; - - public signHex(hex: HexString): HexString; - - public verify(hSigVal: string): boolean | 0; - } - } - } - - //// RSAKEY TYPES - - class RSAKey { - public n: BigInteger | null; - - public e: number; - - public d: BigInteger | null; - - public p: BigInteger | null; - - public q: BigInteger | null; - - public dmp1: BigInteger | null; - - public dmq1: BigInteger | null; - - public coeff: BigInteger | null; - - public type: 'RSA'; - - public isPrivate?: boolean; - - public isPublic?: boolean; - - //// RSA PUBLIC - - protected doPublic(x: BigInteger): BigInteger; - - public setPublic(N: BigInteger, E: number): void; - - public setPublic(N: HexString, E: HexString): void; - - public encrypt(text: string): HexString | null; - - public encryptOAEP(text: string, hash?: string, hashLen?: number): HexString | null; - - public encryptOAEP(text: string, hash?: (s: string) => string, hashLen?: number): HexString | null; - - //// RSA PRIVATE - - protected doPrivate(x: BigInteger): BigInteger; - - public setPrivate(N: BigInteger, E: number, D: BigInteger): void; - - public setPrivate(N: HexString, E: HexString, D: HexString): void; - - public setPrivateEx(N: HexString, E: HexString, D?: HexString | null, P?: HexString | null, Q?: HexString | null, DP?: HexString | null, DQ?: HexString | null, C?: HexString | null): void; - - public generate(B: number, E: HexString): void; - - public decrypt(ctext: HexString): string; - - public decryptOAEP(ctext: HexString, hash?: string, hashLen?: number): string | null; - - public encryptOAEP(ctext: HexString, hash?: (s: string) => string, hashLen?: number): string | null; - - //// RSA PEM - - public getPosArrayOfChildrenFromHex(hPrivateKey: PEM): Idx[]; - - public getHexValueArrayOfChildrenFromHex(hPrivateKey: PEM): Idx[]; - - public readPrivateKeyFromPEMString(keyPEM: PEM): void; - - public readPKCS5PrvKeyHex(h: HexString): void; - - public readPKCS8PrvKeyHex(h: HexString): void; - - public readPKCS5PubKeyHex(h: HexString): void; - - public readPKCS8PubKeyHex(h: HexString): void; - - public readCertPubKeyHex(h: HexString, nthPKI: Nth): void; - - //// RSA SIGN - - public sign(s: string, hashAlg: string): HexString; - - public signWithMessageHash(sHashHex: HexString, hashAlg: string): HexString; - - public signPSS(s: string, hashAlg: string, sLen: number): HexString; - - public signWithMessageHashPSS(hHash: HexString, hashAlg: string, sLen: number): HexString; - - public verify(sMsg: string, hSig: HexString): boolean | 0; - - public verifyWithMessageHash(sHashHex: HexString, hSig: HexString): boolean | 0; - - public verifyPSS(sMsg: string, hSig: HexString, hashAlg: string, sLen: number): boolean; - - public verifyWithMessageHashPSS(hHash: HexString, hSig: HexString, hashAlg: string, sLen: number): boolean; - - public static SALT_LEN_HLEN: -1; - - public static SALT_LEN_MAX: -2; - - public static SALT_LEN_RECOVER: -2; - } - - /// RNG TYPES - class SecureRandom { - public nextBytes(ba: Mutable): void; - } - - //// X509 TYPES - - type ExtInfo = { - critical: boolean; - oid: OID; - vidx: Idx; - }; - - type ExtAIAInfo = Record<'ocsp' | 'caissuer', string>; - - type ExtCertificatePolicy = { - id: OIDName; - } & Partial<{ - cps: string; - } | { - unotice: string; - }>; - - class X509 { - public hex: HexString | null; - - public version: number; - - public foffset: number; - - public aExtInfo: null; - - public getVersion(): number; - - public getSerialNumberHex(): ASN1V; - - public getSignatureAlgorithmField(): OIDName; - - public getIssuerHex(): ASN1TLV; - - public getIssuerString(): HexString; - - public getSubjectHex(): ASN1TLV; - - public getSubjectString(): HexString; - - public getNotBefore(): TimeValue; - - public getNotAfter(): TimeValue; - - public getPublicKeyHex(): ASN1TLV; - - public getPublicKeyIdx(): Idx>; - - public getPublicKeyContentIdx(): Idx>; - - public getPublicKey(): RSAKey | KJUR.crypto.ECDSA | KJUR.crypto.DSA; - - public getSignatureAlgorithmName(): OIDName; - - public getSignatureValueHex(): ASN1V; - - public verifySignature(pubKey: GetKeyParam): boolean | 0; - - public parseExt(): void; - - public getExtInfo(oidOrName: OID | string): ExtInfo | undefined; - - public getExtBasicConstraints(): ExtInfo | {} | { - cA: true; - pathLen?: number; - }; - - public getExtKeyUsageBin(): BinString; - - public getExtKeyUsageString(): string; - - public getExtSubjectKeyIdentifier(): ASN1V | undefined; - - public getExtAuthorityKeyIdentifier(): { - kid: ASN1V; - } | undefined; - - public getExtExtKeyUsageName(): OIDName[] | undefined; - - public getExtSubjectAltName(): Deprecated; - - public getExtSubjectAltName2(): ['MAIL' | 'DNS' | 'DN' | 'URI' | 'IP', string][] | undefined; - - public getExtCRLDistributionPointsURI(): string[] | undefined; - - public getExtAIAInfo(): ExtAIAInfo | undefined; - - public getExtCertificatePolicies(): ExtCertificatePolicy[] | undefined; - - public readCertPEM(sCertPEM: PEM): void; - - public readCertHex(sCertHex: HexString): void; - - public getInfo(): string; - - public static hex2dn(hex: HexString, idx?: Idx): string; - - public static hex2rdn(hex: HexString, idx?: Idx): string; - - public static hex2attrTypeValue(hex: HexString, idx?: Idx): string; - - public static getPublicKeyFromCertPEM(sCertPEM: PEM): RSAKey | KJUR.crypto.ECDSA | KJUR.crypto.DSA; - - public static getPublicKeyInfoPropOfCertPEM(sCertPEM: PEM): { - algparam: ASN1V | null; - leyhex: ASN1V; - algoid: ASN1V; - }; - } -} diff --git a/src/client/app/common/views/deck/deck.user-column.vue b/src/client/app/common/views/deck/deck.user-column.vue index bc8cbc3154..a8e81eba74 100644 --- a/src/client/app/common/views/deck/deck.user-column.vue +++ b/src/client/app/common/views/deck/deck.user-column.vue @@ -20,6 +20,7 @@ @{{ user | acct }} + Moved to {{ $t('follows-you') }} @@ -176,7 +177,7 @@ export default Vue.extend({ text-shadow 0 0 8px #000 color #fff - > .acct + > .acct, .moved display block font-size 14px opacity 0.7 diff --git a/src/client/app/desktop/views/home/user/user.header.vue b/src/client/app/desktop/views/home/user/user.header.vue index 7cef73f618..67b395e7f8 100644 --- a/src/client/app/desktop/views/home/user/user.header.vue +++ b/src/client/app/desktop/views/home/user/user.header.vue @@ -10,6 +10,7 @@
+ moved to
{{ $t('follows-you') }} diff --git a/src/client/app/mobile/views/pages/user/index.vue b/src/client/app/mobile/views/pages/user/index.vue index 43fb1e273a..81bb96168c 100644 --- a/src/client/app/mobile/views/pages/user/index.vue +++ b/src/client/app/mobile/views/pages/user/index.vue @@ -19,6 +19,7 @@

+ moved to {{ $t('follows-you') }}
@@ -229,13 +230,16 @@ export default Vue.extend({ font-size 20px color var(--mobileUserPageName) - > .username + > .username, .moved display inline-block line-height 20px font-size 16px font-weight bold color var(--mobileUserPageAcct) + > .moved + margin-left 8px + > .followed margin-left 8px padding 2px 4px diff --git a/src/client/app/sw/sw.js b/src/client/app/sw/sw.js index 97c7106368..21e15d6358 100644 --- a/src/client/app/sw/sw.js +++ b/src/client/app/sw/sw.js @@ -3,7 +3,7 @@ */ import composeNotification from './compose-notification'; -import { version } from '../config' +import { version } from '../config'; // インストールされたとき self.addEventListener('install', ev => { @@ -48,15 +48,15 @@ self.addEventListener('notificationclick', function(event) { // This looks to see if the current is already open and // focuses if it is - event.waitUntil(clients.matchAll({ - type: "window" + event.waitUntil(self.clients.matchAll({ + type: 'window' }).then(function(clientList) { for (var i = 0; i < clientList.length; i++) { var client = clientList[i]; if (client.url == '/' && 'focus' in client) return client.focus(); } - if (clients.openWindow) - return clients.openWindow('/'); + if (self.clients.openWindow) + return self.clients.openWindow('/'); })); }); diff --git a/src/emojilist.json b/src/emojilist.json index 30cf6dd735..e68518d132 100644 --- a/src/emojilist.json +++ b/src/emojilist.json @@ -93,6 +93,16 @@ { "category": "face", "char": "🥱", "name": "yawning", "keywords": ["face", "tired", "yawning"] }, { "category": "face", "char": "😴", "name": "sleeping", "keywords": ["face", "tired", "sleepy", "night", "zzz"] }, { "category": "face", "char": "💤", "name": "zzz", "keywords": ["sleepy", "tired", "dream"] }, + { "category": "face", "char": "\uD83D\uDE36\u200D\uD83C\uDF2B\uFE0F", "name": "face_in_clouds", "keywords": [] }, + { "category": "face", "char": "\uD83D\uDE2E\u200D\uD83D\uDCA8", "name": "face_exhaling", "keywords": [] }, + { "category": "face", "char": "\uD83D\uDE35\u200D\uD83D\uDCAB", "name": "face_with_spiral_eyes", "keywords": [] }, + { "category": "face", "char": "\uD83E\uDEE0", "name": "melting_face", "keywords": ["disappear", "dissolve", "liquid", "melt", "toketa"] }, + { "category": "face", "char": "\uD83E\uDEE2", "name": "face_with_open_eyes_and_hand_over_mouth", "keywords": ["amazement", "awe", "disbelief", "embarrass", "scared", "surprise", "ohoho"] }, + { "category": "face", "char": "\uD83E\uDEE3", "name": "face_with_peeking_eye", "keywords": ["captivated", "peep", "stare", "chunibyo"] }, + { "category": "face", "char": "\uD83E\uDEE1", "name": "saluting_face", "keywords": ["ok", "salute", "sunny", "troops", "yes", "raja"] }, + { "category": "face", "char": "\uD83E\uDEE5", "name": "dotted_line_face", "keywords": ["depressed", "disappear", "hide", "introvert", "invisible", "tensen"] }, + { "category": "face", "char": "\uD83E\uDEE4", "name": "face_with_diagonal_mouth", "keywords": ["disappointed", "meh", "skeptical", "unsure"] }, + { "category": "face", "char": "\uD83E\uDD79", "name": "face_holding_back_tears", "keywords": ["angry", "cry", "proud", "resist", "sad"] }, { "category": "face", "char": "💩", "name": "poop", "keywords": ["hankey", "shitface", "fail", "turd", "shit"] }, { "category": "face", "char": "😈", "name": "smiling_imp", "keywords": ["devil", "horns"] }, { "category": "face", "char": "👿", "name": "imp", "keywords": ["devil", "angry", "horns"] }, @@ -146,11 +156,19 @@ { "category": "people", "char": "🤞", "name": "crossed_fingers", "keywords": ["good", "lucky"] }, { "category": "people", "char": "🖖", "name": "vulcan_salute", "keywords": ["hand", "fingers", "spock", "star trek"] }, { "category": "people", "char": "✍", "name": "writing_hand", "keywords": ["lower_left_ballpoint_pen", "stationery", "write", "compose"] }, + { "category": "people", "char": "\uD83E\uDEF0", "name": "hand_with_index_finger_and_thumb_crossed", "keywords": [] }, + { "category": "people", "char": "\uD83E\uDEF1", "name": "rightwards_hand", "keywords": [] }, + { "category": "people", "char": "\uD83E\uDEF2", "name": "leftwards_hand", "keywords": [] }, + { "category": "people", "char": "\uD83E\uDEF3", "name": "palm_down_hand", "keywords": [] }, + { "category": "people", "char": "\uD83E\uDEF4", "name": "palm_up_hand", "keywords": [] }, + { "category": "people", "char": "\uD83E\uDEF5", "name": "index_pointing_at_the_viewer", "keywords": [] }, + { "category": "people", "char": "\uD83E\uDEF6", "name": "heart_hands", "keywords": ["moemoekyun"] }, { "category": "people", "char": "🤏", "name": "pinching_hand", "keywords": ["hand", "fingers"] }, { "category": "people", "char": "🤌", "name": "pinched_fingers", "keywords": ["hand", "fingers"] }, { "category": "people", "char": "🤳", "name": "selfie", "keywords": ["camera", "phone"] }, { "category": "people", "char": "💅", "name": "nail_care", "keywords": ["beauty", "manicure", "finger", "fashion", "nail"] }, { "category": "people", "char": "👄", "name": "lips", "keywords": ["mouth", "kiss"] }, + { "category": "people", "char": "\uD83E\uDEE6", "name": "biting_lip", "keywords": [] }, { "category": "people", "char": "🦷", "name": "tooth", "keywords": ["teeth", "dentist"] }, { "category": "people", "char": "👅", "name": "tongue", "keywords": ["mouth", "playful"] }, { "category": "people", "char": "👂", "name": "ear", "keywords": ["face", "hear", "sound", "listen"] }, @@ -272,7 +290,11 @@ { "category": "people", "char": "🧚‍♀️", "name": "woman_fairy", "keywords": ["woman", "female"] }, { "category": "people", "char": "🧚‍♂️", "name": "man_fairy", "keywords": ["man", "male"] }, { "category": "people", "char": "👼", "name": "angel", "keywords": ["heaven", "wings", "halo"] }, + { "category": "people", "char": "\uD83E\uDDCC", "name": "troll", "keywords": [] }, { "category": "people", "char": "🤰", "name": "pregnant_woman", "keywords": ["baby"] }, + { "category": "people", "char": "\uD83E\uDEC3", "name": "pregnant_man", "keywords": [] }, + { "category": "people", "char": "\uD83E\uDEC4", "name": "pregnant_person", "keywords": [] }, + { "category": "people", "char": "\uD83E\uDEC5", "name": "person_with_crown", "keywords": [] }, { "category": "people", "char": "🤱", "name": "breastfeeding", "keywords": ["nursing", "baby"] }, { "category": "people", "char": "\uD83D\uDC69\u200D\uD83C\uDF7C", "name": "woman_feeding_baby", "keywords": [] }, { "category": "people", "char": "\uD83D\uDC68\u200D\uD83C\uDF7C", "name": "man_feeding_baby", "keywords": [] }, @@ -456,7 +478,7 @@ { "category": "animals_and_nature", "char": "🐛", "name": "bug", "keywords": ["animal", "insect", "nature", "worm"] }, { "category": "animals_and_nature", "char": "🦋", "name": "butterfly", "keywords": ["animal", "insect", "nature", "caterpillar"] }, { "category": "animals_and_nature", "char": "🐌", "name": "snail", "keywords": ["slow", "animal", "shell"] }, - { "category": "animals_and_nature", "char": "🐞", "name": "beetle", "keywords": ["animal", "insect", "nature", "ladybug"] }, + { "category": "animals_and_nature", "char": "🐞", "name": "lady_beetle", "keywords": ["animal", "insect", "nature", "ladybug"] }, { "category": "animals_and_nature", "char": "🐜", "name": "ant", "keywords": ["animal", "insect", "nature", "bug"] }, { "category": "animals_and_nature", "char": "🦗", "name": "grasshopper", "keywords": ["animal", "cricket", "chirp"] }, { "category": "animals_and_nature", "char": "🕷", "name": "spider", "keywords": ["animal", "arachnid"] }, @@ -612,6 +634,10 @@ { "category": "animals_and_nature", "char": "💧", "name": "droplet", "keywords": ["water", "drip", "faucet", "spring"] }, { "category": "animals_and_nature", "char": "💦", "name": "sweat_drops", "keywords": ["water", "drip", "oops"] }, { "category": "animals_and_nature", "char": "🌊", "name": "ocean", "keywords": ["sea", "water", "wave", "nature", "tsunami", "disaster"] }, + { "category": "animals_and_nature", "char": "\uD83E\uDEB7", "name": "lotus", "keywords": [] }, + { "category": "animals_and_nature", "char": "\uD83E\uDEB8", "name": "coral", "keywords": [] }, + { "category": "animals_and_nature", "char": "\uD83E\uDEB9", "name": "empty_nest", "keywords": [] }, + { "category": "animals_and_nature", "char": "\uD83E\uDEBA", "name": "nest_with_eggs", "keywords": [] }, { "category": "food_and_drink", "char": "🍏", "name": "green_apple", "keywords": ["fruit", "nature"] }, { "category": "food_and_drink", "char": "🍎", "name": "apple", "keywords": ["fruit", "mac", "school"] }, { "category": "food_and_drink", "char": "🍐", "name": "pear", "keywords": ["fruit", "nature", "food"] }, @@ -734,6 +760,9 @@ { "category": "food_and_drink", "char": "🥣", "name": "bowl_with_spoon", "keywords": ["food", "breakfast", "cereal", "oatmeal", "porridge"] }, { "category": "food_and_drink", "char": "🥡", "name": "takeout_box", "keywords": ["food", "leftovers"] }, { "category": "food_and_drink", "char": "🥢", "name": "chopsticks", "keywords": ["food"] }, + { "category": "food_and_drink", "char": "\uD83E\uDED7", "name": "pouring_liquid", "keywords": [] }, + { "category": "food_and_drink", "char": "\uD83E\uDED8", "name": "beans", "keywords": [] }, + { "category": "food_and_drink", "char": "\uD83E\uDED9", "name": "jar", "keywords": [] }, { "category": "activity", "char": "⚽", "name": "soccer", "keywords": ["sports", "football"] }, { "category": "activity", "char": "🏀", "name": "basketball", "keywords": ["sports", "balls", "NBA"] }, { "category": "activity", "char": "🏈", "name": "football", "keywords": ["sports", "balls", "NFL"] }, @@ -841,6 +870,8 @@ { "category": "activity", "char": "🪄", "name": "magic_wand", "keywords": [] }, { "category": "activity", "char": "🪅", "name": "pinata", "keywords": [] }, { "category": "activity", "char": "🪆", "name": "nesting_dolls", "keywords": [] }, + { "category": "activity", "char": "\uD83E\uDEAC", "name": "hamsa", "keywords": [] }, + { "category": "activity", "char": "\uD83E\uDEA9", "name": "mirror_ball", "keywords": [] }, { "category": "travel_and_places", "char": "🚗", "name": "red_car", "keywords": ["red", "transportation", "vehicle"] }, { "category": "travel_and_places", "char": "🚕", "name": "taxi", "keywords": ["uber", "vehicle", "cars", "transportation"] }, { "category": "travel_and_places", "char": "🚙", "name": "blue_car", "keywords": ["transportation", "vehicle"] }, @@ -968,11 +999,12 @@ { "category": "travel_and_places", "char": "🕋", "name": "kaaba", "keywords": ["mecca", "mosque", "islam"] }, { "category": "travel_and_places", "char": "⛩", "name": "shinto_shrine", "keywords": ["temple", "japan", "kyoto"] }, { "category": "travel_and_places", "char": "🛕", "name": "hindu_temple", "keywords": ["temple"] }, - { "category": "travel_and_places", "char": "🪨", "name": "rock", "keywords": [] }, { "category": "travel_and_places", "char": "🪵", "name": "wood", "keywords": [] }, { "category": "travel_and_places", "char": "🛖", "name": "hut", "keywords": [] }, - + { "category": "travel_and_places", "char": "\uD83D\uDEDD", "name": "playground_slide", "keywords": [] }, + { "category": "travel_and_places", "char": "\uD83D\uDEDE", "name": "wheel", "keywords": [] }, + { "category": "travel_and_places", "char": "\uD83D\uDEDF", "name": "ring_buoy", "keywords": [] }, { "category": "objects", "char": "⌚", "name": "watch", "keywords": ["time", "accessories"] }, { "category": "objects", "char": "📱", "name": "iphone", "keywords": ["technology", "apple", "gadgets", "dial"] }, { "category": "objects", "char": "📲", "name": "calling", "keywords": ["iphone", "incoming"] }, @@ -1013,6 +1045,7 @@ { "category": "objects", "char": "⌛", "name": "hourglass", "keywords": ["time", "clock", "oldschool", "limit", "exam", "quiz", "test"] }, { "category": "objects", "char": "📡", "name": "satellite", "keywords": ["communication", "future", "radio", "space"] }, { "category": "objects", "char": "🔋", "name": "battery", "keywords": ["power", "energy", "sustain"] }, + { "category": "objects", "char": "\uD83E\uDEAB", "name": "battery", "keywords": [] }, { "category": "objects", "char": "🔌", "name": "electric_plug", "keywords": ["charger", "power"] }, { "category": "objects", "char": "💡", "name": "bulb", "keywords": ["light", "electricity", "idea"] }, { "category": "objects", "char": "🔦", "name": "flashlight", "keywords": ["dark", "camping", "sight", "night"] }, @@ -1028,6 +1061,7 @@ { "category": "objects", "char": "💰", "name": "moneybag", "keywords": ["dollar", "payment", "coins", "sale"] }, { "category": "objects", "char": "🪙", "name": "coin", "keywords": ["dollar", "payment", "coins", "sale"] }, { "category": "objects", "char": "💳", "name": "credit_card", "keywords": ["money", "sales", "dollar", "bill", "payment", "shopping"] }, + { "category": "objects", "char": "\uD83E\uDEAB", "name": "identification_card", "keywords": [] }, { "category": "objects", "char": "💎", "name": "gem", "keywords": ["blue", "ruby", "diamond", "jewelry"] }, { "category": "objects", "char": "⚖", "name": "balance_scale", "keywords": ["law", "fairness", "weight"] }, { "category": "objects", "char": "🧰", "name": "toolbox", "keywords": ["tools", "diy", "fix", "maintainer", "mechanic"] }, @@ -1074,6 +1108,8 @@ { "category": "objects", "char": "🩹", "name": "adhesive_bandage", "keywords": ["health", "hospital", "medicine", "needle", "doctor", "nurse"] }, { "category": "objects", "char": "🩺", "name": "stethoscope", "keywords": ["health", "hospital", "medicine", "needle", "doctor", "nurse"] }, { "category": "objects", "char": "🪒", "name": "razor", "keywords": ["health"] }, + { "category": "objects", "char": "\uD83E\uDE7B", "name": "xray", "keywords": [] }, + { "category": "objects", "char": "\uD83E\uDE7C", "name": "crutch", "keywords": [] }, { "category": "objects", "char": "🧬", "name": "dna", "keywords": ["biologist", "genetics", "life"] }, { "category": "objects", "char": "🧫", "name": "petri_dish", "keywords": ["bacteria", "biology", "culture", "lab"] }, { "category": "objects", "char": "🧪", "name": "test_tube", "keywords": ["chemistry", "experiment", "lab", "science"] }, @@ -1108,6 +1144,7 @@ { "category": "objects", "char": "🪤", "name": "mouse_trap", "keywords": ["household"] }, { "category": "objects", "char": "🪣", "name": "bucket", "keywords": ["household"] }, { "category": "objects", "char": "🪥", "name": "toothbrush", "keywords": ["household"] }, + { "category": "objects", "char": "\uD83E\uDEE7", "name": "bubbles", "keywords": [] }, { "category": "objects", "char": "⛱", "name": "parasol_on_ground", "keywords": ["weather", "summer"] }, { "category": "objects", "char": "🗿", "name": "moyai", "keywords": ["rock", "easter island", "moai"] }, { "category": "objects", "char": "🛍", "name": "shopping", "keywords": ["mall", "buy", "purchase"] }, @@ -1219,6 +1256,8 @@ { "category": "symbols", "char": "💘", "name": "cupid", "keywords": ["love", "like", "heart", "affection", "valentines"] }, { "category": "symbols", "char": "💝", "name": "gift_heart", "keywords": ["love", "valentines"] }, { "category": "symbols", "char": "💟", "name": "heart_decoration", "keywords": ["purple-square", "love", "like"] }, + { "category": "symbols", "char": "\u2764\uFE0F\u200D\uD83D\uDD25", "name": "heart_on_fire", "keywords": [] }, + { "category": "symbols", "char": "\u2764\uFE0F\u200D\uD83E\uDE79", "name": "mending_heart", "keywords": [] }, { "category": "symbols", "char": "☮", "name": "peace_symbol", "keywords": ["hippie"] }, { "category": "symbols", "char": "✝", "name": "latin_cross", "keywords": ["christianity"] }, { "category": "symbols", "char": "☪", "name": "star_and_crescent", "keywords": ["islam"] }, @@ -1399,6 +1438,7 @@ { "category": "symbols", "char": "➖", "name": "heavy_minus_sign", "keywords": ["math", "calculation", "subtract", "less"] }, { "category": "symbols", "char": "➗", "name": "heavy_division_sign", "keywords": ["divide", "math", "calculation"] }, { "category": "symbols", "char": "✖️", "name": "heavy_multiplication_x", "keywords": ["math", "calculation"] }, + { "category": "symbols", "char": "\uD83D\uDFF0", "name": "heavy_equals_sign", "keywords": [] }, { "category": "symbols", "char": "♾", "name": "infinity", "keywords": ["forever"] }, { "category": "symbols", "char": "💲", "name": "heavy_dollar_sign", "keywords": ["money", "sales", "payment", "currency", "buck"] }, { "category": "symbols", "char": "💱", "name": "currency_exchange", "keywords": ["money", "sales", "dollar", "travel"] }, diff --git a/src/misc/get-note-summary.ts b/src/misc/get-note-summary.ts index e3458cb189..83396727d8 100644 --- a/src/misc/get-note-summary.ts +++ b/src/misc/get-note-summary.ts @@ -22,7 +22,7 @@ const summarize = (note: any): string => { // ファイルが添付されているとき if ((note.files || []).length != 0) { - summary += ` (${note.files.length}つのファイル)`; + summary += ` (📎${note.files.length})`; } // 投票が添付されているとき diff --git a/src/misc/twemoji-base.ts b/src/misc/twemoji-base.ts index e08556bd49..d2359fee72 100644 --- a/src/misc/twemoji-base.ts +++ b/src/misc/twemoji-base.ts @@ -1 +1,8 @@ -export const twemojiSvgBase = 'https://twemoji.maxcdn.com/v/latest/svg'; +// Official (Slow, no IPv6) +// export const twemojiSvgBase = 'https://twemoji.maxcdn.com/v/latest/svg'; + +// Alternative (Fast, with IPv6) +export const twemojiSvgBase = 'https://cdn.jsdelivr.net/npm/@discordapp/twemoji@14.0.2/dist/svg'; + +// Self hosting +//export const twemojiSvgBase = '/twemoji'; diff --git a/src/models/entities/user.ts b/src/models/entities/user.ts index ec30bd023b..601e065b36 100644 --- a/src/models/entities/user.ts +++ b/src/models/entities/user.ts @@ -106,26 +106,6 @@ export class User { }) public tags: string[]; - @Column('varchar', { - length: 512, nullable: true, - }) - public avatarUrl: string | null; - - @Column('varchar', { - length: 512, nullable: true, - }) - public bannerUrl: string | null; - - @Column('varchar', { - length: 128, nullable: true, - }) - public avatarBlurhash: string | null; - - @Column('varchar', { - length: 128, nullable: true, - }) - public bannerBlurhash: string | null; - @Column('boolean', { default: false, comment: 'Whether the User is suspended.' @@ -248,6 +228,19 @@ export class User { }) public token: string | null; + @Column({ + ...id(), + nullable: true, + comment: 'Moved to user ID', + }) + public movedToUserId: User['id'] | null; + + @OneToOne(type => User, { + onDelete: 'SET NULL', + }) + @JoinColumn() + public movedToUser: User | null; + constructor(data: Partial) { if (data == null) return; diff --git a/src/models/repositories/drive-file.ts b/src/models/repositories/drive-file.ts index 6d1a99ec13..731fe76e76 100644 --- a/src/models/repositories/drive-file.ts +++ b/src/models/repositories/drive-file.ts @@ -195,7 +195,7 @@ export const packedDriveFileSchema = { type: 'string' as const, optional: false as const, nullable: true as const, description: 'The blurhash of image.', - example: 'ySFzz31U1?=nZO,+JOofR*oHnhjYX6S50J=n]DEol8JEw}R*xaNgXTW=ruxBxbWZS2obe.n~bFaxR%s*aKoIW.WY=}NgOAs*enoIWU', + example: 'yGCOe55x1Y]^Uw=UAmoMR+oHnif6baR:0J=m|}A3pwE.,wR*xGR;S~kCiy$d=}NgJPxAa0w@O9fQR%s*nOf6XRNg=zNgNsskaKoIa#', }, properties: { type: 'object' as const, diff --git a/src/models/repositories/user.ts b/src/models/repositories/user.ts index 8510c37412..4a5536c6f2 100644 --- a/src/models/repositories/user.ts +++ b/src/models/repositories/user.ts @@ -1,7 +1,7 @@ import $ from 'cafy'; import { EntityRepository, Repository, In, Not } from 'typeorm'; import { User, ILocalUser, IRemoteUser } from '../entities/user'; -import { Emojis, Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles, UserSecurityKeys, UserGroupJoinings, Pages, Instances, DriveFiles, Announcements, AnnouncementReads, Antennas, AntennaNotes, ChannelFollowings } from '..'; +import { Emojis, Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles, UserSecurityKeys, UserGroupJoinings, Pages, Instances, DriveFiles, Users, Announcements, AnnouncementReads, Antennas, AntennaNotes, ChannelFollowings } from '..'; import { ensure } from '../../prelude/ensure'; import config from '../../config'; import { Packed } from '../../misc/schema'; @@ -157,6 +157,8 @@ export class UserRepository extends Repository { ); } + // 何とかしたい + /* public getAvatarUrl(user: User): string { if (user.avatarUrl) { return user.avatarUrl; @@ -164,6 +166,7 @@ export class UserRepository extends Repository { return `${config.url}/random-avatar/${user.id}`; } } + */ public async pack( src: User['id'] | User, @@ -239,9 +242,9 @@ export class UserRepository extends Repository { name: user.name, username: user.username, host: user.host, - avatarUrl: this.getAvatarUrl(user), - avatarBlurhash: user.avatarBlurhash, - avatarColor: null, // 後方互換性のため + avatarUrl: user.avatar ? DriveFiles.getPublicUrl(user.avatar, true) : config.url + '/random-avatar/' + user.id, + avatarBlurhash: user.avatar?.blurhash || null, + avatarColor: null, isAdmin: user.isAdmin || falsy, isBot: user.isBot || falsy, isCat: user.isCat || falsy, @@ -280,7 +283,7 @@ export class UserRepository extends Repository { createdAt: user.createdAt.toISOString(), updatedAt: user.updatedAt ? user.updatedAt.toISOString() : null, bannerUrl: user.banner ? DriveFiles.getPublicUrl(user.banner, false) : null, - bannerBlurhash: user.bannerBlurhash, + bannerBlurhash: user.avatar?.blurhash || null, bannerColor: null, // 後方互換性のため isLocked: user.isLocked, isModerator: user.isModerator || falsy, @@ -319,6 +322,8 @@ export class UserRepository extends Repository { username: profile!.discordUsername, discriminator: profile!.discordDiscriminator } : null, + movedToUserId: user.movedToUserId, + movedToUser: user.movedToUserId ? Users.pack(user.movedToUserId) : null, } : {}), ...(opts.detail && meId === user.id ? { diff --git a/src/remote/activitypub/db-resolver.ts b/src/remote/activitypub/db-resolver.ts index 1252f5c222..56f2ca1231 100644 --- a/src/remote/activitypub/db-resolver.ts +++ b/src/remote/activitypub/db-resolver.ts @@ -78,14 +78,14 @@ export default class DbResolver { public async getAuthUserFromKeyId(keyId: string): Promise { const key = await UserPublickeys.findOne({ keyId + }, { + relations: ['user'], }); if (key == null) return null; - const user = await Users.findOne(key.userId) as IRemoteUser; - return { - user, + user: key.user as IRemoteUser, key }; } diff --git a/src/remote/activitypub/models/person.ts b/src/remote/activitypub/models/person.ts index 0b08407e87..9c21c3e467 100644 --- a/src/remote/activitypub/models/person.ts +++ b/src/remote/activitypub/models/person.ts @@ -14,7 +14,7 @@ import { extractApHashtags } from './tag'; import { apLogger } from '../logger'; import { Note } from '../../../models/entities/note'; import { updateUsertags } from '../../../services/update-hashtag'; -import { Users, Instances, DriveFiles, Followings, UserProfiles, UserPublickeys } from '../../../models'; +import { Users, Instances, Followings, UserProfiles, UserPublickeys } from '../../../models'; import { User, IRemoteUser } from '../../../models/entities/user'; import { Emoji } from '../../../models/entities/emoji'; import { UserNotePining } from '../../../models/entities/user-note-pinings'; @@ -33,6 +33,7 @@ import { resolveUser } from '../../resolve-user'; import { StatusError } from '@/misc/fetch'; import { MAX_NAME_LENGTH, MAX_SUMMARY_LENGTH } from '../../../misc/hard-limits'; import { truncate } from '@/misc/truncate'; +import { resolveAnotherUser } from '../resolve-another-user'; const logger = apLogger; @@ -141,6 +142,14 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise { + logger.warn(`Error in movedTo: ${e}`); + return null; + }) + : null; + // Create user let user: IRemoteUser; try { @@ -166,7 +175,8 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise transactionalEntityManager.findOneOrFail(User, x.identifiers[0])) as IRemoteUser; await transactionalEntityManager.insert(UserProfile, { @@ -236,26 +246,14 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise { + logger.warn(`Error in movedTo: ${e}`); + return null; + }) + : null; + const updates = { lastFetchedAt: new Date(), inbox: person.inbox, @@ -345,18 +351,15 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint isLady: (person as any).isLady === true, isLocked: !!person.manuallyApprovesFollowers, isExplorable: !!person.discoverable, + movedToUserId: movedTo?.id || null, } as Partial; if (avatar) { updates.avatarId = avatar.id; - updates.avatarUrl = DriveFiles.getPublicUrl(avatar, true); - updates.avatarBlurhash = avatar.blurhash; } if (banner) { updates.bannerId = banner.id; - updates.bannerUrl = DriveFiles.getPublicUrl(banner); - updates.bannerBlurhash = banner.blurhash; } // Update user diff --git a/src/remote/activitypub/renderer/note.ts b/src/remote/activitypub/renderer/note.ts index f9912b0ddc..0247f1626a 100644 --- a/src/remote/activitypub/renderer/note.ts +++ b/src/remote/activitypub/renderer/note.ts @@ -12,6 +12,12 @@ import { Emoji } from '../../../models/entities/emoji'; import { Poll } from '../../../models/entities/poll'; import { ensure } from '../../../prelude/ensure'; +/** + * Render Note object + * @param note Note object from DB + * @param dive Deprecated, Always ignored + * @param isTalk isTalk + */ export default async function renderNote(note: Note, dive = true, isTalk = false): Promise { const getPromisedFiles = async (ids: string[]) => { if (!ids || ids.length === 0) return []; @@ -19,29 +25,11 @@ export default async function renderNote(note: Note, dive = true, isTalk = false return ids.map(id => items.find(item => item.id === id)).filter(item => item != null) as DriveFile[]; }; - let inReplyTo; - let inReplyToNote: Note | undefined; + let inReplyTo: string | null; if (note.replyId) { - inReplyToNote = await Notes.findOne(note.replyId); - - if (inReplyToNote != null) { - const inReplyToUser = await Users.findOne(inReplyToNote.userId); - - if (inReplyToUser != null) { - if (inReplyToNote.uri) { - inReplyTo = inReplyToNote.uri; - } else { - if (dive) { - inReplyTo = await renderNote(inReplyToNote, false); - } else { - inReplyTo = `${config.url}/notes/${inReplyToNote.id}`; - } - } - } - } - } else { - inReplyTo = null; + const inReplyToNote = note.reply || await Notes.findOne(note.replyId); + inReplyTo = inReplyToNote ? `${config.url}/notes/${inReplyToNote.id}` : null; } let quote; @@ -54,9 +42,7 @@ export default async function renderNote(note: Note, dive = true, isTalk = false } } - const user = await Users.findOne(note.userId).then(ensure); - - const attributedTo = `${config.url}/users/${user.id}`; + const attributedTo = `${config.url}/users/${note.userId}`; const mentions = (JSON.parse(note.mentionedRemoteUsers) as IMentionedRemoteUsers).map(x => x.uri); diff --git a/src/remote/activitypub/renderer/person.ts b/src/remote/activitypub/renderer/person.ts index 00bab5748c..a9ca6e8256 100644 --- a/src/remote/activitypub/renderer/person.ts +++ b/src/remote/activitypub/renderer/person.ts @@ -17,8 +17,8 @@ export async function renderPerson(user: ILocalUser) { const isSystem = !!user.username.match(/\./); const [avatar, banner, profile] = await Promise.all([ - user.avatarId ? DriveFiles.findOne(user.avatarId) : Promise.resolve(undefined), - user.bannerId ? DriveFiles.findOne(user.bannerId) : Promise.resolve(undefined), + (user.avatar === undefined && user.avatarId) ? DriveFiles.findOne(user.avatarId) : user.avatar, + (user.banner === undefined && user.bannerId) ? DriveFiles.findOne(user.bannerId) : user.banner, UserProfiles.findOne(user.id).then(ensure) ]); diff --git a/src/remote/activitypub/resolve-another-user.ts b/src/remote/activitypub/resolve-another-user.ts new file mode 100644 index 0000000000..0f5ad6409e --- /dev/null +++ b/src/remote/activitypub/resolve-another-user.ts @@ -0,0 +1,31 @@ +import Resolver from './resolver'; +import { getApId, IObject } from './type'; +import { resolvePerson } from './models/person'; + +/** + * Resolve user with loop detection + * @param selfUri Origin AP Actor id + * @param target Target + * @param resolver Resolver + * @returns Remote/Local user as DB object + */ +export async function resolveAnotherUser(selfUri: string, target: string | IObject | undefined, resolver: Resolver) { + if (target == null) return null; + + const targetUri = getApId(target); + + if (selfUri === targetUri) { + throw new Error(`target is self ${selfUri}`); + } + + const user = await resolvePerson(targetUri, resolver) + .catch(e => { + throw new Error(`failed to resolvePerson ${selfUri} => ${targetUri}, ${e}`); + }); + + if (selfUri === user?.uri) { + throw new Error(`result is self ${selfUri}`); + } + + return user; +} diff --git a/src/remote/activitypub/type.ts b/src/remote/activitypub/type.ts index 0e30e84f95..65d620a86f 100644 --- a/src/remote/activitypub/type.ts +++ b/src/remote/activitypub/type.ts @@ -166,6 +166,7 @@ export interface IActor extends IObject { }; 'vcard:bday'?: string; 'vcard:Address'?: string; + movedTo?: string | IObject; } export const isCollection = (object: IObject): object is ICollection => diff --git a/src/server/activitypub.ts b/src/server/activitypub.ts index d00fb711d2..9870367dd0 100644 --- a/src/server/activitypub.ts +++ b/src/server/activitypub.ts @@ -74,8 +74,10 @@ router.get('/notes/:note', async (ctx, next) => { const note = await Notes.findOne({ id: ctx.params.note, - visibility: In(['public', 'home']), + visibility: In(['public' as const, 'home' as const]), localOnly: false + }, { + relations: ['user'] }); if (note == null) { @@ -103,8 +105,10 @@ router.get('/notes/:note/activity', async ctx => { const note = await Notes.findOne({ id: ctx.params.note, userHost: null, - visibility: In(['public', 'home']), + visibility: In(['public' as const, 'home' as const]), localOnly: false + }, { + relations: ['user'] }); if (note == null) { @@ -175,6 +179,8 @@ router.get('/users/:user', async (ctx, next) => { id: userId, host: null, isSuspended: false + }, { + relations: ['avatar', 'banner'], }); await userInfo(ctx, user); @@ -187,6 +193,8 @@ router.get('/@:user', async (ctx, next) => { usernameLower: ctx.params.user.toLowerCase(), host: null, isSuspended: false + }, { + relations: ['avatar', 'banner'], }); await userInfo(ctx, user); diff --git a/src/server/activitypub/featured.ts b/src/server/activitypub/featured.ts index 7b87fa109f..0a93abe233 100644 --- a/src/server/activitypub/featured.ts +++ b/src/server/activitypub/featured.ts @@ -31,7 +31,7 @@ export default async (ctx: Router.RouterContext) => { const pinnedNotes = await Promise.all(pinings.map(pining => Notes.findOne(pining.noteId).then(ensure))); - const renderedNotes = await Promise.all(pinnedNotes.map(note => renderNote(note))); + const renderedNotes = await Promise.all(pinnedNotes.map(note => renderNote(note, false))); const rendered = renderOrderedCollection( `${config.url}/users/${userId}/collections/featured`, diff --git a/src/server/api/common/getters.ts b/src/server/api/common/getters.ts index 04716d19c6..31016ff924 100644 --- a/src/server/api/common/getters.ts +++ b/src/server/api/common/getters.ts @@ -6,8 +6,22 @@ import { Notes, Users } from '../../../models'; /** * Get note for API processing */ -export async function getNote(noteId: Note['id']) { - const note = await Notes.findOne(noteId); + export async function getNote(noteId: Note['id'], withRelations = false) { + const note = await Notes.findOne(noteId, { + relations: withRelations ? [ + 'user', + 'user.avatar', + 'user.banner', + 'reply', + 'renote', + 'reply.user', + 'reply.user.avatar', + 'reply.user.banner', + 'renote.user', + 'renote.user.avatar', + 'renote.user.banner', + ] : undefined + }); if (note == null) { throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', 'No such note.'); diff --git a/src/server/api/endpoints/i/notifications.ts b/src/server/api/endpoints/i/notifications.ts index de84c7d298..b03bfeb474 100644 --- a/src/server/api/endpoints/i/notifications.ts +++ b/src/server/api/endpoints/i/notifications.ts @@ -85,7 +85,7 @@ export default define(meta, async (ps, user) => { const query = makePaginationQuery(Notifications.createQueryBuilder('notification'), ps.sinceId, ps.untilId) .andWhere(`notification.notifieeId = :meId`, { meId: user.id }) - .leftJoinAndSelect('notification.notifier', 'notifier') + .innerJoinAndSelect('notification.notifier', 'notifier') .leftJoinAndSelect('notification.note', 'note') .leftJoinAndSelect('note.user', 'user') .leftJoinAndSelect('note.reply', 'reply') diff --git a/src/server/api/endpoints/i/update.ts b/src/server/api/endpoints/i/update.ts index a5b713b18d..1e310cb5fb 100644 --- a/src/server/api/endpoints/i/update.ts +++ b/src/server/api/endpoints/i/update.ts @@ -250,12 +250,6 @@ export default define(meta, async (ps, user, app) => { if (avatar == null || avatar.userId !== user.id) throw new ApiError(meta.errors.noSuchAvatar); if (!avatar.type.startsWith('image/')) throw new ApiError(meta.errors.avatarNotAnImage); - - updates.avatarUrl = DriveFiles.getPublicUrl(avatar, true); - - if (avatar.blurhash) { - updates.avatarBlurhash = avatar.blurhash; - } } if (ps.bannerId) { @@ -263,12 +257,6 @@ export default define(meta, async (ps, user, app) => { if (banner == null || banner.userId !== user.id) throw new ApiError(meta.errors.noSuchBanner); if (!banner.type.startsWith('image/')) throw new ApiError(meta.errors.bannerNotAnImage); - - updates.bannerUrl = DriveFiles.getPublicUrl(banner, false); - - if (banner.blurhash) { - updates.bannerBlurhash = banner.blurhash; - } } if (ps.pinnedPageId) { diff --git a/src/server/api/endpoints/notes.ts b/src/server/api/endpoints/notes.ts index 708ce38c0e..1a9ebd23e6 100644 --- a/src/server/api/endpoints/notes.ts +++ b/src/server/api/endpoints/notes.ts @@ -76,11 +76,17 @@ export default define(meta, async (ps) => { const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) .andWhere(`note.visibility = 'public'`) .andWhere(`note.localOnly = FALSE`) - .leftJoinAndSelect('note.user', 'user') + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('renote.user', 'renoteUser'); + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); if (ps.local) { query.andWhere('note.userHost IS NULL'); diff --git a/src/server/api/endpoints/notes/children.ts b/src/server/api/endpoints/notes/children.ts index f0e97372b6..0307eee68f 100644 --- a/src/server/api/endpoints/notes/children.ts +++ b/src/server/api/endpoints/notes/children.ts @@ -64,11 +64,17 @@ export default define(meta, async (ps, user) => { })); })); })) - .leftJoinAndSelect('note.user', 'user') + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('renote.user', 'renoteUser'); + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); generateVisibilityQuery(query, user); if (user) generateMuteQuery(query, user); diff --git a/src/server/api/endpoints/notes/create.ts b/src/server/api/endpoints/notes/create.ts index 2d692e879a..8f055c462f 100644 --- a/src/server/api/endpoints/notes/create.ts +++ b/src/server/api/endpoints/notes/create.ts @@ -19,7 +19,7 @@ setInterval(() => { fetchMeta().then(m => { maxNoteTextLength = m.maxNoteTextLength; }); -}, 3000); +}, 60 * 1000); export const meta = { stability: 'stable', diff --git a/src/server/api/endpoints/notes/featured.ts b/src/server/api/endpoints/notes/featured.ts index ee587a2db7..372eaa5ae9 100644 --- a/src/server/api/endpoints/notes/featured.ts +++ b/src/server/api/endpoints/notes/featured.ts @@ -45,11 +45,17 @@ export default define(meta, async (ps, user) => { .where('note.userHost IS NULL') .andWhere(`note.createdAt > :date`, { date: new Date(Date.now() - day) }) .andWhere(`note.visibility = 'public'`) - .leftJoinAndSelect('note.user', 'user') + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('renote.user', 'renoteUser'); + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); if (user) generateMuteQuery(query, user); diff --git a/src/server/api/endpoints/notes/global-timeline.ts b/src/server/api/endpoints/notes/global-timeline.ts index a35435e31d..3a9a1ecbfd 100644 --- a/src/server/api/endpoints/notes/global-timeline.ts +++ b/src/server/api/endpoints/notes/global-timeline.ts @@ -78,11 +78,17 @@ export default define(meta, async (ps, user) => { .andWhere('note.visibility = \'public\'') .andWhere('note.replyId IS NULL') .andWhere('note.channelId IS NULL') - .leftJoinAndSelect('note.user', 'user') + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('renote.user', 'renoteUser'); + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); if (user) generateMuteQuery(query, user); diff --git a/src/server/api/endpoints/notes/hybrid-timeline.ts b/src/server/api/endpoints/notes/hybrid-timeline.ts index 4e13981d07..426af745a9 100644 --- a/src/server/api/endpoints/notes/hybrid-timeline.ts +++ b/src/server/api/endpoints/notes/hybrid-timeline.ts @@ -125,11 +125,17 @@ export default define(meta, async (ps, user) => { qb.where(`((note.userId IN (${ followingQuery.getQuery() })) OR (note.userId = :meId))`, { meId: user.id }) .orWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)'); })) - .leftJoinAndSelect('note.user', 'user') + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner') .setParameters(followingQuery.getParameters()); generateChannelQuery(query, user); diff --git a/src/server/api/endpoints/notes/local-timeline.ts b/src/server/api/endpoints/notes/local-timeline.ts index bef0eb9f66..195c95c50c 100644 --- a/src/server/api/endpoints/notes/local-timeline.ts +++ b/src/server/api/endpoints/notes/local-timeline.ts @@ -94,11 +94,17 @@ export default define(meta, async (ps, user) => { const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('(note.visibility = \'public\') AND (note.userHost IS NULL)') - .leftJoinAndSelect('note.user', 'user') + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('renote.user', 'renoteUser'); + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); generateChannelQuery(query, user); generateVisibilityQuery(query, user); diff --git a/src/server/api/endpoints/notes/mentions.ts b/src/server/api/endpoints/notes/mentions.ts index 1fc892f32a..06b03d001e 100644 --- a/src/server/api/endpoints/notes/mentions.ts +++ b/src/server/api/endpoints/notes/mentions.ts @@ -63,11 +63,17 @@ export default define(meta, async (ps, user) => { .where(`'{"${user.id}"}' <@ note.mentions`) .orWhere(`'{"${user.id}"}' <@ note.visibleUserIds`); })) - .leftJoinAndSelect('note.user', 'user') + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('renote.user', 'renoteUser'); + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); generateVisibilityQuery(query, user); generateMuteQuery(query, user); diff --git a/src/server/api/endpoints/notes/renotes.ts b/src/server/api/endpoints/notes/renotes.ts index f3d29af4a9..af0a937b6e 100644 --- a/src/server/api/endpoints/notes/renotes.ts +++ b/src/server/api/endpoints/notes/renotes.ts @@ -68,11 +68,17 @@ export default define(meta, async (ps, user) => { const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) .andWhere(`note.renoteId = :renoteId`, { renoteId: note.id }) - .leftJoinAndSelect('note.user', 'user') - .leftJoinAndSelect('note.reply', 'reply') + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') + .leftJoinAndSelect('note.reply', 'reply') // このAPIはQuoteも返すのでいる .leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('renote.user', 'renoteUser'); + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); generateVisibilityQuery(query, user); if (user) generateMuteQuery(query, user); diff --git a/src/server/api/endpoints/notes/replies.ts b/src/server/api/endpoints/notes/replies.ts index e7c5297d6d..5747a9bebf 100644 --- a/src/server/api/endpoints/notes/replies.ts +++ b/src/server/api/endpoints/notes/replies.ts @@ -59,11 +59,17 @@ export const meta = { export default define(meta, async (ps, user) => { const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) .andWhere('note.replyId = :replyId', { replyId: ps.noteId }) - .leftJoinAndSelect('note.user', 'user') + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('renote.user', 'renoteUser'); + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); generateVisibilityQuery(query, user); if (user) generateMuteQuery(query, user); diff --git a/src/server/api/endpoints/notes/search-by-tag.ts b/src/server/api/endpoints/notes/search-by-tag.ts index 67e3d2e784..200b0ba649 100644 --- a/src/server/api/endpoints/notes/search-by-tag.ts +++ b/src/server/api/endpoints/notes/search-by-tag.ts @@ -95,11 +95,17 @@ export const meta = { export default define(meta, async (ps, me) => { const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) - .leftJoinAndSelect('note.user', 'user') + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('renote.user', 'renoteUser'); + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); generateVisibilityQuery(query, me); if (me) generateMuteQuery(query, me); diff --git a/src/server/api/endpoints/notes/show.ts b/src/server/api/endpoints/notes/show.ts index 75abbae55f..375e96bbff 100644 --- a/src/server/api/endpoints/notes/show.ts +++ b/src/server/api/endpoints/notes/show.ts @@ -43,7 +43,7 @@ export const meta = { }; export default define(meta, async (ps, user) => { - const note = await getNote(ps.noteId).catch(e => { + const note = await getNote(ps.noteId, true).catch(e => { if (e.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); throw e; }); diff --git a/src/server/api/endpoints/notes/timeline.ts b/src/server/api/endpoints/notes/timeline.ts index 599da311cf..c7c6cea99a 100644 --- a/src/server/api/endpoints/notes/timeline.ts +++ b/src/server/api/endpoints/notes/timeline.ts @@ -111,11 +111,17 @@ export default define(meta, async (ps, user) => { .where(`note.userId IN (${ followingQuery.getQuery() })`) .orWhere('note.userId = :meId', { meId: user.id }); })) - .leftJoinAndSelect('note.user', 'user') + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner') .setParameters(followingQuery.getParameters()); generateChannelQuery(query, user); diff --git a/src/server/api/endpoints/notes/user-list-timeline.ts b/src/server/api/endpoints/notes/user-list-timeline.ts index a6149fba01..af76d8a87e 100644 --- a/src/server/api/endpoints/notes/user-list-timeline.ts +++ b/src/server/api/endpoints/notes/user-list-timeline.ts @@ -126,13 +126,19 @@ export default define(meta, async (ps, user) => { //#region Construct query const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId) .innerJoin(UserListJoinings.metadata.targetName, 'userListJoining', 'userListJoining.userId = note.userId') - .leftJoinAndSelect('note.user', 'user') + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('reply.user', 'replyUser') + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner') .andWhere('userListJoining.userListId = :userListId', { userListId: list.id }); - + generateVisibilityQuery(query, user); if (ps.includeMyRenotes === false) { diff --git a/src/server/api/endpoints/users/notes.ts b/src/server/api/endpoints/users/notes.ts index ed3faebe71..8768d5433e 100644 --- a/src/server/api/endpoints/users/notes.ts +++ b/src/server/api/endpoints/users/notes.ts @@ -131,11 +131,17 @@ export default define(meta, async (ps, me) => { //#region Construct query const query = makePaginationQuery(Notes.createQueryBuilder('note'), ps.sinceId, ps.untilId, ps.sinceDate, ps.untilDate) .andWhere('note.userId = :userId', { userId: user.id }) - .leftJoinAndSelect('note.user', 'user') + .innerJoinAndSelect('note.user', 'user') + .leftJoinAndSelect('user.avatar', 'avatar') + .leftJoinAndSelect('user.banner', 'banner') .leftJoinAndSelect('note.reply', 'reply') .leftJoinAndSelect('note.renote', 'renote') .leftJoinAndSelect('reply.user', 'replyUser') - .leftJoinAndSelect('renote.user', 'renoteUser'); + .leftJoinAndSelect('replyUser.avatar', 'replyUserAvatar') + .leftJoinAndSelect('replyUser.banner', 'replyUserBanner') + .leftJoinAndSelect('renote.user', 'renoteUser') + .leftJoinAndSelect('renoteUser.avatar', 'renoteUserAvatar') + .leftJoinAndSelect('renoteUser.banner', 'renoteUserBanner'); generateVisibilityQuery(query, me); if (me) generateMuteQuery(query, me, user); diff --git a/src/server/web/index.ts b/src/server/web/index.ts index 4856ac2eec..d7dfd94607 100644 --- a/src/server/web/index.ts +++ b/src/server/web/index.ts @@ -81,6 +81,22 @@ router.get('/apple-touch-icon.png', async ctx => { }); }); +router.get('/twemoji/(.*)', async ctx => { + const path = ctx.path.replace('/twemoji/', ''); + + if (!path.match(/^[0-9a-f-]+\.svg$/)) { + ctx.status = 404; + return; + } + + ctx.set('Content-Security-Policy', 'default-src \'none\'; style-src \'unsafe-inline\''); + + await send(ctx as any, path, { + root: `${__dirname}/../../../node_modules/@discordapp/twemoji/dist/svg/`, + maxage: ms('30 days'), + }); +}); + // ServiceWorker router.get(/^\/sw\.(.+?)\.js$/, async ctx => { await send(ctx as any, `/assets/sw.${ctx.params[0]}.js`, { @@ -174,12 +190,14 @@ router.get('/@:user.json', async ctx => { // User router.get(['/@:user', '/@:user/:sub'], async (ctx, next) => { const { username, host } = parseAcct(ctx.params.user); - const user = await Users.findOne({ + const _user = await Users.findOne({ usernameLower: username.toLowerCase(), host, isSuspended: false }); + const user = await Users.pack(_user!); + if (user != null) { const profile = await UserProfiles.findOne(user.id).then(ensure); const meta = await fetchMeta(); @@ -222,6 +240,64 @@ router.get('/users/:user', async ctx => { router.get('/notes/:note', async ctx => { const note = await Notes.findOne(ctx.params.note); + if (note) { + const _note = await Notes.pack(note); + const profile = await UserProfiles.findOne(note.userId).then(ensure); + + const meta = await fetchMeta(); + + const video = (_note.files || []) + .filter((file: any) => file.type.match(/^video/) && !file.isSensitive) + .shift() as any; + + const audio = (_note.files || []) + .filter((file: any) => file.type.match(/^audio/) && !file.isSensitive) + .shift() as any; + + const image = (_note.files || []) + .filter((file: any) => file.type.match(/^image/) && !file.isSensitive) + .shift() as any; + + let imageUrl = video?.thumbnailUrl || image?.thumbnailUrl; + + // or avatar + if (imageUrl == null || imageUrl === '') { + imageUrl = (_note.user as any)?.avatarUrl; + } + + const stream = video?.url || audio?.url; + const type = video?.type || audio?.type; + const player = (video || audio) ? `${config.url}/notes/${_note?.id}/embed` : null; + const width = 530; // TODO: thumbnail width + const height = 255; + + await ctx.render('note', { + note: _note, + profile, + summary: getNoteSummary(_note), + imageUrl, + instanceName: meta.name || 'Misskey', + icon: meta.iconUrl, + player, width, height, stream, type, + }); + + if (['public', 'home'].includes(note.visibility)) { + ctx.set('Cache-Control', 'public, max-age=180'); + } else { + ctx.set('Cache-Control', 'private, max-age=0, must-revalidate'); + } + + return; + } + + ctx.status = 404; +}); + +router.get('/notes/:note/embed', async ctx => { + ctx.remove('X-Frame-Options'); + + const note = await Notes.findOne(ctx.params.note); + if (note) { const _note = await Notes.pack(note); const profile = await UserProfiles.findOne(note.userId).then(ensure); @@ -249,6 +325,20 @@ router.get('/notes/:note', async ctx => { icon: meta.iconUrl }); + const video = (_note.files || []) + .filter((file: any) => file.type.match(/^video/) && !file.isSensitive) + .shift() as any; + const audio = video ? undefined : (_note.files || []) + .filter((file: any) => file.type.match(/^audio/) && !file.isSensitive) + .shift() as any; + + await ctx.render('note-embed', { + video: video?.url, + audio: audio?.url, + type: (video || audio)?.type, + autoplay: ctx.query.autoplay != null, + }); + if (['public', 'home'].includes(note.visibility)) { setCache(ctx, 'public, max-age=180'); } else { diff --git a/src/server/web/views/base.pug b/src/server/web/views/base.pug index 5e4936b783..7c49fb3306 100644 --- a/src/server/web/views/base.pug +++ b/src/server/web/views/base.pug @@ -18,6 +18,8 @@ html link(rel='icon' href= icon || '/favicon.ico') link(rel='apple-touch-icon' href= icon || '/apple-touch-icon.png') link(rel='manifest' href='/manifest.json') + meta(name='twitter:card' content= player ? 'player' : 'summary') + meta(property='og:site_name' content= instanceName || 'Misskey') title block title @@ -29,8 +31,8 @@ html block meta block og - meta(property='og:title' content= title || 'Ayuskey') - meta(property='og:description' content= desc || '✨🌎✨ A federated blogging platform ✨🚀✨') + meta(property='og:title' content= instanceName || 'Ayuskey') + meta(property='og:description' content= desc || '✨🌎✨ A federated blogging platform ✨🚀✨') meta(property='og:image' content= img) style diff --git a/src/server/web/views/note-embed.pug b/src/server/web/views/note-embed.pug new file mode 100644 index 0000000000..90f6e64d53 --- /dev/null +++ b/src/server/web/views/note-embed.pug @@ -0,0 +1,19 @@ +doctype html + +html + head + meta(charset="UTF-8") + title Misskey + style. + body, video, audio { + width:100%; + height: 100%; + overflow: hidden; + position: absolute; + } + + body + if video + video(src=video type=type controls autoplay=autoplay) + if audio + audio(src=audio type=type controls autoplay=autoplay) diff --git a/src/server/web/views/note.pug b/src/server/web/views/note.pug index a929c7c4a9..ac2fb15bb9 100644 --- a/src/server/web/views/note.pug +++ b/src/server/web/views/note.pug @@ -18,6 +18,7 @@ block og meta(property='og:description' content= summary) meta(property='og:url' content= url) meta(property='og:image' content= imageUrl) + meta(property='og:published_time' content= note.createdAt) block meta if user.host || isRenote || profile.noCrawle @@ -27,6 +28,18 @@ block meta meta(name='misskey:user-id' content=user.id) meta(name='misskey:note-id' content=note.id) + if player + meta(name='twitter:player' content=player) + meta(name='twitter:player:width' content=width) + meta(name='twitter:player:height' content=height) + meta(name='twitter:player:stream' content=stream) + meta(name='twitter:player:stream:content_type' content=type) + meta(property='og:video:url' content=player) + meta(property='og:video:secure_url' content=player) + meta(property='og:video:type' content='text/html') + meta(property='og:video:width' content=width) + meta(property='og:video:height' content=height) + if user.twitter meta(name='twitter:creator' content=`@${user.twitter.screenName}`) diff --git a/src/server/web/views/page.pug b/src/server/web/views/page.pug index cd7ff471e0..ffded08c7f 100644 --- a/src/server/web/views/page.pug +++ b/src/server/web/views/page.pug @@ -26,8 +26,8 @@ block meta meta(name='misskey:user-id' content=user.id) meta(name='misskey:page-id' content=page.id) - //if user.host - // meta(name='robots' content='noindex') + if user.host + meta(name='robots' content='noindex') // todo if user.twitter diff --git a/src/server/well-known.ts b/src/server/well-known.ts index 5a94522c53..8ac571a42d 100644 --- a/src/server/well-known.ts +++ b/src/server/well-known.ts @@ -43,6 +43,7 @@ router.get('/.well-known/host-meta', async ctx => { ctx.set('Content-Type', xrd); ctx.body = XRD({ element: 'Link', attributes: { + rel: 'lrdd', type: xrd, template: `${config.url}${webFingerPath}?resource={uri}` }}); diff --git a/src/services/blocking/create.ts b/src/services/blocking/create.ts index cfd1a99e9a..85103a8fec 100644 --- a/src/services/blocking/create.ts +++ b/src/services/blocking/create.ts @@ -84,17 +84,12 @@ async function unFollow(follower: User, followee: User) { return; } - Followings.delete(following.id); - - //#region Decrement following count - Users.decrement({ id: follower.id }, 'followingCount', 1); - //#endregion - - //#region Decrement followers count - Users.decrement({ id: followee.id }, 'followersCount', 1); - //#endregion - - perUserFollowingChart.update(follower, followee, false); + await Promise.all([ + Followings.delete(following.id), + Users.decrement({ id: follower.id }, 'followingCount', 1), + Users.decrement({ id: followee.id }, 'followersCount', 1), + perUserFollowingChart.update(follower, followee, false), + ]); // Publish unfollow event if (Users.isLocalUser(follower)) { diff --git a/src/services/create-notification.ts b/src/services/create-notification.ts index 3c6d72d865..9a2e8790df 100644 --- a/src/services/create-notification.ts +++ b/src/services/create-notification.ts @@ -41,7 +41,8 @@ export async function createNotification( } // Create notification - const notification = await Notifications.save(data); + const notification = await Notifications.insert(data) + .then(x => Notifications.findOneOrFail(x.identifiers[0])); const packed = await Notifications.pack(notification, {}); diff --git a/src/services/following/create.ts b/src/services/following/create.ts index daa7c94213..bcb72228aa 100644 --- a/src/services/following/create.ts +++ b/src/services/following/create.ts @@ -53,8 +53,10 @@ export async function insertFollowingDoc(followee: User, follower: User) { if (alreadyFollowed) return; //#region Increment counts - Users.increment({ id: follower.id }, 'followingCount', 1); - Users.increment({ id: followee.id }, 'followersCount', 1); + await Promise.all([ + Users.increment({ id: follower.id }, 'followingCount', 1), + Users.increment({ id: followee.id }, 'followersCount', 1), + ]); //#endregion //#region Update instance stats diff --git a/src/services/following/delete.ts b/src/services/following/delete.ts index 1a6d883577..3d4c80a95b 100644 --- a/src/services/following/delete.ts +++ b/src/services/following/delete.ts @@ -43,12 +43,11 @@ export default async function(follower: User, followee: User, silent = false) { } export async function decrementFollowing(follower: User, followee: User) { - //#region Decrement following count - Users.decrement({ id: follower.id }, 'followingCount', 1); - //#endregion - - //#region Decrement followers count - Users.decrement({ id: followee.id }, 'followersCount', 1); + //#region Decrement following / followers counts + await Promise.all([ + Users.decrement({ id: follower.id }, 'followingCount', 1), + Users.decrement({ id: followee.id }, 'followersCount', 1), + ]); //#endregion //#region Update instance stats diff --git a/test/docker-compose.yml b/test/docker-compose.yml new file mode 100644 index 0000000000..a51868f826 --- /dev/null +++ b/test/docker-compose.yml @@ -0,0 +1,15 @@ +version: "3" + +services: + redistest: + image: redis:4.0-alpine + ports: + - "127.0.0.1:56311:6379" + + dbtest: + image: postgres:11.2-alpine + ports: + - "127.0.0.1:54311:5432" + environment: + POSTGRES_DB: "test-misskey" + POSTGRES_HOST_AUTH_METHOD: trust diff --git a/test/fetch-resource.ts b/test/fetch-resource.ts index 8fd7d39a42..4b3f836ee9 100644 --- a/test/fetch-resource.ts +++ b/test/fetch-resource.ts @@ -12,7 +12,7 @@ process.env.NODE_ENV = 'test'; import * as assert from 'assert'; import * as childProcess from 'child_process'; -import { async, startServer, signup, post, api, simpleGet, port, shutdownServer } from './utils'; +import { async, startServer, signup, post, api, simpleGet, port, shutdownServer, uploadFile, getDocument } from './utils'; import * as openapi from '@redocly/openapi-core'; // Request Accept @@ -29,15 +29,86 @@ const HTML = 'text/html; charset=utf-8'; describe('Fetch resource', () => { let p: childProcess.ChildProcess; + let admin: any; + let instanceBanner: any; + let instance: any; + let alice: any; + let avatar: any; let alicesPost: any; + let image: any; + let alicesPostImage: any; + let video: any; + let alicesPostVideo: any; before(async () => { p = await startServer(); + + // admin + admin = await signup({ username: 'admin' }); + + // upload instance banner + instanceBanner = await uploadFile(admin); + //console.log('instanceBanner', instanceBanner); + + // update instance + await api('admin/update-meta', { + name: 'Instance Name', + description: 'Instance Desc', + bannerUrl: instanceBanner.url, + }, admin); + + instance = (await api('meta', {})).body; + //console.log('instance', instance); + + // signup alice = await signup({ username: 'alice' }); + //console.log('alice', alice); + + // upload avatar + avatar = await uploadFile(alice); + //console.log('avatar', avatar); + + // update profile + const token = alice.token; + + const res = await api('i/update', { + name: 'Alice', + description: 'Alice Desc', + avatarId: avatar.id, + }, alice); + + alice = res.body; + alice.token = token; // tokenはsignup以外では返ってこない + //console.log('alice-2', alice); + + // post alicesPost = await post(alice, { - text: 'test' + text: 'test', + }); + //console.log('alicesPost', alicesPost); + + // upload image + image = await uploadFile(alice); + //console.log('image', image); + + // post image + alicesPostImage = await post(alice, { + text: 'image', + fileIds: [ image.id ], + }); + //console.log('alicesPostImage', alicesPostImage); + + // upload video + video = await uploadFile(alice, 'anime.mp4'); + //console.log('video', video); + + // post video + alicesPostVideo = await post(alice, { + text: 'video', + fileIds: [ video.id ], }); + //console.log('alicesPostVideo', alicesPostVideo); }); after(async () => { @@ -200,4 +271,185 @@ describe('Fetch resource', () => { assert.strictEqual(res.type, 'application/json; charset=utf-8'); })); }); + + describe('HTML meta', () => { + const parse = (doc: Document) => { + return { + // Title + 'title': doc.querySelector('title')?.textContent, + 'og:title': doc.querySelector('meta[property="og:title"]')?.getAttribute('content'), + 'og:site_name': doc.querySelector('meta[property="og:site_name"]')?.getAttribute('content'), + + // Description + 'description': doc.querySelector('meta[name=description]')?.getAttribute('content'), + 'og:description': doc.querySelector('meta[property="og:description"]')?.getAttribute('content'), + + // Twitter card + 'twitter:card': doc.querySelector('meta[name="twitter:card"]')?.getAttribute('content'), + + // Misskey + 'misskey:user-username': doc.querySelector('meta[name="misskey:user-username"]')?.getAttribute('content'), + 'misskey:user-id': doc.querySelector('meta[name="misskey:user-id"]')?.getAttribute('content'), + + // Generic - og + 'og:url': doc.querySelector('meta[property="og:url"]')?.getAttribute('content'), + 'og:image': doc.querySelector('meta[property="og:image"]')?.getAttribute('content'), + 'og:published_time': doc.querySelector('meta[property="og:published_time"]')?.getAttribute('content'), + + // Player - Twitter + 'twitter:player': doc.querySelector('meta[name="twitter:player"]')?.getAttribute('content'), + 'twitter:player:width': doc.querySelector('meta[name="twitter:player:width"]')?.getAttribute('content'), + 'twitter:player:height': doc.querySelector('meta[name="twitter:player:height"]')?.getAttribute('content'), + 'twitter:player:stream': doc.querySelector('meta[name="twitter:player:stream"]')?.getAttribute('content'), + 'twitter:player:stream:content_type': doc.querySelector('meta[name="twitter:player:stream:content_type"]')?.getAttribute('content'), + + // Player - og + 'og:video:url': doc.querySelector('meta[property="og:video:url"]')?.getAttribute('content'), + 'og:video:secure_url': doc.querySelector('meta[property="og:video:secure_url"]')?.getAttribute('content'), + 'og:video:type': doc.querySelector('meta[property="og:video:type"]')?.getAttribute('content'), + 'og:video:width': doc.querySelector('meta[property="og:video:width"]')?.getAttribute('content'), + 'og:video:height': doc.querySelector('meta[property="og:video:height"]')?.getAttribute('content'), + }; + } + + it('/', async(async () => { + const parsed = parse(await getDocument('/')); + + assert.deepStrictEqual(parsed, { + 'title': instance.name, + 'og:title': instance.name, + 'og:site_name': instance.name, + 'description': instance.description, + 'og:description': instance.description, + 'twitter:card': 'summary', + 'misskey:user-username': undefined, + 'misskey:user-id': undefined, + 'og:url': undefined, + 'og:image': instanceBanner.url, + 'og:published_time': undefined, + 'twitter:player': undefined, + 'twitter:player:width': undefined, + 'twitter:player:height': undefined, + 'twitter:player:stream': undefined, + 'twitter:player:stream:content_type': undefined, + 'og:video:url': undefined, + 'og:video:secure_url': undefined, + 'og:video:type': undefined, + 'og:video:width': undefined, + 'og:video:height': undefined, + }); + })); + + it('user', async(async () => { + const parsed = parse(await getDocument(`/@${alice.username}`)); + + assert.deepStrictEqual(parsed, { + 'title': `${alice.name} (@${alice.username}) | ${instance.name}`, + 'og:title': `${alice.name} (@${alice.username})`, + 'og:site_name': instance.name, + 'description': alice.description, + 'og:description': alice.description, + 'twitter:card': 'summary', + 'misskey:user-username': alice.username, + 'misskey:user-id': alice.id, + 'og:url': `http://misskey.local/@${alice.username}`, + 'og:image': alice.avatarUrl, + 'og:published_time': undefined, + 'twitter:player': undefined, + 'twitter:player:width': undefined, + 'twitter:player:height': undefined, + 'twitter:player:stream': undefined, + 'twitter:player:stream:content_type': undefined, + 'og:video:url': undefined, + 'og:video:secure_url': undefined, + 'og:video:type': undefined, + 'og:video:width': undefined, + 'og:video:height': undefined, + }); + })); + + it('note', async(async () => { + const parsed = parse(await getDocument(`/notes/${alicesPost.id}`)); + + assert.deepStrictEqual(parsed, { + 'title': `${alice.name} (@${alice.username}) | ${instance.name}`, + 'og:title': `${alice.name} (@${alice.username})`, + 'og:site_name': instance.name, + 'description': alicesPost.text, + 'og:description': alicesPost.text, + 'twitter:card': 'summary', + 'misskey:user-username': alice.username, + 'misskey:user-id': alice.id, + 'og:url': `http://misskey.local/notes/${alicesPost.id}`, + 'og:image': alice.avatarUrl, + 'og:published_time': alicesPost.createdAt, + 'twitter:player': undefined, + 'twitter:player:width': undefined, + 'twitter:player:height': undefined, + 'twitter:player:stream': undefined, + 'twitter:player:stream:content_type': undefined, + 'og:video:url': undefined, + 'og:video:secure_url': undefined, + 'og:video:type': undefined, + 'og:video:width': undefined, + 'og:video:height': undefined, + }); + })); + + it('note with image', async(async () => { + const parsed = parse(await getDocument(`/notes/${alicesPostImage.id}`)); + + assert.deepStrictEqual(parsed, { + 'title': `${alice.name} (@${alice.username}) | ${instance.name}`, + 'og:title': `${alice.name} (@${alice.username})`, + 'og:site_name': instance.name, + 'description': `${alicesPostImage.text} (📎1)`, + 'og:description': `${alicesPostImage.text} (📎1)`, + 'twitter:card': 'summary', + 'misskey:user-username': alice.username, + 'misskey:user-id': alice.id, + 'og:url': `http://misskey.local/notes/${alicesPostImage.id}`, + 'og:image': alicesPostImage.files[0].thumbnailUrl, + 'og:published_time': alicesPostImage.createdAt, + 'twitter:player': undefined, + 'twitter:player:width': undefined, + 'twitter:player:height': undefined, + 'twitter:player:stream': undefined, + 'twitter:player:stream:content_type': undefined, + 'og:video:url': undefined, + 'og:video:secure_url': undefined, + 'og:video:type': undefined, + 'og:video:width': undefined, + 'og:video:height': undefined, + }); + })); + + it('note with video', async(async () => { + const parsed = parse(await getDocument(`/notes/${alicesPostVideo.id}`)); + + assert.deepStrictEqual(parsed, { + 'title': `${alice.name} (@${alice.username}) | ${instance.name}`, + 'og:title': `${alice.name} (@${alice.username})`, + 'og:site_name': instance.name, + 'description': `${alicesPostVideo.text} (📎1)`, + 'og:description': `${alicesPostVideo.text} (📎1)`, + 'twitter:card': 'player', + 'misskey:user-username': alice.username, + 'misskey:user-id': alice.id, + 'og:url': `http://misskey.local/notes/${alicesPostVideo.id}`, + 'og:image': alicesPostVideo.files[0].thumbnailUrl, + 'og:published_time': alicesPostVideo.createdAt, + 'twitter:player': `http://misskey.local/notes/${alicesPostVideo.id}/embed`, + 'twitter:player:width': '530', + 'twitter:player:height': '255', + 'twitter:player:stream': alicesPostVideo.files[0].url, + 'twitter:player:stream:content_type': alicesPostVideo.files[0].type, + 'og:video:url': `http://misskey.local/notes/${alicesPostVideo.id}/embed`, + 'og:video:secure_url': `http://misskey.local/notes/${alicesPostVideo.id}/embed`, + 'og:video:type': 'text/html', + 'og:video:width': '530', + 'og:video:height': '255', + }); + })); + }); }); diff --git a/test/get-file-info.ts b/test/get-file-info.ts index 08031f5101..03cdd69a85 100644 --- a/test/get-file-info.ts +++ b/test/get-file-info.ts @@ -111,7 +111,7 @@ describe('Get file info', () => { }, width: 256, height: 256, - blurhash: 'ySFzz31U1?=nZO,+JOofR*oHnhjYX6S50J=n]DEol8JEw}R*xaNgXTW=ruxBxbWZS2obe.n~bFaxR%s*aKoIW.WY=}NgOAs*enoIWU' + blurhash: 'yGCOe55x1Y]^Uw=UAmoMR+oHnif6baR:0J=m|}A3pwE.,wR*xGR;S~kCiy$d=}NgJPxAa0w@O9fQR%s*nOf6XRNg=zNgNsskaKoIa#' }); })); @@ -129,7 +129,7 @@ describe('Get file info', () => { }, width: 256, height: 256, - blurhash: 'ySFzz31U1?=nZO,+JOofR*oHnhjYX6S50J=n]DEol8JEw}R*xaNgXTW=ruxBxbWZS2obe.n~bFaxR%s*aKoIW.WY=}NgOAs*enoIWU' + blurhash: 'yGCOe55x1Y]^Uw=UAmoMR+oHnif6baR:0J=m|}A3pwE.,wR*xGR;S~kCiy$d=}NgJPxAa0w@O9fQR%s*nOf6XRNg=zNgNsskaKoIa#' }); })); diff --git a/test/resources/anime.mp4 b/test/resources/anime.mp4 new file mode 100644 index 0000000000..66b78b053e Binary files /dev/null and b/test/resources/anime.mp4 differ diff --git a/test/test.yml b/test/test.yml index dd27604809..297d2a21b0 100644 --- a/test/test.yml +++ b/test/test.yml @@ -8,7 +8,6 @@ db: pass: '' redis: host: localhost - port: 6379 + port: 56311 id: aid - -# docker run --rm -p 54311:5432 --name pgtest -e POSTGRES_DB=test-misskey -e POSTGRES_HOST_AUTH_METHOD=trust postgres:12-alpine +autoAdmin: true diff --git a/test/utils.ts b/test/utils.ts index 8eb7b3cec4..c8000c5d3e 100644 --- a/test/utils.ts +++ b/test/utils.ts @@ -1,10 +1,16 @@ import * as childProcess from 'child_process'; import fetch from 'node-fetch'; import * as http from 'http'; +import * as fs from 'fs'; +import * as path from 'path'; import loadConfig from '../src/config/load'; import { SIGKILL } from 'constants'; import { createConnection, getConnection } from 'typeorm'; import { entities } from '../src/db/postgre'; +import { getHtml } from '../src/misc/fetch'; +import { JSDOM } from 'jsdom'; +import * as FormData from 'form-data'; +import got from 'got'; const config = loadConfig(); export const port = config.port; @@ -133,6 +139,31 @@ export const post = async (user: any, params?: any): Promise => { return res.body ? res.body.createdNote : null; }; +/** + * Upload file + * @param user User + * @param _path Optional, absolute path or relative from ./resources/ + */ + export const uploadFile = async (user: any, _path?: string): Promise => { + const absPath = _path == null ? `${__dirname}/resources/Lenna.jpg` : path.isAbsolute(_path) ? _path : `${__dirname}/resources/${_path}`; + + const formData = new FormData(); + formData.append('i', user.token); + formData.append('file', fs.createReadStream(absPath)); + formData.append('force', 'true'); + + const res = await got(`http://localhost:${port}/api/drive/files/create`, { + method: 'POST', + body: formData, + timeout: 30 * 1000, + retry: 0, + }); + + const body = res.statusCode !== 204 ? await JSON.parse(res.body) : null; + + return body; +}; + export const simpleGet = async (path: string, accept = '*/*'): Promise<{ status?: number, type?: string, location?: string }> => { // node-fetchだと3xxを取れない return await new Promise((resolve, reject) => { @@ -155,3 +186,10 @@ export const simpleGet = async (path: string, accept = '*/*'): Promise<{ status? req.end(); }); }; + +export const getDocument = async (path: string): Promise => { + const html = await getHtml(`http://localhost:${port}${path}`); + const { window } = new JSDOM(html); + const doc = window.document; + return doc; +}; diff --git a/yarn.lock b/yarn.lock index c1b42da18d..462ff238fe 100644 --- a/yarn.lock +++ b/yarn.lock @@ -87,6 +87,16 @@ ky "^0.25.1" ky-universal "^0.8.2" +"@discordapp/twemoji@13.1.1": + version "13.1.1" + resolved "https://registry.yarnpkg.com/@discordapp/twemoji/-/twemoji-13.1.1.tgz#f750d491ffb740eca619fac0c63650c1de7fff91" + integrity sha512-WDnPjWq/trfCcZk7dzQ2cYH5v5XaIfPzyixJ//O9XKilYYZRVS3p61vFvax5qMwanMMbnNG1iOzeqHKtivO32A== + dependencies: + fs-extra "^8.0.1" + jsonfile "^5.0.0" + twemoji-parser "13.1.0" + universalify "^0.1.2" + "@discoveryjs/json-ext@^0.5.0": version "0.5.2" resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.2.tgz#8f03a22a04de437254e8ce8cc84ba39689288752" @@ -127,12 +137,12 @@ resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.1.2.tgz#c1095b1bbabf19f37f9ff0719db38d92a410bcfe" integrity sha512-wBaAPGz1Awxg05e0PBRkDRuTsy4B3dpBm+zreTTyd9TH4uUM27cAL4xWyWR0rLJCrRwzVsQ4hF3FvM6rqydKPA== -"@fortawesome/fontawesome-svg-core@6.1.1": - version "6.1.1" - resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.1.1.tgz#3424ec6182515951816be9b11665d67efdce5b5f" - integrity sha512-NCg0w2YIp81f4V6cMGD9iomfsIj7GWrqmsa0ZsPh59G7PKiGN1KymZNxmF00ssuAlo/VZmpK6xazsGOwzKYUMg== +"@fortawesome/fontawesome-svg-core@6.1.2": + version "6.1.2" + resolved "https://registry.yarnpkg.com/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.1.2.tgz#11e2e8583a7dea75d734e4d0e53d91c63fae7511" + integrity sha512-853G/Htp0BOdXnPoeCPTjFrVwyrJHpe8MhjB/DYE9XjwhnNDfuBCd3aKc2YUYbEfHEcBws4UAA0kA9dymZKGjA== dependencies: - "@fortawesome/fontawesome-common-types" "6.1.1" + "@fortawesome/fontawesome-common-types" "6.1.2" "@fortawesome/free-brands-svg-icons@6.1.2": version "6.1.2" @@ -141,12 +151,12 @@ dependencies: "@fortawesome/fontawesome-common-types" "6.1.2" -"@fortawesome/free-regular-svg-icons@6.1.1": - version "6.1.1" - resolved "https://registry.yarnpkg.com/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.1.1.tgz#3f2f58262a839edf0643cbacee7a8a8230061c98" - integrity sha512-xXiW7hcpgwmWtndKPOzG+43fPH7ZjxOaoeyooptSztGmJxCAflHZxXNK0GcT0uEsR4jTGQAfGklDZE5NHoBhKg== +"@fortawesome/free-regular-svg-icons@6.1.2": + version "6.1.2" + resolved "https://registry.yarnpkg.com/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.1.2.tgz#9f04009098addcc11d0d185126f058ed042c3099" + integrity sha512-xR4hA+tAwsaTHGfb+25H1gVU/aJ0Rzu+xIUfnyrhaL13yNQ7TWiI2RvzniAaB+VGHDU2a+Pk96Ve+pkN3/+TTQ== dependencies: - "@fortawesome/fontawesome-common-types" "6.1.1" + "@fortawesome/fontawesome-common-types" "6.1.2" "@fortawesome/free-solid-svg-icons@6.1.2": version "6.1.2" @@ -652,6 +662,11 @@ resolved "https://registry.yarnpkg.com/@types/jsonld/-/jsonld-1.5.6.tgz#4396c0b17128abf5773bb68b5453b88fc565b0d4" integrity sha512-OUcfMjRie5IOrJulUQwVNvV57SOdKcTfBj3pjXNxzXqeOIrY2aGDNGW/Tlp83EQPkz4tCE6YWVrGuc/ZeaAQGg== +"@types/jsrsasign@10.5.2": + version "10.5.2" + resolved "https://registry.yarnpkg.com/@types/jsrsasign/-/jsrsasign-10.5.2.tgz#c8d5a7bccffd2fdee73553a130876a88e91419ec" + integrity sha512-oroCALq37fnUKPRYatawNq3oBNITN7lROpy6JBUanYLhuMZwG5shVxCyZ1/wM3RQCNJ/Ac5/+g7yZaZ+tVBy3A== + "@types/katex@0.14.0": version "0.14.0" resolved "https://registry.yarnpkg.com/@types/katex/-/katex-0.14.0.tgz#b84c0afc3218069a5ad64fe2a95321881021b5fe" @@ -1618,24 +1633,11 @@ append-field@^1.0.0: resolved "https://registry.yarnpkg.com/append-field/-/append-field-1.0.0.tgz#1e3440e915f0b1203d23748e78edd7b9b5b43e56" integrity sha1-HjRA6RXwsSA9I3SOeO3XubW0PlY= -aproba@^1.0.3: - version "1.2.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" - integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== - archy@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" integrity sha1-+cjBN1fMHde8N5rHeyxipcKGjEA= -are-we-there-yet@~1.1.2: - version "1.1.5" - resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" - integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== - dependencies: - delegates "^1.0.0" - readable-stream "^2.0.6" - arg@^4.1.0: version "4.1.3" resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" @@ -2704,10 +2706,10 @@ color-name@^1.0.0, color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== -color-string@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.6.0.tgz#c3915f61fe267672cb7e1e064c9d692219f6c312" - integrity sha512-c/hGS+kRWJutUBEngKKmk4iH3sD59MBkoxVapS/0wgpCz2u7XsNloxknyvBhzwEs1IbV36D9PwqLPJ2DTu3vMA== +color-string@^1.9.0: + version "1.9.1" + resolved "https://registry.yarnpkg.com/color-string/-/color-string-1.9.1.tgz#4467f9146f036f855b764dfb5bf8582bf342c7a4" + integrity sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg== dependencies: color-name "^1.0.0" simple-swizzle "^0.2.2" @@ -2717,13 +2719,13 @@ color-support@^1.1.3: resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" integrity sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg== -color@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/color/-/color-4.0.1.tgz#21df44cd10245a91b1ccf5ba031609b0e10e7d67" - integrity sha512-rpZjOKN5O7naJxkH2Rx1sZzzBgaiWECc6BYXjeCE6kF0kcASJYbUq02u7JqIHwCb/j3NhV+QhRL2683aICeGZA== +color@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/color/-/color-4.2.3.tgz#d781ecb5e57224ee43ea9627560107c0e0c6463a" + integrity sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A== dependencies: color-convert "^2.0.1" - color-string "^1.6.0" + color-string "^1.9.0" colord@^2.9.1: version "2.9.1" @@ -2858,11 +2860,6 @@ consola@^2.15.0: resolved "https://registry.yarnpkg.com/consola/-/consola-2.15.0.tgz#40fc4eefa4d2f8ef2e2806147f056ea207fcc0e9" integrity sha512-vlcSGgdYS26mPf7qNi+dCisbhiyDnrN1zaRbw3CSuc2wGOMEGGPsp46PdRG5gqXwgtJfjxDkxRNAgRPr1B77vQ== -console-control-strings@^1.0.0, console-control-strings@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" - integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= - consolidate@^0.15.1: version "0.15.1" resolved "https://registry.yarnpkg.com/consolidate/-/consolidate-0.15.1.tgz#21ab043235c71a07d45d9aad98593b0dba56bab7" @@ -3292,13 +3289,6 @@ debug@^3.1.1, debug@^3.2.6: dependencies: ms "^2.1.1" -debug@^4.3.4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== - dependencies: - ms "2.1.2" - debug@~3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" @@ -3469,10 +3459,10 @@ detect-indent@^5.0.0: resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" integrity sha1-OHHMCmoALow+Wzz38zYmRnXwa50= -detect-libc@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" - integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= +detect-libc@^2.0.0, detect-libc@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.1.tgz#e1897aa88fa6ad197862937fbc0441ef352ee0cd" + integrity sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w== detect-node@2.1.0, detect-node@^2.0.4, detect-node@^2.1.0: version "2.1.0" @@ -3949,7 +3939,7 @@ eslint-visitor-keys@^2.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== -eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^3.3.0: +eslint-visitor-keys@^3.3.0: version "3.3.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz#f6480fa6b1f30efe2d1968aa8ac745b862469826" integrity sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA== @@ -4524,6 +4514,15 @@ fs-constants@^1.0.0: resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== +fs-extra@^8.0.1: + version "8.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" + integrity sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^4.0.0" + universalify "^0.1.0" + fs-minipass@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-2.1.0.tgz#7f5036fdbf12c63c169190cbe4199c852271f9fb" @@ -4592,20 +4591,6 @@ functions-have-names@^1.2.2: resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834" integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ== -gauge@~2.7.3: - version "2.7.4" - resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" - integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= - dependencies: - aproba "^1.0.3" - console-control-strings "^1.0.0" - has-unicode "^2.0.0" - object-assign "^4.1.0" - signal-exit "^3.0.0" - string-width "^1.0.1" - strip-ansi "^3.0.1" - wide-align "^1.1.0" - get-caller-file@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a" @@ -4860,6 +4845,11 @@ graceful-fs@^4.0.0, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6, resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.9.tgz#041b05df45755e587a24942279b9d113146e1c96" integrity sha512-NtNxqUcXgpW2iMrfqSfR73Glt39K+BLwWsPs94yR63v45T0Wbej7eRmL5cWfwEgqXnmjQp3zaJTshdRW/qC2ZQ== +graceful-fs@^4.2.0: + version "4.2.10" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" + integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA== + growl@1.10.5: version "1.10.5" resolved "https://registry.yarnpkg.com/growl/-/growl-1.10.5.tgz#f2735dc2283674fa67478b10181059355c369e5e" @@ -5042,11 +5032,6 @@ has-tostringtag@^1.0.0: dependencies: has-symbols "^1.0.2" -has-unicode@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" - integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= - has-value@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" @@ -6141,6 +6126,22 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" +jsonfile@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" + integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= + optionalDependencies: + graceful-fs "^4.1.6" + +jsonfile@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-5.0.0.tgz#e6b718f73da420d612823996fdf14a03f6ff6922" + integrity sha512-NQRZ5CRo74MhMMC3/3r5g2k4fjodJ/wh8MxjFbCViWKFjxrnudWSY5vomh+23ZaXzAS7J3fBZIR2dV6WbmfM0w== + dependencies: + universalify "^0.1.2" + optionalDependencies: + graceful-fs "^4.1.6" + jsonld@5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/jsonld/-/jsonld-5.2.0.tgz#d1e8af38a334cb95edf0f2ae4e2b58baf8d2b5a9" @@ -7327,10 +7328,10 @@ node-addon-api@^1.2.0: resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.2.tgz#3df30b95720b53c24e59948b49532b662444f54d" integrity sha512-ibPK3iA+vaY1eEjESkQkM0BbCqFOaZMiXRTtdB0u7b4djtY6JnsjvPdUHVMg6xQt3B8fpTTWHI9A+ADjM9frzg== -node-addon-api@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-4.2.0.tgz#117cbb5a959dff0992e1c586ae0393573e4d2a87" - integrity sha512-eazsqzwG2lskuzBqCGPi7Ac2UgOoMz8JVOXVhTvvPDYhthvNpefx8jWD8Np7Gv+2Sz0FlPWZk0nJV0z598Wn8Q== +node-addon-api@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-5.0.0.tgz#7d7e6f9ef89043befdb20c1989c905ebde18c501" + integrity sha512-CvkDw2OEnme7ybCykJpVcKH+uAOLV2qLqiyla128dN9TkEWfrYmxG6C2boDe5KcNQqZF3orkqzGgOMvZ/JNekA== node-fetch@2.6.7, node-fetch@^2.6.1: version "2.6.7" @@ -7455,16 +7456,6 @@ npm-run-path@^4.0.1: dependencies: path-key "^3.0.0" -npmlog@^4.0.1: - version "4.1.2" - resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" - integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== - dependencies: - are-we-there-yet "~1.1.2" - console-control-strings "~1.1.0" - gauge "~2.7.3" - set-blocking "~2.0.0" - nprogress@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/nprogress/-/nprogress-0.2.0.tgz#cb8f34c53213d895723fcbab907e9422adbcafb1" @@ -7509,7 +7500,7 @@ object-assign@^3.0.0: resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-3.0.0.tgz#9bedd5ca0897949bca47e7ff408062d549f587f2" integrity sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I= -object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4.0.1, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -8460,19 +8451,18 @@ postgres-interval@^1.1.0: dependencies: xtend "^4.0.0" -prebuild-install@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.0.0.tgz#3c5ce3902f1cb9d6de5ae94ca53575e4af0c1574" - integrity sha512-IvSenf33K7JcgddNz2D5w521EgO+4aMMjFt73Uk9FRzQ7P+QZPKrp7qPsDydsSwjGt3T5xRNnM1bj1zMTD5fTA== +prebuild-install@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/prebuild-install/-/prebuild-install-7.1.1.tgz#de97d5b34a70a0c81334fd24641f2a1702352e45" + integrity sha512-jAXscXWMcCK8GgCoHOfIr0ODh5ai8mj63L2nWrjuAgXE6tDyYGnx4/8o/rCgU+B4JSyZBKbeZqzhtwtC3ovxjw== dependencies: - detect-libc "^1.0.3" + detect-libc "^2.0.0" expand-template "^2.0.3" github-from-package "0.0.0" minimist "^1.2.3" mkdirp-classic "^0.5.3" napi-build-utils "^1.0.1" node-abi "^3.3.0" - npmlog "^4.0.1" pump "^3.0.0" rc "^1.2.7" simple-get "^4.0.0" @@ -8898,7 +8888,7 @@ readable-stream@1.1.x: string_decoder "^1.1.1" util-deprecate "^1.0.1" -readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: +readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6, readable-stream@~2.3.6: version "2.3.7" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== @@ -9388,14 +9378,7 @@ semver@^6.0.0, semver@^6.3.0: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== -semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.6: - version "7.3.7" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" - integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== - dependencies: - lru-cache "^6.0.0" - -semver@^7.3.7: +semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.6, semver@^7.3.7: version "7.3.7" resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.7.tgz#12c5b649afdbf9049707796e22a4028814ce523f" integrity sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g== @@ -9458,7 +9441,7 @@ serve-static@1.14.2: parseurl "~1.3.3" send "0.17.2" -set-blocking@^2.0.0, set-blocking@~2.0.0: +set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= @@ -9508,17 +9491,17 @@ shallow-clone@^3.0.0: dependencies: kind-of "^6.0.2" -sharp@0.29.3: - version "0.29.3" - resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.29.3.tgz#0da183d626094c974516a48fab9b3e4ba92eb5c2" - integrity sha512-fKWUuOw77E4nhpyzCCJR1ayrttHoFHBT2U/kR/qEMRhvPEcluG4BKj324+SCO1e84+knXHwhJ1HHJGnUt4ElGA== +sharp@0.30.7: + version "0.30.7" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.30.7.tgz#7862bda98804fdd1f0d5659c85e3324b90d94c7c" + integrity sha512-G+MY2YW33jgflKPTXXptVO28HvNOo9G3j0MybYAHeEmby+QuD2U98dT6ueht9cv/XDqZspSpIhoSW+BAKJ7Hig== dependencies: - color "^4.0.1" - detect-libc "^1.0.3" - node-addon-api "^4.2.0" - prebuild-install "^7.0.0" - semver "^7.3.5" - simple-get "^4.0.0" + color "^4.2.3" + detect-libc "^2.0.1" + node-addon-api "^5.0.0" + prebuild-install "^7.1.1" + semver "^7.3.7" + simple-get "^4.0.1" tar-fs "^2.1.1" tunnel-agent "^0.6.0" @@ -9584,7 +9567,7 @@ sigmund@^1.0.1: resolved "https://registry.yarnpkg.com/sigmund/-/sigmund-1.0.1.tgz#3ff21f198cad2175f9f3b781853fd94d0d19b590" integrity sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA= -signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3: +signal-exit@^3.0.2, signal-exit@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.3.tgz#a1410c2edd8f077b08b4e253c8eacfcaf057461c" integrity sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA== @@ -9594,7 +9577,7 @@ simple-concat@^1.0.0: resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.1.tgz#f46976082ba35c2263f1c8ab5edfe26c41c9552f" integrity sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q== -simple-get@^4.0.0: +simple-get@^4.0.0, simple-get@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-4.0.1.tgz#4a39db549287c979d352112fa03fd99fd6bc3543" integrity sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA== @@ -10628,6 +10611,11 @@ tweetnacl@^0.14.3, tweetnacl@~0.14.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= +twemoji-parser@13.1.0: + version "13.1.0" + resolved "https://registry.yarnpkg.com/twemoji-parser/-/twemoji-parser-13.1.0.tgz#65e7e449c59258791b22ac0b37077349127e3ea4" + integrity sha512-AQOzLJpYlpWMy8n+0ATyKKZzWlZBJN+G0C+5lhX7Ftc2PeEVdUU/7ns2Pn2vVje26AIZ/OHwFoUbdv6YYD/wGg== + type-check@^0.4.0, type-check@~0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1" @@ -10798,7 +10786,7 @@ unique-stream@^2.0.2: json-stable-stringify-without-jsonify "^1.0.1" through2-filter "^3.0.0" -universalify@^0.1.2: +universalify@^0.1.0, universalify@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== @@ -11471,7 +11459,7 @@ which@^1.1.1, which@^1.2.14, which@^1.2.9: dependencies: isexe "^2.0.0" -wide-align@1.1.3, wide-align@^1.1.0: +wide-align@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==