Merge branch 'develop'

This commit is contained in:
syuilo 2023-02-22 18:06:25 +09:00
commit 7658351041
606 changed files with 4254 additions and 8195 deletions

View File

@ -2,10 +2,10 @@
"name": "Misskey", "name": "Misskey",
"dockerComposeFile": "docker-compose.yml", "dockerComposeFile": "docker-compose.yml",
"service": "app", "service": "app",
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}", "workspaceFolder": "/workspace",
"features": { "features": {
"ghcr.io/devcontainers-contrib/features/pnpm:2": {} "ghcr.io/devcontainers-contrib/features/pnpm:2": {}
}, },
"forwardPorts": [3000], "forwardPorts": [3000],
"postCreateCommand": ".devcontainer/init.sh" "postCreateCommand": "sudo chmod 755 .devcontainer/init.sh && .devcontainer/init.sh"
} }

View File

@ -7,7 +7,7 @@ services:
dockerfile: Dockerfile dockerfile: Dockerfile
volumes: volumes:
- ../..:/workspaces:cached - ../:/workspace:cached
command: sleep infinity command: sleep infinity
@ -21,7 +21,7 @@ services:
networks: networks:
- internal_network - internal_network
volumes: volumes:
- ../redis:/data - redis-data:/data
healthcheck: healthcheck:
test: "redis-cli ping" test: "redis-cli ping"
interval: 5s interval: 5s
@ -37,7 +37,7 @@ services:
POSTGRES_PASSWORD: postgres POSTGRES_PASSWORD: postgres
POSTGRES_DB: misskey POSTGRES_DB: misskey
volumes: volumes:
- ../db:/var/lib/postgresql/data - postgres-data:/var/lib/postgresql/data
healthcheck: healthcheck:
test: "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB" test: "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"
interval: 5s interval: 5s
@ -45,6 +45,7 @@ services:
volumes: volumes:
postgres-data: postgres-data:
redis-data:
networks: networks:
internal_network: internal_network:

View File

@ -2,6 +2,7 @@
set -xe set -xe
sudo chown -R node /workspace
git submodule update --init git submodule update --init
pnpm install --frozen-lockfile pnpm install --frozen-lockfile
cp .devcontainer/devcontainer.yml .config/default.yml cp .devcontainer/devcontainer.yml .config/default.yml

View File

@ -5,6 +5,7 @@ indent_style = tab
indent_size = 2 indent_size = 2
charset = utf-8 charset = utf-8
insert_final_newline = true insert_final_newline = true
end_of_line = lf
[*.yml] [*.yml]
indent_style = space indent_style = space

1
.gitattributes vendored
View File

@ -5,3 +5,4 @@
*.glb -diff -text *.glb -diff -text
*.blend -diff -text *.blend -diff -text
*.afdesign -diff -text *.afdesign -diff -text
* text=auto eol=lf

View File

@ -15,7 +15,10 @@ jobs:
- name: Check out the repo - name: Check out the repo
uses: actions/checkout@v3.3.0 uses: actions/checkout@v3.3.0
- name: Set up Docker Buildx - name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2.3.0 uses: docker/setup-buildx-action@v2.3.0
with:
platforms: linux/amd64,linux/arm64
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v4 uses: docker/metadata-action@v4
@ -27,10 +30,13 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and Push to Docker Hub - name: Build and Push to Docker Hub
uses: docker/build-push-action@v3 uses: docker/build-push-action@v4
with: with:
builder: ${{ steps.buildx.outputs.name }}
context: . context: .
push: true push: true
platforms: ${{ steps.buildx.outputs.platforms }}
provenance: false
tags: misskey/misskey:develop tags: misskey/misskey:develop
labels: develop labels: develop
cache-from: type=gha cache-from: type=gha

View File

@ -13,6 +13,11 @@ jobs:
steps: steps:
- name: Check out the repo - name: Check out the repo
uses: actions/checkout@v3.3.0 uses: actions/checkout@v3.3.0
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v2.3.0
with:
platforms: linux/amd64,linux/arm64
- name: Docker meta - name: Docker meta
id: meta id: meta
uses: docker/metadata-action@v4 uses: docker/metadata-action@v4
@ -31,9 +36,14 @@ jobs:
username: ${{ secrets.DOCKER_USERNAME }} username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }} password: ${{ secrets.DOCKER_PASSWORD }}
- name: Build and Push to Docker Hub - name: Build and Push to Docker Hub
uses: docker/build-push-action@v3 uses: docker/build-push-action@v4
with: with:
builder: ${{ steps.buildx.outputs.name }}
context: . context: .
push: true push: true
platforms: ${{ steps.buildx.outputs.platforms }}
provenance: false
tags: ${{ steps.meta.outputs.tags }} tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }} labels: ${{ steps.meta.outputs.labels }}
cache-from: type=gha
cache-to: type=gha,mode=max

View File

@ -36,6 +36,8 @@ jobs:
- backend - backend
- frontend - frontend
- sw - sw
lint:
- eslint
steps: steps:
- uses: actions/checkout@v3.3.0 - uses: actions/checkout@v3.3.0
with: with:
@ -51,4 +53,4 @@ jobs:
cache: 'pnpm' cache: 'pnpm'
- run: corepack enable - run: corepack enable
- run: pnpm i --frozen-lockfile - run: pnpm i --frozen-lockfile
- run: pnpm --filter ${{ matrix.workspace }} run lint - run: pnpm --filter ${{ matrix.workspace }} run ${{ matrix.lint }}

View File

@ -1,5 +1,6 @@
{ {
"search.exclude": { "search.exclude": {
"**/node_modules": true "**/node_modules": true
} },
"typescript.tsdk": "node_modules/typescript/lib"
} }

View File

@ -8,6 +8,28 @@
You should also include the user name that made the change. You should also include the user name that made the change.
--> -->
## 13.7.0 (2023/02/22)
### Changes
- チャット機能が削除されました
### Improvements
- Server: URLプレビューsummalyはプロキシを通すように
- Client: 2FA設定のUIをまともにした
- セキュリティキーの名前を変更できるように
- enhance(client): add quiz preset for play
- 広告開始時期を設定できるように
- みつけるで公開ロール一覧とそのメンバーを閲覧できるように
- enhance(client): MFMのx3, x4が含まれていたらートをたたむように
- enhance(client): make possible to reload page of window
### Bugfixes
- ユーザー検索ダイアログでローカルユーザーを絞って検索できない問題を修正
- fix(client): MkHeader及びデッキのカラムでチャンネル一覧を選択したとき、最大5個までしか表示されない
- 管理画面の広告を10個以上見えるように
- Moderation note が保存できない
- ユーザーのハッシュタグ検索が機能していないのを修正
## 13.6.1 (2023/02/12) ## 13.6.1 (2023/02/12)
### Improvements ### Improvements

View File

@ -114,6 +114,7 @@ command.
### Dev Container ### Dev Container
Instead of running `pnpm` locally, you can use Dev Container to set up your development environment. Instead of running `pnpm` locally, you can use Dev Container to set up your development environment.
To use Dev Container, open the project directory on VSCode with Dev Containers installed. To use Dev Container, open the project directory on VSCode with Dev Containers installed.
**Note:** If you are using Windows, please clone the repository with WSL. Using Git for Windows will result in broken files due to the difference in how newlines are handled.
It will run the following command automatically inside the container. It will run the following command automatically inside the container.
``` bash ``` bash

View File

@ -1,3 +1,5 @@
# syntax = docker/dockerfile:1.4
ARG NODE_VERSION=18.13.0-bullseye ARG NODE_VERSION=18.13.0-bullseye
FROM node:${NODE_VERSION} AS builder FROM node:${NODE_VERSION} AS builder
@ -14,16 +16,16 @@ RUN corepack enable
WORKDIR /misskey WORKDIR /misskey
COPY ["pnpm-lock.yaml", "pnpm-workspace.yaml", "package.json", "./"] COPY --link ["pnpm-lock.yaml", "pnpm-workspace.yaml", "package.json", "./"]
COPY ["scripts", "./scripts"] COPY --link ["scripts", "./scripts"]
COPY ["packages/backend/package.json", "./packages/backend/"] COPY --link ["packages/backend/package.json", "./packages/backend/"]
COPY ["packages/frontend/package.json", "./packages/frontend/"] COPY --link ["packages/frontend/package.json", "./packages/frontend/"]
COPY ["packages/sw/package.json", "./packages/sw/"] COPY --link ["packages/sw/package.json", "./packages/sw/"]
RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \ RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \
pnpm i --frozen-lockfile --aggregate-output pnpm i --frozen-lockfile --aggregate-output
COPY . ./ COPY --link . ./
ARG NODE_ENV=production ARG NODE_ENV=production

View File

@ -5,9 +5,9 @@ Also, the later tasks are more indefinite and are subject to change as developme
## (1) Improve maintainability \<current phase\> ## (1) Improve maintainability \<current phase\>
This is the phase we are at now. We need to make a high-maintenance environment that can withstand future development. This is the phase we are at now. We need to make a high-maintenance environment that can withstand future development.
- Make the number of type errors zero (backend) - ~~Make the number of type errors zero (backend)~~ → Done ✔️
- Improve CI - Improve CI
- Fix tests - ~~Fix tests~~ → Done ✔️
- Fix random test failures - https://github.com/misskey-dev/misskey/issues/7985 and https://github.com/misskey-dev/misskey/issues/7986 - Fix random test failures - https://github.com/misskey-dev/misskey/issues/7985 and https://github.com/misskey-dev/misskey/issues/7986
- Add more tests - Add more tests
- ~~May need to implement a mechanism that allows for DI~~ → Done ✔️ - ~~May need to implement a mechanism that allows for DI~~ → Done ✔️

View File

@ -1,3 +1,4 @@
apiVersion: v2 apiVersion: v2
name: misskey name: misskey
version: 0.0.0 version: 0.0.0
description: This chart is created for the purpose of previewing Pull Requests. Do not use this for production use.

5
codecov.yml Normal file
View File

@ -0,0 +1,5 @@
coverage:
status:
project:
default:
only_pulls: true

View File

@ -103,6 +103,8 @@ renoted: "Renote getätigt."
cantRenote: "Renote dieses Beitrags nicht möglich." cantRenote: "Renote dieses Beitrags nicht möglich."
cantReRenote: "Renote einer Renote nicht möglich." cantReRenote: "Renote einer Renote nicht möglich."
quote: "Zitieren" quote: "Zitieren"
inChannelRenote: "Kanal-interner Renote"
inChannelQuote: "Kanal-internes Zitat"
pinnedNote: "Angeheftete Notiz" pinnedNote: "Angeheftete Notiz"
pinned: "Angeheftet" pinned: "Angeheftet"
you: "Du" you: "Du"
@ -467,6 +469,8 @@ youHaveNoGroups: "Keine Gruppen vorhanden"
joinOrCreateGroup: "Lass dich zu einer Gruppe einladen oder erstelle deine eigene." joinOrCreateGroup: "Lass dich zu einer Gruppe einladen oder erstelle deine eigene."
noHistory: "Kein Verlauf gefunden" noHistory: "Kein Verlauf gefunden"
signinHistory: "Anmeldungsverlauf" signinHistory: "Anmeldungsverlauf"
enableAdvancedMfm: "Erweitertes MFM aktivieren"
enableAnimatedMfm: "Animiertes MFM aktivieren"
doing: "In Bearbeitung …" doing: "In Bearbeitung …"
category: "Kategorie" category: "Kategorie"
tags: "Schlagwörter" tags: "Schlagwörter"
@ -945,6 +949,14 @@ selectFromPresets: "Aus Vorlagen wählen"
achievements: "Errungenschaften" achievements: "Errungenschaften"
gotInvalidResponseError: "Ungültige Antwort des Servers" gotInvalidResponseError: "Ungültige Antwort des Servers"
gotInvalidResponseErrorDescription: "Eventuell ist der Server momentan nicht erreichbar oder untergeht Wartungsarbeiten. Bitte versuche es später noch einmal." gotInvalidResponseErrorDescription: "Eventuell ist der Server momentan nicht erreichbar oder untergeht Wartungsarbeiten. Bitte versuche es später noch einmal."
thisPostMayBeAnnoying: "Dieser Beitrag stört eventuell andere Benutzer."
thisPostMayBeAnnoyingHome: "Zur Startseite schicken"
thisPostMayBeAnnoyingCancel: "Abbrechen"
thisPostMayBeAnnoyingIgnore: "Trotzdem schicken"
collapseRenotes: "Bereits gesehene Renotes verkürzt anzeigen"
internalServerError: "Serverinterner Fehler"
internalServerErrorDescription: "Im Server ist ein unerwarteter Fehler aufgetreten."
copyErrorInfo: "Fehlerdetails kopieren"
_achievements: _achievements:
earnedAt: "Freigeschaltet am" earnedAt: "Freigeschaltet am"
_types: _types:

View File

@ -103,6 +103,8 @@ renoted: "Renoted."
cantRenote: "This post can't be renoted." cantRenote: "This post can't be renoted."
cantReRenote: "A renote can't be renoted." cantReRenote: "A renote can't be renoted."
quote: "Quote" quote: "Quote"
inChannelRenote: "Channel-only Renote"
inChannelQuote: "Channel-only Quote"
pinnedNote: "Pinned note" pinnedNote: "Pinned note"
pinned: "Pin to profile" pinned: "Pin to profile"
you: "You" you: "You"
@ -468,7 +470,7 @@ joinOrCreateGroup: "Get invited to a group or create your own."
noHistory: "No history available" noHistory: "No history available"
signinHistory: "Login history" signinHistory: "Login history"
enableAdvancedMfm: "Enable advanced MFM" enableAdvancedMfm: "Enable advanced MFM"
enableAnimatedMfm: "Enable MFM with animation" enableAnimatedMfm: "Enable animated MFM"
doing: "Processing..." doing: "Processing..."
category: "Category" category: "Category"
tags: "Tags" tags: "Tags"
@ -951,6 +953,10 @@ thisPostMayBeAnnoying: "This note may annoy others."
thisPostMayBeAnnoyingHome: "Post to home timeline" thisPostMayBeAnnoyingHome: "Post to home timeline"
thisPostMayBeAnnoyingCancel: "Cancel" thisPostMayBeAnnoyingCancel: "Cancel"
thisPostMayBeAnnoyingIgnore: "Post anyway" thisPostMayBeAnnoyingIgnore: "Post anyway"
collapseRenotes: "Collapse renotes you've already seen"
internalServerError: "Internal Server Error"
internalServerErrorDescription: "The server has run into an unexpected error."
copyErrorInfo: "Copy error details"
_achievements: _achievements:
earnedAt: "Unlocked at" earnedAt: "Unlocked at"
_types: _types:

View File

@ -84,12 +84,12 @@ error: "Errore"
somethingHappened: "Si è verificato un problema" somethingHappened: "Si è verificato un problema"
retry: "Riprova" retry: "Riprova"
pageLoadError: "Caricamento pagina non riuscito. " pageLoadError: "Caricamento pagina non riuscito. "
pageLoadErrorDescription: "Questo viene normalmente causato dalla rete o dalla cache del browser. Si prega di pulire la cache, o di attendere e riprovare più tardi." pageLoadErrorDescription: "Questo problema viene normalmente causato da errori di rete o dalla cache del browser. Si prega di pulire la cache, o di attendere e riprovare più tardi."
serverIsDead: "Il server non risponde. Si prega di attendere e riprovare più tardi." serverIsDead: "Il server non risponde. Si prega di attendere e riprovare più tardi."
youShouldUpgradeClient: "Per visualizzare la pagina è necessario aggiornare il client alla nuova versione e ricaricare." youShouldUpgradeClient: "Per visualizzare la pagina è necessario aggiornare il client alla nuova versione e ricaricare."
enterListName: "Nome della lista" enterListName: "Nome della lista"
privacy: "Privacy" privacy: "Privacy"
makeFollowManuallyApprove: "Richiedi di approvare i follower manualmente" makeFollowManuallyApprove: "Approva i follower manualmente"
defaultNoteVisibility: "Privacy predefinita delle note" defaultNoteVisibility: "Privacy predefinita delle note"
follow: "Segui" follow: "Segui"
followRequest: "Richiesta di follow" followRequest: "Richiesta di follow"
@ -103,6 +103,8 @@ renoted: "Rinotato!"
cantRenote: "È impossibile rinotare questa nota." cantRenote: "È impossibile rinotare questa nota."
cantReRenote: "È impossibile rinotare una Rinota." cantReRenote: "È impossibile rinotare una Rinota."
quote: "Cita" quote: "Cita"
inChannelRenote: "Rinota nel canale"
inChannelQuote: "Cita nel canale"
pinnedNote: "Nota fissata" pinnedNote: "Nota fissata"
pinned: "Fissa sul profilo" pinned: "Fissa sul profilo"
you: "Tu" you: "Tu"
@ -129,6 +131,7 @@ unblockConfirm: "Vuoi davvero sbloccare il profilo?"
suspendConfirm: "Vuoi sospendere questo profilo?" suspendConfirm: "Vuoi sospendere questo profilo?"
unsuspendConfirm: "Vuoi revocare la sospensione si questo profilo?" unsuspendConfirm: "Vuoi revocare la sospensione si questo profilo?"
selectList: "Seleziona una lista" selectList: "Seleziona una lista"
selectChannel: "Seleziona canale"
selectAntenna: "Scegli un'antenna" selectAntenna: "Scegli un'antenna"
selectWidget: "Seleziona il riquadro" selectWidget: "Seleziona il riquadro"
editWidgets: "Modifica i riquadri" editWidgets: "Modifica i riquadri"
@ -256,6 +259,8 @@ noMoreHistory: "Non c'è più cronologia da visualizzare"
startMessaging: "Nuovo messaggio" startMessaging: "Nuovo messaggio"
nUsersRead: "Letto da {n} persone" nUsersRead: "Letto da {n} persone"
agreeTo: "Sono d'accordo con {0}" agreeTo: "Sono d'accordo con {0}"
agreeBelow: "Accetto quanto riportato sotto"
basicNotesBeforeCreateAccount: "Note importanti"
tos: "Termini di servizio" tos: "Termini di servizio"
start: "Inizia!" start: "Inizia!"
home: "Home" home: "Home"
@ -464,6 +469,8 @@ youHaveNoGroups: "Nessun gruppo"
joinOrCreateGroup: "Puoi creare il tuo gruppo o essere invitat@ a gruppi che già esistono." joinOrCreateGroup: "Puoi creare il tuo gruppo o essere invitat@ a gruppi che già esistono."
noHistory: "Nessuna cronologia" noHistory: "Nessuna cronologia"
signinHistory: "Storico degli accessi al profilo" signinHistory: "Storico degli accessi al profilo"
enableAdvancedMfm: "Attiva MFM avanzati"
enableAnimatedMfm: "Attiva MFM animati"
doing: "In corso..." doing: "In corso..."
category: "Categoria" category: "Categoria"
tags: "Tag" tags: "Tag"
@ -860,6 +867,8 @@ failedToFetchAccountInformation: "Impossibile recuperare le informazioni sul pro
rateLimitExceeded: "Superato il limite di velocità." rateLimitExceeded: "Superato il limite di velocità."
cropImage: "Ritaglio dell'immagine" cropImage: "Ritaglio dell'immagine"
cropImageAsk: "Si desidera ritagliare l'immagine?" cropImageAsk: "Si desidera ritagliare l'immagine?"
cropYes: "Ritaglia"
cropNo: "Non ritagliare"
file: "Allegati" file: "Allegati"
recentNHours: "Ultime {n} ore" recentNHours: "Ultime {n} ore"
recentNDays: "Ultimi {n} giorni" recentNDays: "Ultimi {n} giorni"
@ -938,6 +947,16 @@ cannotPerformTemporaryDescription: "L'attività non può essere svolta, poiché
preset: "Preimpostato" preset: "Preimpostato"
selectFromPresets: "Seleziona preimpostato" selectFromPresets: "Seleziona preimpostato"
achievements: "Obiettivi raggiunti" achievements: "Obiettivi raggiunti"
gotInvalidResponseError: "Risposta del server non valida"
gotInvalidResponseErrorDescription: "Il server potrebbe essere irraggiungibile o in manutenzione. Riprova più tardi."
thisPostMayBeAnnoying: "Questa nota potrebbe essere offensiva"
thisPostMayBeAnnoyingHome: "Pubblica sulla timeline principale"
thisPostMayBeAnnoyingCancel: "Annulla"
thisPostMayBeAnnoyingIgnore: "Pubblica lo stesso"
collapseRenotes: "Comprimi i Rinota già letti"
internalServerError: "Errore interno del server"
internalServerErrorDescription: "Si è verificato un errore imprevisto all'interno del server"
copyErrorInfo: "Copia le informazioni sull'errore"
_achievements: _achievements:
earnedAt: "Data di conseguimento" earnedAt: "Data di conseguimento"
_types: _types:
@ -1526,12 +1545,15 @@ _permissions:
"read:gallery-likes": "Visualizza i contenuti della galleria." "read:gallery-likes": "Visualizza i contenuti della galleria."
"write:gallery-likes": "Manipolazione dei \"Mi piace\" della galleria." "write:gallery-likes": "Manipolazione dei \"Mi piace\" della galleria."
_auth: _auth:
shareAccessTitle: "Permessi dell'applicazione"
shareAccess: "Vuoi autorizzare {name} ad accedere al tuo profilo?" shareAccess: "Vuoi autorizzare {name} ad accedere al tuo profilo?"
shareAccessAsk: "Vuoi autorizzare questa App ad accedere al tuo profilo?" shareAccessAsk: "Vuoi autorizzare questa App ad accedere al tuo profilo?"
permission: "{name} richiede i permessi seguenti"
permissionAsk: "Questa app richiede le seguenti autorizzazioni:" permissionAsk: "Questa app richiede le seguenti autorizzazioni:"
pleaseGoBack: "Si prega di ritornare sulla app" pleaseGoBack: "Si prega di ritornare sulla app"
callback: "Ritornando sulla app" callback: "Ritornando sulla app"
denied: "Accesso negato" denied: "Accesso negato"
pleaseLogin: "Per favore accedi al tuo account per cambiare i permessi dell'applicazione"
_antennaSources: _antennaSources:
all: "Tutte le note" all: "Tutte le note"
homeTimeline: "Note dagli utenti che segui" homeTimeline: "Note dagli utenti che segui"

View File

@ -392,17 +392,20 @@ userList: "リスト"
about: "情報" about: "情報"
aboutMisskey: "Misskeyについて" aboutMisskey: "Misskeyについて"
administrator: "管理者" administrator: "管理者"
token: "トークン" token: "確認コード"
twoStepAuthentication: "二段階認証" 2fa: "二要素認証"
totp: "認証アプリ"
totpDescription: "認証アプリを使ってワンタイムパスワードを入力"
moderator: "モデレーター" moderator: "モデレーター"
moderation: "モデレーション" moderation: "モデレーション"
nUsersMentioned: "{n}人が投稿" nUsersMentioned: "{n}人が投稿"
securityKeyAndPasskey: "セキュリティキー・パスキー"
securityKey: "セキュリティキー" securityKey: "セキュリティキー"
securityKeyName: "キーの名前"
registerSecurityKey: "セキュリティキーを登録する"
lastUsed: "最後の使用" lastUsed: "最後の使用"
lastUsedAt: "最後の使用: {t}"
unregister: "登録を解除" unregister: "登録を解除"
passwordLessLogin: "パスワード無しログイン" passwordLessLogin: "パスワードレスログイン"
passwordLessLoginDescription: "パスワードを使用せず、セキュリティキーやパスキーなどのみでログインします"
resetPassword: "パスワードをリセット" resetPassword: "パスワードをリセット"
newPasswordIs: "新しいパスワードは「{password}」です" newPasswordIs: "新しいパスワードは「{password}」です"
reduceUiAnimation: "UIのアニメーションを減らす" reduceUiAnimation: "UIのアニメーションを減らす"
@ -417,24 +420,15 @@ markAsReadAllTalkMessages: "すべてのチャットを既読にする"
help: "ヘルプ" help: "ヘルプ"
inputMessageHere: "ここにメッセージを入力" inputMessageHere: "ここにメッセージを入力"
close: "閉じる" close: "閉じる"
group: "グループ"
groups: "グループ"
createGroup: "グループを作成"
ownedGroups: "所有グループ"
joinedGroups: "参加しているグループ"
invites: "招待" invites: "招待"
groupName: "グループ名"
members: "メンバー" members: "メンバー"
transfer: "譲渡" transfer: "譲渡"
messagingWithUser: "ユーザーとチャット"
messagingWithGroup: "グループでチャット"
title: "タイトル" title: "タイトル"
text: "テキスト" text: "テキスト"
enable: "有効にする" enable: "有効にする"
next: "次" next: "次"
retype: "再入力" retype: "再入力"
noteOf: "{user}のノート" noteOf: "{user}のノート"
inviteToGroup: "グループに招待"
quoteAttached: "引用付き" quoteAttached: "引用付き"
quoteQuestion: "引用として添付しますか?" quoteQuestion: "引用として添付しますか?"
noMessagesYet: "まだチャットはありません" noMessagesYet: "まだチャットはありません"
@ -456,17 +450,13 @@ passwordMatched: "一致しました"
passwordNotMatched: "一致していません" passwordNotMatched: "一致していません"
signinWith: "{x}でログイン" signinWith: "{x}でログイン"
signinFailed: "ログインできませんでした。ユーザー名とパスワードを確認してください。" signinFailed: "ログインできませんでした。ユーザー名とパスワードを確認してください。"
tapSecurityKey: "セキュリティキーにタッチ"
or: "もしくは" or: "もしくは"
language: "言語" language: "言語"
uiLanguage: "UIの表示言語" uiLanguage: "UIの表示言語"
groupInvited: "グループに招待されました"
aboutX: "{x}について" aboutX: "{x}について"
emojiStyle: "絵文字のスタイル" emojiStyle: "絵文字のスタイル"
native: "ネイティブ" native: "ネイティブ"
disableDrawer: "メニューをドロワーで表示しない" disableDrawer: "メニューをドロワーで表示しない"
youHaveNoGroups: "グループがありません"
joinOrCreateGroup: "既存のグループに招待してもらうか、新しくグループを作成してください。"
noHistory: "履歴はありません" noHistory: "履歴はありません"
signinHistory: "ログイン履歴" signinHistory: "ログイン履歴"
enableAdvancedMfm: "高度なMFMを有効にする" enableAdvancedMfm: "高度なMFMを有効にする"
@ -789,6 +779,7 @@ popularPosts: "人気の投稿"
shareWithNote: "ノートで共有" shareWithNote: "ノートで共有"
ads: "広告" ads: "広告"
expiration: "期限" expiration: "期限"
startingperiod: "開始期間"
memo: "メモ" memo: "メモ"
priority: "優先度" priority: "優先度"
high: "高" high: "高"
@ -840,8 +831,6 @@ deleteAccountConfirm: "アカウントが削除されます。よろしいです
incorrectPassword: "パスワードが間違っています。" incorrectPassword: "パスワードが間違っています。"
voteConfirm: "「{choice}」に投票しますか?" voteConfirm: "「{choice}」に投票しますか?"
hide: "隠す" hide: "隠す"
leaveGroup: "グループから抜ける"
leaveGroupConfirm: "「{name}」から抜けますか?"
useDrawerReactionPickerForMobile: "モバイルデバイスのときドロワーで表示" useDrawerReactionPickerForMobile: "モバイルデバイスのときドロワーで表示"
welcomeBackWithName: "おかえりなさい、{name}さん" welcomeBackWithName: "おかえりなさい、{name}さん"
clickToFinishEmailVerification: "[{ok}]を押して、メールアドレスの確認を完了してください。" clickToFinishEmailVerification: "[{ok}]を押して、メールアドレスの確認を完了してください。"
@ -957,6 +946,9 @@ collapseRenotes: "見たことのあるRenoteを省略して表示"
internalServerError: "サーバー内部エラー" internalServerError: "サーバー内部エラー"
internalServerErrorDescription: "サーバー内部で予期しないエラーが発生しました。" internalServerErrorDescription: "サーバー内部で予期しないエラーが発生しました。"
copyErrorInfo: "エラー情報をコピー" copyErrorInfo: "エラー情報をコピー"
joinThisServer: "このサーバーに登録する"
exploreOtherServers: "他のサーバーを探す"
letsLookAtTimeline: "タイムラインを見てみる"
_achievements: _achievements:
earnedAt: "獲得日時" earnedAt: "獲得日時"
@ -1532,14 +1524,29 @@ _tutorial:
_2fa: _2fa:
alreadyRegistered: "既に設定は完了しています。" alreadyRegistered: "既に設定は完了しています。"
registerDevice: "デバイスを登録" registerTOTP: "認証アプリの設定を開始"
registerKey: "キーを登録" passwordToTOTP: "パスワードを入力してください"
step1: "まず、{a}や{b}などの認証アプリをお使いのデバイスにインストールします。" step1: "まず、{a}や{b}などの認証アプリをお使いのデバイスにインストールします。"
step2: "次に、表示されているQRコードをアプリでスキャンします。" step2: "次に、表示されているQRコードをアプリでスキャンします。"
step2Url: "デスクトップアプリでは次のURLを入力します:" step2Click: "QRコードをクリックすると、お使いの端末にインストールされている認証アプリやキーリングに登録できます。"
step3: "アプリに表示されているトークンを入力して完了です。" step2Url: "デスクトップアプリでは次のURIを入力します:"
step4: "これからログインするときも、同じようにトークンを入力します。" step3Title: "確認コードを入力"
securityKeyInfo: "FIDO2をサポートするハードウェアセキュリティキーもしくは端末の指紋認証やPINを使用してログインするように設定できます。" step3: "アプリに表示されている確認コード(トークン)を入力して完了です。"
step4: "これからログインするときも、同じように確認コードを入力します。"
securityKeyNotSupported: "お使いのブラウザはセキュリティキーに対応していません。"
registerTOTPBeforeKey: "セキュリティキー・パスキーを登録するには、まず認証アプリの設定を行なってください。"
securityKeyInfo: "FIDO2をサポートするハードウェアセキュリティキー、端末の生体認証やPINロック、パスキーといった、WebAuthn由来の鍵を登録します。"
chromePasskeyNotSupported: "Chromeのパスキーは現在サポートしていません。"
registerSecurityKey: "セキュリティキー・パスキーを登録する"
securityKeyName: "キーの名前を入力"
tapSecurityKey: "ブラウザの指示に従い、セキュリティキーやパスキーを登録してください"
removeKey: "セキュリティキーを削除"
removeKeyConfirm: "{name}を削除しますか?"
whyTOTPOnlyRenew: "セキュリティキーが登録されている場合、認証アプリの設定は解除できません。"
renewTOTP: "認証アプリを再設定"
renewTOTPConfirm: "今までの認証アプリの確認コードは使用できなくなります"
renewTOTPOk: "再設定する"
renewTOTPCancel: "やめておく"
_permissions: _permissions:
"read:account": "アカウントの情報を見る" "read:account": "アカウントの情報を見る"
@ -1591,7 +1598,6 @@ _antennaSources:
homeTimeline: "フォローしているユーザーのノート" homeTimeline: "フォローしているユーザーのノート"
users: "指定した一人または複数のユーザーのノート" users: "指定した一人または複数のユーザーのノート"
userList: "指定したリストのユーザーのノート" userList: "指定したリストのユーザーのノート"
userGroup: "指定したグループのユーザーのノート"
_weekday: _weekday:
sunday: "日曜日" sunday: "日曜日"
@ -1821,12 +1827,9 @@ _notification:
youGotReply: "{name}からのリプライ" youGotReply: "{name}からのリプライ"
youGotQuote: "{name}による引用" youGotQuote: "{name}による引用"
youRenoted: "{name}がRenoteしました" youRenoted: "{name}がRenoteしました"
youGotMessagingMessageFromUser: "{name}からのチャットがあります"
youGotMessagingMessageFromGroup: "{name}のチャットがあります"
youWereFollowed: "フォローされました" youWereFollowed: "フォローされました"
youReceivedFollowRequest: "フォローリクエストが来ました" youReceivedFollowRequest: "フォローリクエストが来ました"
yourFollowRequestAccepted: "フォローリクエストが承認されました" yourFollowRequestAccepted: "フォローリクエストが承認されました"
youWereInvitedToGroup: "{userName}があなたをグループに招待しました"
pollEnded: "アンケートの結果が出ました" pollEnded: "アンケートの結果が出ました"
unreadAntennaNote: "アンテナ {name}" unreadAntennaNote: "アンテナ {name}"
emptyPushNotificationMessage: "プッシュ通知の更新をしました" emptyPushNotificationMessage: "プッシュ通知の更新をしました"
@ -1843,7 +1846,6 @@ _notification:
pollEnded: "アンケートが終了" pollEnded: "アンケートが終了"
receiveFollowRequest: "フォロー申請を受け取った" receiveFollowRequest: "フォロー申請を受け取った"
followRequestAccepted: "フォローが受理された" followRequestAccepted: "フォローが受理された"
groupInvited: "グループに招待された"
app: "連携アプリからの通知" app: "連携アプリからの通知"
_actions: _actions:
@ -1879,3 +1881,7 @@ _deck:
channel: "チャンネル" channel: "チャンネル"
mentions: "あなた宛て" mentions: "あなた宛て"
direct: "ダイレクト" direct: "ダイレクト"
_dialog:
charactersExceeded: "最大文字数を超えています! 現在 {current} / 制限 {max}"
charactersBelow: "最小文字数を下回っています! 現在 {current} / 制限 {min}"

View File

@ -103,6 +103,8 @@ renoted: "Renoteしたで。"
cantRenote: "この投稿はRenoteできへんらしい。" cantRenote: "この投稿はRenoteできへんらしい。"
cantReRenote: "Renote自体はRenoteできへんで。" cantReRenote: "Renote自体はRenoteできへんで。"
quote: "引用" quote: "引用"
inChannelRenote: "チャンネル内Renote"
inChannelQuote: "チャンネル内引用"
pinnedNote: "ピン留めされとるノート" pinnedNote: "ピン留めされとるノート"
pinned: "ピン留めしとく" pinned: "ピン留めしとく"
you: "あんた" you: "あんた"
@ -948,35 +950,168 @@ achievements: "実績"
gotInvalidResponseError: "サーバー黙っとるわ、知らんけど" gotInvalidResponseError: "サーバー黙っとるわ、知らんけど"
gotInvalidResponseErrorDescription: "サーバーいま日曜日。またきて月曜日。" gotInvalidResponseErrorDescription: "サーバーいま日曜日。またきて月曜日。"
thisPostMayBeAnnoying: "この投稿は迷惑かもしらんで。" thisPostMayBeAnnoying: "この投稿は迷惑かもしらんで。"
thisPostMayBeAnnoyingHome: "ホームに投稿"
thisPostMayBeAnnoyingCancel: "やめとく"
thisPostMayBeAnnoyingIgnore: "このまま投稿"
collapseRenotes: "見たことあるRenoteは省略やで" collapseRenotes: "見たことあるRenoteは省略やで"
internalServerError: "サーバー内部エラー"
internalServerErrorDescription: "サーバー内部でよう分からんエラーやわ"
copyErrorInfo: "エラー情報をコピー"
_achievements: _achievements:
earnedAt: "貰った日ぃ" earnedAt: "貰った日ぃ"
_types: _types:
_notes1: _notes1:
title: "まいど!" title: "まいど!"
description: "初めてノート投稿したった" description: "初めてノート投稿したった"
flavor: "Misskeyを楽しんでな"
_notes10: _notes10:
title: "ノートの天保山" title: "ノートの天保山"
description: "ートを10回投稿した"
_notes100: _notes100:
title: "ノートの真田山" title: "ノートの真田山"
description: "ートを100回投稿した"
_notes500: _notes500:
title: "ノートの生駒山" title: "ノートの生駒山"
description: "ートを500回投稿した"
_notes1000:
title: "ノートの山"
description: "ートを1,000回投稿した"
_notes5000: _notes5000:
title: "箕面の滝からノート" title: "箕面の滝からノート"
description: "ートを5,000回投稿した"
_notes10000:
title: "スーパーノート"
description: "ートを10,000回投稿した"
_notes20000:
title: "ニードモアノート"
description: "ートを20,000回投稿した"
_notes30000:
title: "ノートノートノート"
description: "ートを30,000回投稿した"
_notes40000:
title: "ノート工場"
description: "ートを40,000回投稿した"
_notes50000:
title: "ノートの惑星"
description: "ートを50,000回投稿した"
_notes60000:
title: "ノートクエーサー"
description: "ートを60,000回投稿した"
_notes70000:
title: "ブラックノートホール"
description: "ートを70,000回投稿した"
_notes80000:
title: "ノートギャラクシー"
description: "ートを80,000回投稿した"
_notes90000:
title: "ノートバース"
description: "ートを90,000回投稿した"
_notes100000:
title: "ALL YOUR NOTE ARE BELONG TO US"
description: "ートを100,000回投稿した"
flavor: "そんなに書くことあるんか?"
_login3: _login3:
title: "ビギナーⅠ"
description: "通算ログイン日数が3日"
flavor: "今日からワシはミスキストやで" flavor: "今日からワシはミスキストやで"
_login7:
title: "ビギナーⅡ"
description: "通算ログイン日数が7日"
flavor: "慣れてきたんちゃう?"
_login15:
title: "ビギナーⅢ"
description: "通算ログイン日数が15日"
_login30:
title: "ミスキストⅠ"
description: "通算ログイン日数が30日"
_login60:
title: "ミスキストⅡ"
description: "通算ログイン日数が60日"
_login100:
title: "ミスキストⅢ"
description: "通算ログイン日数が100日"
flavor: "そのユーザー、ミスキストにつき"
_login200:
title: "常連Ⅰ"
_followers500:
title: "基地局"
description: "フォロワーが500人を超した"
_followers1000:
title: "インフルエンサー"
description: "フォロワーが1,000人を超した"
_collectAchievements30:
title: "実績コレクター"
description: "実績を30個以上獲得した"
_viewAchievements3min:
title: "実績好き"
description: "実績一覧を3分以上眺め続けた"
_iLoveMisskey: _iLoveMisskey:
title: "Misskey好きやねん" title: "Misskey好きやねん"
description: "\"I ❤ #Misskey\"を投稿した"
flavor: "Misskeyを使ってくれてありがとうな by 開発チーム"
_foundTreasure: _foundTreasure:
title: "なんでも鑑定団" title: "なんでも鑑定団"
description: "隠されたお宝を発見した"
_client30min: _client30min:
title: "ねんね" title: "ねんね"
description: "クライアントを起動してから30分以上経過した"
_noteDeletedWithin1min: _noteDeletedWithin1min:
title: "*おおっと*" title: "*おおっと*"
description: "投稿してから1分以内にその投稿を消した"
_postedAtLateNight:
title: "夜行性"
description: "深夜にノートを投稿した"
flavor: "そろそろ寝よか"
_postedAt0min0sec:
title: "時報"
description: "0分0秒にートを投稿した"
flavor: "ポッ ポッ ポッ ピーン"
_selfQuote:
title: "自己言及"
description: "自分のノートを引用した"
_htl20npm:
title: "流れるTL"
description: "ホームタイムラインの流速が20npmを超す"
_viewInstanceChart:
title: "アナリスト"
description: "インスタンスのチャートを表示した"
_outputHelloWorldOnScratchpad:
title: "Hello, world!"
description: "スクラッチパッドで hello worldを出力した"
_open3windows: _open3windows:
title: "マド開けすぎ" title: "マド開けすぎ"
description: "ウィンドウを3つ以上開いた状態にした"
_driveFolderCircularReference: _driveFolderCircularReference:
title: "環状線" title: "環状線"
description: "ドライブのフォルダを再帰的な入れ子にしようとした"
_reactWithoutRead:
title: "ちゃんと読んだんか?"
description: "100文字以上のテキストを含むートに投稿されてから3秒以内にリアクションした"
_clickedClickHere:
title: "ここをクリック"
description: "ここをクリックした"
_justPlainLucky:
title: "単なるラッキー"
description: "10秒ごとに0.005%の確率で獲得"
_setNameToSyuilo:
title: "神様コンプレックス"
description: "名前を syuilo に設定した"
_passedSinceAccountCreated1:
title: "一周年"
description: "アカウント作成から1年経過した"
_passedSinceAccountCreated2:
title: "二周年"
description: "アカウント作成から2年経過した"
_passedSinceAccountCreated3:
title: "三周年"
description: "アカウント作成から3年経過した"
_loggedInOnBirthday:
title: "ハッピーバースデー!"
description: "誕生日にログインした"
_loggedInOnNewYearsDay:
title: "あけましておめでとうございます!"
description: "元旦にログインした"
flavor: "今年も弊インスタンスをよろしくお願いします"
_role: _role:
new: "ロールの作成" new: "ロールの作成"
edit: "ロールの編集" edit: "ロールの編集"

View File

@ -1113,6 +1113,8 @@ _achievements:
_loggedInOnNewYearsDay: _loggedInOnNewYearsDay:
title: "З Новим роком!" title: "З Новим роком!"
description: "Увійшли в перший день року" description: "Увійшли в перший день року"
_cookieClicked:
flavor: "Чекайте, це вірний сайт?"
_brainDiver: _brainDiver:
title: "Brain Diver" title: "Brain Diver"
description: "Відправити посилання на \"Brain Diver\"" description: "Відправити посилання на \"Brain Diver\""

View File

@ -103,6 +103,8 @@ renoted: "已转发。"
cantRenote: "该帖无法转发。" cantRenote: "该帖无法转发。"
cantReRenote: "转发无法被再次转发。" cantReRenote: "转发无法被再次转发。"
quote: "引用" quote: "引用"
inChannelRenote: "在频道内转发"
inChannelQuote: "在频道内引用"
pinnedNote: "已置顶的帖子" pinnedNote: "已置顶的帖子"
pinned: "置顶" pinned: "置顶"
you: "您" you: "您"
@ -951,6 +953,10 @@ thisPostMayBeAnnoying: "这个帖子可能会让其他人感到困扰。"
thisPostMayBeAnnoyingHome: "发到首页" thisPostMayBeAnnoyingHome: "发到首页"
thisPostMayBeAnnoyingCancel: "取消" thisPostMayBeAnnoyingCancel: "取消"
thisPostMayBeAnnoyingIgnore: "就这样发布" thisPostMayBeAnnoyingIgnore: "就这样发布"
collapseRenotes: "省略显示已经看过的转发内容"
internalServerError: "内部服务器错误"
internalServerErrorDescription: "内部服务器发生了预期外的错误"
copyErrorInfo: "复制错误信息"
_achievements: _achievements:
earnedAt: "达成时间" earnedAt: "达成时间"
_types: _types:

View File

@ -103,6 +103,8 @@ renoted: "轉傳成功"
cantRenote: "無法轉發此貼文。" cantRenote: "無法轉發此貼文。"
cantReRenote: "無法轉傳之前已經轉傳過的內容。" cantReRenote: "無法轉傳之前已經轉傳過的內容。"
quote: "引用" quote: "引用"
inChannelRenote: "在頻道內轉發"
inChannelQuote: "在頻道內引用"
pinnedNote: "已置頂的貼文" pinnedNote: "已置頂的貼文"
pinned: "置頂" pinned: "置頂"
you: "您" you: "您"
@ -952,6 +954,9 @@ thisPostMayBeAnnoyingHome: "發布到首頁"
thisPostMayBeAnnoyingCancel: "退出" thisPostMayBeAnnoyingCancel: "退出"
thisPostMayBeAnnoyingIgnore: "直接發布貼文" thisPostMayBeAnnoyingIgnore: "直接發布貼文"
collapseRenotes: "省略顯示已看過的轉發貼文" collapseRenotes: "省略顯示已看過的轉發貼文"
internalServerError: "內部伺服器錯誤"
internalServerErrorDescription: "內部伺服器發生了非預期的錯誤。"
copyErrorInfo: "複製錯誤資訊"
_achievements: _achievements:
earnedAt: "獲得日期" earnedAt: "獲得日期"
_types: _types:

View File

@ -1,6 +1,6 @@
{ {
"name": "misskey", "name": "misskey",
"version": "13.6.1", "version": "13.7.0",
"codename": "nasubi", "codename": "nasubi",
"repository": { "repository": {
"type": "git", "type": "git",
@ -54,12 +54,12 @@
"devDependencies": { "devDependencies": {
"@types/gulp": "4.0.10", "@types/gulp": "4.0.10",
"@types/gulp-rename": "2.0.1", "@types/gulp-rename": "2.0.1",
"@typescript-eslint/eslint-plugin": "5.51.0", "@typescript-eslint/eslint-plugin": "5.52.0",
"@typescript-eslint/parser": "5.51.0", "@typescript-eslint/parser": "5.52.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cypress": "12.5.1", "cypress": "12.6.0",
"eslint": "8.33.0", "eslint": "8.34.0",
"start-server-and-test": "1.15.3" "start-server-and-test": "1.15.4"
}, },
"optionalDependencies": { "optionalDependencies": {
"@tensorflow/tfjs-core": "4.2.0" "@tensorflow/tfjs-core": "4.2.0"

View File

@ -1,14 +0,0 @@
// https://github.com/facebook/jest/issues/12270#issuecomment-1194746382
const nativeModule = require('node:module');
function resolver(module, options) {
const { basedir, defaultResolver } = options;
try {
return defaultResolver(module, options);
} catch (error) {
return nativeModule.createRequire(basedir).resolve(module);
}
}
module.exports = resolver;

View File

@ -83,7 +83,14 @@ module.exports = {
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module // A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
moduleNameMapper: { moduleNameMapper: {
"^@/(.*?).js": "<rootDir>/src/$1.ts", // Do not resolve .wasm.js to .wasm by the rule below
'^(.+)\\.wasm\\.js$': '$1.wasm.js',
// SWC converts @/foo/bar.js to `../../src/foo/bar.js`, and then this rule
// converts it again to `../../src/foo/bar` which then can be resolved to
// `.ts` files.
// See https://github.com/swc-project/jest/issues/64#issuecomment-1029753225
// TODO: Use `--allowImportingTsExtensions` on TypeScript 5.0 so that we can
// directly import `.ts` files without this hack.
'^(\\.{1,2}/.*)\\.js$': '$1', '^(\\.{1,2}/.*)\\.js$': '$1',
}, },
@ -112,7 +119,7 @@ module.exports = {
// resetModules: false, // resetModules: false,
// A path to a custom resolver // A path to a custom resolver
resolver: './jest-resolver.cjs', // resolver: './jest-resolver.cjs',
// Automatically restore mock state between every test // Automatically restore mock state between every test
restoreMocks: true, restoreMocks: true,

View File

@ -0,0 +1,35 @@
export class dropGroup1676434944993 {
name = 'dropGroup1676434944993'
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "antenna" DROP CONSTRAINT "FK_ccbf5a8c0be4511133dcc50ddeb"`);
await queryRunner.query(`ALTER TABLE "notification" DROP CONSTRAINT "FK_8fe87814e978053a53b1beb7e98"`);
await queryRunner.query(`ALTER TABLE "antenna" DROP COLUMN "userGroupJoiningId"`);
await queryRunner.query(`ALTER TABLE "notification" DROP COLUMN "userGroupInvitationId"`);
await queryRunner.query(`ALTER TYPE "public"."antenna_src_enum" RENAME TO "antenna_src_enum_old"`);
await queryRunner.query(`CREATE TYPE "public"."antenna_src_enum" AS ENUM('home', 'all', 'users', 'list')`);
await queryRunner.query(`ALTER TABLE "antenna" ALTER COLUMN "src" TYPE "public"."antenna_src_enum" USING "src"::"text"::"public"."antenna_src_enum"`);
await queryRunner.query(`DROP TYPE "public"."antenna_src_enum_old"`);
await queryRunner.query(`ALTER TYPE "public"."notification_type_enum" RENAME TO "notification_type_enum_old"`);
await queryRunner.query(`CREATE TYPE "public"."notification_type_enum" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'achievementEarned', 'app')`);
await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "type" TYPE "public"."notification_type_enum" USING "type"::"text"::"public"."notification_type_enum"`);
await queryRunner.query(`DROP TYPE "public"."notification_type_enum_old"`);
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "emailNotificationTypes" SET DEFAULT '["follow","receiveFollowRequest"]'`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "emailNotificationTypes" SET DEFAULT '["follow", "receiveFollowRequest", "groupInvited"]'`);
await queryRunner.query(`CREATE TYPE "public"."notification_type_enum_old" AS ENUM('follow', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote', 'pollEnded', 'receiveFollowRequest', 'followRequestAccepted', 'groupInvited', 'achievementEarned', 'app')`);
await queryRunner.query(`ALTER TABLE "notification" ALTER COLUMN "type" TYPE "public"."notification_type_enum_old" USING "type"::"text"::"public"."notification_type_enum_old"`);
await queryRunner.query(`DROP TYPE "public"."notification_type_enum"`);
await queryRunner.query(`ALTER TYPE "public"."notification_type_enum_old" RENAME TO "notification_type_enum"`);
await queryRunner.query(`CREATE TYPE "public"."antenna_src_enum_old" AS ENUM('home', 'all', 'users', 'list', 'group')`);
await queryRunner.query(`ALTER TABLE "antenna" ALTER COLUMN "src" TYPE "public"."antenna_src_enum_old" USING "src"::"text"::"public"."antenna_src_enum_old"`);
await queryRunner.query(`DROP TYPE "public"."antenna_src_enum"`);
await queryRunner.query(`ALTER TYPE "public"."antenna_src_enum_old" RENAME TO "antenna_src_enum"`);
await queryRunner.query(`ALTER TABLE "notification" ADD "userGroupInvitationId" character varying(32)`);
await queryRunner.query(`ALTER TABLE "antenna" ADD "userGroupJoiningId" character varying(32)`);
await queryRunner.query(`ALTER TABLE "notification" ADD CONSTRAINT "FK_8fe87814e978053a53b1beb7e98" FOREIGN KEY ("userGroupInvitationId") REFERENCES "user_group_invitation"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
await queryRunner.query(`ALTER TABLE "antenna" ADD CONSTRAINT "FK_ccbf5a8c0be4511133dcc50ddeb" FOREIGN KEY ("userGroupJoiningId") REFERENCES "user_group_joining"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
}
}

View File

@ -0,0 +1,9 @@
export class ad1676438468213 {
name = 'ad1676438468213';
async up(queryRunner) {
await queryRunner.query(`ALTER TABLE "ad" ADD "startsAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now()`);
}
async down(queryRunner) {
await queryRunner.query(`ALTER TABLE "role" DROP COLUMN "startsAt"`);
}
}

View File

@ -11,7 +11,9 @@
"watch:swc": "swc src -d built -D -w", "watch:swc": "swc src -d built -D -w",
"build": "tsc -p tsconfig.json || echo done. && tsc-alias -p tsconfig.json", "build": "tsc -p tsconfig.json || echo done. && tsc-alias -p tsconfig.json",
"watch": "node watch.mjs", "watch": "node watch.mjs",
"lint": "tsc --noEmit && eslint --quiet \"src/**/*.ts\"", "typecheck": "tsc --noEmit",
"eslint": "eslint --quiet \"src/**/*.ts\"",
"lint": "pnpm typecheck && pnpm eslint",
"jest": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --runInBand", "jest": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --runInBand",
"jest-and-coverage": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit --runInBand", "jest-and-coverage": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit --runInBand",
"jest-clear": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --clearCache", "jest-clear": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --clearCache",
@ -23,30 +25,30 @@
"@tensorflow/tfjs-node": "4.2.0" "@tensorflow/tfjs-node": "4.2.0"
}, },
"dependencies": { "dependencies": {
"@bull-board/api": "4.11.1", "@bull-board/api": "4.12.1",
"@bull-board/fastify": "4.11.1", "@bull-board/fastify": "4.12.1",
"@bull-board/ui": "4.11.1", "@bull-board/ui": "4.12.1",
"@discordapp/twemoji": "14.0.2", "@discordapp/twemoji": "14.0.2",
"@fastify/accepts": "4.1.0", "@fastify/accepts": "4.1.0",
"@fastify/cookie": "8.3.0", "@fastify/cookie": "8.3.0",
"@fastify/cors": "8.2.0", "@fastify/cors": "8.2.0",
"@fastify/http-proxy": "8.4.0", "@fastify/http-proxy": "8.4.0",
"@fastify/multipart": "7.4.0", "@fastify/multipart": "7.4.1",
"@fastify/static": "6.8.0", "@fastify/static": "6.9.0",
"@fastify/view": "7.4.1", "@fastify/view": "7.4.1",
"@nestjs/common": "9.3.7", "@nestjs/common": "9.3.9",
"@nestjs/core": "9.3.7", "@nestjs/core": "9.3.9",
"@nestjs/testing": "9.3.7", "@nestjs/testing": "9.3.9",
"@peertube/http-signature": "1.7.0", "@peertube/http-signature": "1.7.0",
"@sinonjs/fake-timers": "10.0.2", "@sinonjs/fake-timers": "10.0.2",
"accepts": "1.3.8", "accepts": "1.3.8",
"ajv": "8.12.0", "ajv": "8.12.0",
"archiver": "5.3.1", "archiver": "5.3.1",
"autwh": "0.1.0", "autwh": "0.1.0",
"aws-sdk": "2.1295.0", "aws-sdk": "2.1318.0",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"blurhash": "2.0.4", "blurhash": "2.0.5",
"bull": "4.10.3", "bull": "4.10.4",
"cacheable-lookup": "6.1.0", "cacheable-lookup": "6.1.0",
"cbor": "8.1.0", "cbor": "8.1.0",
"chalk": "5.2.0", "chalk": "5.2.0",
@ -58,12 +60,13 @@
"date-fns": "2.29.3", "date-fns": "2.29.3",
"deep-email-validator": "0.1.21", "deep-email-validator": "0.1.21",
"escape-regexp": "0.0.1", "escape-regexp": "0.0.1",
"fastify": "4.12.0", "fastify": "4.13.0",
"feed": "4.2.2", "feed": "4.2.2",
"file-type": "18.2.0", "file-type": "18.2.1",
"fluent-ffmpeg": "2.1.2", "fluent-ffmpeg": "2.1.2",
"form-data": "4.0.0", "form-data": "4.0.0",
"got": "12.5.3", "got": "12.5.3",
"happy-dom": "^8.7.0",
"hpagent": "1.2.0", "hpagent": "1.2.0",
"ioredis": "4.28.5", "ioredis": "4.28.5",
"ip-cidr": "3.1.0", "ip-cidr": "3.1.0",
@ -83,6 +86,7 @@
"nsfwjs": "2.4.2", "nsfwjs": "2.4.2",
"oauth": "0.10.0", "oauth": "0.10.0",
"os-utils": "0.0.14", "os-utils": "0.0.14",
"otpauth": "^9.0.2",
"parse5": "7.1.2", "parse5": "7.1.2",
"pg": "8.9.0", "pg": "8.9.0",
"private-ip": "3.0.0", "private-ip": "3.0.0",
@ -102,15 +106,14 @@
"rss-parser": "3.12.0", "rss-parser": "3.12.0",
"rxjs": "7.8.0", "rxjs": "7.8.0",
"s-age": "1.1.2", "s-age": "1.1.2",
"sanitize-html": "2.9.0", "sanitize-html": "2.10.0",
"seedrandom": "3.0.5", "seedrandom": "3.0.5",
"semver": "7.3.8", "semver": "7.3.8",
"sharp": "0.31.3", "sharp": "0.31.3",
"speakeasy": "2.0.0",
"strict-event-emitter-types": "2.0.0", "strict-event-emitter-types": "2.0.0",
"stringz": "2.1.0", "stringz": "2.1.0",
"summaly": "2.7.0", "summaly": "github:misskey-dev/summaly",
"systeminformation": "5.17.8", "systeminformation": "5.17.9",
"tinycolor2": "1.6.0", "tinycolor2": "1.6.0",
"tmp": "0.2.1", "tmp": "0.2.1",
"tsc-alias": "1.8.2", "tsc-alias": "1.8.2",
@ -124,14 +127,14 @@
"vary": "1.1.2", "vary": "1.1.2",
"web-push": "3.5.0", "web-push": "3.5.0",
"websocket": "1.0.34", "websocket": "1.0.34",
"ws": "8.12.0", "ws": "8.12.1",
"xev": "3.0.2" "xev": "3.0.2"
}, },
"devDependencies": { "devDependencies": {
"@jest/globals": "29.4.2", "@jest/globals": "29.4.3",
"@redocly/openapi-core": "1.0.0-beta.123", "@redocly/openapi-core": "1.0.0-beta.123",
"@swc/cli": "0.1.61", "@swc/cli": "0.1.62",
"@swc/core": "1.3.34", "@swc/core": "1.3.35",
"@swc/jest": "0.2.24", "@swc/jest": "0.2.24",
"@types/accepts": "1.3.5", "@types/accepts": "1.3.5",
"@types/archiver": "5.3.1", "@types/archiver": "5.3.1",
@ -149,7 +152,7 @@
"@types/jsonld": "1.5.8", "@types/jsonld": "1.5.8",
"@types/jsrsasign": "10.5.5", "@types/jsrsasign": "10.5.5",
"@types/mime-types": "2.1.1", "@types/mime-types": "2.1.1",
"@types/node": "18.13.0", "@types/node": "18.14.0",
"@types/node-fetch": "3.0.3", "@types/node-fetch": "3.0.3",
"@types/nodemailer": "6.4.7", "@types/nodemailer": "6.4.7",
"@types/oauth": "0.9.1", "@types/oauth": "0.9.1",
@ -165,7 +168,6 @@
"@types/semver": "7.3.13", "@types/semver": "7.3.13",
"@types/sharp": "0.31.1", "@types/sharp": "0.31.1",
"@types/sinonjs__fake-timers": "8.1.2", "@types/sinonjs__fake-timers": "8.1.2",
"@types/speakeasy": "2.0.7",
"@types/tinycolor2": "1.4.3", "@types/tinycolor2": "1.4.3",
"@types/tmp": "0.2.3", "@types/tmp": "0.2.3",
"@types/unzipper": "0.10.5", "@types/unzipper": "0.10.5",
@ -174,13 +176,13 @@
"@types/web-push": "3.3.2", "@types/web-push": "3.3.2",
"@types/websocket": "1.0.5", "@types/websocket": "1.0.5",
"@types/ws": "8.5.4", "@types/ws": "8.5.4",
"@typescript-eslint/eslint-plugin": "5.51.0", "@typescript-eslint/eslint-plugin": "5.52.0",
"@typescript-eslint/parser": "5.51.0", "@typescript-eslint/parser": "5.52.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"eslint": "8.33.0", "eslint": "8.34.0",
"eslint-plugin-import": "2.27.5", "eslint-plugin-import": "2.27.5",
"execa": "6.1.0", "execa": "6.1.0",
"jest": "29.4.2", "jest": "29.4.3",
"jest-mock": "29.4.2" "jest-mock": "29.4.3"
} }
} }

View File

@ -0,0 +1,8 @@
declare module 'redis-lock' {
import type Redis from 'ioredis';
type Lock = (lockName: string, timeout?: number, taskToPerform?: () => Promise<void>) => void;
function redisLock(client: Redis.Redis, retryDelay: number): Lock;
export = redisLock;
}

View File

@ -32,7 +32,7 @@ export class AccountUpdateService {
// フォロワーがリモートユーザーかつ投稿者がローカルユーザーならUpdateを配信 // フォロワーがリモートユーザーかつ投稿者がローカルユーザーならUpdateを配信
if (this.userEntityService.isLocalUser(user)) { if (this.userEntityService.isLocalUser(user)) {
const content = this.apRendererService.renderActivity(this.apRendererService.renderUpdate(await this.apRendererService.renderPerson(user), user)); const content = this.apRendererService.addContext(this.apRendererService.renderUpdate(await this.apRendererService.renderPerson(user), user));
this.apDeliverManagerService.deliverToFollowers(user, content); this.apDeliverManagerService.deliverToFollowers(user, content);
this.relayService.deliverToRelays(user, content); this.relayService.deliverToRelays(user, content);
} }

View File

@ -12,7 +12,7 @@ import { PushNotificationService } from '@/core/PushNotificationService.js';
import * as Acct from '@/misc/acct.js'; import * as Acct from '@/misc/acct.js';
import type { Packed } from '@/misc/schema.js'; import type { Packed } from '@/misc/schema.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { MutingsRepository, NotesRepository, AntennaNotesRepository, AntennasRepository, UserGroupJoiningsRepository, UserListJoiningsRepository } from '@/models/index.js'; import type { MutingsRepository, NotesRepository, AntennaNotesRepository, AntennasRepository, UserListJoiningsRepository } from '@/models/index.js';
import { UtilityService } from '@/core/UtilityService.js'; import { UtilityService } from '@/core/UtilityService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { StreamMessages } from '@/server/api/stream/types.js'; import { StreamMessages } from '@/server/api/stream/types.js';
@ -39,9 +39,6 @@ export class AntennaService implements OnApplicationShutdown {
@Inject(DI.antennasRepository) @Inject(DI.antennasRepository)
private antennasRepository: AntennasRepository, private antennasRepository: AntennasRepository,
@Inject(DI.userGroupJoiningsRepository)
private userGroupJoiningsRepository: UserGroupJoiningsRepository,
@Inject(DI.userListJoiningsRepository) @Inject(DI.userListJoiningsRepository)
private userListJoiningsRepository: UserListJoiningsRepository, private userListJoiningsRepository: UserListJoiningsRepository,
@ -160,14 +157,6 @@ export class AntennaService implements OnApplicationShutdown {
})).map(x => x.userId); })).map(x => x.userId);
if (!listUsers.includes(note.userId)) return false; if (!listUsers.includes(note.userId)) return false;
} else if (antenna.src === 'group') {
const joining = await this.userGroupJoiningsRepository.findOneByOrFail({ id: antenna.userGroupJoiningId! });
const groupUsers = (await this.userGroupJoiningsRepository.findBy({
userGroupId: joining.userGroupId,
})).map(x => x.userId);
if (!groupUsers.includes(note.userId)) return false;
} else if (antenna.src === 'users') { } else if (antenna.src === 'users') {
const accts = antenna.users.map(x => { const accts = antenna.users.map(x => {
const { username, host } = Acct.parse(x); const { username, host } = Acct.parse(x);

View File

@ -12,7 +12,7 @@ const retryDelay = 100;
@Injectable() @Injectable()
export class AppLockService { export class AppLockService {
private lock: (key: string, timeout?: number) => Promise<() => void>; private lock: (key: string, timeout?: number, _?: (() => Promise<void>) | undefined) => Promise<() => void>;
constructor( constructor(
@Inject(DI.redis) @Inject(DI.redis)

View File

@ -1,5 +1,4 @@
import { Module } from '@nestjs/common'; import { Module } from '@nestjs/common';
import { DI } from '../di-symbols.js';
import { AccountUpdateService } from './AccountUpdateService.js'; import { AccountUpdateService } from './AccountUpdateService.js';
import { AiService } from './AiService.js'; import { AiService } from './AiService.js';
import { AntennaService } from './AntennaService.js'; import { AntennaService } from './AntennaService.js';
@ -22,7 +21,6 @@ import { IdService } from './IdService.js';
import { ImageProcessingService } from './ImageProcessingService.js'; import { ImageProcessingService } from './ImageProcessingService.js';
import { InstanceActorService } from './InstanceActorService.js'; import { InstanceActorService } from './InstanceActorService.js';
import { InternalStorageService } from './InternalStorageService.js'; import { InternalStorageService } from './InternalStorageService.js';
import { MessagingService } from './MessagingService.js';
import { MetaService } from './MetaService.js'; import { MetaService } from './MetaService.js';
import { MfmService } from './MfmService.js'; import { MfmService } from './MfmService.js';
import { ModerationLogService } from './ModerationLogService.js'; import { ModerationLogService } from './ModerationLogService.js';
@ -82,7 +80,6 @@ import { GalleryLikeEntityService } from './entities/GalleryLikeEntityService.js
import { GalleryPostEntityService } from './entities/GalleryPostEntityService.js'; import { GalleryPostEntityService } from './entities/GalleryPostEntityService.js';
import { HashtagEntityService } from './entities/HashtagEntityService.js'; import { HashtagEntityService } from './entities/HashtagEntityService.js';
import { InstanceEntityService } from './entities/InstanceEntityService.js'; import { InstanceEntityService } from './entities/InstanceEntityService.js';
import { MessagingMessageEntityService } from './entities/MessagingMessageEntityService.js';
import { ModerationLogEntityService } from './entities/ModerationLogEntityService.js'; import { ModerationLogEntityService } from './entities/ModerationLogEntityService.js';
import { MutingEntityService } from './entities/MutingEntityService.js'; import { MutingEntityService } from './entities/MutingEntityService.js';
import { NoteEntityService } from './entities/NoteEntityService.js'; import { NoteEntityService } from './entities/NoteEntityService.js';
@ -93,8 +90,6 @@ import { PageEntityService } from './entities/PageEntityService.js';
import { PageLikeEntityService } from './entities/PageLikeEntityService.js'; import { PageLikeEntityService } from './entities/PageLikeEntityService.js';
import { SigninEntityService } from './entities/SigninEntityService.js'; import { SigninEntityService } from './entities/SigninEntityService.js';
import { UserEntityService } from './entities/UserEntityService.js'; import { UserEntityService } from './entities/UserEntityService.js';
import { UserGroupEntityService } from './entities/UserGroupEntityService.js';
import { UserGroupInvitationEntityService } from './entities/UserGroupInvitationEntityService.js';
import { UserListEntityService } from './entities/UserListEntityService.js'; import { UserListEntityService } from './entities/UserListEntityService.js';
import { FlashEntityService } from './entities/FlashEntityService.js'; import { FlashEntityService } from './entities/FlashEntityService.js';
import { FlashLikeEntityService } from './entities/FlashLikeEntityService.js'; import { FlashLikeEntityService } from './entities/FlashLikeEntityService.js';
@ -146,7 +141,6 @@ const $IdService: Provider = { provide: 'IdService', useExisting: IdService };
const $ImageProcessingService: Provider = { provide: 'ImageProcessingService', useExisting: ImageProcessingService }; const $ImageProcessingService: Provider = { provide: 'ImageProcessingService', useExisting: ImageProcessingService };
const $InstanceActorService: Provider = { provide: 'InstanceActorService', useExisting: InstanceActorService }; const $InstanceActorService: Provider = { provide: 'InstanceActorService', useExisting: InstanceActorService };
const $InternalStorageService: Provider = { provide: 'InternalStorageService', useExisting: InternalStorageService }; const $InternalStorageService: Provider = { provide: 'InternalStorageService', useExisting: InternalStorageService };
const $MessagingService: Provider = { provide: 'MessagingService', useExisting: MessagingService };
const $MetaService: Provider = { provide: 'MetaService', useExisting: MetaService }; const $MetaService: Provider = { provide: 'MetaService', useExisting: MetaService };
const $MfmService: Provider = { provide: 'MfmService', useExisting: MfmService }; const $MfmService: Provider = { provide: 'MfmService', useExisting: MfmService };
const $ModerationLogService: Provider = { provide: 'ModerationLogService', useExisting: ModerationLogService }; const $ModerationLogService: Provider = { provide: 'ModerationLogService', useExisting: ModerationLogService };
@ -207,7 +201,6 @@ const $GalleryLikeEntityService: Provider = { provide: 'GalleryLikeEntityService
const $GalleryPostEntityService: Provider = { provide: 'GalleryPostEntityService', useExisting: GalleryPostEntityService }; const $GalleryPostEntityService: Provider = { provide: 'GalleryPostEntityService', useExisting: GalleryPostEntityService };
const $HashtagEntityService: Provider = { provide: 'HashtagEntityService', useExisting: HashtagEntityService }; const $HashtagEntityService: Provider = { provide: 'HashtagEntityService', useExisting: HashtagEntityService };
const $InstanceEntityService: Provider = { provide: 'InstanceEntityService', useExisting: InstanceEntityService }; const $InstanceEntityService: Provider = { provide: 'InstanceEntityService', useExisting: InstanceEntityService };
const $MessagingMessageEntityService: Provider = { provide: 'MessagingMessageEntityService', useExisting: MessagingMessageEntityService };
const $ModerationLogEntityService: Provider = { provide: 'ModerationLogEntityService', useExisting: ModerationLogEntityService }; const $ModerationLogEntityService: Provider = { provide: 'ModerationLogEntityService', useExisting: ModerationLogEntityService };
const $MutingEntityService: Provider = { provide: 'MutingEntityService', useExisting: MutingEntityService }; const $MutingEntityService: Provider = { provide: 'MutingEntityService', useExisting: MutingEntityService };
const $NoteEntityService: Provider = { provide: 'NoteEntityService', useExisting: NoteEntityService }; const $NoteEntityService: Provider = { provide: 'NoteEntityService', useExisting: NoteEntityService };
@ -218,8 +211,6 @@ const $PageEntityService: Provider = { provide: 'PageEntityService', useExisting
const $PageLikeEntityService: Provider = { provide: 'PageLikeEntityService', useExisting: PageLikeEntityService }; const $PageLikeEntityService: Provider = { provide: 'PageLikeEntityService', useExisting: PageLikeEntityService };
const $SigninEntityService: Provider = { provide: 'SigninEntityService', useExisting: SigninEntityService }; const $SigninEntityService: Provider = { provide: 'SigninEntityService', useExisting: SigninEntityService };
const $UserEntityService: Provider = { provide: 'UserEntityService', useExisting: UserEntityService }; const $UserEntityService: Provider = { provide: 'UserEntityService', useExisting: UserEntityService };
const $UserGroupEntityService: Provider = { provide: 'UserGroupEntityService', useExisting: UserGroupEntityService };
const $UserGroupInvitationEntityService: Provider = { provide: 'UserGroupInvitationEntityService', useExisting: UserGroupInvitationEntityService };
const $UserListEntityService: Provider = { provide: 'UserListEntityService', useExisting: UserListEntityService }; const $UserListEntityService: Provider = { provide: 'UserListEntityService', useExisting: UserListEntityService };
const $FlashEntityService: Provider = { provide: 'FlashEntityService', useExisting: FlashEntityService }; const $FlashEntityService: Provider = { provide: 'FlashEntityService', useExisting: FlashEntityService };
const $FlashLikeEntityService: Provider = { provide: 'FlashLikeEntityService', useExisting: FlashLikeEntityService }; const $FlashLikeEntityService: Provider = { provide: 'FlashLikeEntityService', useExisting: FlashLikeEntityService };
@ -273,7 +264,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
ImageProcessingService, ImageProcessingService,
InstanceActorService, InstanceActorService,
InternalStorageService, InternalStorageService,
MessagingService,
MetaService, MetaService,
MfmService, MfmService,
ModerationLogService, ModerationLogService,
@ -333,7 +323,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
GalleryPostEntityService, GalleryPostEntityService,
HashtagEntityService, HashtagEntityService,
InstanceEntityService, InstanceEntityService,
MessagingMessageEntityService,
ModerationLogEntityService, ModerationLogEntityService,
MutingEntityService, MutingEntityService,
NoteEntityService, NoteEntityService,
@ -344,8 +333,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
PageLikeEntityService, PageLikeEntityService,
SigninEntityService, SigninEntityService,
UserEntityService, UserEntityService,
UserGroupEntityService,
UserGroupInvitationEntityService,
UserListEntityService, UserListEntityService,
FlashEntityService, FlashEntityService,
FlashLikeEntityService, FlashLikeEntityService,
@ -394,7 +381,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$ImageProcessingService, $ImageProcessingService,
$InstanceActorService, $InstanceActorService,
$InternalStorageService, $InternalStorageService,
$MessagingService,
$MetaService, $MetaService,
$MfmService, $MfmService,
$ModerationLogService, $ModerationLogService,
@ -454,7 +440,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$GalleryPostEntityService, $GalleryPostEntityService,
$HashtagEntityService, $HashtagEntityService,
$InstanceEntityService, $InstanceEntityService,
$MessagingMessageEntityService,
$ModerationLogEntityService, $ModerationLogEntityService,
$MutingEntityService, $MutingEntityService,
$NoteEntityService, $NoteEntityService,
@ -465,8 +450,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$PageLikeEntityService, $PageLikeEntityService,
$SigninEntityService, $SigninEntityService,
$UserEntityService, $UserEntityService,
$UserGroupEntityService,
$UserGroupInvitationEntityService,
$UserListEntityService, $UserListEntityService,
$FlashEntityService, $FlashEntityService,
$FlashLikeEntityService, $FlashLikeEntityService,
@ -516,7 +499,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
ImageProcessingService, ImageProcessingService,
InstanceActorService, InstanceActorService,
InternalStorageService, InternalStorageService,
MessagingService,
MetaService, MetaService,
MfmService, MfmService,
ModerationLogService, ModerationLogService,
@ -575,7 +557,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
GalleryPostEntityService, GalleryPostEntityService,
HashtagEntityService, HashtagEntityService,
InstanceEntityService, InstanceEntityService,
MessagingMessageEntityService,
ModerationLogEntityService, ModerationLogEntityService,
MutingEntityService, MutingEntityService,
NoteEntityService, NoteEntityService,
@ -586,8 +567,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
PageLikeEntityService, PageLikeEntityService,
SigninEntityService, SigninEntityService,
UserEntityService, UserEntityService,
UserGroupEntityService,
UserGroupInvitationEntityService,
UserListEntityService, UserListEntityService,
FlashEntityService, FlashEntityService,
FlashLikeEntityService, FlashLikeEntityService,
@ -636,7 +615,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$ImageProcessingService, $ImageProcessingService,
$InstanceActorService, $InstanceActorService,
$InternalStorageService, $InternalStorageService,
$MessagingService,
$MetaService, $MetaService,
$MfmService, $MfmService,
$ModerationLogService, $ModerationLogService,
@ -695,7 +673,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$GalleryPostEntityService, $GalleryPostEntityService,
$HashtagEntityService, $HashtagEntityService,
$InstanceEntityService, $InstanceEntityService,
$MessagingMessageEntityService,
$ModerationLogEntityService, $ModerationLogEntityService,
$MutingEntityService, $MutingEntityService,
$NoteEntityService, $NoteEntityService,
@ -706,8 +683,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
$PageLikeEntityService, $PageLikeEntityService,
$SigninEntityService, $SigninEntityService,
$UserEntityService, $UserEntityService,
$UserGroupEntityService,
$UserGroupInvitationEntityService,
$UserListEntityService, $UserListEntityService,
$FlashEntityService, $FlashEntityService,
$FlashLikeEntityService, $FlashLikeEntityService,

View File

@ -61,7 +61,7 @@ export class CustomEmojiService {
await this.db.queryResultCache!.remove(['meta_emojis']); await this.db.queryResultCache!.remove(['meta_emojis']);
this.globalEventService.publishBroadcastStream('emojiAdded', { this.globalEventService.publishBroadcastStream('emojiAdded', {
emoji: await this.emojiEntityService.pack(emoji.id), emoji: await this.emojiEntityService.packDetailed(emoji.id),
}); });
} }

View File

@ -7,7 +7,7 @@ import { DI } from '@/di-symbols.js';
import type { DriveFilesRepository, UsersRepository, DriveFoldersRepository, UserProfilesRepository } from '@/models/index.js'; import type { DriveFilesRepository, UsersRepository, DriveFoldersRepository, UserProfilesRepository } from '@/models/index.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import Logger from '@/logger.js'; import Logger from '@/logger.js';
import type { IRemoteUser, User } from '@/models/entities/User.js'; import type { RemoteUser, User } from '@/models/entities/User.js';
import { MetaService } from '@/core/MetaService.js'; import { MetaService } from '@/core/MetaService.js';
import { DriveFile } from '@/models/entities/DriveFile.js'; import { DriveFile } from '@/models/entities/DriveFile.js';
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
@ -255,7 +255,7 @@ export class DriveService {
return { return {
webpublic: null, webpublic: null,
thumbnail: null, thumbnail: null,
} };
} }
try { try {
@ -399,7 +399,7 @@ export class DriveService {
} }
@bindThis @bindThis
private async deleteOldFile(user: IRemoteUser) { private async deleteOldFile(user: RemoteUser) {
const q = this.driveFilesRepository.createQueryBuilder('file') const q = this.driveFilesRepository.createQueryBuilder('file')
.where('file.userId = :userId', { userId: user.id }) .where('file.userId = :userId', { userId: user.id })
.andWhere('file.isLink = FALSE'); .andWhere('file.isLink = FALSE');
@ -500,7 +500,7 @@ export class DriveService {
throw new IdentifiableError('c6244ed2-a39a-4e1c-bf93-f0fbd7764fa6', 'No free space.'); throw new IdentifiableError('c6244ed2-a39a-4e1c-bf93-f0fbd7764fa6', 'No free space.');
} else { } else {
// (アバターまたはバナーを含まず)最も古いファイルを削除する // (アバターまたはバナーを含まず)最も古いファイルを削除する
this.deleteOldFile(await this.usersRepository.findOneByOrFail({ id: user.id }) as IRemoteUser); this.deleteOldFile(await this.usersRepository.findOneByOrFail({ id: user.id }) as RemoteUser);
} }
} }
} }

View File

@ -2,7 +2,6 @@ import { URL } from 'node:url';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { JSDOM } from 'jsdom'; import { JSDOM } from 'jsdom';
import tinycolor from 'tinycolor2'; import tinycolor from 'tinycolor2';
import fetch from 'node-fetch';
import type { Instance } from '@/models/entities/Instance.js'; import type { Instance } from '@/models/entities/Instance.js';
import type { InstancesRepository } from '@/models/index.js'; import type { InstancesRepository } from '@/models/index.js';
import { AppLockService } from '@/core/AppLockService.js'; import { AppLockService } from '@/core/AppLockService.js';

View File

@ -3,7 +3,7 @@ import * as crypto from 'node:crypto';
import { join } from 'node:path'; import { join } from 'node:path';
import * as stream from 'node:stream'; import * as stream from 'node:stream';
import * as util from 'node:util'; import * as util from 'node:util';
import { Inject, Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { FSWatcher } from 'chokidar'; import { FSWatcher } from 'chokidar';
import { fileTypeFromFile } from 'file-type'; import { fileTypeFromFile } from 'file-type';
import FFmpeg from 'fluent-ffmpeg'; import FFmpeg from 'fluent-ffmpeg';

View File

@ -3,21 +3,15 @@ import Redis from 'ioredis';
import type { User } from '@/models/entities/User.js'; import type { User } from '@/models/entities/User.js';
import type { Note } from '@/models/entities/Note.js'; import type { Note } from '@/models/entities/Note.js';
import type { UserList } from '@/models/entities/UserList.js'; import type { UserList } from '@/models/entities/UserList.js';
import type { UserGroup } from '@/models/entities/UserGroup.js';
import type { Antenna } from '@/models/entities/Antenna.js'; import type { Antenna } from '@/models/entities/Antenna.js';
import type { Channel } from '@/models/entities/Channel.js';
import type { import type {
StreamChannels, StreamChannels,
AdminStreamTypes, AdminStreamTypes,
AntennaStreamTypes, AntennaStreamTypes,
BroadcastTypes, BroadcastTypes,
ChannelStreamTypes,
DriveStreamTypes, DriveStreamTypes,
GroupMessagingStreamTypes,
InternalStreamTypes, InternalStreamTypes,
MainStreamTypes, MainStreamTypes,
MessagingIndexStreamTypes,
MessagingStreamTypes,
NoteStreamTypes, NoteStreamTypes,
UserListStreamTypes, UserListStreamTypes,
UserStreamTypes, UserStreamTypes,
@ -83,11 +77,6 @@ export class GlobalEventService {
}); });
} }
@bindThis
public publishChannelStream<K extends keyof ChannelStreamTypes>(channelId: Channel['id'], type: K, value?: ChannelStreamTypes[K]): void {
this.publish(`channelStream:${channelId}`, type, typeof value === 'undefined' ? null : value);
}
@bindThis @bindThis
public publishUserListStream<K extends keyof UserListStreamTypes>(listId: UserList['id'], type: K, value?: UserListStreamTypes[K]): void { public publishUserListStream<K extends keyof UserListStreamTypes>(listId: UserList['id'], type: K, value?: UserListStreamTypes[K]): void {
this.publish(`userListStream:${listId}`, type, typeof value === 'undefined' ? null : value); this.publish(`userListStream:${listId}`, type, typeof value === 'undefined' ? null : value);
@ -98,21 +87,6 @@ export class GlobalEventService {
this.publish(`antennaStream:${antennaId}`, type, typeof value === 'undefined' ? null : value); this.publish(`antennaStream:${antennaId}`, type, typeof value === 'undefined' ? null : value);
} }
@bindThis
public publishMessagingStream<K extends keyof MessagingStreamTypes>(userId: User['id'], otherpartyId: User['id'], type: K, value?: MessagingStreamTypes[K]): void {
this.publish(`messagingStream:${userId}-${otherpartyId}`, type, typeof value === 'undefined' ? null : value);
}
@bindThis
public publishGroupMessagingStream<K extends keyof GroupMessagingStreamTypes>(groupId: UserGroup['id'], type: K, value?: GroupMessagingStreamTypes[K]): void {
this.publish(`messagingStream:${groupId}`, type, typeof value === 'undefined' ? null : value);
}
@bindThis
public publishMessagingIndexStream<K extends keyof MessagingIndexStreamTypes>(userId: User['id'], type: K, value?: MessagingIndexStreamTypes[K]): void {
this.publish(`messagingIndexStream:${userId}`, type, typeof value === 'undefined' ? null : value);
}
@bindThis @bindThis
public publishNotesStream(note: Packed<'Note'>): void { public publishNotesStream(note: Packed<'Note'>): void {
this.publish('notesStream', null, note); this.publish('notesStream', null, note);

View File

@ -99,7 +99,6 @@ export class HttpRequestService {
const res = await this.send(url, { const res = await this.send(url, {
method: 'GET', method: 'GET',
headers: Object.assign({ headers: Object.assign({
'User-Agent': this.config.userAgent,
Accept: accept, Accept: accept,
}, headers ?? {}), }, headers ?? {}),
timeout: 5000, timeout: 5000,
@ -114,7 +113,6 @@ export class HttpRequestService {
const res = await this.send(url, { const res = await this.send(url, {
method: 'GET', method: 'GET',
headers: Object.assign({ headers: Object.assign({
'User-Agent': this.config.userAgent,
Accept: accept, Accept: accept,
}, headers ?? {}), }, headers ?? {}),
timeout: 5000, timeout: 5000,
@ -144,7 +142,10 @@ export class HttpRequestService {
const res = await fetch(url, { const res = await fetch(url, {
method: args.method ?? 'GET', method: args.method ?? 'GET',
headers: args.headers, headers: {
'User-Agent': this.config.userAgent,
...(args.headers ?? {})
},
body: args.body, body: args.body,
size: args.size ?? 10 * 1024 * 1024, size: args.size ?? 10 * 1024 * 1024,
agent: (url) => this.getAgentByUrl(url), agent: (url) => this.getAgentByUrl(url),

View File

@ -107,7 +107,7 @@ export class ImageProcessingService {
withoutEnlargement: true, withoutEnlargement: true,
}) })
.rotate() .rotate()
.webp(options) .webp(options);
return { return {
data, data,

View File

@ -1,6 +1,6 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { IsNull } from 'typeorm'; import { IsNull } from 'typeorm';
import type { ILocalUser } from '@/models/entities/User.js'; import type { LocalUser } from '@/models/entities/User.js';
import type { UsersRepository } from '@/models/index.js'; import type { UsersRepository } from '@/models/index.js';
import { Cache } from '@/misc/cache.js'; import { Cache } from '@/misc/cache.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
@ -11,7 +11,7 @@ const ACTOR_USERNAME = 'instance.actor' as const;
@Injectable() @Injectable()
export class InstanceActorService { export class InstanceActorService {
private cache: Cache<ILocalUser>; private cache: Cache<LocalUser>;
constructor( constructor(
@Inject(DI.usersRepository) @Inject(DI.usersRepository)
@ -19,24 +19,24 @@ export class InstanceActorService {
private createSystemUserService: CreateSystemUserService, private createSystemUserService: CreateSystemUserService,
) { ) {
this.cache = new Cache<ILocalUser>(Infinity); this.cache = new Cache<LocalUser>(Infinity);
} }
@bindThis @bindThis
public async getInstanceActor(): Promise<ILocalUser> { public async getInstanceActor(): Promise<LocalUser> {
const cached = this.cache.get(null); const cached = this.cache.get(null);
if (cached) return cached; if (cached) return cached;
const user = await this.usersRepository.findOneBy({ const user = await this.usersRepository.findOneBy({
host: IsNull(), host: IsNull(),
username: ACTOR_USERNAME, username: ACTOR_USERNAME,
}) as ILocalUser | undefined; }) as LocalUser | undefined;
if (user) { if (user) {
this.cache.set(null, user); this.cache.set(null, user);
return user; return user;
} else { } else {
const created = await this.createSystemUserService.createSystemUser(ACTOR_USERNAME) as ILocalUser; const created = await this.createSystemUserService.createSystemUser(ACTOR_USERNAME) as LocalUser;
this.cache.set(null, created); this.cache.set(null, created);
return created; return created;
} }

View File

@ -1,307 +0,0 @@
import { Inject, Injectable } from '@nestjs/common';
import { In, Not } from 'typeorm';
import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import type { DriveFile } from '@/models/entities/DriveFile.js';
import type { MessagingMessage } from '@/models/entities/MessagingMessage.js';
import type { Note } from '@/models/entities/Note.js';
import type { User, CacheableUser, IRemoteUser } from '@/models/entities/User.js';
import type { UserGroup } from '@/models/entities/UserGroup.js';
import { QueueService } from '@/core/QueueService.js';
import { toArray } from '@/misc/prelude/array.js';
import { IdentifiableError } from '@/misc/identifiable-error.js';
import type { MessagingMessagesRepository, MutingsRepository, UserGroupJoiningsRepository, UsersRepository } from '@/models/index.js';
import { IdService } from '@/core/IdService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
import { MessagingMessageEntityService } from '@/core/entities/MessagingMessageEntityService.js';
import { PushNotificationService } from '@/core/PushNotificationService.js';
import { bindThis } from '@/decorators.js';
@Injectable()
export class MessagingService {
constructor(
@Inject(DI.config)
private config: Config,
@Inject(DI.usersRepository)
private usersRepository: UsersRepository,
@Inject(DI.messagingMessagesRepository)
private messagingMessagesRepository: MessagingMessagesRepository,
@Inject(DI.userGroupJoiningsRepository)
private userGroupJoiningsRepository: UserGroupJoiningsRepository,
@Inject(DI.mutingsRepository)
private mutingsRepository: MutingsRepository,
private userEntityService: UserEntityService,
private messagingMessageEntityService: MessagingMessageEntityService,
private idService: IdService,
private globalEventService: GlobalEventService,
private apRendererService: ApRendererService,
private queueService: QueueService,
private pushNotificationService: PushNotificationService,
) {
}
@bindThis
public async createMessage(user: { id: User['id']; host: User['host']; }, recipientUser: CacheableUser | undefined, recipientGroup: UserGroup | undefined, text: string | null | undefined, file: DriveFile | null, uri?: string) {
const message = {
id: this.idService.genId(),
createdAt: new Date(),
fileId: file ? file.id : null,
recipientId: recipientUser ? recipientUser.id : null,
groupId: recipientGroup ? recipientGroup.id : null,
text: text ? text.trim() : null,
userId: user.id,
isRead: false,
reads: [] as any[],
uri,
} as MessagingMessage;
await this.messagingMessagesRepository.insert(message);
const messageObj = await this.messagingMessageEntityService.pack(message);
if (recipientUser) {
if (this.userEntityService.isLocalUser(user)) {
// 自分のストリーム
this.globalEventService.publishMessagingStream(message.userId, recipientUser.id, 'message', messageObj);
this.globalEventService.publishMessagingIndexStream(message.userId, 'message', messageObj);
this.globalEventService.publishMainStream(message.userId, 'messagingMessage', messageObj);
}
if (this.userEntityService.isLocalUser(recipientUser)) {
// 相手のストリーム
this.globalEventService.publishMessagingStream(recipientUser.id, message.userId, 'message', messageObj);
this.globalEventService.publishMessagingIndexStream(recipientUser.id, 'message', messageObj);
this.globalEventService.publishMainStream(recipientUser.id, 'messagingMessage', messageObj);
}
} else if (recipientGroup) {
// グループのストリーム
this.globalEventService.publishGroupMessagingStream(recipientGroup.id, 'message', messageObj);
// メンバーのストリーム
const joinings = await this.userGroupJoiningsRepository.findBy({ userGroupId: recipientGroup.id });
for (const joining of joinings) {
this.globalEventService.publishMessagingIndexStream(joining.userId, 'message', messageObj);
this.globalEventService.publishMainStream(joining.userId, 'messagingMessage', messageObj);
}
}
// 2秒経っても(今回作成した)メッセージが既読にならなかったら「未読のメッセージがありますよ」イベントを発行する
setTimeout(async () => {
const freshMessage = await this.messagingMessagesRepository.findOneBy({ id: message.id });
if (freshMessage == null) return; // メッセージが削除されている場合もある
if (recipientUser && this.userEntityService.isLocalUser(recipientUser)) {
if (freshMessage.isRead) return; // 既読
//#region ただしミュートされているなら発行しない
const mute = await this.mutingsRepository.findBy({
muterId: recipientUser.id,
});
if (mute.map(m => m.muteeId).includes(user.id)) return;
//#endregion
this.globalEventService.publishMainStream(recipientUser.id, 'unreadMessagingMessage', messageObj);
this.pushNotificationService.pushNotification(recipientUser.id, 'unreadMessagingMessage', messageObj);
} else if (recipientGroup) {
const joinings = await this.userGroupJoiningsRepository.findBy({ userGroupId: recipientGroup.id, userId: Not(user.id) });
for (const joining of joinings) {
if (freshMessage.reads.includes(joining.userId)) return; // 既読
this.globalEventService.publishMainStream(joining.userId, 'unreadMessagingMessage', messageObj);
this.pushNotificationService.pushNotification(joining.userId, 'unreadMessagingMessage', messageObj);
}
}
}, 2000);
if (recipientUser && this.userEntityService.isLocalUser(user) && this.userEntityService.isRemoteUser(recipientUser)) {
const note = {
id: message.id,
createdAt: message.createdAt,
fileIds: message.fileId ? [message.fileId] : [],
text: message.text,
userId: message.userId,
visibility: 'specified',
mentions: [recipientUser].map(u => u.id),
mentionedRemoteUsers: JSON.stringify([recipientUser].map(u => ({
uri: u.uri,
username: u.username,
host: u.host,
}))),
} as Note;
const activity = this.apRendererService.renderActivity(this.apRendererService.renderCreate(await this.apRendererService.renderNote(note, false, true), note));
this.queueService.deliver(user, activity, recipientUser.inbox);
}
return messageObj;
}
@bindThis
public async deleteMessage(message: MessagingMessage) {
await this.messagingMessagesRepository.delete(message.id);
this.postDeleteMessage(message);
}
@bindThis
private async postDeleteMessage(message: MessagingMessage) {
if (message.recipientId) {
const user = await this.usersRepository.findOneByOrFail({ id: message.userId });
const recipient = await this.usersRepository.findOneByOrFail({ id: message.recipientId });
if (this.userEntityService.isLocalUser(user)) this.globalEventService.publishMessagingStream(message.userId, message.recipientId, 'deleted', message.id);
if (this.userEntityService.isLocalUser(recipient)) this.globalEventService.publishMessagingStream(message.recipientId, message.userId, 'deleted', message.id);
if (this.userEntityService.isLocalUser(user) && this.userEntityService.isRemoteUser(recipient)) {
const activity = this.apRendererService.renderActivity(this.apRendererService.renderDelete(this.apRendererService.renderTombstone(`${this.config.url}/notes/${message.id}`), user));
this.queueService.deliver(user, activity, recipient.inbox);
}
} else if (message.groupId) {
this.globalEventService.publishGroupMessagingStream(message.groupId, 'deleted', message.id);
}
}
/**
* Mark messages as read
*/
@bindThis
public async readUserMessagingMessage(
userId: User['id'],
otherpartyId: User['id'],
messageIds: MessagingMessage['id'][],
) {
if (messageIds.length === 0) return;
const messages = await this.messagingMessagesRepository.findBy({
id: In(messageIds),
});
for (const message of messages) {
if (message.recipientId !== userId) {
throw new IdentifiableError('e140a4bf-49ce-4fb6-b67c-b78dadf6b52f', 'Access denied (user).');
}
}
// Update documents
await this.messagingMessagesRepository.update({
id: In(messageIds),
userId: otherpartyId,
recipientId: userId,
isRead: false,
}, {
isRead: true,
});
// Publish event
this.globalEventService.publishMessagingStream(otherpartyId, userId, 'read', messageIds);
this.globalEventService.publishMessagingIndexStream(userId, 'read', messageIds);
if (!await this.userEntityService.getHasUnreadMessagingMessage(userId)) {
// 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行
this.globalEventService.publishMainStream(userId, 'readAllMessagingMessages');
this.pushNotificationService.pushNotification(userId, 'readAllMessagingMessages', undefined);
} else {
// そのユーザーとのメッセージで未読がなければイベント発行
const count = await this.messagingMessagesRepository.count({
where: {
userId: otherpartyId,
recipientId: userId,
isRead: false,
},
take: 1,
});
if (!count) {
this.pushNotificationService.pushNotification(userId, 'readAllMessagingMessagesOfARoom', { userId: otherpartyId });
}
}
}
/**
* Mark messages as read
*/
@bindThis
public async readGroupMessagingMessage(
userId: User['id'],
groupId: UserGroup['id'],
messageIds: MessagingMessage['id'][],
) {
if (messageIds.length === 0) return;
// check joined
const joining = await this.userGroupJoiningsRepository.findOneBy({
userId: userId,
userGroupId: groupId,
});
if (joining == null) {
throw new IdentifiableError('930a270c-714a-46b2-b776-ad27276dc569', 'Access denied (group).');
}
const messages = await this.messagingMessagesRepository.findBy({
id: In(messageIds),
});
const reads: MessagingMessage['id'][] = [];
for (const message of messages) {
if (message.userId === userId) continue;
if (message.reads.includes(userId)) continue;
// Update document
await this.messagingMessagesRepository.createQueryBuilder().update()
.set({
reads: (() => `array_append("reads", '${joining.userId}')`) as any,
})
.where('id = :id', { id: message.id })
.execute();
reads.push(message.id);
}
// Publish event
this.globalEventService.publishGroupMessagingStream(groupId, 'read', {
ids: reads,
userId: userId,
});
this.globalEventService.publishMessagingIndexStream(userId, 'read', reads);
if (!await this.userEntityService.getHasUnreadMessagingMessage(userId)) {
// 全ての(いままで未読だった)自分宛てのメッセージを(これで)読みましたよというイベントを発行
this.globalEventService.publishMainStream(userId, 'readAllMessagingMessages');
this.pushNotificationService.pushNotification(userId, 'readAllMessagingMessages', undefined);
} else {
// そのグループにおいて未読がなければイベント発行
const unreadExist = await this.messagingMessagesRepository.createQueryBuilder('message')
.where('message.groupId = :groupId', { groupId: groupId })
.andWhere('message.userId != :userId', { userId: userId })
.andWhere('NOT (:userId = ANY(message.reads))', { userId: userId })
.andWhere('message.createdAt > :joinedAt', { joinedAt: joining.createdAt }) // 自分が加入する前の会話については、未読扱いしない
.getOne().then(x => x != null);
if (!unreadExist) {
this.pushNotificationService.pushNotification(userId, 'readAllMessagingMessagesOfARoom', { groupId });
}
}
}
@bindThis
public async deliverReadActivity(user: { id: User['id']; host: null; }, recipient: IRemoteUser, messages: MessagingMessage | MessagingMessage[]) {
messages = toArray(messages).filter(x => x.uri);
const contents = messages.map(x => this.apRendererService.renderRead(user, x));
if (contents.length > 1) {
const collection = this.apRendererService.renderOrderedCollection(null, contents.length, undefined, undefined, contents);
this.queueService.deliver(user, this.apRendererService.renderActivity(collection), recipient.inbox);
} else {
for (const content of contents) {
this.queueService.deliver(user, this.apRendererService.renderActivity(content), recipient.inbox);
}
}
}
}

View File

@ -1,9 +1,8 @@
import { URL } from 'node:url'; import { URL } from 'node:url';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import * as parse5 from 'parse5'; import * as parse5 from 'parse5';
import { JSDOM } from 'jsdom'; import { Window } from 'happy-dom';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { UsersRepository } from '@/models/index.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import { intersperse } from '@/misc/prelude/array.js'; import { intersperse } from '@/misc/prelude/array.js';
import type { IMentionedRemoteUsers } from '@/models/entities/Note.js'; import type { IMentionedRemoteUsers } from '@/models/entities/Note.js';
@ -236,7 +235,7 @@ export class MfmService {
return null; return null;
} }
const { window } = new JSDOM(''); const { window } = new Window();
const doc = window.document; const doc = window.document;
@ -301,7 +300,7 @@ export class MfmService {
hashtag: (node) => { hashtag: (node) => {
const a = doc.createElement('a'); const a = doc.createElement('a');
a.href = `${this.config.url}/tags/${node.props.hashtag}`; a.setAttribute('href', `${this.config.url}/tags/${node.props.hashtag}`);
a.textContent = `#${node.props.hashtag}`; a.textContent = `#${node.props.hashtag}`;
a.setAttribute('rel', 'tag'); a.setAttribute('rel', 'tag');
return a; return a;
@ -327,7 +326,7 @@ export class MfmService {
link: (node) => { link: (node) => {
const a = doc.createElement('a'); const a = doc.createElement('a');
a.href = node.props.url; a.setAttribute('href', node.props.url);
appendChildren(node.children, a); appendChildren(node.children, a);
return a; return a;
}, },
@ -336,7 +335,7 @@ export class MfmService {
const a = doc.createElement('a'); const a = doc.createElement('a');
const { username, host, acct } = node.props; const { username, host, acct } = node.props;
const remoteUserInfo = mentionedRemoteUsers.find(remoteUser => remoteUser.username === username && remoteUser.host === host); const remoteUserInfo = mentionedRemoteUsers.find(remoteUser => remoteUser.username === username && remoteUser.host === host);
a.href = remoteUserInfo ? (remoteUserInfo.url ? remoteUserInfo.url : remoteUserInfo.uri) : `${this.config.url}/${acct}`; a.setAttribute('href', remoteUserInfo ? (remoteUserInfo.url ? remoteUserInfo.url : remoteUserInfo.uri) : `${this.config.url}/${acct}`);
a.className = 'u-url mention'; a.className = 'u-url mention';
a.textContent = acct; a.textContent = acct;
return a; return a;
@ -361,14 +360,14 @@ export class MfmService {
url: (node) => { url: (node) => {
const a = doc.createElement('a'); const a = doc.createElement('a');
a.href = node.props.url; a.setAttribute('href', node.props.url);
a.textContent = node.props.url; a.textContent = node.props.url;
return a; return a;
}, },
search: (node) => { search: (node) => {
const a = doc.createElement('a'); const a = doc.createElement('a');
a.href = `https://www.google.com/search?q=${node.props.query}`; a.setAttribute('href', `https://www.google.com/search?q=${node.props.query}`);
a.textContent = node.props.content; a.textContent = node.props.content;
return a; return a;
}, },

View File

@ -1,5 +1,5 @@
import * as mfm from 'mfm-js'; import * as mfm from 'mfm-js';
import { Not, In, DataSource } from 'typeorm'; import { In, DataSource } from 'typeorm';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { extractMentions } from '@/misc/extract-mentions.js'; import { extractMentions } from '@/misc/extract-mentions.js';
import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js'; import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js';
@ -11,7 +11,7 @@ import type { DriveFile } from '@/models/entities/DriveFile.js';
import type { App } from '@/models/entities/App.js'; import type { App } from '@/models/entities/App.js';
import { concat } from '@/misc/prelude/array.js'; import { concat } from '@/misc/prelude/array.js';
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
import type { User, ILocalUser, IRemoteUser } from '@/models/entities/User.js'; import type { User, LocalUser, RemoteUser } from '@/models/entities/User.js';
import type { IPoll } from '@/models/entities/Poll.js'; import type { IPoll } from '@/models/entities/Poll.js';
import { Poll } from '@/models/entities/Poll.js'; import { Poll } from '@/models/entities/Poll.js';
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js'; import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
@ -52,7 +52,7 @@ class NotificationManager {
private notifier: { id: User['id']; }; private notifier: { id: User['id']; };
private note: Note; private note: Note;
private queue: { private queue: {
target: ILocalUser['id']; target: LocalUser['id'];
reason: NotificationType; reason: NotificationType;
}[]; }[];
@ -68,7 +68,7 @@ class NotificationManager {
} }
@bindThis @bindThis
public push(notifiee: ILocalUser['id'], reason: NotificationType) { public push(notifiee: LocalUser['id'], reason: NotificationType) {
// 自分自身へは通知しない // 自分自身へは通知しない
if (this.notifier.id === notifiee) return; if (this.notifier.id === notifiee) return;
@ -605,7 +605,7 @@ export class NoteCreateService {
// メンションされたリモートユーザーに配送 // メンションされたリモートユーザーに配送
for (const u of mentionedUsers.filter(u => this.userEntityService.isRemoteUser(u))) { for (const u of mentionedUsers.filter(u => this.userEntityService.isRemoteUser(u))) {
dm.addDirectRecipe(u as IRemoteUser); dm.addDirectRecipe(u as RemoteUser);
} }
// 投稿がリプライかつ投稿者がローカルユーザーかつリプライ先の投稿の投稿者がリモートユーザーなら配送 // 投稿がリプライかつ投稿者がローカルユーザーかつリプライ先の投稿の投稿者がリモートユーザーなら配送
@ -711,7 +711,7 @@ export class NoteCreateService {
? this.apRendererService.renderAnnounce(data.renote.uri ? data.renote.uri : `${this.config.url}/notes/${data.renote.id}`, note) ? this.apRendererService.renderAnnounce(data.renote.uri ? data.renote.uri : `${this.config.url}/notes/${data.renote.id}`, note)
: this.apRendererService.renderCreate(await this.apRendererService.renderNote(note, false), note); : this.apRendererService.renderCreate(await this.apRendererService.renderNote(note, false), note);
return this.apRendererService.renderActivity(content); return this.apRendererService.addContext(content);
} }
@bindThis @bindThis

View File

@ -1,6 +1,6 @@
import { Brackets, In } from 'typeorm'; import { Brackets, In } from 'typeorm';
import { Injectable, Inject } from '@nestjs/common'; import { Injectable, Inject } from '@nestjs/common';
import type { User, ILocalUser, IRemoteUser } from '@/models/entities/User.js'; import type { User, LocalUser, RemoteUser } from '@/models/entities/User.js';
import type { Note, IMentionedRemoteUsers } from '@/models/entities/Note.js'; import type { Note, IMentionedRemoteUsers } from '@/models/entities/Note.js';
import type { InstancesRepository, NotesRepository, UsersRepository } from '@/models/index.js'; import type { InstancesRepository, NotesRepository, UsersRepository } from '@/models/index.js';
import { RelayService } from '@/core/RelayService.js'; import { RelayService } from '@/core/RelayService.js';
@ -78,7 +78,7 @@ export class NoteDeleteService {
}); });
} }
const content = this.apRendererService.renderActivity(renote const content = this.apRendererService.addContext(renote
? this.apRendererService.renderUndo(this.apRendererService.renderAnnounce(renote.uri ?? `${this.config.url}/notes/${renote.id}`, note), user) ? this.apRendererService.renderUndo(this.apRendererService.renderAnnounce(renote.uri ?? `${this.config.url}/notes/${renote.id}`, note), user)
: this.apRendererService.renderDelete(this.apRendererService.renderTombstone(`${this.config.url}/notes/${note.id}`), user)); : this.apRendererService.renderDelete(this.apRendererService.renderTombstone(`${this.config.url}/notes/${note.id}`), user));
@ -90,7 +90,7 @@ export class NoteDeleteService {
for (const cascadingNote of cascadingNotes) { for (const cascadingNote of cascadingNotes) {
if (!cascadingNote.user) continue; if (!cascadingNote.user) continue;
if (!this.userEntityService.isLocalUser(cascadingNote.user)) continue; if (!this.userEntityService.isLocalUser(cascadingNote.user)) continue;
const content = this.apRendererService.renderActivity(this.apRendererService.renderDelete(this.apRendererService.renderTombstone(`${this.config.url}/notes/${cascadingNote.id}`), cascadingNote.user)); const content = this.apRendererService.addContext(this.apRendererService.renderDelete(this.apRendererService.renderTombstone(`${this.config.url}/notes/${cascadingNote.id}`), cascadingNote.user));
this.deliverToConcerned(cascadingNote.user, cascadingNote, content); this.deliverToConcerned(cascadingNote.user, cascadingNote, content);
} }
//#endregion //#endregion
@ -159,11 +159,11 @@ export class NoteDeleteService {
return await this.usersRepository.find({ return await this.usersRepository.find({
where, where,
}) as IRemoteUser[]; }) as RemoteUser[];
} }
@bindThis @bindThis
private async deliverToConcerned(user: { id: ILocalUser['id']; host: null; }, note: Note, content: any) { private async deliverToConcerned(user: { id: LocalUser['id']; host: null; }, note: Note, content: any) {
this.apDeliverManagerService.deliverToFollowers(user, content); this.apDeliverManagerService.deliverToFollowers(user, content);
this.relayService.deliverToRelays(user, content); this.relayService.deliverToRelays(user, content);
const remoteUsers = await this.getMentionedRemoteUsers(note); const remoteUsers = await this.getMentionedRemoteUsers(note);

View File

@ -115,7 +115,7 @@ export class NotePiningService {
const target = `${this.config.url}/users/${user.id}/collections/featured`; const target = `${this.config.url}/users/${user.id}/collections/featured`;
const item = `${this.config.url}/notes/${noteId}`; const item = `${this.config.url}/notes/${noteId}`;
const content = this.apRendererService.renderActivity(isAddition ? this.apRendererService.renderAdd(user, target, item) : this.apRendererService.renderRemove(user, target, item)); const content = this.apRendererService.addContext(isAddition ? this.apRendererService.renderAdd(user, target, item) : this.apRendererService.renderRemove(user, target, item));
this.apDeliverManagerService.deliverToFollowers(user, content); this.apDeliverManagerService.deliverToFollowers(user, content);
this.relayService.deliverToRelays(user, content); this.relayService.deliverToRelays(user, content);

View File

@ -2,13 +2,12 @@ import { Inject, Injectable } from '@nestjs/common';
import { In } from 'typeorm'; import { In } from 'typeorm';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { NotificationsRepository } from '@/models/index.js'; import type { NotificationsRepository } from '@/models/index.js';
import type { UsersRepository } from '@/models/index.js';
import type { User } from '@/models/entities/User.js'; import type { User } from '@/models/entities/User.js';
import type { Notification } from '@/models/entities/Notification.js'; import type { Notification } from '@/models/entities/Notification.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js';
import { GlobalEventService } from './GlobalEventService.js'; import { GlobalEventService } from './GlobalEventService.js';
import { PushNotificationService } from './PushNotificationService.js'; import { PushNotificationService } from './PushNotificationService.js';
import { bindThis } from '@/decorators.js';
@Injectable() @Injectable()
export class NotificationService { export class NotificationService {
@ -66,7 +65,6 @@ export class NotificationService {
@bindThis @bindThis
private postReadNotifications(userId: User['id'], notificationIds: Notification['id'][]) { private postReadNotifications(userId: User['id'], notificationIds: Notification['id'][]) {
this.globalEventService.publishMainStream(userId, 'readNotifications', notificationIds);
return this.pushNotificationService.pushNotification(userId, 'readNotifications', { notificationIds }); return this.pushNotificationService.pushNotification(userId, 'readNotifications', { notificationIds });
} }
} }

View File

@ -1,10 +1,8 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { Not } from 'typeorm';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { NotesRepository, UsersRepository, PollsRepository, PollVotesRepository } from '@/models/index.js'; import type { NotesRepository, UsersRepository, PollsRepository, PollVotesRepository, User } from '@/models/index.js';
import type { Note } from '@/models/entities/Note.js'; import type { Note } from '@/models/entities/Note.js';
import { RelayService } from '@/core/RelayService.js'; import { RelayService } from '@/core/RelayService.js';
import type { CacheableUser } from '@/models/entities/User.js';
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
@ -39,7 +37,7 @@ export class PollService {
} }
@bindThis @bindThis
public async vote(user: CacheableUser, note: Note, choice: number) { public async vote(user: User, note: Note, choice: number) {
const poll = await this.pollsRepository.findOneBy({ noteId: note.id }); const poll = await this.pollsRepository.findOneBy({ noteId: note.id });
if (poll == null) throw new Error('poll not found'); if (poll == null) throw new Error('poll not found');
@ -97,7 +95,7 @@ export class PollService {
if (user == null) throw new Error('note not found'); if (user == null) throw new Error('note not found');
if (this.userEntityService.isLocalUser(user)) { if (this.userEntityService.isLocalUser(user)) {
const content = this.apRendererService.renderActivity(this.apRendererService.renderUpdate(await this.apRendererService.renderNote(note, false), user)); const content = this.apRendererService.addContext(this.apRendererService.renderUpdate(await this.apRendererService.renderNote(note, false), user));
this.apDeliverManagerService.deliverToFollowers(user, content); this.apDeliverManagerService.deliverToFollowers(user, content);
this.relayService.deliverToRelays(user, content); this.relayService.deliverToRelays(user, content);
} }

View File

@ -1,6 +1,6 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import type { UsersRepository } from '@/models/index.js'; import type { UsersRepository } from '@/models/index.js';
import type { ILocalUser, User } from '@/models/entities/User.js'; import type { LocalUser } from '@/models/entities/User.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { MetaService } from '@/core/MetaService.js'; import { MetaService } from '@/core/MetaService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
@ -16,9 +16,9 @@ export class ProxyAccountService {
} }
@bindThis @bindThis
public async fetch(): Promise<ILocalUser | null> { public async fetch(): Promise<LocalUser | null> {
const meta = await this.metaService.fetch(); const meta = await this.metaService.fetch();
if (meta.proxyAccountId == null) return null; if (meta.proxyAccountId == null) return null;
return await this.usersRepository.findOneByOrFail({ id: meta.proxyAccountId }) as ILocalUser; return await this.usersRepository.findOneByOrFail({ id: meta.proxyAccountId }) as LocalUser;
} }
} }

View File

@ -9,24 +9,21 @@ import { MetaService } from '@/core/MetaService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
// Defined also packages/sw/types.ts#L13 // Defined also packages/sw/types.ts#L13
type pushNotificationsTypes = { type PushNotificationsTypes = {
'notification': Packed<'Notification'>; 'notification': Packed<'Notification'>;
'unreadMessagingMessage': Packed<'MessagingMessage'>;
'unreadAntennaNote': { 'unreadAntennaNote': {
antenna: { id: string, name: string }; antenna: { id: string, name: string };
note: Packed<'Note'>; note: Packed<'Note'>;
}; };
'readNotifications': { notificationIds: string[] }; 'readNotifications': { notificationIds: string[] };
'readAllNotifications': undefined; 'readAllNotifications': undefined;
'readAllMessagingMessages': undefined;
'readAllMessagingMessagesOfARoom': { userId: string } | { groupId: string };
'readAntenna': { antennaId: string }; 'readAntenna': { antennaId: string };
'readAllAntennas': undefined; 'readAllAntennas': undefined;
}; };
// Reduce length because push message servers have character limits // Reduce length because push message servers have character limits
function truncateBody<T extends keyof pushNotificationsTypes>(type: T, body: pushNotificationsTypes[T]): pushNotificationsTypes[T] { function truncateBody<T extends keyof PushNotificationsTypes>(type: T, body: PushNotificationsTypes[T]): PushNotificationsTypes[T] {
if (body === undefined) return body; if (typeof body !== 'object') return body;
return { return {
...body, ...body,
@ -40,11 +37,9 @@ function truncateBody<T extends keyof pushNotificationsTypes>(type: T, body: pus
reply: undefined, reply: undefined,
renote: undefined, renote: undefined,
user: type === 'notification' ? undefined as any : body.note.user, user: type === 'notification' ? undefined as any : body.note.user,
} },
} : {}), } : {}),
}; };
return body;
} }
@Injectable() @Injectable()
@ -61,7 +56,7 @@ export class PushNotificationService {
} }
@bindThis @bindThis
public async pushNotification<T extends keyof pushNotificationsTypes>(userId: string, type: T, body: pushNotificationsTypes[T]) { public async pushNotification<T extends keyof PushNotificationsTypes>(userId: string, type: T, body: PushNotificationsTypes[T]) {
const meta = await this.metaService.fetch(); const meta = await this.metaService.fetch();
if (!meta.enableServiceWorker || meta.swPublicKey == null || meta.swPrivateKey == null) return; if (!meta.enableServiceWorker || meta.swPublicKey == null || meta.swPrivateKey == null) return;
@ -81,8 +76,6 @@ export class PushNotificationService {
if ([ if ([
'readNotifications', 'readNotifications',
'readAllNotifications', 'readAllNotifications',
'readAllMessagingMessages',
'readAllMessagingMessagesOfARoom',
'readAntenna', 'readAntenna',
'readAllAntennas', 'readAllAntennas',
].includes(type) && !subscription.sendReadMessage) continue; ].includes(type) && !subscription.sendReadMessage) continue;

View File

@ -3,7 +3,7 @@ import { IsNull } from 'typeorm';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { EmojisRepository, BlockingsRepository, NoteReactionsRepository, UsersRepository, NotesRepository } from '@/models/index.js'; import type { EmojisRepository, BlockingsRepository, NoteReactionsRepository, UsersRepository, NotesRepository } from '@/models/index.js';
import { IdentifiableError } from '@/misc/identifiable-error.js'; import { IdentifiableError } from '@/misc/identifiable-error.js';
import type { IRemoteUser, User } from '@/models/entities/User.js'; import type { RemoteUser, User } from '@/models/entities/User.js';
import type { Note } from '@/models/entities/Note.js'; import type { Note } from '@/models/entities/Note.js';
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
import type { NoteReaction } from '@/models/entities/NoteReaction.js'; import type { NoteReaction } from '@/models/entities/NoteReaction.js';
@ -85,7 +85,7 @@ export class ReactionService {
} }
@bindThis @bindThis
public async create(user: { id: User['id']; host: User['host']; isBot: User['isBot'] }, note: Note, reaction?: string) { public async create(user: { id: User['id']; host: User['host']; isBot: User['isBot'] }, note: Note, reaction?: string | null) {
// Check blocking // Check blocking
if (note.userId !== user.id) { if (note.userId !== user.id) {
const blocked = await this.userBlockingService.checkBlocked(note.userId, user.id); const blocked = await this.userBlockingService.checkBlocked(note.userId, user.id);
@ -177,11 +177,11 @@ export class ReactionService {
//#region 配信 //#region 配信
if (this.userEntityService.isLocalUser(user) && !note.localOnly) { if (this.userEntityService.isLocalUser(user) && !note.localOnly) {
const content = this.apRendererService.renderActivity(await this.apRendererService.renderLike(record, note)); const content = this.apRendererService.addContext(await this.apRendererService.renderLike(record, note));
const dm = this.apDeliverManagerService.createDeliverManager(user, content); const dm = this.apDeliverManagerService.createDeliverManager(user, content);
if (note.userHost !== null) { if (note.userHost !== null) {
const reactee = await this.usersRepository.findOneBy({ id: note.userId }); const reactee = await this.usersRepository.findOneBy({ id: note.userId });
dm.addDirectRecipe(reactee as IRemoteUser); dm.addDirectRecipe(reactee as RemoteUser);
} }
if (['public', 'home', 'followers'].includes(note.visibility)) { if (['public', 'home', 'followers'].includes(note.visibility)) {
@ -189,7 +189,7 @@ export class ReactionService {
} else if (note.visibility === 'specified') { } else if (note.visibility === 'specified') {
const visibleUsers = await Promise.all(note.visibleUserIds.map(id => this.usersRepository.findOneBy({ id }))); const visibleUsers = await Promise.all(note.visibleUserIds.map(id => this.usersRepository.findOneBy({ id })));
for (const u of visibleUsers.filter(u => u && this.userEntityService.isRemoteUser(u))) { for (const u of visibleUsers.filter(u => u && this.userEntityService.isRemoteUser(u))) {
dm.addDirectRecipe(u as IRemoteUser); dm.addDirectRecipe(u as RemoteUser);
} }
} }
@ -235,11 +235,11 @@ export class ReactionService {
//#region 配信 //#region 配信
if (this.userEntityService.isLocalUser(user) && !note.localOnly) { if (this.userEntityService.isLocalUser(user) && !note.localOnly) {
const content = this.apRendererService.renderActivity(this.apRendererService.renderUndo(await this.apRendererService.renderLike(exist, note), user)); const content = this.apRendererService.addContext(this.apRendererService.renderUndo(await this.apRendererService.renderLike(exist, note), user));
const dm = this.apDeliverManagerService.createDeliverManager(user, content); const dm = this.apDeliverManagerService.createDeliverManager(user, content);
if (note.userHost !== null) { if (note.userHost !== null) {
const reactee = await this.usersRepository.findOneBy({ id: note.userId }); const reactee = await this.usersRepository.findOneBy({ id: note.userId });
dm.addDirectRecipe(reactee as IRemoteUser); dm.addDirectRecipe(reactee as RemoteUser);
} }
dm.addFollowersRecipe(); dm.addFollowersRecipe();
dm.execute(); dm.execute();

View File

@ -1,6 +1,6 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { IsNull } from 'typeorm'; import { IsNull } from 'typeorm';
import type { ILocalUser, User } from '@/models/entities/User.js'; import type { LocalUser, User } from '@/models/entities/User.js';
import type { RelaysRepository, UsersRepository } from '@/models/index.js'; import type { RelaysRepository, UsersRepository } from '@/models/index.js';
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
import { Cache } from '@/misc/cache.js'; import { Cache } from '@/misc/cache.js';
@ -34,16 +34,16 @@ export class RelayService {
} }
@bindThis @bindThis
private async getRelayActor(): Promise<ILocalUser> { private async getRelayActor(): Promise<LocalUser> {
const user = await this.usersRepository.findOneBy({ const user = await this.usersRepository.findOneBy({
host: IsNull(), host: IsNull(),
username: ACTOR_USERNAME, username: ACTOR_USERNAME,
}); });
if (user) return user as ILocalUser; if (user) return user as LocalUser;
const created = await this.createSystemUserService.createSystemUser(ACTOR_USERNAME); const created = await this.createSystemUserService.createSystemUser(ACTOR_USERNAME);
return created as ILocalUser; return created as LocalUser;
} }
@bindThis @bindThis
@ -56,7 +56,7 @@ export class RelayService {
const relayActor = await this.getRelayActor(); const relayActor = await this.getRelayActor();
const follow = await this.apRendererService.renderFollowRelay(relay, relayActor); const follow = await this.apRendererService.renderFollowRelay(relay, relayActor);
const activity = this.apRendererService.renderActivity(follow); const activity = this.apRendererService.addContext(follow);
this.queueService.deliver(relayActor, activity, relay.inbox); this.queueService.deliver(relayActor, activity, relay.inbox);
return relay; return relay;
@ -75,7 +75,7 @@ export class RelayService {
const relayActor = await this.getRelayActor(); const relayActor = await this.getRelayActor();
const follow = this.apRendererService.renderFollowRelay(relay, relayActor); const follow = this.apRendererService.renderFollowRelay(relay, relayActor);
const undo = this.apRendererService.renderUndo(follow, relayActor); const undo = this.apRendererService.renderUndo(follow, relayActor);
const activity = this.apRendererService.renderActivity(undo); const activity = this.apRendererService.addContext(undo);
this.queueService.deliver(relayActor, activity, relay.inbox); this.queueService.deliver(relayActor, activity, relay.inbox);
await this.relaysRepository.delete(relay.id); await this.relaysRepository.delete(relay.id);

View File

@ -1,7 +1,6 @@
import { Inject, Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import type Logger from '@/logger.js'; import type Logger from '@/logger.js';
import { LoggerService } from '@/core/LoggerService.js'; import { LoggerService } from '@/core/LoggerService.js';
import { bindThis } from '@/decorators.js';
@Injectable() @Injectable()
export class RemoteLoggerService { export class RemoteLoggerService {

View File

@ -4,7 +4,7 @@ import chalk from 'chalk';
import { IsNull } from 'typeorm'; import { IsNull } from 'typeorm';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { UsersRepository } from '@/models/index.js'; import type { UsersRepository } from '@/models/index.js';
import type { IRemoteUser, User } from '@/models/entities/User.js'; import type { RemoteUser, User } from '@/models/entities/User.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import type Logger from '@/logger.js'; import type Logger from '@/logger.js';
import { UtilityService } from '@/core/UtilityService.js'; import { UtilityService } from '@/core/UtilityService.js';
@ -60,7 +60,7 @@ export class RemoteUserResolveService {
}); });
} }
const user = await this.usersRepository.findOneBy({ usernameLower, host }) as IRemoteUser | null; const user = await this.usersRepository.findOneBy({ usernameLower, host }) as RemoteUser | null;
const acctLower = `${usernameLower}@${host}`; const acctLower = `${usernameLower}@${host}`;
@ -82,7 +82,7 @@ export class RemoteUserResolveService {
const self = await this.resolveSelf(acctLower); const self = await this.resolveSelf(acctLower);
if (user.uri !== self.href) { if (user.uri !== self.href) {
// if uri mismatch, Fix (user@host <=> AP's Person id(IRemoteUser.uri)) mapping. // if uri mismatch, Fix (user@host <=> AP's Person id(RemoteUser.uri)) mapping.
this.logger.info(`uri missmatch: ${acctLower}`); this.logger.info(`uri missmatch: ${acctLower}`);
this.logger.info(`recovery missmatch uri for (username=${username}, host=${host}) from ${user.uri} to ${self.href}`); this.logger.info(`recovery missmatch uri for (username=${username}, host=${host}) from ${user.uri} to ${self.href}`);

View File

@ -3,7 +3,7 @@ import Redis from 'ioredis';
import { In } from 'typeorm'; import { In } from 'typeorm';
import type { Role, RoleAssignment, RoleAssignmentsRepository, RolesRepository, UsersRepository } from '@/models/index.js'; import type { Role, RoleAssignment, RoleAssignmentsRepository, RolesRepository, UsersRepository } from '@/models/index.js';
import { Cache } from '@/misc/cache.js'; import { Cache } from '@/misc/cache.js';
import type { CacheableLocalUser, CacheableUser, ILocalUser, User } from '@/models/entities/User.js'; import type { User } from '@/models/entities/User.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { MetaService } from '@/core/MetaService.js'; import { MetaService } from '@/core/MetaService.js';

View File

@ -2,7 +2,7 @@
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common'; import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
import Redis from 'ioredis'; import Redis from 'ioredis';
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
import type { CacheableUser, User } from '@/models/entities/User.js'; import type { User } from '@/models/entities/User.js';
import type { Blocking } from '@/models/entities/Blocking.js'; import type { Blocking } from '@/models/entities/Blocking.js';
import { QueueService } from '@/core/QueueService.js'; import { QueueService } from '@/core/QueueService.js';
import { GlobalEventService } from '@/core/GlobalEventService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js';
@ -117,7 +117,7 @@ export class UserBlockingService implements OnApplicationShutdown {
}); });
if (this.userEntityService.isLocalUser(blocker) && this.userEntityService.isRemoteUser(blockee)) { if (this.userEntityService.isLocalUser(blocker) && this.userEntityService.isRemoteUser(blockee)) {
const content = this.apRendererService.renderActivity(this.apRendererService.renderBlock(blocking)); const content = this.apRendererService.addContext(this.apRendererService.renderBlock(blocking));
this.queueService.deliver(blocker, content, blockee.inbox); this.queueService.deliver(blocker, content, blockee.inbox);
} }
} }
@ -162,13 +162,13 @@ export class UserBlockingService implements OnApplicationShutdown {
// リモートにフォローリクエストをしていたらUndoFollow送信 // リモートにフォローリクエストをしていたらUndoFollow送信
if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) { if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
const content = this.apRendererService.renderActivity(this.apRendererService.renderUndo(this.apRendererService.renderFollow(follower, followee), follower)); const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderFollow(follower, followee), follower));
this.queueService.deliver(follower, content, followee.inbox); this.queueService.deliver(follower, content, followee.inbox);
} }
// リモートからフォローリクエストを受けていたらReject送信 // リモートからフォローリクエストを受けていたらReject送信
if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) { if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
const content = this.apRendererService.renderActivity(this.apRendererService.renderReject(this.apRendererService.renderFollow(follower, followee, request.requestId!), followee)); const content = this.apRendererService.addContext(this.apRendererService.renderReject(this.apRendererService.renderFollow(follower, followee, request.requestId!), followee));
this.queueService.deliver(followee, content, follower.inbox); this.queueService.deliver(followee, content, follower.inbox);
} }
} }
@ -210,13 +210,13 @@ export class UserBlockingService implements OnApplicationShutdown {
// リモートにフォローをしていたらUndoFollow送信 // リモートにフォローをしていたらUndoFollow送信
if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) { if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
const content = this.apRendererService.renderActivity(this.apRendererService.renderUndo(this.apRendererService.renderFollow(follower, followee), follower)); const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderFollow(follower, followee), follower));
this.queueService.deliver(follower, content, followee.inbox); this.queueService.deliver(follower, content, followee.inbox);
} }
// リモートからフォローをされていたらRejectFollow送信 // リモートからフォローをされていたらRejectFollow送信
if (this.userEntityService.isLocalUser(followee) && this.userEntityService.isRemoteUser(follower)) { if (this.userEntityService.isLocalUser(followee) && this.userEntityService.isRemoteUser(follower)) {
const content = this.apRendererService.renderActivity(this.apRendererService.renderReject(this.apRendererService.renderFollow(follower, followee), followee)); const content = this.apRendererService.addContext(this.apRendererService.renderReject(this.apRendererService.renderFollow(follower, followee), followee));
this.queueService.deliver(followee, content, follower.inbox); this.queueService.deliver(followee, content, follower.inbox);
} }
} }
@ -236,7 +236,7 @@ export class UserBlockingService implements OnApplicationShutdown {
} }
@bindThis @bindThis
public async unblock(blocker: CacheableUser, blockee: CacheableUser) { public async unblock(blocker: User, blockee: User) {
const blocking = await this.blockingsRepository.findOneBy({ const blocking = await this.blockingsRepository.findOneBy({
blockerId: blocker.id, blockerId: blocker.id,
blockeeId: blockee.id, blockeeId: blockee.id,
@ -261,7 +261,7 @@ export class UserBlockingService implements OnApplicationShutdown {
// deliver if remote bloking // deliver if remote bloking
if (this.userEntityService.isLocalUser(blocker) && this.userEntityService.isRemoteUser(blockee)) { if (this.userEntityService.isLocalUser(blocker) && this.userEntityService.isRemoteUser(blockee)) {
const content = this.apRendererService.renderActivity(this.apRendererService.renderUndo(this.apRendererService.renderBlock(blocking), blocker)); const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderBlock(blocking), blocker));
this.queueService.deliver(blocker, content, blockee.inbox); this.queueService.deliver(blocker, content, blockee.inbox);
} }
} }

View File

@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
import Redis from 'ioredis'; import Redis from 'ioredis';
import type { UsersRepository } from '@/models/index.js'; import type { UsersRepository } from '@/models/index.js';
import { Cache } from '@/misc/cache.js'; import { Cache } from '@/misc/cache.js';
import type { CacheableLocalUser, CacheableUser, ILocalUser, User } from '@/models/entities/User.js'; import type { LocalUser, User } from '@/models/entities/User.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
@ -11,10 +11,10 @@ import type { OnApplicationShutdown } from '@nestjs/common';
@Injectable() @Injectable()
export class UserCacheService implements OnApplicationShutdown { export class UserCacheService implements OnApplicationShutdown {
public userByIdCache: Cache<CacheableUser>; public userByIdCache: Cache<User>;
public localUserByNativeTokenCache: Cache<CacheableLocalUser | null>; public localUserByNativeTokenCache: Cache<LocalUser | null>;
public localUserByIdCache: Cache<CacheableLocalUser>; public localUserByIdCache: Cache<LocalUser>;
public uriPersonCache: Cache<CacheableUser | null>; public uriPersonCache: Cache<User | null>;
constructor( constructor(
@Inject(DI.redisSubscriber) @Inject(DI.redisSubscriber)
@ -27,10 +27,10 @@ export class UserCacheService implements OnApplicationShutdown {
) { ) {
//this.onMessage = this.onMessage.bind(this); //this.onMessage = this.onMessage.bind(this);
this.userByIdCache = new Cache<CacheableUser>(Infinity); this.userByIdCache = new Cache<User>(Infinity);
this.localUserByNativeTokenCache = new Cache<CacheableLocalUser | null>(Infinity); this.localUserByNativeTokenCache = new Cache<LocalUser | null>(Infinity);
this.localUserByIdCache = new Cache<CacheableLocalUser>(Infinity); this.localUserByIdCache = new Cache<LocalUser>(Infinity);
this.uriPersonCache = new Cache<CacheableUser | null>(Infinity); this.uriPersonCache = new Cache<User | null>(Infinity);
this.redisSubscriber.on('message', this.onMessage); this.redisSubscriber.on('message', this.onMessage);
} }
@ -58,7 +58,7 @@ export class UserCacheService implements OnApplicationShutdown {
break; break;
} }
case 'userTokenRegenerated': { case 'userTokenRegenerated': {
const user = await this.usersRepository.findOneByOrFail({ id: body.id }) as ILocalUser; const user = await this.usersRepository.findOneByOrFail({ id: body.id }) as LocalUser;
this.localUserByNativeTokenCache.delete(body.oldToken); this.localUserByNativeTokenCache.delete(body.oldToken);
this.localUserByNativeTokenCache.set(body.newToken, user); this.localUserByNativeTokenCache.set(body.newToken, user);
break; break;

View File

@ -1,5 +1,5 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import type { CacheableUser, ILocalUser, IRemoteUser, User } from '@/models/entities/User.js'; import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js';
import { IdentifiableError } from '@/misc/identifiable-error.js'; import { IdentifiableError } from '@/misc/identifiable-error.js';
import { QueueService } from '@/core/QueueService.js'; import { QueueService } from '@/core/QueueService.js';
import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js'; import PerUserFollowingChart from '@/core/chart/charts/per-user-following.js';
@ -21,16 +21,16 @@ import Logger from '../logger.js';
const logger = new Logger('following/create'); const logger = new Logger('following/create');
type Local = ILocalUser | { type Local = LocalUser | {
id: ILocalUser['id']; id: LocalUser['id'];
host: ILocalUser['host']; host: LocalUser['host'];
uri: ILocalUser['uri'] uri: LocalUser['uri']
}; };
type Remote = IRemoteUser | { type Remote = RemoteUser | {
id: IRemoteUser['id']; id: RemoteUser['id'];
host: IRemoteUser['host']; host: RemoteUser['host'];
uri: IRemoteUser['uri']; uri: RemoteUser['uri'];
inbox: IRemoteUser['inbox']; inbox: RemoteUser['inbox'];
}; };
type Both = Local | Remote; type Both = Local | Remote;
@ -81,7 +81,7 @@ export class UserFollowingService {
if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee) && blocked) { if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee) && blocked) {
// リモートフォローを受けてブロックしていた場合は、エラーにするのではなくRejectを送り返しておしまい。 // リモートフォローを受けてブロックしていた場合は、エラーにするのではなくRejectを送り返しておしまい。
const content = this.apRendererService.renderActivity(this.apRendererService.renderReject(this.apRendererService.renderFollow(follower, followee, requestId), followee)); const content = this.apRendererService.addContext(this.apRendererService.renderReject(this.apRendererService.renderFollow(follower, followee, requestId), followee));
this.queueService.deliver(followee, content, follower.inbox); this.queueService.deliver(followee, content, follower.inbox);
return; return;
} else if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee) && blocking) { } else if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee) && blocking) {
@ -130,7 +130,7 @@ export class UserFollowingService {
await this.insertFollowingDoc(followee, follower); await this.insertFollowingDoc(followee, follower);
if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) { if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
const content = this.apRendererService.renderActivity(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee, requestId), followee)); const content = this.apRendererService.addContext(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee, requestId), followee));
this.queueService.deliver(followee, content, follower.inbox); this.queueService.deliver(followee, content, follower.inbox);
} }
} }
@ -293,13 +293,13 @@ export class UserFollowingService {
} }
if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) { if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
const content = this.apRendererService.renderActivity(this.apRendererService.renderUndo(this.apRendererService.renderFollow(follower, followee), follower)); const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderFollow(follower, followee), follower));
this.queueService.deliver(follower, content, followee.inbox); this.queueService.deliver(follower, content, followee.inbox);
} }
if (this.userEntityService.isLocalUser(followee) && this.userEntityService.isRemoteUser(follower)) { if (this.userEntityService.isLocalUser(followee) && this.userEntityService.isRemoteUser(follower)) {
// local user has null host // local user has null host
const content = this.apRendererService.renderActivity(this.apRendererService.renderReject(this.apRendererService.renderFollow(follower, followee), followee)); const content = this.apRendererService.addContext(this.apRendererService.renderReject(this.apRendererService.renderFollow(follower, followee), followee));
this.queueService.deliver(followee, content, follower.inbox); this.queueService.deliver(followee, content, follower.inbox);
} }
} }
@ -388,7 +388,7 @@ export class UserFollowingService {
} }
if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) { if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) {
const content = this.apRendererService.renderActivity(this.apRendererService.renderFollow(follower, followee)); const content = this.apRendererService.addContext(this.apRendererService.renderFollow(follower, followee));
this.queueService.deliver(follower, content, followee.inbox); this.queueService.deliver(follower, content, followee.inbox);
} }
} }
@ -403,7 +403,7 @@ export class UserFollowingService {
}, },
): Promise<void> { ): Promise<void> {
if (this.userEntityService.isRemoteUser(followee)) { if (this.userEntityService.isRemoteUser(followee)) {
const content = this.apRendererService.renderActivity(this.apRendererService.renderUndo(this.apRendererService.renderFollow(follower, followee), follower)); const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderFollow(follower, followee), follower));
if (this.userEntityService.isLocalUser(follower)) { // 本来このチェックは不要だけどTSに怒られるので if (this.userEntityService.isLocalUser(follower)) { // 本来このチェックは不要だけどTSに怒られるので
this.queueService.deliver(follower, content, followee.inbox); this.queueService.deliver(follower, content, followee.inbox);
@ -434,7 +434,7 @@ export class UserFollowingService {
followee: { followee: {
id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox']; id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox'];
}, },
follower: CacheableUser, follower: User,
): Promise<void> { ): Promise<void> {
const request = await this.followRequestsRepository.findOneBy({ const request = await this.followRequestsRepository.findOneBy({
followeeId: followee.id, followeeId: followee.id,
@ -448,7 +448,7 @@ export class UserFollowingService {
await this.insertFollowingDoc(followee, follower); await this.insertFollowingDoc(followee, follower);
if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) { if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) {
const content = this.apRendererService.renderActivity(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee, request.requestId!), followee)); const content = this.apRendererService.addContext(this.apRendererService.renderAccept(this.apRendererService.renderFollow(follower, followee, request.requestId!), followee));
this.queueService.deliver(followee, content, follower.inbox); this.queueService.deliver(followee, content, follower.inbox);
} }
@ -556,7 +556,7 @@ export class UserFollowingService {
followerId: follower.id, followerId: follower.id,
}); });
const content = this.apRendererService.renderActivity(this.apRendererService.renderReject(this.apRendererService.renderFollow(follower, followee, request?.requestId ?? undefined), followee)); const content = this.apRendererService.addContext(this.apRendererService.renderReject(this.apRendererService.renderFollow(follower, followee, request?.requestId ?? undefined), followee));
this.queueService.deliver(followee, content, follower.inbox); this.queueService.deliver(followee, content, follower.inbox);
} }

View File

@ -35,7 +35,7 @@ export class UserSuspendService {
if (this.userEntityService.isLocalUser(user)) { if (this.userEntityService.isLocalUser(user)) {
// 知り得る全SharedInboxにDelete配信 // 知り得る全SharedInboxにDelete配信
const content = this.apRendererService.renderActivity(this.apRendererService.renderDelete(`${this.config.url}/users/${user.id}`, user)); const content = this.apRendererService.addContext(this.apRendererService.renderDelete(`${this.config.url}/users/${user.id}`, user));
const queue: string[] = []; const queue: string[] = [];
@ -65,7 +65,7 @@ export class UserSuspendService {
if (this.userEntityService.isLocalUser(user)) { if (this.userEntityService.isLocalUser(user)) {
// 知り得る全SharedInboxにUndo Delete配信 // 知り得る全SharedInboxにUndo Delete配信
const content = this.apRendererService.renderActivity(this.apRendererService.renderUndo(this.apRendererService.renderDelete(`${this.config.url}/users/${user.id}`, user), user)); const content = this.apRendererService.addContext(this.apRendererService.renderUndo(this.apRendererService.renderDelete(`${this.config.url}/users/${user.id}`, user), user));
const queue: string[] = []; const queue: string[] = [];

View File

@ -53,7 +53,7 @@ export class VideoProcessingService {
thumbnail: '1', thumbnail: '1',
url, url,
}) })
) );
} }
} }

View File

@ -1,11 +1,9 @@
import { Inject, Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { In } from 'typeorm';
import promiseLimit from 'promise-limit'; import promiseLimit from 'promise-limit';
import { DI } from '@/di-symbols.js'; import type { RemoteUser, User } from '@/models/entities/User.js';
import type { CacheableRemoteUser, CacheableUser } from '@/models/entities/User.js'; import { concat, unique } from '@/misc/prelude/array.js';
import { concat, toArray, toSingle, unique } from '@/misc/prelude/array.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isPost, isRead, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js'; import { getApIds } from './type.js';
import { ApPersonService } from './models/ApPersonService.js'; import { ApPersonService } from './models/ApPersonService.js';
import type { ApObject } from './type.js'; import type { ApObject } from './type.js';
import type { Resolver } from './ApResolverService.js'; import type { Resolver } from './ApResolverService.js';
@ -14,8 +12,8 @@ type Visibility = 'public' | 'home' | 'followers' | 'specified';
type AudienceInfo = { type AudienceInfo = {
visibility: Visibility, visibility: Visibility,
mentionedUsers: CacheableUser[], mentionedUsers: User[],
visibleUsers: CacheableUser[], visibleUsers: User[],
}; };
@Injectable() @Injectable()
@ -26,16 +24,16 @@ export class ApAudienceService {
} }
@bindThis @bindThis
public async parseAudience(actor: CacheableRemoteUser, to?: ApObject, cc?: ApObject, resolver?: Resolver): Promise<AudienceInfo> { public async parseAudience(actor: RemoteUser, to?: ApObject, cc?: ApObject, resolver?: Resolver): Promise<AudienceInfo> {
const toGroups = this.groupingAudience(getApIds(to), actor); const toGroups = this.groupingAudience(getApIds(to), actor);
const ccGroups = this.groupingAudience(getApIds(cc), actor); const ccGroups = this.groupingAudience(getApIds(cc), actor);
const others = unique(concat([toGroups.other, ccGroups.other])); const others = unique(concat([toGroups.other, ccGroups.other]));
const limit = promiseLimit<CacheableUser | null>(2); const limit = promiseLimit<User | null>(2);
const mentionedUsers = (await Promise.all( const mentionedUsers = (await Promise.all(
others.map(id => limit(() => this.apPersonService.resolvePerson(id, resolver).catch(() => null))), others.map(id => limit(() => this.apPersonService.resolvePerson(id, resolver).catch(() => null))),
)).filter((x): x is CacheableUser => x != null); )).filter((x): x is User => x != null);
if (toGroups.public.length > 0) { if (toGroups.public.length > 0) {
return { return {
@ -69,7 +67,7 @@ export class ApAudienceService {
} }
@bindThis @bindThis
private groupingAudience(ids: string[], actor: CacheableRemoteUser) { private groupingAudience(ids: string[], actor: RemoteUser) {
const groups = { const groups = {
public: [] as string[], public: [] as string[],
followers: [] as string[], followers: [] as string[],
@ -101,7 +99,7 @@ export class ApAudienceService {
} }
@bindThis @bindThis
private isFollowers(id: string, actor: CacheableRemoteUser) { private isFollowers(id: string, actor: RemoteUser) {
return ( return (
id === (actor.followersUri ?? `${actor.uri}/followers`) id === (actor.followersUri ?? `${actor.uri}/followers`)
); );

View File

@ -1,15 +1,14 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import escapeRegexp from 'escape-regexp'; import escapeRegexp from 'escape-regexp';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { MessagingMessagesRepository, NotesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js'; import type { NotesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import type { CacheableRemoteUser, CacheableUser } from '@/models/entities/User.js';
import { Cache } from '@/misc/cache.js'; import { Cache } from '@/misc/cache.js';
import type { UserPublickey } from '@/models/entities/UserPublickey.js'; import type { UserPublickey } from '@/models/entities/UserPublickey.js';
import { UserCacheService } from '@/core/UserCacheService.js'; import { UserCacheService } from '@/core/UserCacheService.js';
import type { Note } from '@/models/entities/Note.js'; import type { Note } from '@/models/entities/Note.js';
import type { MessagingMessage } from '@/models/entities/MessagingMessage.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { RemoteUser, User } from '@/models/entities/User.js';
import { getApId } from './type.js'; import { getApId } from './type.js';
import { ApPersonService } from './models/ApPersonService.js'; import { ApPersonService } from './models/ApPersonService.js';
import type { IObject } from './type.js'; import type { IObject } from './type.js';
@ -42,9 +41,6 @@ export class ApDbResolverService {
@Inject(DI.usersRepository) @Inject(DI.usersRepository)
private usersRepository: UsersRepository, private usersRepository: UsersRepository,
@Inject(DI.messagingMessagesRepository)
private messagingMessagesRepository: MessagingMessagesRepository,
@Inject(DI.notesRepository) @Inject(DI.notesRepository)
private notesRepository: NotesRepository, private notesRepository: NotesRepository,
@ -101,28 +97,11 @@ export class ApDbResolverService {
} }
} }
@bindThis
public async getMessageFromApId(value: string | IObject): Promise<MessagingMessage | null> {
const parsed = this.parseUri(value);
if (parsed.local) {
if (parsed.type !== 'notes') return null;
return await this.messagingMessagesRepository.findOneBy({
id: parsed.id,
});
} else {
return await this.messagingMessagesRepository.findOneBy({
uri: parsed.uri,
});
}
}
/** /**
* AP Person => Misskey User in DB * AP Person => Misskey User in DB
*/ */
@bindThis @bindThis
public async getUserFromApId(value: string | IObject): Promise<CacheableUser | null> { public async getUserFromApId(value: string | IObject): Promise<User | null> {
const parsed = this.parseUri(value); const parsed = this.parseUri(value);
if (parsed.local) { if (parsed.local) {
@ -143,7 +122,7 @@ export class ApDbResolverService {
*/ */
@bindThis @bindThis
public async getAuthUserFromKeyId(keyId: string): Promise<{ public async getAuthUserFromKeyId(keyId: string): Promise<{
user: CacheableRemoteUser; user: RemoteUser;
key: UserPublickey; key: UserPublickey;
} | null> { } | null> {
const key = await this.publicKeyCache.fetch(keyId, async () => { const key = await this.publicKeyCache.fetch(keyId, async () => {
@ -159,7 +138,7 @@ export class ApDbResolverService {
if (key == null) return null; if (key == null) return null;
return { return {
user: await this.userCacheService.findById(key.userId) as CacheableRemoteUser, user: await this.userCacheService.findById(key.userId) as RemoteUser,
key, key,
}; };
} }
@ -169,10 +148,10 @@ export class ApDbResolverService {
*/ */
@bindThis @bindThis
public async getAuthUserFromApId(uri: string): Promise<{ public async getAuthUserFromApId(uri: string): Promise<{
user: CacheableRemoteUser; user: RemoteUser;
key: UserPublickey | null; key: UserPublickey | null;
} | null> { } | null> {
const user = await this.apPersonService.resolvePerson(uri) as CacheableRemoteUser; const user = await this.apPersonService.resolvePerson(uri) as RemoteUser;
if (user == null) return null; if (user == null) return null;

View File

@ -3,7 +3,7 @@ import { IsNull, Not } from 'typeorm';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { FollowingsRepository, UsersRepository } from '@/models/index.js'; import type { FollowingsRepository, UsersRepository } from '@/models/index.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import type { ILocalUser, IRemoteUser, User } from '@/models/entities/User.js'; import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js';
import { QueueService } from '@/core/QueueService.js'; import { QueueService } from '@/core/QueueService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
@ -18,7 +18,7 @@ interface IFollowersRecipe extends IRecipe {
interface IDirectRecipe extends IRecipe { interface IDirectRecipe extends IRecipe {
type: 'Direct'; type: 'Direct';
to: IRemoteUser; to: RemoteUser;
} }
const isFollowers = (recipe: any): recipe is IFollowersRecipe => const isFollowers = (recipe: any): recipe is IFollowersRecipe =>
@ -50,7 +50,7 @@ export class ApDeliverManagerService {
* @param from Followee * @param from Followee
*/ */
@bindThis @bindThis
public async deliverToFollowers(actor: { id: ILocalUser['id']; host: null; }, activity: any) { public async deliverToFollowers(actor: { id: LocalUser['id']; host: null; }, activity: any) {
const manager = new DeliverManager( const manager = new DeliverManager(
this.userEntityService, this.userEntityService,
this.followingsRepository, this.followingsRepository,
@ -68,7 +68,7 @@ export class ApDeliverManagerService {
* @param to Target user * @param to Target user
*/ */
@bindThis @bindThis
public async deliverToUser(actor: { id: ILocalUser['id']; host: null; }, activity: any, to: IRemoteUser) { public async deliverToUser(actor: { id: LocalUser['id']; host: null; }, activity: any, to: RemoteUser) {
const manager = new DeliverManager( const manager = new DeliverManager(
this.userEntityService, this.userEntityService,
this.followingsRepository, this.followingsRepository,
@ -132,7 +132,7 @@ class DeliverManager {
* @param to To * @param to To
*/ */
@bindThis @bindThis
public addDirectRecipe(to: IRemoteUser) { public addDirectRecipe(to: RemoteUser) {
const recipe = { const recipe = {
type: 'Direct', type: 'Direct',
to, to,

View File

@ -2,7 +2,6 @@ import { Inject, Injectable } from '@nestjs/common';
import { In } from 'typeorm'; import { In } from 'typeorm';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import type { CacheableRemoteUser } from '@/models/entities/User.js';
import { UserFollowingService } from '@/core/UserFollowingService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js';
import { ReactionService } from '@/core/ReactionService.js'; import { ReactionService } from '@/core/ReactionService.js';
import { RelayService } from '@/core/RelayService.js'; import { RelayService } from '@/core/RelayService.js';
@ -20,9 +19,10 @@ import { UtilityService } from '@/core/UtilityService.js';
import { NoteEntityService } from '@/core/entities/NoteEntityService.js'; import { NoteEntityService } from '@/core/entities/NoteEntityService.js';
import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js';
import { QueueService } from '@/core/QueueService.js'; import { QueueService } from '@/core/QueueService.js';
import { MessagingService } from '@/core/MessagingService.js'; import type { UsersRepository, NotesRepository, FollowingsRepository, AbuseUserReportsRepository, FollowRequestsRepository } from '@/models/index.js';
import type { UsersRepository, NotesRepository, FollowingsRepository, MessagingMessagesRepository, AbuseUserReportsRepository, FollowRequestsRepository } from '@/models/index.js'; import { bindThis } from '@/decorators.js';
import { getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isPost, isRead, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js'; import type { RemoteUser } from '@/models/entities/User.js';
import { getApId, getApIds, getApType, isAccept, isActor, isAdd, isAnnounce, isBlock, isCollection, isCollectionOrOrderedCollection, isCreate, isDelete, isFlag, isFollow, isLike, isPost, isReject, isRemove, isTombstone, isUndo, isUpdate, validActor, validPost } from './type.js';
import { ApNoteService } from './models/ApNoteService.js'; import { ApNoteService } from './models/ApNoteService.js';
import { ApLoggerService } from './ApLoggerService.js'; import { ApLoggerService } from './ApLoggerService.js';
import { ApDbResolverService } from './ApDbResolverService.js'; import { ApDbResolverService } from './ApDbResolverService.js';
@ -31,8 +31,7 @@ import { ApAudienceService } from './ApAudienceService.js';
import { ApPersonService } from './models/ApPersonService.js'; import { ApPersonService } from './models/ApPersonService.js';
import { ApQuestionService } from './models/ApQuestionService.js'; import { ApQuestionService } from './models/ApQuestionService.js';
import type { Resolver } from './ApResolverService.js'; import type { Resolver } from './ApResolverService.js';
import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IObject, IRead, IReject, IRemove, IUndo, IUpdate } from './type.js'; import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IObject, IReject, IRemove, IUndo, IUpdate } from './type.js';
import { bindThis } from '@/decorators.js';
@Injectable() @Injectable()
export class ApInboxService { export class ApInboxService {
@ -51,9 +50,6 @@ export class ApInboxService {
@Inject(DI.followingsRepository) @Inject(DI.followingsRepository)
private followingsRepository: FollowingsRepository, private followingsRepository: FollowingsRepository,
@Inject(DI.messagingMessagesRepository)
private messagingMessagesRepository: MessagingMessagesRepository,
@Inject(DI.abuseUserReportsRepository) @Inject(DI.abuseUserReportsRepository)
private abuseUserReportsRepository: AbuseUserReportsRepository, private abuseUserReportsRepository: AbuseUserReportsRepository,
@ -81,13 +77,12 @@ export class ApInboxService {
private apPersonService: ApPersonService, private apPersonService: ApPersonService,
private apQuestionService: ApQuestionService, private apQuestionService: ApQuestionService,
private queueService: QueueService, private queueService: QueueService,
private messagingService: MessagingService,
) { ) {
this.logger = this.apLoggerService.logger; this.logger = this.apLoggerService.logger;
} }
@bindThis @bindThis
public async performActivity(actor: CacheableRemoteUser, activity: IObject) { public async performActivity(actor: RemoteUser, activity: IObject) {
if (isCollectionOrOrderedCollection(activity)) { if (isCollectionOrOrderedCollection(activity)) {
const resolver = this.apResolverService.createResolver(); const resolver = this.apResolverService.createResolver();
for (const item of toArray(isCollection(activity) ? activity.items : activity.orderedItems)) { for (const item of toArray(isCollection(activity) ? activity.items : activity.orderedItems)) {
@ -115,7 +110,7 @@ export class ApInboxService {
} }
@bindThis @bindThis
public async performOneActivity(actor: CacheableRemoteUser, activity: IObject): Promise<void> { public async performOneActivity(actor: RemoteUser, activity: IObject): Promise<void> {
if (actor.isSuspended) return; if (actor.isSuspended) return;
if (isCreate(activity)) { if (isCreate(activity)) {
@ -124,8 +119,6 @@ export class ApInboxService {
await this.delete(actor, activity); await this.delete(actor, activity);
} else if (isUpdate(activity)) { } else if (isUpdate(activity)) {
await this.update(actor, activity); await this.update(actor, activity);
} else if (isRead(activity)) {
await this.read(actor, activity);
} else if (isFollow(activity)) { } else if (isFollow(activity)) {
await this.follow(actor, activity); await this.follow(actor, activity);
} else if (isAccept(activity)) { } else if (isAccept(activity)) {
@ -152,7 +145,7 @@ export class ApInboxService {
} }
@bindThis @bindThis
private async follow(actor: CacheableRemoteUser, activity: IFollow): Promise<string> { private async follow(actor: RemoteUser, activity: IFollow): Promise<string> {
const followee = await this.apDbResolverService.getUserFromApId(activity.object); const followee = await this.apDbResolverService.getUserFromApId(activity.object);
if (followee == null) { if (followee == null) {
@ -168,7 +161,7 @@ export class ApInboxService {
} }
@bindThis @bindThis
private async like(actor: CacheableRemoteUser, activity: ILike): Promise<string> { private async like(actor: RemoteUser, activity: ILike): Promise<string> {
const targetUri = getApId(activity.object); const targetUri = getApId(activity.object);
const note = await this.apNoteService.fetchNote(targetUri); const note = await this.apNoteService.fetchNote(targetUri);
@ -186,30 +179,7 @@ export class ApInboxService {
} }
@bindThis @bindThis
private async read(actor: CacheableRemoteUser, activity: IRead): Promise<string> { private async accept(actor: RemoteUser, activity: IAccept): Promise<string> {
const id = await getApId(activity.object);
if (!this.utilityService.isSelfHost(this.utilityService.extractDbHost(id))) {
return `skip: Read to foreign host (${id})`;
}
const messageId = id.split('/').pop();
const message = await this.messagingMessagesRepository.findOneBy({ id: messageId });
if (message == null) {
return 'skip: message not found';
}
if (actor.id !== message.recipientId) {
return 'skip: actor is not a message recipient';
}
await this.messagingService.readUserMessagingMessage(message.recipientId!, message.userId, [message.id]);
return `ok: mark as read (${message.userId} => ${message.recipientId} ${message.id})`;
}
@bindThis
private async accept(actor: CacheableRemoteUser, activity: IAccept): Promise<string> {
const uri = activity.id ?? activity; const uri = activity.id ?? activity;
this.logger.info(`Accept: ${uri}`); this.logger.info(`Accept: ${uri}`);
@ -227,7 +197,7 @@ export class ApInboxService {
} }
@bindThis @bindThis
private async acceptFollow(actor: CacheableRemoteUser, activity: IFollow): Promise<string> { private async acceptFollow(actor: RemoteUser, activity: IFollow): Promise<string> {
// ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある // ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある
const follower = await this.apDbResolverService.getUserFromApId(activity.actor); const follower = await this.apDbResolverService.getUserFromApId(activity.actor);
@ -251,7 +221,7 @@ export class ApInboxService {
} }
@bindThis @bindThis
private async add(actor: CacheableRemoteUser, activity: IAdd): Promise<void> { private async add(actor: RemoteUser, activity: IAdd): Promise<void> {
if ('actor' in activity && actor.uri !== activity.actor) { if ('actor' in activity && actor.uri !== activity.actor) {
throw new Error('invalid actor'); throw new Error('invalid actor');
} }
@ -271,7 +241,7 @@ export class ApInboxService {
} }
@bindThis @bindThis
private async announce(actor: CacheableRemoteUser, activity: IAnnounce): Promise<void> { private async announce(actor: RemoteUser, activity: IAnnounce): Promise<void> {
const uri = getApId(activity); const uri = getApId(activity);
this.logger.info(`Announce: ${uri}`); this.logger.info(`Announce: ${uri}`);
@ -282,7 +252,7 @@ export class ApInboxService {
} }
@bindThis @bindThis
private async announceNote(actor: CacheableRemoteUser, activity: IAnnounce, targetUri: string): Promise<void> { private async announceNote(actor: RemoteUser, activity: IAnnounce, targetUri: string): Promise<void> {
const uri = getApId(activity); const uri = getApId(activity);
if (actor.isSuspended) { if (actor.isSuspended) {
@ -342,7 +312,7 @@ export class ApInboxService {
} }
@bindThis @bindThis
private async block(actor: CacheableRemoteUser, activity: IBlock): Promise<string> { private async block(actor: RemoteUser, activity: IBlock): Promise<string> {
// ※ activity.objectにブロック対象があり、それは存在するローカルユーザーのはず // ※ activity.objectにブロック対象があり、それは存在するローカルユーザーのはず
const blockee = await this.apDbResolverService.getUserFromApId(activity.object); const blockee = await this.apDbResolverService.getUserFromApId(activity.object);
@ -360,7 +330,7 @@ export class ApInboxService {
} }
@bindThis @bindThis
private async create(actor: CacheableRemoteUser, activity: ICreate): Promise<void> { private async create(actor: RemoteUser, activity: ICreate): Promise<void> {
const uri = getApId(activity); const uri = getApId(activity);
this.logger.info(`Create: ${uri}`); this.logger.info(`Create: ${uri}`);
@ -396,7 +366,7 @@ export class ApInboxService {
} }
@bindThis @bindThis
private async createNote(resolver: Resolver, actor: CacheableRemoteUser, note: IObject, silent = false, activity?: ICreate): Promise<string> { private async createNote(resolver: Resolver, actor: RemoteUser, note: IObject, silent = false, activity?: ICreate): Promise<string> {
const uri = getApId(note); const uri = getApId(note);
if (typeof note === 'object') { if (typeof note === 'object') {
@ -431,7 +401,7 @@ export class ApInboxService {
} }
@bindThis @bindThis
private async delete(actor: CacheableRemoteUser, activity: IDelete): Promise<string> { private async delete(actor: RemoteUser, activity: IDelete): Promise<string> {
if ('actor' in activity && actor.uri !== activity.actor) { if ('actor' in activity && actor.uri !== activity.actor) {
throw new Error('invalid actor'); throw new Error('invalid actor');
} }
@ -473,7 +443,7 @@ export class ApInboxService {
} }
@bindThis @bindThis
private async deleteActor(actor: CacheableRemoteUser, uri: string): Promise<string> { private async deleteActor(actor: RemoteUser, uri: string): Promise<string> {
this.logger.info(`Deleting the Actor: ${uri}`); this.logger.info(`Deleting the Actor: ${uri}`);
if (actor.uri !== uri) { if (actor.uri !== uri) {
@ -482,7 +452,7 @@ export class ApInboxService {
const user = await this.usersRepository.findOneByOrFail({ id: actor.id }); const user = await this.usersRepository.findOneByOrFail({ id: actor.id });
if (user.isDeleted) { if (user.isDeleted) {
this.logger.info('skip: already deleted'); return 'skip: already deleted';
} }
const job = await this.queueService.createDeleteAccountJob(actor); const job = await this.queueService.createDeleteAccountJob(actor);
@ -495,7 +465,7 @@ export class ApInboxService {
} }
@bindThis @bindThis
private async deleteNote(actor: CacheableRemoteUser, uri: string): Promise<string> { private async deleteNote(actor: RemoteUser, uri: string): Promise<string> {
this.logger.info(`Deleting the Note: ${uri}`); this.logger.info(`Deleting the Note: ${uri}`);
const unlock = await this.appLockService.getApLock(uri); const unlock = await this.appLockService.getApLock(uri);
@ -504,16 +474,7 @@ export class ApInboxService {
const note = await this.apDbResolverService.getNoteFromApId(uri); const note = await this.apDbResolverService.getNoteFromApId(uri);
if (note == null) { if (note == null) {
const message = await this.apDbResolverService.getMessageFromApId(uri); return 'message not found';
if (message == null) return 'message not found';
if (message.userId !== actor.id) {
return '投稿を削除しようとしているユーザーは投稿の作成者ではありません';
}
await this.messagingService.deleteMessage(message);
return 'ok: message deleted';
} }
if (note.userId !== actor.id) { if (note.userId !== actor.id) {
@ -528,7 +489,7 @@ export class ApInboxService {
} }
@bindThis @bindThis
private async flag(actor: CacheableRemoteUser, activity: IFlag): Promise<string> { private async flag(actor: RemoteUser, activity: IFlag): Promise<string> {
// objectは `(User|Note) | (User|Note)[]` だけど、全パターンDBスキーマと対応させられないので // objectは `(User|Note) | (User|Note)[]` だけど、全パターンDBスキーマと対応させられないので
// 対象ユーザーは一番最初のユーザー として あとはコメントとして格納する // 対象ユーザーは一番最初のユーザー として あとはコメントとして格納する
const uris = getApIds(activity.object); const uris = getApIds(activity.object);
@ -553,7 +514,7 @@ export class ApInboxService {
} }
@bindThis @bindThis
private async reject(actor: CacheableRemoteUser, activity: IReject): Promise<string> { private async reject(actor: RemoteUser, activity: IReject): Promise<string> {
const uri = activity.id ?? activity; const uri = activity.id ?? activity;
this.logger.info(`Reject: ${uri}`); this.logger.info(`Reject: ${uri}`);
@ -571,7 +532,7 @@ export class ApInboxService {
} }
@bindThis @bindThis
private async rejectFollow(actor: CacheableRemoteUser, activity: IFollow): Promise<string> { private async rejectFollow(actor: RemoteUser, activity: IFollow): Promise<string> {
// ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある // ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある
const follower = await this.apDbResolverService.getUserFromApId(activity.actor); const follower = await this.apDbResolverService.getUserFromApId(activity.actor);
@ -595,7 +556,7 @@ export class ApInboxService {
} }
@bindThis @bindThis
private async remove(actor: CacheableRemoteUser, activity: IRemove): Promise<void> { private async remove(actor: RemoteUser, activity: IRemove): Promise<void> {
if ('actor' in activity && actor.uri !== activity.actor) { if ('actor' in activity && actor.uri !== activity.actor) {
throw new Error('invalid actor'); throw new Error('invalid actor');
} }
@ -615,7 +576,7 @@ export class ApInboxService {
} }
@bindThis @bindThis
private async undo(actor: CacheableRemoteUser, activity: IUndo): Promise<string> { private async undo(actor: RemoteUser, activity: IUndo): Promise<string> {
if ('actor' in activity && actor.uri !== activity.actor) { if ('actor' in activity && actor.uri !== activity.actor) {
throw new Error('invalid actor'); throw new Error('invalid actor');
} }
@ -641,7 +602,7 @@ export class ApInboxService {
} }
@bindThis @bindThis
private async undoAccept(actor: CacheableRemoteUser, activity: IAccept): Promise<string> { private async undoAccept(actor: RemoteUser, activity: IAccept): Promise<string> {
const follower = await this.apDbResolverService.getUserFromApId(activity.object); const follower = await this.apDbResolverService.getUserFromApId(activity.object);
if (follower == null) { if (follower == null) {
return 'skip: follower not found'; return 'skip: follower not found';
@ -661,7 +622,7 @@ export class ApInboxService {
} }
@bindThis @bindThis
private async undoAnnounce(actor: CacheableRemoteUser, activity: IAnnounce): Promise<string> { private async undoAnnounce(actor: RemoteUser, activity: IAnnounce): Promise<string> {
const uri = getApId(activity); const uri = getApId(activity);
const note = await this.notesRepository.findOneBy({ const note = await this.notesRepository.findOneBy({
@ -676,7 +637,7 @@ export class ApInboxService {
} }
@bindThis @bindThis
private async undoBlock(actor: CacheableRemoteUser, activity: IBlock): Promise<string> { private async undoBlock(actor: RemoteUser, activity: IBlock): Promise<string> {
const blockee = await this.apDbResolverService.getUserFromApId(activity.object); const blockee = await this.apDbResolverService.getUserFromApId(activity.object);
if (blockee == null) { if (blockee == null) {
@ -692,7 +653,7 @@ export class ApInboxService {
} }
@bindThis @bindThis
private async undoFollow(actor: CacheableRemoteUser, activity: IFollow): Promise<string> { private async undoFollow(actor: RemoteUser, activity: IFollow): Promise<string> {
const followee = await this.apDbResolverService.getUserFromApId(activity.object); const followee = await this.apDbResolverService.getUserFromApId(activity.object);
if (followee == null) { if (followee == null) {
return 'skip: followee not found'; return 'skip: followee not found';
@ -726,7 +687,7 @@ export class ApInboxService {
} }
@bindThis @bindThis
private async undoLike(actor: CacheableRemoteUser, activity: ILike): Promise<string> { private async undoLike(actor: RemoteUser, activity: ILike): Promise<string> {
const targetUri = getApId(activity.object); const targetUri = getApId(activity.object);
const note = await this.apNoteService.fetchNote(targetUri); const note = await this.apNoteService.fetchNote(targetUri);
@ -741,7 +702,7 @@ export class ApInboxService {
} }
@bindThis @bindThis
private async update(actor: CacheableRemoteUser, activity: IUpdate): Promise<string> { private async update(actor: RemoteUser, activity: IUpdate): Promise<string> {
if ('actor' in activity && actor.uri !== activity.actor) { if ('actor' in activity && actor.uri !== activity.actor) {
return 'skip: invalid actor'; return 'skip: invalid actor';
} }

View File

@ -1,7 +1,6 @@
import { Inject, Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import type Logger from '@/logger.js'; import type Logger from '@/logger.js';
import { RemoteLoggerService } from '@/core/RemoteLoggerService.js'; import { RemoteLoggerService } from '@/core/RemoteLoggerService.js';
import { bindThis } from '@/decorators.js';
@Injectable() @Injectable()
export class ApLoggerService { export class ApLoggerService {

View File

@ -5,7 +5,7 @@ import { v4 as uuid } from 'uuid';
import * as mfm from 'mfm-js'; import * as mfm from 'mfm-js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import type { ILocalUser, IRemoteUser, User } from '@/models/entities/User.js'; import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js';
import type { IMentionedRemoteUsers, Note } from '@/models/entities/Note.js'; import type { IMentionedRemoteUsers, Note } from '@/models/entities/Note.js';
import type { Blocking } from '@/models/entities/Blocking.js'; import type { Blocking } from '@/models/entities/Blocking.js';
import type { Relay } from '@/models/entities/Relay.js'; import type { Relay } from '@/models/entities/Relay.js';
@ -13,7 +13,6 @@ import type { DriveFile } from '@/models/entities/DriveFile.js';
import type { NoteReaction } from '@/models/entities/NoteReaction.js'; import type { NoteReaction } from '@/models/entities/NoteReaction.js';
import type { Emoji } from '@/models/entities/Emoji.js'; import type { Emoji } from '@/models/entities/Emoji.js';
import type { Poll } from '@/models/entities/Poll.js'; import type { Poll } from '@/models/entities/Poll.js';
import type { MessagingMessage } from '@/models/entities/MessagingMessage.js';
import type { PollVote } from '@/models/entities/PollVote.js'; import type { PollVote } from '@/models/entities/PollVote.js';
import { UserKeypairStoreService } from '@/core/UserKeypairStoreService.js'; import { UserKeypairStoreService } from '@/core/UserKeypairStoreService.js';
import { MfmService } from '@/core/MfmService.js'; import { MfmService } from '@/core/MfmService.js';
@ -24,7 +23,7 @@ import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFil
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { LdSignatureService } from './LdSignatureService.js'; import { LdSignatureService } from './LdSignatureService.js';
import { ApMfmService } from './ApMfmService.js'; import { ApMfmService } from './ApMfmService.js';
import type { IActivity, IObject } from './type.js'; import type { IAccept, IActivity, IAdd, IAnnounce, IApDocument, IApEmoji, IApHashtag, IApImage, IApMention, IBlock, ICreate, IDelete, IFlag, IFollow, IKey, ILike, IObject, IPost, IQuestion, IReject, IRemove, ITombstone, IUndo, IUpdate } from './type.js';
import type { IIdentifier } from './models/identifier.js'; import type { IIdentifier } from './models/identifier.js';
@Injectable() @Injectable()
@ -61,7 +60,7 @@ export class ApRendererService {
} }
@bindThis @bindThis
public renderAccept(object: any, user: { id: User['id']; host: null }) { public renderAccept(object: any, user: { id: User['id']; host: null }): IAccept {
return { return {
type: 'Accept', type: 'Accept',
actor: `${this.config.url}/users/${user.id}`, actor: `${this.config.url}/users/${user.id}`,
@ -70,7 +69,7 @@ export class ApRendererService {
} }
@bindThis @bindThis
public renderAdd(user: ILocalUser, target: any, object: any) { public renderAdd(user: LocalUser, target: any, object: any): IAdd {
return { return {
type: 'Add', type: 'Add',
actor: `${this.config.url}/users/${user.id}`, actor: `${this.config.url}/users/${user.id}`,
@ -80,7 +79,7 @@ export class ApRendererService {
} }
@bindThis @bindThis
public renderAnnounce(object: any, note: Note) { public renderAnnounce(object: any, note: Note): IAnnounce {
const attributedTo = `${this.config.url}/users/${note.userId}`; const attributedTo = `${this.config.url}/users/${note.userId}`;
let to: string[] = []; let to: string[] = [];
@ -93,7 +92,7 @@ export class ApRendererService {
to = [`${attributedTo}/followers`]; to = [`${attributedTo}/followers`];
cc = ['https://www.w3.org/ns/activitystreams#Public']; cc = ['https://www.w3.org/ns/activitystreams#Public'];
} else { } else {
return null; throw new Error('renderAnnounce: cannot render non-public note');
} }
return { return {
@ -113,7 +112,7 @@ export class ApRendererService {
* @param block The block to be rendered. The blockee relation must be loaded. * @param block The block to be rendered. The blockee relation must be loaded.
*/ */
@bindThis @bindThis
public renderBlock(block: Blocking) { public renderBlock(block: Blocking): IBlock {
if (block.blockee?.uri == null) { if (block.blockee?.uri == null) {
throw new Error('renderBlock: missing blockee uri'); throw new Error('renderBlock: missing blockee uri');
} }
@ -127,14 +126,14 @@ export class ApRendererService {
} }
@bindThis @bindThis
public renderCreate(object: any, note: Note) { public renderCreate(object: IObject, note: Note): ICreate {
const activity = { const activity = {
id: `${this.config.url}/notes/${note.id}/activity`, id: `${this.config.url}/notes/${note.id}/activity`,
actor: `${this.config.url}/users/${note.userId}`, actor: `${this.config.url}/users/${note.userId}`,
type: 'Create', type: 'Create',
published: note.createdAt.toISOString(), published: note.createdAt.toISOString(),
object, object,
} as any; } as ICreate;
if (object.to) activity.to = object.to; if (object.to) activity.to = object.to;
if (object.cc) activity.cc = object.cc; if (object.cc) activity.cc = object.cc;
@ -143,7 +142,7 @@ export class ApRendererService {
} }
@bindThis @bindThis
public renderDelete(object: any, user: { id: User['id']; host: null }) { public renderDelete(object: IObject | string, user: { id: User['id']; host: null }): IDelete {
return { return {
type: 'Delete', type: 'Delete',
actor: `${this.config.url}/users/${user.id}`, actor: `${this.config.url}/users/${user.id}`,
@ -153,7 +152,7 @@ export class ApRendererService {
} }
@bindThis @bindThis
public renderDocument(file: DriveFile) { public renderDocument(file: DriveFile): IApDocument {
return { return {
type: 'Document', type: 'Document',
mediaType: file.type, mediaType: file.type,
@ -163,12 +162,12 @@ export class ApRendererService {
} }
@bindThis @bindThis
public renderEmoji(emoji: Emoji) { public renderEmoji(emoji: Emoji): IApEmoji {
return { return {
id: `${this.config.url}/emojis/${emoji.name}`, id: `${this.config.url}/emojis/${emoji.name}`,
type: 'Emoji', type: 'Emoji',
name: `:${emoji.name}:`, name: `:${emoji.name}:`,
updated: emoji.updatedAt != null ? emoji.updatedAt.toISOString() : new Date().toISOString, updated: emoji.updatedAt != null ? emoji.updatedAt.toISOString() : new Date().toISOString(),
icon: { icon: {
type: 'Image', type: 'Image',
mediaType: emoji.type ?? 'image/png', mediaType: emoji.type ?? 'image/png',
@ -179,9 +178,8 @@ export class ApRendererService {
} }
// to anonymise reporters, the reporting actor must be a system user // to anonymise reporters, the reporting actor must be a system user
// object has to be a uri or array of uris
@bindThis @bindThis
public renderFlag(user: ILocalUser, object: [string], content: string) { public renderFlag(user: LocalUser, object: IObject | string, content: string): IFlag {
return { return {
type: 'Flag', type: 'Flag',
actor: `${this.config.url}/users/${user.id}`, actor: `${this.config.url}/users/${user.id}`,
@ -191,15 +189,13 @@ export class ApRendererService {
} }
@bindThis @bindThis
public renderFollowRelay(relay: Relay, relayActor: ILocalUser) { public renderFollowRelay(relay: Relay, relayActor: LocalUser): IFollow {
const follow = { return {
id: `${this.config.url}/activities/follow-relay/${relay.id}`, id: `${this.config.url}/activities/follow-relay/${relay.id}`,
type: 'Follow', type: 'Follow',
actor: `${this.config.url}/users/${relayActor.id}`, actor: `${this.config.url}/users/${relayActor.id}`,
object: 'https://www.w3.org/ns/activitystreams#Public', object: 'https://www.w3.org/ns/activitystreams#Public',
}; };
return follow;
} }
/** /**
@ -217,19 +213,17 @@ export class ApRendererService {
follower: { id: User['id']; host: User['host']; uri: User['host'] }, follower: { id: User['id']; host: User['host']; uri: User['host'] },
followee: { id: User['id']; host: User['host']; uri: User['host'] }, followee: { id: User['id']; host: User['host']; uri: User['host'] },
requestId?: string, requestId?: string,
) { ): IFollow {
const follow = { return {
id: requestId ?? `${this.config.url}/follows/${follower.id}/${followee.id}`, id: requestId ?? `${this.config.url}/follows/${follower.id}/${followee.id}`,
type: 'Follow', type: 'Follow',
actor: this.userEntityService.isLocalUser(follower) ? `${this.config.url}/users/${follower.id}` : follower.uri, actor: this.userEntityService.isLocalUser(follower) ? `${this.config.url}/users/${follower.id}` : follower.uri!,
object: this.userEntityService.isLocalUser(followee) ? `${this.config.url}/users/${followee.id}` : followee.uri, object: this.userEntityService.isLocalUser(followee) ? `${this.config.url}/users/${followee.id}` : followee.uri!,
} as any; };
return follow;
} }
@bindThis @bindThis
public renderHashtag(tag: string) { public renderHashtag(tag: string): IApHashtag {
return { return {
type: 'Hashtag', type: 'Hashtag',
href: `${this.config.url}/tags/${encodeURIComponent(tag)}`, href: `${this.config.url}/tags/${encodeURIComponent(tag)}`,
@ -238,7 +232,7 @@ export class ApRendererService {
} }
@bindThis @bindThis
public renderImage(file: DriveFile) { public renderImage(file: DriveFile): IApImage {
return { return {
type: 'Image', type: 'Image',
url: this.driveFileEntityService.getPublicUrl(file), url: this.driveFileEntityService.getPublicUrl(file),
@ -248,7 +242,7 @@ export class ApRendererService {
} }
@bindThis @bindThis
public renderKey(user: ILocalUser, key: UserKeypair, postfix?: string) { public renderKey(user: LocalUser, key: UserKeypair, postfix?: string): IKey {
return { return {
id: `${this.config.url}/users/${user.id}${postfix ?? '/publickey'}`, id: `${this.config.url}/users/${user.id}${postfix ?? '/publickey'}`,
type: 'Key', type: 'Key',
@ -261,7 +255,7 @@ export class ApRendererService {
} }
@bindThis @bindThis
public async renderLike(noteReaction: NoteReaction, note: { uri: string | null }) { public async renderLike(noteReaction: NoteReaction, note: { uri: string | null }): Promise<ILike> {
const reaction = noteReaction.reaction; const reaction = noteReaction.reaction;
const object = { const object = {
@ -271,10 +265,11 @@ export class ApRendererService {
object: note.uri ? note.uri : `${this.config.url}/notes/${noteReaction.noteId}`, object: note.uri ? note.uri : `${this.config.url}/notes/${noteReaction.noteId}`,
content: reaction, content: reaction,
_misskey_reaction: reaction, _misskey_reaction: reaction,
} as any; } as ILike;
if (reaction.startsWith(':')) { if (reaction.startsWith(':')) {
const name = reaction.replaceAll(':', ''); const name = reaction.replaceAll(':', '');
// TODO: cache
const emoji = await this.emojisRepository.findOneBy({ const emoji = await this.emojisRepository.findOneBy({
name, name,
host: IsNull(), host: IsNull(),
@ -287,16 +282,16 @@ export class ApRendererService {
} }
@bindThis @bindThis
public renderMention(mention: User) { public renderMention(mention: User): IApMention {
return { return {
type: 'Mention', type: 'Mention',
href: this.userEntityService.isRemoteUser(mention) ? mention.uri : `${this.config.url}/users/${(mention as ILocalUser).id}`, href: this.userEntityService.isRemoteUser(mention) ? mention.uri! : `${this.config.url}/users/${(mention as LocalUser).id}`,
name: this.userEntityService.isRemoteUser(mention) ? `@${mention.username}@${mention.host}` : `@${(mention as ILocalUser).username}`, name: this.userEntityService.isRemoteUser(mention) ? `@${mention.username}@${mention.host}` : `@${(mention as LocalUser).username}`,
}; };
} }
@bindThis @bindThis
public async renderNote(note: Note, dive = true, isTalk = false): Promise<IObject> { public async renderNote(note: Note, dive = true): Promise<IPost> {
const getPromisedFiles = async (ids: string[]) => { const getPromisedFiles = async (ids: string[]) => {
if (!ids || ids.length === 0) return []; if (!ids || ids.length === 0) return [];
const items = await this.driveFilesRepository.findBy({ id: In(ids) }); const items = await this.driveFilesRepository.findBy({ id: In(ids) });
@ -409,11 +404,7 @@ export class ApRendererService {
totalItems: poll!.votes[i], totalItems: poll!.votes[i],
}, },
})), })),
} : {}; } as const : {};
const asTalk = isTalk ? {
_misskey_talk: true,
} : {};
return { return {
id: `${this.config.url}/notes/${note.id}`, id: `${this.config.url}/notes/${note.id}`,
@ -436,12 +427,11 @@ export class ApRendererService {
sensitive: note.cw != null || files.some(file => file.isSensitive), sensitive: note.cw != null || files.some(file => file.isSensitive),
tag, tag,
...asPoll, ...asPoll,
...asTalk,
}; };
} }
@bindThis @bindThis
public async renderPerson(user: ILocalUser) { public async renderPerson(user: LocalUser) {
const id = `${this.config.url}/users/${user.id}`; const id = `${this.config.url}/users/${user.id}`;
const isSystem = !!user.username.match(/\./); const isSystem = !!user.username.match(/\./);
@ -518,8 +508,8 @@ export class ApRendererService {
} }
@bindThis @bindThis
public async renderQuestion(user: { id: User['id'] }, note: Note, poll: Poll) { public renderQuestion(user: { id: User['id'] }, note: Note, poll: Poll): IQuestion {
const question = { return {
type: 'Question', type: 'Question',
id: `${this.config.url}/questions/${note.id}`, id: `${this.config.url}/questions/${note.id}`,
actor: `${this.config.url}/users/${user.id}`, actor: `${this.config.url}/users/${user.id}`,
@ -533,21 +523,10 @@ export class ApRendererService {
}, },
})), })),
}; };
return question;
} }
@bindThis @bindThis
public renderRead(user: { id: User['id'] }, message: MessagingMessage) { public renderReject(object: any, user: { id: User['id'] }): IReject {
return {
type: 'Read',
actor: `${this.config.url}/users/${user.id}`,
object: message.uri,
};
}
@bindThis
public renderReject(object: any, user: { id: User['id'] }) {
return { return {
type: 'Reject', type: 'Reject',
actor: `${this.config.url}/users/${user.id}`, actor: `${this.config.url}/users/${user.id}`,
@ -556,7 +535,7 @@ export class ApRendererService {
} }
@bindThis @bindThis
public renderRemove(user: { id: User['id'] }, target: any, object: any) { public renderRemove(user: { id: User['id'] }, target: any, object: any): IRemove {
return { return {
type: 'Remove', type: 'Remove',
actor: `${this.config.url}/users/${user.id}`, actor: `${this.config.url}/users/${user.id}`,
@ -566,7 +545,7 @@ export class ApRendererService {
} }
@bindThis @bindThis
public renderTombstone(id: string) { public renderTombstone(id: string): ITombstone {
return { return {
id, id,
type: 'Tombstone', type: 'Tombstone',
@ -574,8 +553,7 @@ export class ApRendererService {
} }
@bindThis @bindThis
public renderUndo(object: any, user: { id: User['id'] }) { public renderUndo(object: any, user: { id: User['id'] }): IUndo {
if (object == null) return null;
const id = typeof object.id === 'string' && object.id.startsWith(this.config.url) ? `${object.id}/undo` : undefined; const id = typeof object.id === 'string' && object.id.startsWith(this.config.url) ? `${object.id}/undo` : undefined;
return { return {
@ -588,21 +566,19 @@ export class ApRendererService {
} }
@bindThis @bindThis
public renderUpdate(object: any, user: { id: User['id'] }) { public renderUpdate(object: any, user: { id: User['id'] }): IUpdate {
const activity = { return {
id: `${this.config.url}/users/${user.id}#updates/${new Date().getTime()}`, id: `${this.config.url}/users/${user.id}#updates/${new Date().getTime()}`,
actor: `${this.config.url}/users/${user.id}`, actor: `${this.config.url}/users/${user.id}`,
type: 'Update', type: 'Update',
to: ['https://www.w3.org/ns/activitystreams#Public'], to: ['https://www.w3.org/ns/activitystreams#Public'],
object, object,
published: new Date().toISOString(), published: new Date().toISOString(),
} as any; };
return activity;
} }
@bindThis @bindThis
public renderVote(user: { id: User['id'] }, vote: PollVote, note: Note, poll: Poll, pollOwner: IRemoteUser) { public renderVote(user: { id: User['id'] }, vote: PollVote, note: Note, poll: Poll, pollOwner: RemoteUser): ICreate {
return { return {
id: `${this.config.url}/users/${user.id}#votes/${vote.id}/activity`, id: `${this.config.url}/users/${user.id}#votes/${vote.id}/activity`,
actor: `${this.config.url}/users/${user.id}`, actor: `${this.config.url}/users/${user.id}`,
@ -621,9 +597,7 @@ export class ApRendererService {
} }
@bindThis @bindThis
public renderActivity(x: any): IActivity | null { public addContext<T extends IObject>(x: T): T & { '@context': any; id: string; } {
if (x == null) return null;
if (typeof x === 'object' && x.id == null) { if (typeof x === 'object' && x.id == null) {
x.id = `${this.config.url}/${uuid()}`; x.id = `${this.config.url}/${uuid()}`;
} }
@ -653,13 +627,12 @@ export class ApRendererService {
'_misskey_quote': 'misskey:_misskey_quote', '_misskey_quote': 'misskey:_misskey_quote',
'_misskey_reaction': 'misskey:_misskey_reaction', '_misskey_reaction': 'misskey:_misskey_reaction',
'_misskey_votes': 'misskey:_misskey_votes', '_misskey_votes': 'misskey:_misskey_votes',
'_misskey_talk': 'misskey:_misskey_talk',
'isCat': 'misskey:isCat', 'isCat': 'misskey:isCat',
// vcard // vcard
vcard: 'http://www.w3.org/2006/vcard/ns#', vcard: 'http://www.w3.org/2006/vcard/ns#',
}, },
], ],
}, x); }, x as T & { id: string; });
} }
@bindThis @bindThis

View File

@ -1,5 +1,5 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import type { ILocalUser } from '@/models/entities/User.js'; import type { LocalUser } from '@/models/entities/User.js';
import { InstanceActorService } from '@/core/InstanceActorService.js'; import { InstanceActorService } from '@/core/InstanceActorService.js';
import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository } from '@/models/index.js'; import type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository } from '@/models/index.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
@ -18,7 +18,7 @@ import type { IObject, ICollection, IOrderedCollection } from './type.js';
export class Resolver { export class Resolver {
private history: Set<string>; private history: Set<string>;
private user?: ILocalUser; private user?: LocalUser;
private logger: Logger; private logger: Logger;
constructor( constructor(
@ -38,8 +38,7 @@ export class Resolver {
private recursionLimit = 100, private recursionLimit = 100,
) { ) {
this.history = new Set(); this.history = new Set();
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition this.logger = this.loggerService.getLogger('ap-resolve');
this.logger = this.loggerService?.getLogger('ap-resolve'); // なぜか TypeError: Cannot read properties of undefined (reading 'getLogger') と言われる
} }
@bindThis @bindThis
@ -124,17 +123,17 @@ export class Resolver {
switch (parsed.type) { switch (parsed.type) {
case 'notes': case 'notes':
return this.notesRepository.findOneByOrFail({ id: parsed.id }) return this.notesRepository.findOneByOrFail({ id: parsed.id })
.then(note => { .then(async note => {
if (parsed.rest === 'activity') { if (parsed.rest === 'activity') {
// this refers to the create activity and not the note itself // this refers to the create activity and not the note itself
return this.apRendererService.renderActivity(this.apRendererService.renderCreate(this.apRendererService.renderNote(note), note)); return this.apRendererService.addContext(this.apRendererService.renderCreate(await this.apRendererService.renderNote(note), note));
} else { } else {
return this.apRendererService.renderNote(note); return this.apRendererService.renderNote(note);
} }
}); });
case 'users': case 'users':
return this.usersRepository.findOneByOrFail({ id: parsed.id }) return this.usersRepository.findOneByOrFail({ id: parsed.id })
.then(user => this.apRendererService.renderPerson(user as ILocalUser)); .then(user => this.apRendererService.renderPerson(user as LocalUser));
case 'questions': case 'questions':
// Polls are indexed by the note they are attached to. // Polls are indexed by the note they are attached to.
return Promise.all([ return Promise.all([
@ -143,8 +142,8 @@ export class Resolver {
]) ])
.then(([note, poll]) => this.apRendererService.renderQuestion({ id: note.userId }, note, poll)); .then(([note, poll]) => this.apRendererService.renderQuestion({ id: note.userId }, note, poll));
case 'likes': case 'likes':
return this.noteReactionsRepository.findOneByOrFail({ id: parsed.id }).then(reaction => return this.noteReactionsRepository.findOneByOrFail({ id: parsed.id }).then(async reaction =>
this.apRendererService.renderActivity(this.apRendererService.renderLike(reaction, { uri: null }))!); this.apRendererService.addContext(await this.apRendererService.renderLike(reaction, { uri: null })));
case 'follows': case 'follows':
// rest should be <followee id> // rest should be <followee id>
if (parsed.rest == null || !/^\w+$/.test(parsed.rest)) throw new Error('resolveLocal: invalid follow URI'); if (parsed.rest == null || !/^\w+$/.test(parsed.rest)) throw new Error('resolveLocal: invalid follow URI');
@ -152,7 +151,7 @@ export class Resolver {
return Promise.all( return Promise.all(
[parsed.id, parsed.rest].map(id => this.usersRepository.findOneByOrFail({ id })), [parsed.id, parsed.rest].map(id => this.usersRepository.findOneByOrFail({ id })),
) )
.then(([follower, followee]) => this.apRendererService.renderActivity(this.apRendererService.renderFollow(follower, followee, url))); .then(([follower, followee]) => this.apRendererService.addContext(this.apRendererService.renderFollow(follower, followee, url)));
default: default:
throw new Error(`resolveLocal: type ${parsed.type} unhandled`); throw new Error(`resolveLocal: type ${parsed.type} unhandled`);
} }
@ -184,6 +183,7 @@ export class ApResolverService {
private httpRequestService: HttpRequestService, private httpRequestService: HttpRequestService,
private apRendererService: ApRendererService, private apRendererService: ApRendererService,
private apDbResolverService: ApDbResolverService, private apDbResolverService: ApDbResolverService,
private loggerService: LoggerService,
) { ) {
} }
@ -202,6 +202,7 @@ export class ApResolverService {
this.httpRequestService, this.httpRequestService,
this.apRendererService, this.apRendererService,
this.apDbResolverService, this.apDbResolverService,
this.loggerService,
); );
} }
} }

View File

@ -1,6 +1,5 @@
import * as crypto from 'node:crypto'; import * as crypto from 'node:crypto';
import { Inject, Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import jsonld from 'jsonld';
import { HttpRequestService } from '@/core/HttpRequestService.js'; import { HttpRequestService } from '@/core/HttpRequestService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { CONTEXTS } from './misc/contexts.js'; import { CONTEXTS } from './misc/contexts.js';
@ -85,7 +84,9 @@ class LdSignature {
@bindThis @bindThis
public async normalize(data: any) { public async normalize(data: any) {
const customLoader = this.getLoader(); const customLoader = this.getLoader();
return await jsonld.normalize(data, { // XXX: Importing jsonld dynamically since Jest frequently fails to import it statically
// https://github.com/misskey-dev/misskey/pull/9894#discussion_r1103753595
return (await import('jsonld')).default.normalize(data, {
documentLoader: customLoader, documentLoader: customLoader,
}); });
} }

View File

@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { DriveFilesRepository } from '@/models/index.js'; import type { DriveFilesRepository } from '@/models/index.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import type { CacheableRemoteUser } from '@/models/entities/User.js'; import type { RemoteUser } from '@/models/entities/User.js';
import type { DriveFile } from '@/models/entities/DriveFile.js'; import type { DriveFile } from '@/models/entities/DriveFile.js';
import { MetaService } from '@/core/MetaService.js'; import { MetaService } from '@/core/MetaService.js';
import { truncate } from '@/misc/truncate.js'; import { truncate } from '@/misc/truncate.js';
@ -36,7 +36,7 @@ export class ApImageService {
* Imageを作成します * Imageを作成します
*/ */
@bindThis @bindThis
public async createImage(actor: CacheableRemoteUser, value: any): Promise<DriveFile> { public async createImage(actor: RemoteUser, value: any): Promise<DriveFile> {
// 投稿者が凍結されていたらスキップ // 投稿者が凍結されていたらスキップ
if (actor.isSuspended) { if (actor.isSuspended) {
throw new Error('actor has been suspended'); throw new Error('actor has been suspended');
@ -88,7 +88,7 @@ export class ApImageService {
* Misskeyに登録しそれを返します * Misskeyに登録しそれを返します
*/ */
@bindThis @bindThis
public async resolveImage(actor: CacheableRemoteUser, value: any): Promise<DriveFile> { public async resolveImage(actor: RemoteUser, value: any): Promise<DriveFile> {
// TODO // TODO
// リモートサーバーからフェッチしてきて登録 // リモートサーバーからフェッチしてきて登録

View File

@ -1,15 +1,14 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import promiseLimit from 'promise-limit'; import promiseLimit from 'promise-limit';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { UsersRepository } from '@/models/index.js'; import type { User } from '@/models/index.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import { toArray, unique } from '@/misc/prelude/array.js'; import { toArray, unique } from '@/misc/prelude/array.js';
import type { CacheableUser } from '@/models/entities/User.js'; import { bindThis } from '@/decorators.js';
import { isMention } from '../type.js'; import { isMention } from '../type.js';
import { ApResolverService, Resolver } from '../ApResolverService.js'; import { ApResolverService, Resolver } from '../ApResolverService.js';
import { ApPersonService } from './ApPersonService.js'; import { ApPersonService } from './ApPersonService.js';
import type { IObject, IApMention } from '../type.js'; import type { IObject, IApMention } from '../type.js';
import { bindThis } from '@/decorators.js';
@Injectable() @Injectable()
export class ApMentionService { export class ApMentionService {
@ -26,10 +25,10 @@ export class ApMentionService {
public async extractApMentions(tags: IObject | IObject[] | null | undefined, resolver: Resolver) { public async extractApMentions(tags: IObject | IObject[] | null | undefined, resolver: Resolver) {
const hrefs = unique(this.extractApMentionObjects(tags).map(x => x.href as string)); const hrefs = unique(this.extractApMentionObjects(tags).map(x => x.href as string));
const limit = promiseLimit<CacheableUser | null>(2); const limit = promiseLimit<User | null>(2);
const mentionedUsers = (await Promise.all( const mentionedUsers = (await Promise.all(
hrefs.map(x => limit(() => this.apPersonService.resolvePerson(x, resolver).catch(() => null))), hrefs.map(x => limit(() => this.apPersonService.resolvePerson(x, resolver).catch(() => null))),
)).filter((x): x is CacheableUser => x != null); )).filter((x): x is User => x != null);
return mentionedUsers; return mentionedUsers;
} }

View File

@ -1,9 +1,9 @@
import { forwardRef, Inject, Injectable } from '@nestjs/common'; import { forwardRef, Inject, Injectable } from '@nestjs/common';
import promiseLimit from 'promise-limit'; import promiseLimit from 'promise-limit';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { MessagingMessagesRepository, PollsRepository, EmojisRepository, UsersRepository } from '@/models/index.js'; import type { PollsRepository, EmojisRepository } from '@/models/index.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import type { CacheableRemoteUser } from '@/models/entities/User.js'; import type { RemoteUser } from '@/models/entities/User.js';
import type { Note } from '@/models/entities/Note.js'; import type { Note } from '@/models/entities/Note.js';
import { toArray, toSingle, unique } from '@/misc/prelude/array.js'; import { toArray, toSingle, unique } from '@/misc/prelude/array.js';
import type { Emoji } from '@/models/entities/Emoji.js'; import type { Emoji } from '@/models/entities/Emoji.js';
@ -16,7 +16,6 @@ import { IdService } from '@/core/IdService.js';
import { PollService } from '@/core/PollService.js'; import { PollService } from '@/core/PollService.js';
import { StatusError } from '@/misc/status-error.js'; import { StatusError } from '@/misc/status-error.js';
import { UtilityService } from '@/core/UtilityService.js'; import { UtilityService } from '@/core/UtilityService.js';
import { MessagingService } from '@/core/MessagingService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { getOneApId, getApId, getOneApHrefNullable, validPost, isEmoji, getApType } from '../type.js'; import { getOneApId, getApId, getOneApHrefNullable, validPost, isEmoji, getApType } from '../type.js';
// eslint-disable-next-line @typescript-eslint/consistent-type-imports // eslint-disable-next-line @typescript-eslint/consistent-type-imports
@ -47,9 +46,6 @@ export class ApNoteService {
@Inject(DI.emojisRepository) @Inject(DI.emojisRepository)
private emojisRepository: EmojisRepository, private emojisRepository: EmojisRepository,
@Inject(DI.messagingMessagesRepository)
private messagingMessagesRepository: MessagingMessagesRepository,
private idService: IdService, private idService: IdService,
private apMfmService: ApMfmService, private apMfmService: ApMfmService,
private apResolverService: ApResolverService, private apResolverService: ApResolverService,
@ -64,7 +60,6 @@ export class ApNoteService {
private apImageService: ApImageService, private apImageService: ApImageService,
private apQuestionService: ApQuestionService, private apQuestionService: ApQuestionService,
private metaService: MetaService, private metaService: MetaService,
private messagingService: MessagingService,
private appLockService: AppLockService, private appLockService: AppLockService,
private pollService: PollService, private pollService: PollService,
private noteCreateService: NoteCreateService, private noteCreateService: NoteCreateService,
@ -114,7 +109,7 @@ export class ApNoteService {
public async createNote(value: string | IObject, resolver?: Resolver, silent = false): Promise<Note | null> { public async createNote(value: string | IObject, resolver?: Resolver, silent = false): Promise<Note | null> {
if (resolver == null) resolver = this.apResolverService.createResolver(); if (resolver == null) resolver = this.apResolverService.createResolver();
const object: any = await resolver.resolve(value); const object = await resolver.resolve(value);
const entryUri = getApId(value); const entryUri = getApId(value);
const err = this.validateNote(object, entryUri); const err = this.validateNote(object, entryUri);
@ -129,7 +124,7 @@ export class ApNoteService {
throw new Error('invalid note'); throw new Error('invalid note');
} }
const note: IPost = object; const note: IPost = object as any;
this.logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`); this.logger.debug(`Note fetched: ${JSON.stringify(note, null, 2)}`);
@ -146,7 +141,7 @@ export class ApNoteService {
this.logger.info(`Creating the Note: ${note.id}`); this.logger.info(`Creating the Note: ${note.id}`);
// 投稿者をフェッチ // 投稿者をフェッチ
const actor = await this.apPersonService.resolvePerson(getOneApId(note.attributedTo), resolver) as CacheableRemoteUser; const actor = await this.apPersonService.resolvePerson(getOneApId(note.attributedTo!), resolver) as RemoteUser;
// 投稿者が凍結されていたらスキップ // 投稿者が凍結されていたらスキップ
if (actor.isSuspended) { if (actor.isSuspended) {
@ -165,8 +160,6 @@ export class ApNoteService {
} }
} }
let isMessaging = note._misskey_talk && visibility === 'specified';
const apMentions = await this.apMentionService.extractApMentions(note.tag, resolver); const apMentions = await this.apMentionService.extractApMentions(note.tag, resolver);
const apHashtags = await extractApHashtags(note.tag); const apHashtags = await extractApHashtags(note.tag);
@ -193,17 +186,6 @@ export class ApNoteService {
return x; return x;
} }
}).catch(async err => { }).catch(async err => {
// トークだったらinReplyToのエラーは無視
const uri = getApId(note.inReplyTo);
if (uri.startsWith(this.config.url + '/')) {
const id = uri.split('/').pop();
const talk = await this.messagingMessagesRepository.findOneBy({ id });
if (talk) {
isMessaging = true;
return null;
}
}
this.logger.warn(`Error in inReplyTo ${note.inReplyTo} - ${err.statusCode ?? err}`); this.logger.warn(`Error in inReplyTo ${note.inReplyTo} - ${err.statusCode ?? err}`);
throw err; throw err;
}) })
@ -293,13 +275,6 @@ export class ApNoteService {
const poll = await this.apQuestionService.extractPollFromQuestion(note, resolver).catch(() => undefined); const poll = await this.apQuestionService.extractPollFromQuestion(note, resolver).catch(() => undefined);
if (isMessaging) {
for (const recipient of visibleUsers) {
await this.messagingService.createMessage(actor, recipient, undefined, text ?? undefined, (files && files.length > 0) ? files[0] : null, object.id);
return null;
}
}
return await this.noteCreateService.create(actor, { return await this.noteCreateService.create(actor, {
createdAt: note.published ? new Date(note.published) : null, createdAt: note.published ? new Date(note.published) : null,
files, files,

View File

@ -1,11 +1,11 @@
import { forwardRef, Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import promiseLimit from 'promise-limit'; import promiseLimit from 'promise-limit';
import { DataSource } from 'typeorm'; import { DataSource } from 'typeorm';
import { ModuleRef } from '@nestjs/core'; import { ModuleRef } from '@nestjs/core';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { FollowingsRepository, InstancesRepository, UserProfilesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js'; import type { FollowingsRepository, InstancesRepository, UserProfilesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import type { CacheableUser, IRemoteUser } from '@/models/entities/User.js'; import type { RemoteUser } from '@/models/entities/User.js';
import { User } from '@/models/entities/User.js'; import { User } from '@/models/entities/User.js';
import { truncate } from '@/misc/truncate.js'; import { truncate } from '@/misc/truncate.js';
import type { UserCacheService } from '@/core/UserCacheService.js'; import type { UserCacheService } from '@/core/UserCacheService.js';
@ -39,7 +39,7 @@ import type { ApResolverService, Resolver } from '../ApResolverService.js';
import type { ApLoggerService } from '../ApLoggerService.js'; import type { ApLoggerService } from '../ApLoggerService.js';
// eslint-disable-next-line @typescript-eslint/consistent-type-imports // eslint-disable-next-line @typescript-eslint/consistent-type-imports
import type { ApImageService } from './ApImageService.js'; import type { ApImageService } from './ApImageService.js';
import type { IActor, IObject, IApPropertyValue } from '../type.js'; import type { IActor, IObject } from '../type.js';
const nameLength = 128; const nameLength = 128;
const summaryLength = 2048; const summaryLength = 2048;
@ -197,7 +197,7 @@ export class ApPersonService implements OnModuleInit {
* Misskeyに対象のPersonが登録されていればそれを返します * Misskeyに対象のPersonが登録されていればそれを返します
*/ */
@bindThis @bindThis
public async fetchPerson(uri: string, resolver?: Resolver): Promise<CacheableUser | null> { public async fetchPerson(uri: string, resolver?: Resolver): Promise<User | null> {
if (typeof uri !== 'string') throw new Error('uri is not string'); if (typeof uri !== 'string') throw new Error('uri is not string');
const cached = this.userCacheService.uriPersonCache.get(uri); const cached = this.userCacheService.uriPersonCache.get(uri);
@ -259,7 +259,7 @@ export class ApPersonService implements OnModuleInit {
} }
// Create user // Create user
let user: IRemoteUser; let user: RemoteUser;
try { try {
// Start transaction // Start transaction
await this.db.transaction(async transactionalEntityManager => { await this.db.transaction(async transactionalEntityManager => {
@ -284,7 +284,7 @@ export class ApPersonService implements OnModuleInit {
isBot, isBot,
isCat: (person as any).isCat === true, isCat: (person as any).isCat === true,
showTimelineReplies: false, showTimelineReplies: false,
})) as IRemoteUser; })) as RemoteUser;
await transactionalEntityManager.save(new UserProfile({ await transactionalEntityManager.save(new UserProfile({
userId: user.id, userId: user.id,
@ -313,7 +313,7 @@ export class ApPersonService implements OnModuleInit {
}); });
if (u) { if (u) {
user = u as IRemoteUser; user = u as RemoteUser;
} else { } else {
throw new Error('already registered'); throw new Error('already registered');
} }
@ -392,7 +392,7 @@ export class ApPersonService implements OnModuleInit {
} }
//#region このサーバーに既に登録されているか //#region このサーバーに既に登録されているか
const exist = await this.usersRepository.findOneBy({ uri }) as IRemoteUser; const exist = await this.usersRepository.findOneBy({ uri }) as RemoteUser;
if (exist == null) { if (exist == null) {
return; return;
@ -500,7 +500,7 @@ export class ApPersonService implements OnModuleInit {
* Misskeyに登録しそれを返します * Misskeyに登録しそれを返します
*/ */
@bindThis @bindThis
public async resolvePerson(uri: string, resolver?: Resolver): Promise<CacheableUser> { public async resolvePerson(uri: string, resolver?: Resolver): Promise<User> {
if (typeof uri !== 'string') throw new Error('uri is not string'); if (typeof uri !== 'string') throw new Error('uri is not string');
//#region このサーバーに既に登録されていたらそれを返す //#region このサーバーに既に登録されていたらそれを返す

View File

@ -1,25 +1,25 @@
export type obj = { [x: string]: any }; export type Obj = { [x: string]: any };
export type ApObject = IObject | string | (IObject | string)[]; export type ApObject = IObject | string | (IObject | string)[];
export interface IObject { export interface IObject {
'@context': string | string[] | obj | obj[]; '@context'?: string | string[] | Obj | Obj[];
type: string | string[]; type: string | string[];
id?: string; id?: string;
name?: string | null;
summary?: string; summary?: string;
published?: string; published?: string;
cc?: ApObject; cc?: ApObject;
to?: ApObject; to?: ApObject;
attributedTo: ApObject; attributedTo?: ApObject;
attachment?: any[]; attachment?: any[];
inReplyTo?: any; inReplyTo?: any;
replies?: ICollection; replies?: ICollection;
content?: string; content?: string | null;
name?: string;
startTime?: Date; startTime?: Date;
endTime?: Date; endTime?: Date;
icon?: any; icon?: any;
image?: any; image?: any;
url?: ApObject; url?: ApObject | string;
href?: string; href?: string;
tag?: IObject | IObject[]; tag?: IObject | IObject[];
sensitive?: boolean; sensitive?: boolean;
@ -113,11 +113,11 @@ export interface IPost extends IObject {
_misskey_quote?: string; _misskey_quote?: string;
_misskey_content?: string; _misskey_content?: string;
quoteUrl?: string; quoteUrl?: string;
_misskey_talk?: boolean;
} }
export interface IQuestion extends IObject { export interface IQuestion extends IObject {
type: 'Note' | 'Question'; type: 'Note' | 'Question';
actor: string;
source?: { source?: {
content: string; content: string;
mediaType: string; mediaType: string;
@ -200,6 +200,7 @@ export const isPropertyValue = (object: IObject): object is IApPropertyValue =>
export interface IApMention extends IObject { export interface IApMention extends IObject {
type: 'Mention'; type: 'Mention';
href: string; href: string;
name: string;
} }
export const isMention = (object: IObject): object is IApMention => export const isMention = (object: IObject): object is IApMention =>
@ -217,12 +218,30 @@ export const isHashtag = (object: IObject): object is IApHashtag =>
export interface IApEmoji extends IObject { export interface IApEmoji extends IObject {
type: 'Emoji'; type: 'Emoji';
updated: Date; name: string;
updated: string;
} }
export const isEmoji = (object: IObject): object is IApEmoji => export const isEmoji = (object: IObject): object is IApEmoji =>
getApType(object) === 'Emoji' && !Array.isArray(object.icon) && object.icon.url != null; getApType(object) === 'Emoji' && !Array.isArray(object.icon) && object.icon.url != null;
export interface IKey extends IObject {
type: 'Key';
owner: string;
publicKeyPem: string | Buffer;
}
export interface IApDocument extends IObject {
type: 'Document';
name: string | null;
mediaType: string;
}
export interface IApImage extends IObject {
type: 'Image';
name: string | null;
}
export interface ICreate extends IActivity { export interface ICreate extends IActivity {
type: 'Create'; type: 'Create';
} }

View File

@ -1,7 +1,6 @@
import { Inject, Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import type Logger from '@/logger.js'; import type Logger from '@/logger.js';
import { LoggerService } from '@/core/LoggerService.js'; import { LoggerService } from '@/core/LoggerService.js';
import { bindThis } from '@/decorators.js';
@Injectable() @Injectable()
export class ChartLoggerService { export class ChartLoggerService {

View File

@ -1,4 +1,4 @@
import { Injectable, Inject } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import FederationChart from './charts/federation.js'; import FederationChart from './charts/federation.js';

View File

@ -1,5 +1,5 @@
import { Injectable, Inject } from '@nestjs/common'; import { Injectable, Inject } from '@nestjs/common';
import { Not, IsNull, DataSource } from 'typeorm'; import { DataSource } from 'typeorm';
import type { DriveFile } from '@/models/entities/DriveFile.js'; import type { DriveFile } from '@/models/entities/DriveFile.js';
import { AppLockService } from '@/core/AppLockService.js'; import { AppLockService } from '@/core/AppLockService.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';

View File

@ -1,7 +1,6 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { AntennaNotesRepository, AntennasRepository, UserGroupJoiningsRepository } from '@/models/index.js'; import type { AntennaNotesRepository, AntennasRepository } from '@/models/index.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/schema.js'; import type { Packed } from '@/misc/schema.js';
import type { Antenna } from '@/models/entities/Antenna.js'; import type { Antenna } from '@/models/entities/Antenna.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
@ -14,9 +13,6 @@ export class AntennaEntityService {
@Inject(DI.antennaNotesRepository) @Inject(DI.antennaNotesRepository)
private antennaNotesRepository: AntennaNotesRepository, private antennaNotesRepository: AntennaNotesRepository,
@Inject(DI.userGroupJoiningsRepository)
private userGroupJoiningsRepository: UserGroupJoiningsRepository,
) { ) {
} }
@ -27,7 +23,6 @@ export class AntennaEntityService {
const antenna = typeof src === 'object' ? src : await this.antennasRepository.findOneByOrFail({ id: src }); const antenna = typeof src === 'object' ? src : await this.antennasRepository.findOneByOrFail({ id: src });
const hasUnreadNote = (await this.antennaNotesRepository.findOneBy({ antennaId: antenna.id, read: false })) != null; const hasUnreadNote = (await this.antennaNotesRepository.findOneBy({ antennaId: antenna.id, read: false })) != null;
const userGroupJoining = antenna.userGroupJoiningId ? await this.userGroupJoiningsRepository.findOneBy({ id: antenna.userGroupJoiningId }) : null;
return { return {
id: antenna.id, id: antenna.id,
@ -37,7 +32,6 @@ export class AntennaEntityService {
excludeKeywords: antenna.excludeKeywords, excludeKeywords: antenna.excludeKeywords,
src: antenna.src, src: antenna.src,
userListId: antenna.userListId, userListId: antenna.userListId,
userGroupId: userGroupJoining ? userGroupJoining.userGroupId : null,
users: antenna.users, users: antenna.users,
caseSensitive: antenna.caseSensitive, caseSensitive: antenna.caseSensitive,
notify: antenna.notify, notify: antenna.notify,

View File

@ -1,11 +1,9 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { AccessTokensRepository, AppsRepository } from '@/models/index.js'; import type { AccessTokensRepository, AppsRepository } from '@/models/index.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/schema.js'; import type { Packed } from '@/misc/schema.js';
import type { App } from '@/models/entities/App.js'; import type { App } from '@/models/entities/App.js';
import type { User } from '@/models/entities/User.js'; import type { User } from '@/models/entities/User.js';
import { UserEntityService } from './UserEntityService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
@Injectable() @Injectable()

View File

@ -2,10 +2,8 @@ import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { AuthSessionsRepository } from '@/models/index.js'; import type { AuthSessionsRepository } from '@/models/index.js';
import { awaitAll } from '@/misc/prelude/await-all.js'; import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/schema.js';
import type { AuthSession } from '@/models/entities/AuthSession.js'; import type { AuthSession } from '@/models/entities/AuthSession.js';
import type { User } from '@/models/entities/User.js'; import type { User } from '@/models/entities/User.js';
import { UserEntityService } from './UserEntityService.js';
import { AppEntityService } from './AppEntityService.js'; import { AppEntityService } from './AppEntityService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';

View File

@ -1,7 +1,6 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { ChannelFollowingsRepository, ChannelsRepository, DriveFilesRepository, NoteUnreadsRepository } from '@/models/index.js'; import type { ChannelFollowingsRepository, ChannelsRepository, DriveFilesRepository, NoteUnreadsRepository } from '@/models/index.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/schema.js'; import type { Packed } from '@/misc/schema.js';
import type { } from '@/models/entities/Blocking.js'; import type { } from '@/models/entities/Blocking.js';
import type { User } from '@/models/entities/User.js'; import type { User } from '@/models/entities/User.js';

View File

@ -4,7 +4,6 @@ import type { ClipsRepository } from '@/models/index.js';
import { awaitAll } from '@/misc/prelude/await-all.js'; import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/schema.js'; import type { Packed } from '@/misc/schema.js';
import type { } from '@/models/entities/Blocking.js'; import type { } from '@/models/entities/Blocking.js';
import type { User } from '@/models/entities/User.js';
import type { Clip } from '@/models/entities/Clip.js'; import type { Clip } from '@/models/entities/Clip.js';
import { UserEntityService } from './UserEntityService.js'; import { UserEntityService } from './UserEntityService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';

View File

@ -1,6 +1,5 @@
import { forwardRef, Inject, Injectable } from '@nestjs/common'; import { forwardRef, Inject, Injectable } from '@nestjs/common';
import { DataSource, In } from 'typeorm'; import { DataSource } from 'typeorm';
import * as mfm from 'mfm-js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { NotesRepository, DriveFilesRepository } from '@/models/index.js'; import type { NotesRepository, DriveFilesRepository } from '@/models/index.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
@ -11,9 +10,9 @@ import type { DriveFile } from '@/models/entities/DriveFile.js';
import { appendQuery, query } from '@/misc/prelude/url.js'; import { appendQuery, query } from '@/misc/prelude/url.js';
import { deepClone } from '@/misc/clone.js'; import { deepClone } from '@/misc/clone.js';
import { UtilityService } from '../UtilityService.js'; import { UtilityService } from '../UtilityService.js';
import { VideoProcessingService } from '../VideoProcessingService.js';
import { UserEntityService } from './UserEntityService.js'; import { UserEntityService } from './UserEntityService.js';
import { DriveFolderEntityService } from './DriveFolderEntityService.js'; import { DriveFolderEntityService } from './DriveFolderEntityService.js';
import { VideoProcessingService } from '../VideoProcessingService.js';
type PackOptions = { type PackOptions = {
detail?: boolean, detail?: boolean,
@ -74,14 +73,14 @@ export class DriveFileEntityService {
} }
@bindThis @bindThis
private getProxiedUrl(url: string, mode?: 'static' | 'avatar'): string | null { private getProxiedUrl(url: string, mode?: 'static' | 'avatar'): string {
return appendQuery( return appendQuery(
`${this.config.mediaProxy}/${mode ?? 'image'}.webp`, `${this.config.mediaProxy}/${mode ?? 'image'}.webp`,
query({ query({
url, url,
...(mode ? { [mode]: '1' } : {}), ...(mode ? { [mode]: '1' } : {}),
}) }),
) );
} }
@bindThis @bindThis
@ -110,7 +109,7 @@ export class DriveFileEntityService {
} }
@bindThis @bindThis
public getPublicUrl(file: DriveFile, mode?: 'avatar'): string | null { // static = thumbnail public getPublicUrl(file: DriveFile, mode?: 'avatar'): string { // static = thumbnail
// リモートかつメディアプロキシ // リモートかつメディアプロキシ
if (file.uri != null && file.userHost != null && this.config.externalMediaProxyEnabled) { if (file.uri != null && file.userHost != null && this.config.externalMediaProxyEnabled) {
return this.getProxiedUrl(file.uri, mode); return this.getProxiedUrl(file.uri, mode);

View File

@ -4,9 +4,7 @@ import type { DriveFilesRepository, DriveFoldersRepository } from '@/models/inde
import { awaitAll } from '@/misc/prelude/await-all.js'; import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/schema.js'; import type { Packed } from '@/misc/schema.js';
import type { } from '@/models/entities/Blocking.js'; import type { } from '@/models/entities/Blocking.js';
import type { User } from '@/models/entities/User.js';
import type { DriveFolder } from '@/models/entities/DriveFolder.js'; import type { DriveFolder } from '@/models/entities/DriveFolder.js';
import { UserEntityService } from './UserEntityService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
@Injectable() @Injectable()

View File

@ -1,50 +1,63 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { EmojisRepository } from '@/models/index.js'; import type { EmojisRepository } from '@/models/index.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/schema.js'; import type { Packed } from '@/misc/schema.js';
import type { } from '@/models/entities/Blocking.js'; import type { } from '@/models/entities/Blocking.js';
import type { User } from '@/models/entities/User.js';
import type { Emoji } from '@/models/entities/Emoji.js'; import type { Emoji } from '@/models/entities/Emoji.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { UserEntityService } from './UserEntityService.js';
@Injectable() @Injectable()
export class EmojiEntityService { export class EmojiEntityService {
constructor( constructor(
@Inject(DI.emojisRepository) @Inject(DI.emojisRepository)
private emojisRepository: EmojisRepository, private emojisRepository: EmojisRepository,
private userEntityService: UserEntityService,
) { ) {
} }
@bindThis @bindThis
public async pack( public async packSimple(
src: Emoji['id'] | Emoji, src: Emoji['id'] | Emoji,
opts: { omitHost?: boolean; omitId?: boolean; withUrl?: boolean; } = { omitHost: true, omitId: true, withUrl: true }, ): Promise<Packed<'EmojiSimple'>> {
): Promise<Packed<'Emoji'>> {
opts = { omitHost: true, omitId: true, withUrl: true, ...opts }
const emoji = typeof src === 'object' ? src : await this.emojisRepository.findOneByOrFail({ id: src }); const emoji = typeof src === 'object' ? src : await this.emojisRepository.findOneByOrFail({ id: src });
return { return {
id: opts.omitId ? undefined : emoji.id,
aliases: emoji.aliases, aliases: emoji.aliases,
name: emoji.name, name: emoji.name,
category: emoji.category, category: emoji.category,
host: opts.omitHost ? undefined : emoji.host,
// || emoji.originalUrl してるのは後方互換性のためpublicUrlはstringなので??はだめ) // || emoji.originalUrl してるのは後方互換性のためpublicUrlはstringなので??はだめ)
url: opts.withUrl ? (emoji.publicUrl || emoji.originalUrl) : undefined, url: emoji.publicUrl || emoji.originalUrl,
}; };
} }
@bindThis @bindThis
public packMany( public packSimpleMany(
emojis: any[], emojis: any[],
opts: { omitHost?: boolean; omitId?: boolean; withUrl?: boolean; } = {},
) { ) {
return Promise.all(emojis.map(x => this.pack(x, opts))); return Promise.all(emojis.map(x => this.packSimple(x)));
}
@bindThis
public async packDetailed(
src: Emoji['id'] | Emoji,
): Promise<Packed<'EmojiDetailed'>> {
const emoji = typeof src === 'object' ? src : await this.emojisRepository.findOneByOrFail({ id: src });
return {
id: emoji.id,
aliases: emoji.aliases,
name: emoji.name,
category: emoji.category,
host: emoji.host,
// || emoji.originalUrl してるのは後方互換性のためpublicUrlはstringなので??はだめ)
url: emoji.publicUrl || emoji.originalUrl,
};
}
@bindThis
public packDetailedMany(
emojis: any[],
) {
return Promise.all(emojis.map(x => this.packDetailed(x)));
} }
} }

View File

@ -1,13 +1,10 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { FlashLikesRepository } from '@/models/index.js'; import type { FlashLikesRepository } from '@/models/index.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/schema.js';
import type { } from '@/models/entities/Blocking.js'; import type { } from '@/models/entities/Blocking.js';
import type { User } from '@/models/entities/User.js'; import type { User } from '@/models/entities/User.js';
import type { FlashLike } from '@/models/entities/FlashLike.js'; import type { FlashLike } from '@/models/entities/FlashLike.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { UserEntityService } from './UserEntityService.js';
import { FlashEntityService } from './FlashEntityService.js'; import { FlashEntityService } from './FlashEntityService.js';
@Injectable() @Injectable()

View File

@ -1,8 +1,6 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { FollowRequestsRepository } from '@/models/index.js'; import type { FollowRequestsRepository } from '@/models/index.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/schema.js';
import type { } from '@/models/entities/Blocking.js'; import type { } from '@/models/entities/Blocking.js';
import type { User } from '@/models/entities/User.js'; import type { User } from '@/models/entities/User.js';
import type { FollowRequest } from '@/models/entities/FollowRequest.js'; import type { FollowRequest } from '@/models/entities/FollowRequest.js';

View File

@ -1,12 +1,8 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { GalleryLikesRepository } from '@/models/index.js'; import type { GalleryLikesRepository } from '@/models/index.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/schema.js';
import type { } from '@/models/entities/Blocking.js'; import type { } from '@/models/entities/Blocking.js';
import type { User } from '@/models/entities/User.js';
import type { GalleryLike } from '@/models/entities/GalleryLike.js'; import type { GalleryLike } from '@/models/entities/GalleryLike.js';
import { UserEntityService } from './UserEntityService.js';
import { GalleryPostEntityService } from './GalleryPostEntityService.js'; import { GalleryPostEntityService } from './GalleryPostEntityService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';

View File

@ -1,10 +1,8 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { HashtagsRepository } from '@/models/index.js'; import type { HashtagsRepository } from '@/models/index.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/schema.js'; import type { Packed } from '@/misc/schema.js';
import type { } from '@/models/entities/Blocking.js'; import type { } from '@/models/entities/Blocking.js';
import type { User } from '@/models/entities/User.js';
import type { Hashtag } from '@/models/entities/Hashtag.js'; import type { Hashtag } from '@/models/entities/Hashtag.js';
import { UserEntityService } from './UserEntityService.js'; import { UserEntityService } from './UserEntityService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';

View File

@ -1,10 +1,8 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { InstancesRepository } from '@/models/index.js'; import type { InstancesRepository } from '@/models/index.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/schema.js'; import type { Packed } from '@/misc/schema.js';
import type { } from '@/models/entities/Blocking.js'; import type { } from '@/models/entities/Blocking.js';
import type { User } from '@/models/entities/User.js';
import type { Instance } from '@/models/entities/Instance.js'; import type { Instance } from '@/models/entities/Instance.js';
import { MetaService } from '@/core/MetaService.js'; import { MetaService } from '@/core/MetaService.js';
import { UtilityService } from '../UtilityService.js'; import { UtilityService } from '../UtilityService.js';

View File

@ -1,59 +0,0 @@
import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import type { MessagingMessagesRepository } from '@/models/index.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/schema.js';
import type { } from '@/models/entities/Blocking.js';
import type { User } from '@/models/entities/User.js';
import type { MessagingMessage } from '@/models/entities/MessagingMessage.js';
import { UserEntityService } from './UserEntityService.js';
import { DriveFileEntityService } from './DriveFileEntityService.js';
import { UserGroupEntityService } from './UserGroupEntityService.js';
import { bindThis } from '@/decorators.js';
@Injectable()
export class MessagingMessageEntityService {
constructor(
@Inject(DI.messagingMessagesRepository)
private messagingMessagesRepository: MessagingMessagesRepository,
private userEntityService: UserEntityService,
private userGroupEntityService: UserGroupEntityService,
private driveFileEntityService: DriveFileEntityService,
) {
}
@bindThis
public async pack(
src: MessagingMessage['id'] | MessagingMessage,
me?: { id: User['id'] } | null | undefined,
options?: {
populateRecipient?: boolean,
populateGroup?: boolean,
},
): Promise<Packed<'MessagingMessage'>> {
const opts = options ?? {
populateRecipient: true,
populateGroup: true,
};
const message = typeof src === 'object' ? src : await this.messagingMessagesRepository.findOneByOrFail({ id: src });
return {
id: message.id,
createdAt: message.createdAt.toISOString(),
text: message.text,
userId: message.userId,
user: await this.userEntityService.pack(message.user ?? message.userId, me),
recipientId: message.recipientId,
recipient: message.recipientId && opts.populateRecipient ? await this.userEntityService.pack(message.recipient ?? message.recipientId, me) : undefined,
groupId: message.groupId,
group: message.groupId && opts.populateGroup ? await this.userGroupEntityService.pack(message.group ?? message.groupId) : undefined,
fileId: message.fileId,
file: message.fileId ? await this.driveFileEntityService.pack(message.fileId) : null,
isRead: message.isRead,
reads: message.reads,
};
}
}

View File

@ -2,9 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { ModerationLogsRepository } from '@/models/index.js'; import type { ModerationLogsRepository } from '@/models/index.js';
import { awaitAll } from '@/misc/prelude/await-all.js'; import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/schema.js';
import type { } from '@/models/entities/Blocking.js'; import type { } from '@/models/entities/Blocking.js';
import type { User } from '@/models/entities/User.js';
import type { ModerationLog } from '@/models/entities/ModerationLog.js'; import type { ModerationLog } from '@/models/entities/ModerationLog.js';
import { UserEntityService } from './UserEntityService.js'; import { UserEntityService } from './UserEntityService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';

View File

@ -1,9 +1,8 @@
import { forwardRef, Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { DataSource, In } from 'typeorm'; import { DataSource, In } from 'typeorm';
import * as mfm from 'mfm-js'; import * as mfm from 'mfm-js';
import { ModuleRef } from '@nestjs/core'; import { ModuleRef } from '@nestjs/core';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { Config } from '@/config.js';
import type { Packed } from '@/misc/schema.js'; import type { Packed } from '@/misc/schema.js';
import { nyaize } from '@/misc/nyaize.js'; import { nyaize } from '@/misc/nyaize.js';
import { awaitAll } from '@/misc/prelude/await-all.js'; import { awaitAll } from '@/misc/prelude/await-all.js';

View File

@ -1,12 +1,9 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { NoteFavoritesRepository } from '@/models/index.js'; import type { NoteFavoritesRepository } from '@/models/index.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/schema.js';
import type { } from '@/models/entities/Blocking.js'; import type { } from '@/models/entities/Blocking.js';
import type { User } from '@/models/entities/User.js'; import type { User } from '@/models/entities/User.js';
import type { NoteFavorite } from '@/models/entities/NoteFavorite.js'; import type { NoteFavorite } from '@/models/entities/NoteFavorite.js';
import { UserEntityService } from './UserEntityService.js';
import { NoteEntityService } from './NoteEntityService.js'; import { NoteEntityService } from './NoteEntityService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';

View File

@ -1,7 +1,6 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { NoteReactionsRepository } from '@/models/index.js'; import type { NoteReactionsRepository } from '@/models/index.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/schema.js'; import type { Packed } from '@/misc/schema.js';
import type { OnModuleInit } from '@nestjs/common'; import type { OnModuleInit } from '@nestjs/common';
import type { } from '@/models/entities/Blocking.js'; import type { } from '@/models/entities/Blocking.js';

View File

@ -13,13 +13,11 @@ import type { OnModuleInit } from '@nestjs/common';
import type { CustomEmojiService } from '../CustomEmojiService.js'; import type { CustomEmojiService } from '../CustomEmojiService.js';
import type { UserEntityService } from './UserEntityService.js'; import type { UserEntityService } from './UserEntityService.js';
import type { NoteEntityService } from './NoteEntityService.js'; import type { NoteEntityService } from './NoteEntityService.js';
import type { UserGroupInvitationEntityService } from './UserGroupInvitationEntityService.js';
@Injectable() @Injectable()
export class NotificationEntityService implements OnModuleInit { export class NotificationEntityService implements OnModuleInit {
private userEntityService: UserEntityService; private userEntityService: UserEntityService;
private noteEntityService: NoteEntityService; private noteEntityService: NoteEntityService;
private userGroupInvitationEntityService: UserGroupInvitationEntityService;
private customEmojiService: CustomEmojiService; private customEmojiService: CustomEmojiService;
constructor( constructor(
@ -36,7 +34,6 @@ export class NotificationEntityService implements OnModuleInit {
//private userEntityService: UserEntityService, //private userEntityService: UserEntityService,
//private noteEntityService: NoteEntityService, //private noteEntityService: NoteEntityService,
//private userGroupInvitationEntityService: UserGroupInvitationEntityService,
//private customEmojiService: CustomEmojiService, //private customEmojiService: CustomEmojiService,
) { ) {
} }
@ -44,7 +41,6 @@ export class NotificationEntityService implements OnModuleInit {
onModuleInit() { onModuleInit() {
this.userEntityService = this.moduleRef.get('UserEntityService'); this.userEntityService = this.moduleRef.get('UserEntityService');
this.noteEntityService = this.moduleRef.get('NoteEntityService'); this.noteEntityService = this.moduleRef.get('NoteEntityService');
this.userGroupInvitationEntityService = this.moduleRef.get('UserGroupInvitationEntityService');
this.customEmojiService = this.moduleRef.get('CustomEmojiService'); this.customEmojiService = this.moduleRef.get('CustomEmojiService');
} }
@ -111,9 +107,6 @@ export class NotificationEntityService implements OnModuleInit {
_hint_: options._hintForEachNotes_, _hint_: options._hintForEachNotes_,
}), }),
} : {}), } : {}),
...(notification.type === 'groupInvited' ? {
invitation: this.userGroupInvitationEntityService.pack(notification.userGroupInvitationId!),
} : {}),
...(notification.type === 'achievementEarned' ? { ...(notification.type === 'achievementEarned' ? {
achievement: notification.achievement, achievement: notification.achievement,
} : {}), } : {}),

View File

@ -1,12 +1,9 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { PageLikesRepository } from '@/models/index.js'; import type { PageLikesRepository } from '@/models/index.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/schema.js';
import type { } from '@/models/entities/Blocking.js'; import type { } from '@/models/entities/Blocking.js';
import type { User } from '@/models/entities/User.js'; import type { User } from '@/models/entities/User.js';
import type { PageLike } from '@/models/entities/PageLike.js'; import type { PageLike } from '@/models/entities/PageLike.js';
import { UserEntityService } from './UserEntityService.js';
import { PageEntityService } from './PageEntityService.js'; import { PageEntityService } from './PageEntityService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';

View File

@ -2,7 +2,6 @@ import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { RoleAssignmentsRepository, RolesRepository } from '@/models/index.js'; import type { RoleAssignmentsRepository, RolesRepository } from '@/models/index.js';
import { awaitAll } from '@/misc/prelude/await-all.js'; import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/schema.js';
import type { User } from '@/models/entities/User.js'; import type { User } from '@/models/entities/User.js';
import type { Role } from '@/models/entities/Role.js'; import type { Role } from '@/models/entities/Role.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
@ -26,14 +25,7 @@ export class RoleEntityService {
public async pack( public async pack(
src: Role['id'] | Role, src: Role['id'] | Role,
me?: { id: User['id'] } | null | undefined, me?: { id: User['id'] } | null | undefined,
options?: {
detail?: boolean;
},
) { ) {
const opts = Object.assign({
detail: true,
}, options);
const role = typeof src === 'object' ? src : await this.rolesRepository.findOneByOrFail({ id: src }); const role = typeof src === 'object' ? src : await this.rolesRepository.findOneByOrFail({ id: src });
const assigns = await this.roleAssignmentsRepository.findBy({ const assigns = await this.roleAssignmentsRepository.findBy({
@ -66,9 +58,6 @@ export class RoleEntityService {
canEditMembersByModerator: role.canEditMembersByModerator, canEditMembersByModerator: role.canEditMembersByModerator,
policies: policies, policies: policies,
usersCount: assigns.length, usersCount: assigns.length,
...(opts.detail ? {
users: this.userEntityService.packMany(assigns.map(x => x.userId), me),
} : {}),
}); });
} }
@ -76,11 +65,8 @@ export class RoleEntityService {
public packMany( public packMany(
roles: any[], roles: any[],
me: { id: User['id'] }, me: { id: User['id'] },
options?: {
detail?: boolean;
},
) { ) {
return Promise.all(roles.map(x => this.pack(x, me, options))); return Promise.all(roles.map(x => this.pack(x, me)));
} }
} }

View File

@ -1,10 +1,7 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { SigninsRepository } from '@/models/index.js'; import type { SigninsRepository } from '@/models/index.js';
import { awaitAll } from '@/misc/prelude/await-all.js';
import type { Packed } from '@/misc/schema.js';
import type { } from '@/models/entities/Blocking.js'; import type { } from '@/models/entities/Blocking.js';
import type { User } from '@/models/entities/User.js';
import type { Signin } from '@/models/entities/Signin.js'; import type { Signin } from '@/models/entities/Signin.js';
import { UserEntityService } from './UserEntityService.js'; import { UserEntityService } from './UserEntityService.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';

View File

@ -1,4 +1,4 @@
import { forwardRef, Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { In, Not } from 'typeorm'; import { In, Not } from 'typeorm';
import Ajv from 'ajv'; import Ajv from 'ajv';
import { ModuleRef } from '@nestjs/core'; import { ModuleRef } from '@nestjs/core';
@ -10,9 +10,9 @@ import { awaitAll } from '@/misc/prelude/await-all.js';
import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from '@/const.js'; import { USER_ACTIVE_THRESHOLD, USER_ONLINE_THRESHOLD } from '@/const.js';
import { Cache } from '@/misc/cache.js'; import { Cache } from '@/misc/cache.js';
import type { Instance } from '@/models/entities/Instance.js'; import type { Instance } from '@/models/entities/Instance.js';
import type { ILocalUser, IRemoteUser, User } from '@/models/entities/User.js'; import type { LocalUser, RemoteUser, User } from '@/models/entities/User.js';
import { birthdaySchema, descriptionSchema, localUsernameSchema, locationSchema, nameSchema, passwordSchema } from '@/models/entities/User.js'; import { birthdaySchema, descriptionSchema, localUsernameSchema, locationSchema, nameSchema, passwordSchema } from '@/models/entities/User.js';
import type { UsersRepository, UserSecurityKeysRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, MutingsRepository, DriveFilesRepository, NoteUnreadsRepository, ChannelFollowingsRepository, NotificationsRepository, UserNotePiningsRepository, UserProfilesRepository, InstancesRepository, AnnouncementReadsRepository, MessagingMessagesRepository, UserGroupJoiningsRepository, AnnouncementsRepository, AntennaNotesRepository, PagesRepository, UserProfile } from '@/models/index.js'; import type { UsersRepository, UserSecurityKeysRepository, FollowingsRepository, FollowRequestsRepository, BlockingsRepository, MutingsRepository, DriveFilesRepository, NoteUnreadsRepository, ChannelFollowingsRepository, NotificationsRepository, UserNotePiningsRepository, UserProfilesRepository, InstancesRepository, AnnouncementReadsRepository, AnnouncementsRepository, AntennaNotesRepository, PagesRepository, UserProfile } from '@/models/index.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { RoleService } from '@/core/RoleService.js'; import { RoleService } from '@/core/RoleService.js';
import type { OnModuleInit } from '@nestjs/common'; import type { OnModuleInit } from '@nestjs/common';
@ -32,13 +32,13 @@ type IsMeAndIsUserDetailed<ExpectsMe extends boolean | null, Detailed extends bo
const ajv = new Ajv(); const ajv = new Ajv();
function isLocalUser(user: User): user is ILocalUser; function isLocalUser(user: User): user is LocalUser;
function isLocalUser<T extends { host: User['host'] }>(user: T): user is T & { host: null; }; function isLocalUser<T extends { host: User['host'] }>(user: T): user is T & { host: null; };
function isLocalUser(user: User | { host: User['host'] }): boolean { function isLocalUser(user: User | { host: User['host'] }): boolean {
return user.host == null; return user.host == null;
} }
function isRemoteUser(user: User): user is IRemoteUser; function isRemoteUser(user: User): user is RemoteUser;
function isRemoteUser<T extends { host: User['host'] }>(user: T): user is T & { host: string; }; function isRemoteUser<T extends { host: User['host'] }>(user: T): user is T & { host: string; };
function isRemoteUser(user: User | { host: User['host'] }): boolean { function isRemoteUser(user: User | { host: User['host'] }): boolean {
return !isLocalUser(user); return !isLocalUser(user);
@ -102,12 +102,6 @@ export class UserEntityService implements OnModuleInit {
@Inject(DI.announcementReadsRepository) @Inject(DI.announcementReadsRepository)
private announcementReadsRepository: AnnouncementReadsRepository, private announcementReadsRepository: AnnouncementReadsRepository,
@Inject(DI.messagingMessagesRepository)
private messagingMessagesRepository: MessagingMessagesRepository,
@Inject(DI.userGroupJoiningsRepository)
private userGroupJoiningsRepository: UserGroupJoiningsRepository,
@Inject(DI.announcementsRepository) @Inject(DI.announcementsRepository)
private announcementsRepository: AnnouncementsRepository, private announcementsRepository: AnnouncementsRepository,
@ -204,36 +198,6 @@ export class UserEntityService implements OnModuleInit {
}); });
} }
@bindThis
public async getHasUnreadMessagingMessage(userId: User['id']): Promise<boolean> {
const mute = await this.mutingsRepository.findBy({
muterId: userId,
});
const joinings = await this.userGroupJoiningsRepository.findBy({ userId: userId });
const groupQs = Promise.all(joinings.map(j => this.messagingMessagesRepository.createQueryBuilder('message')
.where('message.groupId = :groupId', { groupId: j.userGroupId })
.andWhere('message.userId != :userId', { userId: userId })
.andWhere('NOT (:userId = ANY(message.reads))', { userId: userId })
.andWhere('message.createdAt > :joinedAt', { joinedAt: j.createdAt }) // 自分が加入する前の会話については、未読扱いしない
.getOne().then(x => x != null)));
const [withUser, withGroups] = await Promise.all([
this.messagingMessagesRepository.count({
where: {
recipientId: userId,
isRead: false,
...(mute.length > 0 ? { userId: Not(In(mute.map(x => x.muteeId))) } : {}),
},
take: 1,
}).then(count => count > 0),
groupQs,
]);
return withUser || withGroups.some(x => x);
}
@bindThis @bindThis
public async getHasUnreadAnnouncement(userId: User['id']): Promise<boolean> { public async getHasUnreadAnnouncement(userId: User['id']): Promise<boolean> {
const reads = await this.announcementReadsRepository.findBy({ const reads = await this.announcementReadsRepository.findBy({
@ -492,7 +456,6 @@ export class UserEntityService implements OnModuleInit {
hasUnreadAnnouncement: this.getHasUnreadAnnouncement(user.id), hasUnreadAnnouncement: this.getHasUnreadAnnouncement(user.id),
hasUnreadAntenna: this.getHasUnreadAntenna(user.id), hasUnreadAntenna: this.getHasUnreadAntenna(user.id),
hasUnreadChannel: this.getHasUnreadChannel(user.id), hasUnreadChannel: this.getHasUnreadChannel(user.id),
hasUnreadMessagingMessage: this.getHasUnreadMessagingMessage(user.id),
hasUnreadNotification: this.getHasUnreadNotification(user.id), hasUnreadNotification: this.getHasUnreadNotification(user.id),
hasPendingReceivedFollowRequest: this.getHasPendingReceivedFollowRequest(user.id), hasPendingReceivedFollowRequest: this.getHasPendingReceivedFollowRequest(user.id),
mutedWords: profile!.mutedWords, mutedWords: profile!.mutedWords,

Some files were not shown because too many files have changed in this diff Show More