mirror of
https://git.joinsharkey.org/Sharkey/Sharkey.git
synced 2024-11-27 01:13:08 +02:00
Merge branch 'develop'
This commit is contained in:
commit
7658351041
606 changed files with 4254 additions and 8195 deletions
|
@ -2,10 +2,10 @@
|
|||
"name": "Misskey",
|
||||
"dockerComposeFile": "docker-compose.yml",
|
||||
"service": "app",
|
||||
"workspaceFolder": "/workspaces/${localWorkspaceFolderBasename}",
|
||||
"workspaceFolder": "/workspace",
|
||||
"features": {
|
||||
"ghcr.io/devcontainers-contrib/features/pnpm:2": {}
|
||||
},
|
||||
"forwardPorts": [3000],
|
||||
"postCreateCommand": ".devcontainer/init.sh"
|
||||
"postCreateCommand": "sudo chmod 755 .devcontainer/init.sh && .devcontainer/init.sh"
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ services:
|
|||
dockerfile: Dockerfile
|
||||
|
||||
volumes:
|
||||
- ../..:/workspaces:cached
|
||||
- ../:/workspace:cached
|
||||
|
||||
command: sleep infinity
|
||||
|
||||
|
@ -21,7 +21,7 @@ services:
|
|||
networks:
|
||||
- internal_network
|
||||
volumes:
|
||||
- ../redis:/data
|
||||
- redis-data:/data
|
||||
healthcheck:
|
||||
test: "redis-cli ping"
|
||||
interval: 5s
|
||||
|
@ -37,7 +37,7 @@ services:
|
|||
POSTGRES_PASSWORD: postgres
|
||||
POSTGRES_DB: misskey
|
||||
volumes:
|
||||
- ../db:/var/lib/postgresql/data
|
||||
- postgres-data:/var/lib/postgresql/data
|
||||
healthcheck:
|
||||
test: "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"
|
||||
interval: 5s
|
||||
|
@ -45,6 +45,7 @@ services:
|
|||
|
||||
volumes:
|
||||
postgres-data:
|
||||
redis-data:
|
||||
|
||||
networks:
|
||||
internal_network:
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
|
||||
set -xe
|
||||
|
||||
sudo chown -R node /workspace
|
||||
git submodule update --init
|
||||
pnpm install --frozen-lockfile
|
||||
cp .devcontainer/devcontainer.yml .config/default.yml
|
||||
|
|
|
@ -5,6 +5,7 @@ indent_style = tab
|
|||
indent_size = 2
|
||||
charset = utf-8
|
||||
insert_final_newline = true
|
||||
end_of_line = lf
|
||||
|
||||
[*.yml]
|
||||
indent_style = space
|
||||
|
|
1
.gitattributes
vendored
1
.gitattributes
vendored
|
@ -5,3 +5,4 @@
|
|||
*.glb -diff -text
|
||||
*.blend -diff -text
|
||||
*.afdesign -diff -text
|
||||
* text=auto eol=lf
|
||||
|
|
8
.github/workflows/docker-develop.yml
vendored
8
.github/workflows/docker-develop.yml
vendored
|
@ -15,7 +15,10 @@ jobs:
|
|||
- name: Check out the repo
|
||||
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
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
|
@ -27,10 +30,13 @@ jobs:
|
|||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Build and Push to Docker Hub
|
||||
uses: docker/build-push-action@v3
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
context: .
|
||||
push: true
|
||||
platforms: ${{ steps.buildx.outputs.platforms }}
|
||||
provenance: false
|
||||
tags: misskey/misskey:develop
|
||||
labels: develop
|
||||
cache-from: type=gha
|
||||
|
|
12
.github/workflows/docker.yml
vendored
12
.github/workflows/docker.yml
vendored
|
@ -13,6 +13,11 @@ jobs:
|
|||
steps:
|
||||
- name: Check out the repo
|
||||
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
|
||||
id: meta
|
||||
uses: docker/metadata-action@v4
|
||||
|
@ -31,9 +36,14 @@ jobs:
|
|||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
- name: Build and Push to Docker Hub
|
||||
uses: docker/build-push-action@v3
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
builder: ${{ steps.buildx.outputs.name }}
|
||||
context: .
|
||||
push: true
|
||||
platforms: ${{ steps.buildx.outputs.platforms }}
|
||||
provenance: false
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
|
4
.github/workflows/lint.yml
vendored
4
.github/workflows/lint.yml
vendored
|
@ -36,6 +36,8 @@ jobs:
|
|||
- backend
|
||||
- frontend
|
||||
- sw
|
||||
lint:
|
||||
- eslint
|
||||
steps:
|
||||
- uses: actions/checkout@v3.3.0
|
||||
with:
|
||||
|
@ -51,4 +53,4 @@ jobs:
|
|||
cache: 'pnpm'
|
||||
- run: corepack enable
|
||||
- run: pnpm i --frozen-lockfile
|
||||
- run: pnpm --filter ${{ matrix.workspace }} run lint
|
||||
- run: pnpm --filter ${{ matrix.workspace }} run ${{ matrix.lint }}
|
||||
|
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
|
@ -1,5 +1,6 @@
|
|||
{
|
||||
"search.exclude": {
|
||||
"**/node_modules": true
|
||||
}
|
||||
},
|
||||
"typescript.tsdk": "node_modules/typescript/lib"
|
||||
}
|
22
CHANGELOG.md
22
CHANGELOG.md
|
@ -8,6 +8,28 @@
|
|||
|
||||
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)
|
||||
|
||||
### Improvements
|
||||
|
|
|
@ -114,6 +114,7 @@ command.
|
|||
### Dev Container
|
||||
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.
|
||||
**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.
|
||||
``` bash
|
||||
|
|
14
Dockerfile
14
Dockerfile
|
@ -1,3 +1,5 @@
|
|||
# syntax = docker/dockerfile:1.4
|
||||
|
||||
ARG NODE_VERSION=18.13.0-bullseye
|
||||
|
||||
FROM node:${NODE_VERSION} AS builder
|
||||
|
@ -14,16 +16,16 @@ RUN corepack enable
|
|||
|
||||
WORKDIR /misskey
|
||||
|
||||
COPY ["pnpm-lock.yaml", "pnpm-workspace.yaml", "package.json", "./"]
|
||||
COPY ["scripts", "./scripts"]
|
||||
COPY ["packages/backend/package.json", "./packages/backend/"]
|
||||
COPY ["packages/frontend/package.json", "./packages/frontend/"]
|
||||
COPY ["packages/sw/package.json", "./packages/sw/"]
|
||||
COPY --link ["pnpm-lock.yaml", "pnpm-workspace.yaml", "package.json", "./"]
|
||||
COPY --link ["scripts", "./scripts"]
|
||||
COPY --link ["packages/backend/package.json", "./packages/backend/"]
|
||||
COPY --link ["packages/frontend/package.json", "./packages/frontend/"]
|
||||
COPY --link ["packages/sw/package.json", "./packages/sw/"]
|
||||
|
||||
RUN --mount=type=cache,target=/root/.local/share/pnpm/store,sharing=locked \
|
||||
pnpm i --frozen-lockfile --aggregate-output
|
||||
|
||||
COPY . ./
|
||||
COPY --link . ./
|
||||
|
||||
ARG NODE_ENV=production
|
||||
|
||||
|
|
|
@ -5,9 +5,9 @@ Also, the later tasks are more indefinite and are subject to change as developme
|
|||
## (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.
|
||||
|
||||
- Make the number of type errors zero (backend)
|
||||
- ~~Make the number of type errors zero (backend)~~ → Done ✔️
|
||||
- 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
|
||||
- Add more tests
|
||||
- ~~May need to implement a mechanism that allows for DI~~ → Done ✔️
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
apiVersion: v2
|
||||
name: misskey
|
||||
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
5
codecov.yml
Normal file
|
@ -0,0 +1,5 @@
|
|||
coverage:
|
||||
status:
|
||||
project:
|
||||
default:
|
||||
only_pulls: true
|
|
@ -103,6 +103,8 @@ renoted: "Renote getätigt."
|
|||
cantRenote: "Renote dieses Beitrags nicht möglich."
|
||||
cantReRenote: "Renote einer Renote nicht möglich."
|
||||
quote: "Zitieren"
|
||||
inChannelRenote: "Kanal-interner Renote"
|
||||
inChannelQuote: "Kanal-internes Zitat"
|
||||
pinnedNote: "Angeheftete Notiz"
|
||||
pinned: "Angeheftet"
|
||||
you: "Du"
|
||||
|
@ -467,6 +469,8 @@ youHaveNoGroups: "Keine Gruppen vorhanden"
|
|||
joinOrCreateGroup: "Lass dich zu einer Gruppe einladen oder erstelle deine eigene."
|
||||
noHistory: "Kein Verlauf gefunden"
|
||||
signinHistory: "Anmeldungsverlauf"
|
||||
enableAdvancedMfm: "Erweitertes MFM aktivieren"
|
||||
enableAnimatedMfm: "Animiertes MFM aktivieren"
|
||||
doing: "In Bearbeitung …"
|
||||
category: "Kategorie"
|
||||
tags: "Schlagwörter"
|
||||
|
@ -945,6 +949,14 @@ selectFromPresets: "Aus Vorlagen wählen"
|
|||
achievements: "Errungenschaften"
|
||||
gotInvalidResponseError: "Ungültige Antwort des Servers"
|
||||
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:
|
||||
earnedAt: "Freigeschaltet am"
|
||||
_types:
|
||||
|
|
|
@ -103,6 +103,8 @@ renoted: "Renoted."
|
|||
cantRenote: "This post can't be renoted."
|
||||
cantReRenote: "A renote can't be renoted."
|
||||
quote: "Quote"
|
||||
inChannelRenote: "Channel-only Renote"
|
||||
inChannelQuote: "Channel-only Quote"
|
||||
pinnedNote: "Pinned note"
|
||||
pinned: "Pin to profile"
|
||||
you: "You"
|
||||
|
@ -468,7 +470,7 @@ joinOrCreateGroup: "Get invited to a group or create your own."
|
|||
noHistory: "No history available"
|
||||
signinHistory: "Login history"
|
||||
enableAdvancedMfm: "Enable advanced MFM"
|
||||
enableAnimatedMfm: "Enable MFM with animation"
|
||||
enableAnimatedMfm: "Enable animated MFM"
|
||||
doing: "Processing..."
|
||||
category: "Category"
|
||||
tags: "Tags"
|
||||
|
@ -951,6 +953,10 @@ thisPostMayBeAnnoying: "This note may annoy others."
|
|||
thisPostMayBeAnnoyingHome: "Post to home timeline"
|
||||
thisPostMayBeAnnoyingCancel: "Cancel"
|
||||
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:
|
||||
earnedAt: "Unlocked at"
|
||||
_types:
|
||||
|
|
|
@ -84,12 +84,12 @@ error: "Errore"
|
|||
somethingHappened: "Si è verificato un problema"
|
||||
retry: "Riprova"
|
||||
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."
|
||||
youShouldUpgradeClient: "Per visualizzare la pagina è necessario aggiornare il client alla nuova versione e ricaricare."
|
||||
enterListName: "Nome della lista"
|
||||
privacy: "Privacy"
|
||||
makeFollowManuallyApprove: "Richiedi di approvare i follower manualmente"
|
||||
makeFollowManuallyApprove: "Approva i follower manualmente"
|
||||
defaultNoteVisibility: "Privacy predefinita delle note"
|
||||
follow: "Segui"
|
||||
followRequest: "Richiesta di follow"
|
||||
|
@ -103,6 +103,8 @@ renoted: "Rinotato!"
|
|||
cantRenote: "È impossibile rinotare questa nota."
|
||||
cantReRenote: "È impossibile rinotare una Rinota."
|
||||
quote: "Cita"
|
||||
inChannelRenote: "Rinota nel canale"
|
||||
inChannelQuote: "Cita nel canale"
|
||||
pinnedNote: "Nota fissata"
|
||||
pinned: "Fissa sul profilo"
|
||||
you: "Tu"
|
||||
|
@ -129,6 +131,7 @@ unblockConfirm: "Vuoi davvero sbloccare il profilo?"
|
|||
suspendConfirm: "Vuoi sospendere questo profilo?"
|
||||
unsuspendConfirm: "Vuoi revocare la sospensione si questo profilo?"
|
||||
selectList: "Seleziona una lista"
|
||||
selectChannel: "Seleziona canale"
|
||||
selectAntenna: "Scegli un'antenna"
|
||||
selectWidget: "Seleziona il riquadro"
|
||||
editWidgets: "Modifica i riquadri"
|
||||
|
@ -256,6 +259,8 @@ noMoreHistory: "Non c'è più cronologia da visualizzare"
|
|||
startMessaging: "Nuovo messaggio"
|
||||
nUsersRead: "Letto da {n} persone"
|
||||
agreeTo: "Sono d'accordo con {0}"
|
||||
agreeBelow: "Accetto quanto riportato sotto"
|
||||
basicNotesBeforeCreateAccount: "Note importanti"
|
||||
tos: "Termini di servizio"
|
||||
start: "Inizia!"
|
||||
home: "Home"
|
||||
|
@ -464,6 +469,8 @@ youHaveNoGroups: "Nessun gruppo"
|
|||
joinOrCreateGroup: "Puoi creare il tuo gruppo o essere invitat@ a gruppi che già esistono."
|
||||
noHistory: "Nessuna cronologia"
|
||||
signinHistory: "Storico degli accessi al profilo"
|
||||
enableAdvancedMfm: "Attiva MFM avanzati"
|
||||
enableAnimatedMfm: "Attiva MFM animati"
|
||||
doing: "In corso..."
|
||||
category: "Categoria"
|
||||
tags: "Tag"
|
||||
|
@ -860,6 +867,8 @@ failedToFetchAccountInformation: "Impossibile recuperare le informazioni sul pro
|
|||
rateLimitExceeded: "Superato il limite di velocità."
|
||||
cropImage: "Ritaglio dell'immagine"
|
||||
cropImageAsk: "Si desidera ritagliare l'immagine?"
|
||||
cropYes: "Ritaglia"
|
||||
cropNo: "Non ritagliare"
|
||||
file: "Allegati"
|
||||
recentNHours: "Ultime {n} ore"
|
||||
recentNDays: "Ultimi {n} giorni"
|
||||
|
@ -938,6 +947,16 @@ cannotPerformTemporaryDescription: "L'attività non può essere svolta, poiché
|
|||
preset: "Preimpostato"
|
||||
selectFromPresets: "Seleziona preimpostato"
|
||||
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:
|
||||
earnedAt: "Data di conseguimento"
|
||||
_types:
|
||||
|
@ -1526,12 +1545,15 @@ _permissions:
|
|||
"read:gallery-likes": "Visualizza i contenuti della galleria."
|
||||
"write:gallery-likes": "Manipolazione dei \"Mi piace\" della galleria."
|
||||
_auth:
|
||||
shareAccessTitle: "Permessi dell'applicazione"
|
||||
shareAccess: "Vuoi autorizzare {name} 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:"
|
||||
pleaseGoBack: "Si prega di ritornare sulla app"
|
||||
callback: "Ritornando sulla app"
|
||||
denied: "Accesso negato"
|
||||
pleaseLogin: "Per favore accedi al tuo account per cambiare i permessi dell'applicazione"
|
||||
_antennaSources:
|
||||
all: "Tutte le note"
|
||||
homeTimeline: "Note dagli utenti che segui"
|
||||
|
|
|
@ -392,17 +392,20 @@ userList: "リスト"
|
|||
about: "情報"
|
||||
aboutMisskey: "Misskeyについて"
|
||||
administrator: "管理者"
|
||||
token: "トークン"
|
||||
twoStepAuthentication: "二段階認証"
|
||||
token: "確認コード"
|
||||
2fa: "二要素認証"
|
||||
totp: "認証アプリ"
|
||||
totpDescription: "認証アプリを使ってワンタイムパスワードを入力"
|
||||
moderator: "モデレーター"
|
||||
moderation: "モデレーション"
|
||||
nUsersMentioned: "{n}人が投稿"
|
||||
securityKeyAndPasskey: "セキュリティキー・パスキー"
|
||||
securityKey: "セキュリティキー"
|
||||
securityKeyName: "キーの名前"
|
||||
registerSecurityKey: "セキュリティキーを登録する"
|
||||
lastUsed: "最後の使用"
|
||||
lastUsedAt: "最後の使用: {t}"
|
||||
unregister: "登録を解除"
|
||||
passwordLessLogin: "パスワード無しログイン"
|
||||
passwordLessLogin: "パスワードレスログイン"
|
||||
passwordLessLoginDescription: "パスワードを使用せず、セキュリティキーやパスキーなどのみでログインします"
|
||||
resetPassword: "パスワードをリセット"
|
||||
newPasswordIs: "新しいパスワードは「{password}」です"
|
||||
reduceUiAnimation: "UIのアニメーションを減らす"
|
||||
|
@ -417,24 +420,15 @@ markAsReadAllTalkMessages: "すべてのチャットを既読にする"
|
|||
help: "ヘルプ"
|
||||
inputMessageHere: "ここにメッセージを入力"
|
||||
close: "閉じる"
|
||||
group: "グループ"
|
||||
groups: "グループ"
|
||||
createGroup: "グループを作成"
|
||||
ownedGroups: "所有グループ"
|
||||
joinedGroups: "参加しているグループ"
|
||||
invites: "招待"
|
||||
groupName: "グループ名"
|
||||
members: "メンバー"
|
||||
transfer: "譲渡"
|
||||
messagingWithUser: "ユーザーとチャット"
|
||||
messagingWithGroup: "グループでチャット"
|
||||
title: "タイトル"
|
||||
text: "テキスト"
|
||||
enable: "有効にする"
|
||||
next: "次"
|
||||
retype: "再入力"
|
||||
noteOf: "{user}のノート"
|
||||
inviteToGroup: "グループに招待"
|
||||
quoteAttached: "引用付き"
|
||||
quoteQuestion: "引用として添付しますか?"
|
||||
noMessagesYet: "まだチャットはありません"
|
||||
|
@ -456,17 +450,13 @@ passwordMatched: "一致しました"
|
|||
passwordNotMatched: "一致していません"
|
||||
signinWith: "{x}でログイン"
|
||||
signinFailed: "ログインできませんでした。ユーザー名とパスワードを確認してください。"
|
||||
tapSecurityKey: "セキュリティキーにタッチ"
|
||||
or: "もしくは"
|
||||
language: "言語"
|
||||
uiLanguage: "UIの表示言語"
|
||||
groupInvited: "グループに招待されました"
|
||||
aboutX: "{x}について"
|
||||
emojiStyle: "絵文字のスタイル"
|
||||
native: "ネイティブ"
|
||||
disableDrawer: "メニューをドロワーで表示しない"
|
||||
youHaveNoGroups: "グループがありません"
|
||||
joinOrCreateGroup: "既存のグループに招待してもらうか、新しくグループを作成してください。"
|
||||
noHistory: "履歴はありません"
|
||||
signinHistory: "ログイン履歴"
|
||||
enableAdvancedMfm: "高度なMFMを有効にする"
|
||||
|
@ -789,6 +779,7 @@ popularPosts: "人気の投稿"
|
|||
shareWithNote: "ノートで共有"
|
||||
ads: "広告"
|
||||
expiration: "期限"
|
||||
startingperiod: "開始期間"
|
||||
memo: "メモ"
|
||||
priority: "優先度"
|
||||
high: "高"
|
||||
|
@ -840,8 +831,6 @@ deleteAccountConfirm: "アカウントが削除されます。よろしいです
|
|||
incorrectPassword: "パスワードが間違っています。"
|
||||
voteConfirm: "「{choice}」に投票しますか?"
|
||||
hide: "隠す"
|
||||
leaveGroup: "グループから抜ける"
|
||||
leaveGroupConfirm: "「{name}」から抜けますか?"
|
||||
useDrawerReactionPickerForMobile: "モバイルデバイスのときドロワーで表示"
|
||||
welcomeBackWithName: "おかえりなさい、{name}さん"
|
||||
clickToFinishEmailVerification: "[{ok}]を押して、メールアドレスの確認を完了してください。"
|
||||
|
@ -957,6 +946,9 @@ collapseRenotes: "見たことのあるRenoteを省略して表示"
|
|||
internalServerError: "サーバー内部エラー"
|
||||
internalServerErrorDescription: "サーバー内部で予期しないエラーが発生しました。"
|
||||
copyErrorInfo: "エラー情報をコピー"
|
||||
joinThisServer: "このサーバーに登録する"
|
||||
exploreOtherServers: "他のサーバーを探す"
|
||||
letsLookAtTimeline: "タイムラインを見てみる"
|
||||
|
||||
_achievements:
|
||||
earnedAt: "獲得日時"
|
||||
|
@ -1532,14 +1524,29 @@ _tutorial:
|
|||
|
||||
_2fa:
|
||||
alreadyRegistered: "既に設定は完了しています。"
|
||||
registerDevice: "デバイスを登録"
|
||||
registerKey: "キーを登録"
|
||||
registerTOTP: "認証アプリの設定を開始"
|
||||
passwordToTOTP: "パスワードを入力してください"
|
||||
step1: "まず、{a}や{b}などの認証アプリをお使いのデバイスにインストールします。"
|
||||
step2: "次に、表示されているQRコードをアプリでスキャンします。"
|
||||
step2Url: "デスクトップアプリでは次のURLを入力します:"
|
||||
step3: "アプリに表示されているトークンを入力して完了です。"
|
||||
step4: "これからログインするときも、同じようにトークンを入力します。"
|
||||
securityKeyInfo: "FIDO2をサポートするハードウェアセキュリティキーもしくは端末の指紋認証やPINを使用してログインするように設定できます。"
|
||||
step2Click: "QRコードをクリックすると、お使いの端末にインストールされている認証アプリやキーリングに登録できます。"
|
||||
step2Url: "デスクトップアプリでは次のURIを入力します:"
|
||||
step3Title: "確認コードを入力"
|
||||
step3: "アプリに表示されている確認コード(トークン)を入力して完了です。"
|
||||
step4: "これからログインするときも、同じように確認コードを入力します。"
|
||||
securityKeyNotSupported: "お使いのブラウザはセキュリティキーに対応していません。"
|
||||
registerTOTPBeforeKey: "セキュリティキー・パスキーを登録するには、まず認証アプリの設定を行なってください。"
|
||||
securityKeyInfo: "FIDO2をサポートするハードウェアセキュリティキー、端末の生体認証やPINロック、パスキーといった、WebAuthn由来の鍵を登録します。"
|
||||
chromePasskeyNotSupported: "Chromeのパスキーは現在サポートしていません。"
|
||||
registerSecurityKey: "セキュリティキー・パスキーを登録する"
|
||||
securityKeyName: "キーの名前を入力"
|
||||
tapSecurityKey: "ブラウザの指示に従い、セキュリティキーやパスキーを登録してください"
|
||||
removeKey: "セキュリティキーを削除"
|
||||
removeKeyConfirm: "{name}を削除しますか?"
|
||||
whyTOTPOnlyRenew: "セキュリティキーが登録されている場合、認証アプリの設定は解除できません。"
|
||||
renewTOTP: "認証アプリを再設定"
|
||||
renewTOTPConfirm: "今までの認証アプリの確認コードは使用できなくなります"
|
||||
renewTOTPOk: "再設定する"
|
||||
renewTOTPCancel: "やめておく"
|
||||
|
||||
_permissions:
|
||||
"read:account": "アカウントの情報を見る"
|
||||
|
@ -1591,7 +1598,6 @@ _antennaSources:
|
|||
homeTimeline: "フォローしているユーザーのノート"
|
||||
users: "指定した一人または複数のユーザーのノート"
|
||||
userList: "指定したリストのユーザーのノート"
|
||||
userGroup: "指定したグループのユーザーのノート"
|
||||
|
||||
_weekday:
|
||||
sunday: "日曜日"
|
||||
|
@ -1821,12 +1827,9 @@ _notification:
|
|||
youGotReply: "{name}からのリプライ"
|
||||
youGotQuote: "{name}による引用"
|
||||
youRenoted: "{name}がRenoteしました"
|
||||
youGotMessagingMessageFromUser: "{name}からのチャットがあります"
|
||||
youGotMessagingMessageFromGroup: "{name}のチャットがあります"
|
||||
youWereFollowed: "フォローされました"
|
||||
youReceivedFollowRequest: "フォローリクエストが来ました"
|
||||
yourFollowRequestAccepted: "フォローリクエストが承認されました"
|
||||
youWereInvitedToGroup: "{userName}があなたをグループに招待しました"
|
||||
pollEnded: "アンケートの結果が出ました"
|
||||
unreadAntennaNote: "アンテナ {name}"
|
||||
emptyPushNotificationMessage: "プッシュ通知の更新をしました"
|
||||
|
@ -1843,7 +1846,6 @@ _notification:
|
|||
pollEnded: "アンケートが終了"
|
||||
receiveFollowRequest: "フォロー申請を受け取った"
|
||||
followRequestAccepted: "フォローが受理された"
|
||||
groupInvited: "グループに招待された"
|
||||
app: "連携アプリからの通知"
|
||||
|
||||
_actions:
|
||||
|
@ -1879,3 +1881,7 @@ _deck:
|
|||
channel: "チャンネル"
|
||||
mentions: "あなた宛て"
|
||||
direct: "ダイレクト"
|
||||
|
||||
_dialog:
|
||||
charactersExceeded: "最大文字数を超えています! 現在 {current} / 制限 {max}"
|
||||
charactersBelow: "最小文字数を下回っています! 現在 {current} / 制限 {min}"
|
||||
|
|
|
@ -103,6 +103,8 @@ renoted: "Renoteしたで。"
|
|||
cantRenote: "この投稿はRenoteできへんらしい。"
|
||||
cantReRenote: "Renote自体はRenoteできへんで。"
|
||||
quote: "引用"
|
||||
inChannelRenote: "チャンネル内Renote"
|
||||
inChannelQuote: "チャンネル内引用"
|
||||
pinnedNote: "ピン留めされとるノート"
|
||||
pinned: "ピン留めしとく"
|
||||
you: "あんた"
|
||||
|
@ -948,35 +950,168 @@ achievements: "実績"
|
|||
gotInvalidResponseError: "サーバー黙っとるわ、知らんけど"
|
||||
gotInvalidResponseErrorDescription: "サーバーいま日曜日。またきて月曜日。"
|
||||
thisPostMayBeAnnoying: "この投稿は迷惑かもしらんで。"
|
||||
thisPostMayBeAnnoyingHome: "ホームに投稿"
|
||||
thisPostMayBeAnnoyingCancel: "やめとく"
|
||||
thisPostMayBeAnnoyingIgnore: "このまま投稿"
|
||||
collapseRenotes: "見たことあるRenoteは省略やで"
|
||||
internalServerError: "サーバー内部エラー"
|
||||
internalServerErrorDescription: "サーバー内部でよう分からんエラーやわ"
|
||||
copyErrorInfo: "エラー情報をコピー"
|
||||
_achievements:
|
||||
earnedAt: "貰った日ぃ"
|
||||
_types:
|
||||
_notes1:
|
||||
title: "まいど!"
|
||||
description: "初めてノート投稿したった"
|
||||
flavor: "Misskeyを楽しんでな~"
|
||||
_notes10:
|
||||
title: "ノートの天保山"
|
||||
description: "ノートを10回投稿した"
|
||||
_notes100:
|
||||
title: "ノートの真田山"
|
||||
description: "ノートを100回投稿した"
|
||||
_notes500:
|
||||
title: "ノートの生駒山"
|
||||
description: "ノートを500回投稿した"
|
||||
_notes1000:
|
||||
title: "ノートの山"
|
||||
description: "ノートを1,000回投稿した"
|
||||
_notes5000:
|
||||
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:
|
||||
title: "ビギナーⅠ"
|
||||
description: "通算ログイン日数が3日"
|
||||
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:
|
||||
title: "Misskey好きやねん"
|
||||
description: "\"I ❤ #Misskey\"を投稿した"
|
||||
flavor: "Misskeyを使ってくれてありがとうな~ by 開発チーム"
|
||||
_foundTreasure:
|
||||
title: "なんでも鑑定団"
|
||||
description: "隠されたお宝を発見した"
|
||||
_client30min:
|
||||
title: "ねんね"
|
||||
description: "クライアントを起動してから30分以上経過した"
|
||||
_noteDeletedWithin1min:
|
||||
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:
|
||||
title: "マド開けすぎ"
|
||||
description: "ウィンドウを3つ以上開いた状態にした"
|
||||
_driveFolderCircularReference:
|
||||
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:
|
||||
new: "ロールの作成"
|
||||
edit: "ロールの編集"
|
||||
|
|
|
@ -1113,6 +1113,8 @@ _achievements:
|
|||
_loggedInOnNewYearsDay:
|
||||
title: "З Новим роком!"
|
||||
description: "Увійшли в перший день року"
|
||||
_cookieClicked:
|
||||
flavor: "Чекайте, це вірний сайт?"
|
||||
_brainDiver:
|
||||
title: "Brain Diver"
|
||||
description: "Відправити посилання на \"Brain Diver\""
|
||||
|
|
|
@ -103,6 +103,8 @@ renoted: "已转发。"
|
|||
cantRenote: "该帖无法转发。"
|
||||
cantReRenote: "转发无法被再次转发。"
|
||||
quote: "引用"
|
||||
inChannelRenote: "在频道内转发"
|
||||
inChannelQuote: "在频道内引用"
|
||||
pinnedNote: "已置顶的帖子"
|
||||
pinned: "置顶"
|
||||
you: "您"
|
||||
|
@ -951,6 +953,10 @@ thisPostMayBeAnnoying: "这个帖子可能会让其他人感到困扰。"
|
|||
thisPostMayBeAnnoyingHome: "发到首页"
|
||||
thisPostMayBeAnnoyingCancel: "取消"
|
||||
thisPostMayBeAnnoyingIgnore: "就这样发布"
|
||||
collapseRenotes: "省略显示已经看过的转发内容"
|
||||
internalServerError: "内部服务器错误"
|
||||
internalServerErrorDescription: "内部服务器发生了预期外的错误"
|
||||
copyErrorInfo: "复制错误信息"
|
||||
_achievements:
|
||||
earnedAt: "达成时间"
|
||||
_types:
|
||||
|
|
|
@ -103,6 +103,8 @@ renoted: "轉傳成功"
|
|||
cantRenote: "無法轉發此貼文。"
|
||||
cantReRenote: "無法轉傳之前已經轉傳過的內容。"
|
||||
quote: "引用"
|
||||
inChannelRenote: "在頻道內轉發"
|
||||
inChannelQuote: "在頻道內引用"
|
||||
pinnedNote: "已置頂的貼文"
|
||||
pinned: "置頂"
|
||||
you: "您"
|
||||
|
@ -952,6 +954,9 @@ thisPostMayBeAnnoyingHome: "發布到首頁"
|
|||
thisPostMayBeAnnoyingCancel: "退出"
|
||||
thisPostMayBeAnnoyingIgnore: "直接發布貼文"
|
||||
collapseRenotes: "省略顯示已看過的轉發貼文"
|
||||
internalServerError: "內部伺服器錯誤"
|
||||
internalServerErrorDescription: "內部伺服器發生了非預期的錯誤。"
|
||||
copyErrorInfo: "複製錯誤資訊"
|
||||
_achievements:
|
||||
earnedAt: "獲得日期"
|
||||
_types:
|
||||
|
|
12
package.json
12
package.json
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "misskey",
|
||||
"version": "13.6.1",
|
||||
"version": "13.7.0",
|
||||
"codename": "nasubi",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
@ -54,12 +54,12 @@
|
|||
"devDependencies": {
|
||||
"@types/gulp": "4.0.10",
|
||||
"@types/gulp-rename": "2.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "5.51.0",
|
||||
"@typescript-eslint/parser": "5.51.0",
|
||||
"@typescript-eslint/eslint-plugin": "5.52.0",
|
||||
"@typescript-eslint/parser": "5.52.0",
|
||||
"cross-env": "7.0.3",
|
||||
"cypress": "12.5.1",
|
||||
"eslint": "8.33.0",
|
||||
"start-server-and-test": "1.15.3"
|
||||
"cypress": "12.6.0",
|
||||
"eslint": "8.34.0",
|
||||
"start-server-and-test": "1.15.4"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@tensorflow/tfjs-core": "4.2.0"
|
||||
|
|
|
@ -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;
|
|
@ -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
|
||||
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',
|
||||
},
|
||||
|
||||
|
@ -112,7 +119,7 @@ module.exports = {
|
|||
// resetModules: false,
|
||||
|
||||
// A path to a custom resolver
|
||||
resolver: './jest-resolver.cjs',
|
||||
// resolver: './jest-resolver.cjs',
|
||||
|
||||
// Automatically restore mock state between every test
|
||||
restoreMocks: true,
|
||||
|
|
35
packages/backend/migration/1676434944993-drop-group.js
Normal file
35
packages/backend/migration/1676434944993-drop-group.js
Normal 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`);
|
||||
}
|
||||
}
|
9
packages/backend/migration/1676438468213-ad3.js
Normal file
9
packages/backend/migration/1676438468213-ad3.js
Normal 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"`);
|
||||
}
|
||||
}
|
|
@ -11,7 +11,9 @@
|
|||
"watch:swc": "swc src -d built -D -w",
|
||||
"build": "tsc -p tsconfig.json || echo done. && tsc-alias -p tsconfig.json",
|
||||
"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-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",
|
||||
|
@ -23,30 +25,30 @@
|
|||
"@tensorflow/tfjs-node": "4.2.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@bull-board/api": "4.11.1",
|
||||
"@bull-board/fastify": "4.11.1",
|
||||
"@bull-board/ui": "4.11.1",
|
||||
"@bull-board/api": "4.12.1",
|
||||
"@bull-board/fastify": "4.12.1",
|
||||
"@bull-board/ui": "4.12.1",
|
||||
"@discordapp/twemoji": "14.0.2",
|
||||
"@fastify/accepts": "4.1.0",
|
||||
"@fastify/cookie": "8.3.0",
|
||||
"@fastify/cors": "8.2.0",
|
||||
"@fastify/http-proxy": "8.4.0",
|
||||
"@fastify/multipart": "7.4.0",
|
||||
"@fastify/static": "6.8.0",
|
||||
"@fastify/multipart": "7.4.1",
|
||||
"@fastify/static": "6.9.0",
|
||||
"@fastify/view": "7.4.1",
|
||||
"@nestjs/common": "9.3.7",
|
||||
"@nestjs/core": "9.3.7",
|
||||
"@nestjs/testing": "9.3.7",
|
||||
"@nestjs/common": "9.3.9",
|
||||
"@nestjs/core": "9.3.9",
|
||||
"@nestjs/testing": "9.3.9",
|
||||
"@peertube/http-signature": "1.7.0",
|
||||
"@sinonjs/fake-timers": "10.0.2",
|
||||
"accepts": "1.3.8",
|
||||
"ajv": "8.12.0",
|
||||
"archiver": "5.3.1",
|
||||
"autwh": "0.1.0",
|
||||
"aws-sdk": "2.1295.0",
|
||||
"aws-sdk": "2.1318.0",
|
||||
"bcryptjs": "2.4.3",
|
||||
"blurhash": "2.0.4",
|
||||
"bull": "4.10.3",
|
||||
"blurhash": "2.0.5",
|
||||
"bull": "4.10.4",
|
||||
"cacheable-lookup": "6.1.0",
|
||||
"cbor": "8.1.0",
|
||||
"chalk": "5.2.0",
|
||||
|
@ -58,12 +60,13 @@
|
|||
"date-fns": "2.29.3",
|
||||
"deep-email-validator": "0.1.21",
|
||||
"escape-regexp": "0.0.1",
|
||||
"fastify": "4.12.0",
|
||||
"fastify": "4.13.0",
|
||||
"feed": "4.2.2",
|
||||
"file-type": "18.2.0",
|
||||
"file-type": "18.2.1",
|
||||
"fluent-ffmpeg": "2.1.2",
|
||||
"form-data": "4.0.0",
|
||||
"got": "12.5.3",
|
||||
"happy-dom": "^8.7.0",
|
||||
"hpagent": "1.2.0",
|
||||
"ioredis": "4.28.5",
|
||||
"ip-cidr": "3.1.0",
|
||||
|
@ -83,6 +86,7 @@
|
|||
"nsfwjs": "2.4.2",
|
||||
"oauth": "0.10.0",
|
||||
"os-utils": "0.0.14",
|
||||
"otpauth": "^9.0.2",
|
||||
"parse5": "7.1.2",
|
||||
"pg": "8.9.0",
|
||||
"private-ip": "3.0.0",
|
||||
|
@ -102,15 +106,14 @@
|
|||
"rss-parser": "3.12.0",
|
||||
"rxjs": "7.8.0",
|
||||
"s-age": "1.1.2",
|
||||
"sanitize-html": "2.9.0",
|
||||
"sanitize-html": "2.10.0",
|
||||
"seedrandom": "3.0.5",
|
||||
"semver": "7.3.8",
|
||||
"sharp": "0.31.3",
|
||||
"speakeasy": "2.0.0",
|
||||
"strict-event-emitter-types": "2.0.0",
|
||||
"stringz": "2.1.0",
|
||||
"summaly": "2.7.0",
|
||||
"systeminformation": "5.17.8",
|
||||
"summaly": "github:misskey-dev/summaly",
|
||||
"systeminformation": "5.17.9",
|
||||
"tinycolor2": "1.6.0",
|
||||
"tmp": "0.2.1",
|
||||
"tsc-alias": "1.8.2",
|
||||
|
@ -124,14 +127,14 @@
|
|||
"vary": "1.1.2",
|
||||
"web-push": "3.5.0",
|
||||
"websocket": "1.0.34",
|
||||
"ws": "8.12.0",
|
||||
"ws": "8.12.1",
|
||||
"xev": "3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@jest/globals": "29.4.2",
|
||||
"@jest/globals": "29.4.3",
|
||||
"@redocly/openapi-core": "1.0.0-beta.123",
|
||||
"@swc/cli": "0.1.61",
|
||||
"@swc/core": "1.3.34",
|
||||
"@swc/cli": "0.1.62",
|
||||
"@swc/core": "1.3.35",
|
||||
"@swc/jest": "0.2.24",
|
||||
"@types/accepts": "1.3.5",
|
||||
"@types/archiver": "5.3.1",
|
||||
|
@ -149,7 +152,7 @@
|
|||
"@types/jsonld": "1.5.8",
|
||||
"@types/jsrsasign": "10.5.5",
|
||||
"@types/mime-types": "2.1.1",
|
||||
"@types/node": "18.13.0",
|
||||
"@types/node": "18.14.0",
|
||||
"@types/node-fetch": "3.0.3",
|
||||
"@types/nodemailer": "6.4.7",
|
||||
"@types/oauth": "0.9.1",
|
||||
|
@ -165,7 +168,6 @@
|
|||
"@types/semver": "7.3.13",
|
||||
"@types/sharp": "0.31.1",
|
||||
"@types/sinonjs__fake-timers": "8.1.2",
|
||||
"@types/speakeasy": "2.0.7",
|
||||
"@types/tinycolor2": "1.4.3",
|
||||
"@types/tmp": "0.2.3",
|
||||
"@types/unzipper": "0.10.5",
|
||||
|
@ -174,13 +176,13 @@
|
|||
"@types/web-push": "3.3.2",
|
||||
"@types/websocket": "1.0.5",
|
||||
"@types/ws": "8.5.4",
|
||||
"@typescript-eslint/eslint-plugin": "5.51.0",
|
||||
"@typescript-eslint/parser": "5.51.0",
|
||||
"@typescript-eslint/eslint-plugin": "5.52.0",
|
||||
"@typescript-eslint/parser": "5.52.0",
|
||||
"cross-env": "7.0.3",
|
||||
"eslint": "8.33.0",
|
||||
"eslint": "8.34.0",
|
||||
"eslint-plugin-import": "2.27.5",
|
||||
"execa": "6.1.0",
|
||||
"jest": "29.4.2",
|
||||
"jest-mock": "29.4.2"
|
||||
"jest": "29.4.3",
|
||||
"jest-mock": "29.4.3"
|
||||
}
|
||||
}
|
||||
|
|
8
packages/backend/src/@types/redis-lock.d.ts
vendored
Normal file
8
packages/backend/src/@types/redis-lock.d.ts
vendored
Normal 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;
|
||||
}
|
|
@ -32,7 +32,7 @@ export class AccountUpdateService {
|
|||
|
||||
// フォロワーがリモートユーザーかつ投稿者がローカルユーザーならUpdateを配信
|
||||
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.relayService.deliverToRelays(user, content);
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ import { PushNotificationService } from '@/core/PushNotificationService.js';
|
|||
import * as Acct from '@/misc/acct.js';
|
||||
import type { Packed } from '@/misc/schema.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 { bindThis } from '@/decorators.js';
|
||||
import { StreamMessages } from '@/server/api/stream/types.js';
|
||||
|
@ -39,9 +39,6 @@ export class AntennaService implements OnApplicationShutdown {
|
|||
@Inject(DI.antennasRepository)
|
||||
private antennasRepository: AntennasRepository,
|
||||
|
||||
@Inject(DI.userGroupJoiningsRepository)
|
||||
private userGroupJoiningsRepository: UserGroupJoiningsRepository,
|
||||
|
||||
@Inject(DI.userListJoiningsRepository)
|
||||
private userListJoiningsRepository: UserListJoiningsRepository,
|
||||
|
||||
|
@ -160,14 +157,6 @@ export class AntennaService implements OnApplicationShutdown {
|
|||
})).map(x => x.userId);
|
||||
|
||||
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') {
|
||||
const accts = antenna.users.map(x => {
|
||||
const { username, host } = Acct.parse(x);
|
||||
|
|
|
@ -12,7 +12,7 @@ const retryDelay = 100;
|
|||
|
||||
@Injectable()
|
||||
export class AppLockService {
|
||||
private lock: (key: string, timeout?: number) => Promise<() => void>;
|
||||
private lock: (key: string, timeout?: number, _?: (() => Promise<void>) | undefined) => Promise<() => void>;
|
||||
|
||||
constructor(
|
||||
@Inject(DI.redis)
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { Module } from '@nestjs/common';
|
||||
import { DI } from '../di-symbols.js';
|
||||
import { AccountUpdateService } from './AccountUpdateService.js';
|
||||
import { AiService } from './AiService.js';
|
||||
import { AntennaService } from './AntennaService.js';
|
||||
|
@ -22,7 +21,6 @@ import { IdService } from './IdService.js';
|
|||
import { ImageProcessingService } from './ImageProcessingService.js';
|
||||
import { InstanceActorService } from './InstanceActorService.js';
|
||||
import { InternalStorageService } from './InternalStorageService.js';
|
||||
import { MessagingService } from './MessagingService.js';
|
||||
import { MetaService } from './MetaService.js';
|
||||
import { MfmService } from './MfmService.js';
|
||||
import { ModerationLogService } from './ModerationLogService.js';
|
||||
|
@ -82,7 +80,6 @@ import { GalleryLikeEntityService } from './entities/GalleryLikeEntityService.js
|
|||
import { GalleryPostEntityService } from './entities/GalleryPostEntityService.js';
|
||||
import { HashtagEntityService } from './entities/HashtagEntityService.js';
|
||||
import { InstanceEntityService } from './entities/InstanceEntityService.js';
|
||||
import { MessagingMessageEntityService } from './entities/MessagingMessageEntityService.js';
|
||||
import { ModerationLogEntityService } from './entities/ModerationLogEntityService.js';
|
||||
import { MutingEntityService } from './entities/MutingEntityService.js';
|
||||
import { NoteEntityService } from './entities/NoteEntityService.js';
|
||||
|
@ -93,8 +90,6 @@ import { PageEntityService } from './entities/PageEntityService.js';
|
|||
import { PageLikeEntityService } from './entities/PageLikeEntityService.js';
|
||||
import { SigninEntityService } from './entities/SigninEntityService.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 { FlashEntityService } from './entities/FlashEntityService.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 $InstanceActorService: Provider = { provide: 'InstanceActorService', useExisting: InstanceActorService };
|
||||
const $InternalStorageService: Provider = { provide: 'InternalStorageService', useExisting: InternalStorageService };
|
||||
const $MessagingService: Provider = { provide: 'MessagingService', useExisting: MessagingService };
|
||||
const $MetaService: Provider = { provide: 'MetaService', useExisting: MetaService };
|
||||
const $MfmService: Provider = { provide: 'MfmService', useExisting: MfmService };
|
||||
const $ModerationLogService: Provider = { provide: 'ModerationLogService', useExisting: ModerationLogService };
|
||||
|
@ -207,7 +201,6 @@ const $GalleryLikeEntityService: Provider = { provide: 'GalleryLikeEntityService
|
|||
const $GalleryPostEntityService: Provider = { provide: 'GalleryPostEntityService', useExisting: GalleryPostEntityService };
|
||||
const $HashtagEntityService: Provider = { provide: 'HashtagEntityService', useExisting: HashtagEntityService };
|
||||
const $InstanceEntityService: Provider = { provide: 'InstanceEntityService', useExisting: InstanceEntityService };
|
||||
const $MessagingMessageEntityService: Provider = { provide: 'MessagingMessageEntityService', useExisting: MessagingMessageEntityService };
|
||||
const $ModerationLogEntityService: Provider = { provide: 'ModerationLogEntityService', useExisting: ModerationLogEntityService };
|
||||
const $MutingEntityService: Provider = { provide: 'MutingEntityService', useExisting: MutingEntityService };
|
||||
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 $SigninEntityService: Provider = { provide: 'SigninEntityService', useExisting: SigninEntityService };
|
||||
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 $FlashEntityService: Provider = { provide: 'FlashEntityService', useExisting: FlashEntityService };
|
||||
const $FlashLikeEntityService: Provider = { provide: 'FlashLikeEntityService', useExisting: FlashLikeEntityService };
|
||||
|
@ -273,7 +264,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
ImageProcessingService,
|
||||
InstanceActorService,
|
||||
InternalStorageService,
|
||||
MessagingService,
|
||||
MetaService,
|
||||
MfmService,
|
||||
ModerationLogService,
|
||||
|
@ -333,7 +323,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
GalleryPostEntityService,
|
||||
HashtagEntityService,
|
||||
InstanceEntityService,
|
||||
MessagingMessageEntityService,
|
||||
ModerationLogEntityService,
|
||||
MutingEntityService,
|
||||
NoteEntityService,
|
||||
|
@ -344,8 +333,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
PageLikeEntityService,
|
||||
SigninEntityService,
|
||||
UserEntityService,
|
||||
UserGroupEntityService,
|
||||
UserGroupInvitationEntityService,
|
||||
UserListEntityService,
|
||||
FlashEntityService,
|
||||
FlashLikeEntityService,
|
||||
|
@ -394,7 +381,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
$ImageProcessingService,
|
||||
$InstanceActorService,
|
||||
$InternalStorageService,
|
||||
$MessagingService,
|
||||
$MetaService,
|
||||
$MfmService,
|
||||
$ModerationLogService,
|
||||
|
@ -454,7 +440,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
$GalleryPostEntityService,
|
||||
$HashtagEntityService,
|
||||
$InstanceEntityService,
|
||||
$MessagingMessageEntityService,
|
||||
$ModerationLogEntityService,
|
||||
$MutingEntityService,
|
||||
$NoteEntityService,
|
||||
|
@ -465,8 +450,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
$PageLikeEntityService,
|
||||
$SigninEntityService,
|
||||
$UserEntityService,
|
||||
$UserGroupEntityService,
|
||||
$UserGroupInvitationEntityService,
|
||||
$UserListEntityService,
|
||||
$FlashEntityService,
|
||||
$FlashLikeEntityService,
|
||||
|
@ -516,7 +499,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
ImageProcessingService,
|
||||
InstanceActorService,
|
||||
InternalStorageService,
|
||||
MessagingService,
|
||||
MetaService,
|
||||
MfmService,
|
||||
ModerationLogService,
|
||||
|
@ -575,7 +557,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
GalleryPostEntityService,
|
||||
HashtagEntityService,
|
||||
InstanceEntityService,
|
||||
MessagingMessageEntityService,
|
||||
ModerationLogEntityService,
|
||||
MutingEntityService,
|
||||
NoteEntityService,
|
||||
|
@ -586,8 +567,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
PageLikeEntityService,
|
||||
SigninEntityService,
|
||||
UserEntityService,
|
||||
UserGroupEntityService,
|
||||
UserGroupInvitationEntityService,
|
||||
UserListEntityService,
|
||||
FlashEntityService,
|
||||
FlashLikeEntityService,
|
||||
|
@ -636,7 +615,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
$ImageProcessingService,
|
||||
$InstanceActorService,
|
||||
$InternalStorageService,
|
||||
$MessagingService,
|
||||
$MetaService,
|
||||
$MfmService,
|
||||
$ModerationLogService,
|
||||
|
@ -695,7 +673,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
$GalleryPostEntityService,
|
||||
$HashtagEntityService,
|
||||
$InstanceEntityService,
|
||||
$MessagingMessageEntityService,
|
||||
$ModerationLogEntityService,
|
||||
$MutingEntityService,
|
||||
$NoteEntityService,
|
||||
|
@ -706,8 +683,6 @@ const $ApQuestionService: Provider = { provide: 'ApQuestionService', useExisting
|
|||
$PageLikeEntityService,
|
||||
$SigninEntityService,
|
||||
$UserEntityService,
|
||||
$UserGroupEntityService,
|
||||
$UserGroupInvitationEntityService,
|
||||
$UserListEntityService,
|
||||
$FlashEntityService,
|
||||
$FlashLikeEntityService,
|
||||
|
|
|
@ -61,7 +61,7 @@ export class CustomEmojiService {
|
|||
await this.db.queryResultCache!.remove(['meta_emojis']);
|
||||
|
||||
this.globalEventService.publishBroadcastStream('emojiAdded', {
|
||||
emoji: await this.emojiEntityService.pack(emoji.id),
|
||||
emoji: await this.emojiEntityService.packDetailed(emoji.id),
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@ import { DI } from '@/di-symbols.js';
|
|||
import type { DriveFilesRepository, UsersRepository, DriveFoldersRepository, UserProfilesRepository } from '@/models/index.js';
|
||||
import type { Config } from '@/config.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 { DriveFile } from '@/models/entities/DriveFile.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
|
@ -255,7 +255,7 @@ export class DriveService {
|
|||
return {
|
||||
webpublic: null,
|
||||
thumbnail: null,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -399,7 +399,7 @@ export class DriveService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
private async deleteOldFile(user: IRemoteUser) {
|
||||
private async deleteOldFile(user: RemoteUser) {
|
||||
const q = this.driveFilesRepository.createQueryBuilder('file')
|
||||
.where('file.userId = :userId', { userId: user.id })
|
||||
.andWhere('file.isLink = FALSE');
|
||||
|
@ -500,7 +500,7 @@ export class DriveService {
|
|||
throw new IdentifiableError('c6244ed2-a39a-4e1c-bf93-f0fbd7764fa6', 'No free space.');
|
||||
} else {
|
||||
// (アバターまたはバナーを含まず)最も古いファイルを削除する
|
||||
this.deleteOldFile(await this.usersRepository.findOneByOrFail({ id: user.id }) as IRemoteUser);
|
||||
this.deleteOldFile(await this.usersRepository.findOneByOrFail({ id: user.id }) as RemoteUser);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,6 @@ import { URL } from 'node:url';
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import tinycolor from 'tinycolor2';
|
||||
import fetch from 'node-fetch';
|
||||
import type { Instance } from '@/models/entities/Instance.js';
|
||||
import type { InstancesRepository } from '@/models/index.js';
|
||||
import { AppLockService } from '@/core/AppLockService.js';
|
||||
|
|
|
@ -3,7 +3,7 @@ import * as crypto from 'node:crypto';
|
|||
import { join } from 'node:path';
|
||||
import * as stream from 'node:stream';
|
||||
import * as util from 'node:util';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { FSWatcher } from 'chokidar';
|
||||
import { fileTypeFromFile } from 'file-type';
|
||||
import FFmpeg from 'fluent-ffmpeg';
|
||||
|
|
|
@ -3,21 +3,15 @@ import Redis from 'ioredis';
|
|||
import type { User } from '@/models/entities/User.js';
|
||||
import type { Note } from '@/models/entities/Note.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 { Channel } from '@/models/entities/Channel.js';
|
||||
import type {
|
||||
StreamChannels,
|
||||
AdminStreamTypes,
|
||||
AntennaStreamTypes,
|
||||
BroadcastTypes,
|
||||
ChannelStreamTypes,
|
||||
DriveStreamTypes,
|
||||
GroupMessagingStreamTypes,
|
||||
InternalStreamTypes,
|
||||
MainStreamTypes,
|
||||
MessagingIndexStreamTypes,
|
||||
MessagingStreamTypes,
|
||||
NoteStreamTypes,
|
||||
UserListStreamTypes,
|
||||
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
|
||||
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);
|
||||
|
@ -98,21 +87,6 @@ export class GlobalEventService {
|
|||
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
|
||||
public publishNotesStream(note: Packed<'Note'>): void {
|
||||
this.publish('notesStream', null, note);
|
||||
|
|
|
@ -99,7 +99,6 @@ export class HttpRequestService {
|
|||
const res = await this.send(url, {
|
||||
method: 'GET',
|
||||
headers: Object.assign({
|
||||
'User-Agent': this.config.userAgent,
|
||||
Accept: accept,
|
||||
}, headers ?? {}),
|
||||
timeout: 5000,
|
||||
|
@ -114,7 +113,6 @@ export class HttpRequestService {
|
|||
const res = await this.send(url, {
|
||||
method: 'GET',
|
||||
headers: Object.assign({
|
||||
'User-Agent': this.config.userAgent,
|
||||
Accept: accept,
|
||||
}, headers ?? {}),
|
||||
timeout: 5000,
|
||||
|
@ -144,7 +142,10 @@ export class HttpRequestService {
|
|||
|
||||
const res = await fetch(url, {
|
||||
method: args.method ?? 'GET',
|
||||
headers: args.headers,
|
||||
headers: {
|
||||
'User-Agent': this.config.userAgent,
|
||||
...(args.headers ?? {})
|
||||
},
|
||||
body: args.body,
|
||||
size: args.size ?? 10 * 1024 * 1024,
|
||||
agent: (url) => this.getAgentByUrl(url),
|
||||
|
|
|
@ -107,7 +107,7 @@ export class ImageProcessingService {
|
|||
withoutEnlargement: true,
|
||||
})
|
||||
.rotate()
|
||||
.webp(options)
|
||||
.webp(options);
|
||||
|
||||
return {
|
||||
data,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
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 { Cache } from '@/misc/cache.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
@ -11,7 +11,7 @@ const ACTOR_USERNAME = 'instance.actor' as const;
|
|||
|
||||
@Injectable()
|
||||
export class InstanceActorService {
|
||||
private cache: Cache<ILocalUser>;
|
||||
private cache: Cache<LocalUser>;
|
||||
|
||||
constructor(
|
||||
@Inject(DI.usersRepository)
|
||||
|
@ -19,24 +19,24 @@ export class InstanceActorService {
|
|||
|
||||
private createSystemUserService: CreateSystemUserService,
|
||||
) {
|
||||
this.cache = new Cache<ILocalUser>(Infinity);
|
||||
this.cache = new Cache<LocalUser>(Infinity);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async getInstanceActor(): Promise<ILocalUser> {
|
||||
public async getInstanceActor(): Promise<LocalUser> {
|
||||
const cached = this.cache.get(null);
|
||||
if (cached) return cached;
|
||||
|
||||
const user = await this.usersRepository.findOneBy({
|
||||
host: IsNull(),
|
||||
username: ACTOR_USERNAME,
|
||||
}) as ILocalUser | undefined;
|
||||
}) as LocalUser | undefined;
|
||||
|
||||
if (user) {
|
||||
this.cache.set(null, user);
|
||||
return user;
|
||||
} 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);
|
||||
return created;
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,9 +1,8 @@
|
|||
import { URL } from 'node:url';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import * as parse5 from 'parse5';
|
||||
import { JSDOM } from 'jsdom';
|
||||
import { Window } from 'happy-dom';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { UsersRepository } from '@/models/index.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import { intersperse } from '@/misc/prelude/array.js';
|
||||
import type { IMentionedRemoteUsers } from '@/models/entities/Note.js';
|
||||
|
@ -236,7 +235,7 @@ export class MfmService {
|
|||
return null;
|
||||
}
|
||||
|
||||
const { window } = new JSDOM('');
|
||||
const { window } = new Window();
|
||||
|
||||
const doc = window.document;
|
||||
|
||||
|
@ -301,7 +300,7 @@ export class MfmService {
|
|||
|
||||
hashtag: (node) => {
|
||||
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.setAttribute('rel', 'tag');
|
||||
return a;
|
||||
|
@ -327,7 +326,7 @@ export class MfmService {
|
|||
|
||||
link: (node) => {
|
||||
const a = doc.createElement('a');
|
||||
a.href = node.props.url;
|
||||
a.setAttribute('href', node.props.url);
|
||||
appendChildren(node.children, a);
|
||||
return a;
|
||||
},
|
||||
|
@ -336,7 +335,7 @@ export class MfmService {
|
|||
const a = doc.createElement('a');
|
||||
const { username, host, acct } = node.props;
|
||||
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.textContent = acct;
|
||||
return a;
|
||||
|
@ -361,14 +360,14 @@ export class MfmService {
|
|||
|
||||
url: (node) => {
|
||||
const a = doc.createElement('a');
|
||||
a.href = node.props.url;
|
||||
a.setAttribute('href', node.props.url);
|
||||
a.textContent = node.props.url;
|
||||
return a;
|
||||
},
|
||||
|
||||
search: (node) => {
|
||||
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;
|
||||
return a;
|
||||
},
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import * as mfm from 'mfm-js';
|
||||
import { Not, In, DataSource } from 'typeorm';
|
||||
import { In, DataSource } from 'typeorm';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { extractMentions } from '@/misc/extract-mentions.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 { concat } from '@/misc/prelude/array.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 { Poll } from '@/models/entities/Poll.js';
|
||||
import { isDuplicateKeyValueError } from '@/misc/is-duplicate-key-value-error.js';
|
||||
|
@ -52,7 +52,7 @@ class NotificationManager {
|
|||
private notifier: { id: User['id']; };
|
||||
private note: Note;
|
||||
private queue: {
|
||||
target: ILocalUser['id'];
|
||||
target: LocalUser['id'];
|
||||
reason: NotificationType;
|
||||
}[];
|
||||
|
||||
|
@ -68,7 +68,7 @@ class NotificationManager {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public push(notifiee: ILocalUser['id'], reason: NotificationType) {
|
||||
public push(notifiee: LocalUser['id'], reason: NotificationType) {
|
||||
// 自分自身へは通知しない
|
||||
if (this.notifier.id === notifiee) return;
|
||||
|
||||
|
@ -605,7 +605,7 @@ export class NoteCreateService {
|
|||
|
||||
// メンションされたリモートユーザーに配送
|
||||
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.renderCreate(await this.apRendererService.renderNote(note, false), note);
|
||||
|
||||
return this.apRendererService.renderActivity(content);
|
||||
return this.apRendererService.addContext(content);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Brackets, In } from 'typeorm';
|
||||
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 { InstancesRepository, NotesRepository, UsersRepository } from '@/models/index.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.renderDelete(this.apRendererService.renderTombstone(`${this.config.url}/notes/${note.id}`), user));
|
||||
|
||||
|
@ -90,7 +90,7 @@ export class NoteDeleteService {
|
|||
for (const cascadingNote of cascadingNotes) {
|
||||
if (!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);
|
||||
}
|
||||
//#endregion
|
||||
|
@ -159,11 +159,11 @@ export class NoteDeleteService {
|
|||
|
||||
return await this.usersRepository.find({
|
||||
where,
|
||||
}) as IRemoteUser[];
|
||||
}) as RemoteUser[];
|
||||
}
|
||||
|
||||
@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.relayService.deliverToRelays(user, content);
|
||||
const remoteUsers = await this.getMentionedRemoteUsers(note);
|
||||
|
|
|
@ -115,7 +115,7 @@ export class NotePiningService {
|
|||
|
||||
const target = `${this.config.url}/users/${user.id}/collections/featured`;
|
||||
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.relayService.deliverToRelays(user, content);
|
||||
|
|
|
@ -2,13 +2,12 @@ import { Inject, Injectable } from '@nestjs/common';
|
|||
import { In } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.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 { Notification } from '@/models/entities/Notification.js';
|
||||
import { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { GlobalEventService } from './GlobalEventService.js';
|
||||
import { PushNotificationService } from './PushNotificationService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
||||
@Injectable()
|
||||
export class NotificationService {
|
||||
|
@ -66,7 +65,6 @@ export class NotificationService {
|
|||
|
||||
@bindThis
|
||||
private postReadNotifications(userId: User['id'], notificationIds: Notification['id'][]) {
|
||||
this.globalEventService.publishMainStream(userId, 'readNotifications', notificationIds);
|
||||
return this.pushNotificationService.pushNotification(userId, 'readNotifications', { notificationIds });
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Not } from 'typeorm';
|
||||
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 { RelayService } from '@/core/RelayService.js';
|
||||
import type { CacheableUser } from '@/models/entities/User.js';
|
||||
import { IdService } from '@/core/IdService.js';
|
||||
import { GlobalEventService } from '@/core/GlobalEventService.js';
|
||||
import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
|
||||
|
@ -39,7 +37,7 @@ export class PollService {
|
|||
}
|
||||
|
||||
@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 });
|
||||
|
||||
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 (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.relayService.deliverToRelays(user, content);
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
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 { MetaService } from '@/core/MetaService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
@ -16,9 +16,9 @@ export class ProxyAccountService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public async fetch(): Promise<ILocalUser | null> {
|
||||
public async fetch(): Promise<LocalUser | null> {
|
||||
const meta = await this.metaService.fetch();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,24 +9,21 @@ import { MetaService } from '@/core/MetaService.js';
|
|||
import { bindThis } from '@/decorators.js';
|
||||
|
||||
// Defined also packages/sw/types.ts#L13
|
||||
type pushNotificationsTypes = {
|
||||
type PushNotificationsTypes = {
|
||||
'notification': Packed<'Notification'>;
|
||||
'unreadMessagingMessage': Packed<'MessagingMessage'>;
|
||||
'unreadAntennaNote': {
|
||||
antenna: { id: string, name: string };
|
||||
note: Packed<'Note'>;
|
||||
};
|
||||
'readNotifications': { notificationIds: string[] };
|
||||
'readAllNotifications': undefined;
|
||||
'readAllMessagingMessages': undefined;
|
||||
'readAllMessagingMessagesOfARoom': { userId: string } | { groupId: string };
|
||||
'readAntenna': { antennaId: string };
|
||||
'readAllAntennas': undefined;
|
||||
};
|
||||
|
||||
// Reduce length because push message servers have character limits
|
||||
function truncateBody<T extends keyof pushNotificationsTypes>(type: T, body: pushNotificationsTypes[T]): pushNotificationsTypes[T] {
|
||||
if (body === undefined) return body;
|
||||
function truncateBody<T extends keyof PushNotificationsTypes>(type: T, body: PushNotificationsTypes[T]): PushNotificationsTypes[T] {
|
||||
if (typeof body !== 'object') return body;
|
||||
|
||||
return {
|
||||
...body,
|
||||
|
@ -40,11 +37,9 @@ function truncateBody<T extends keyof pushNotificationsTypes>(type: T, body: pus
|
|||
reply: undefined,
|
||||
renote: undefined,
|
||||
user: type === 'notification' ? undefined as any : body.note.user,
|
||||
}
|
||||
},
|
||||
} : {}),
|
||||
};
|
||||
|
||||
return body;
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
|
@ -61,7 +56,7 @@ export class PushNotificationService {
|
|||
}
|
||||
|
||||
@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();
|
||||
|
||||
if (!meta.enableServiceWorker || meta.swPublicKey == null || meta.swPrivateKey == null) return;
|
||||
|
@ -81,8 +76,6 @@ export class PushNotificationService {
|
|||
if ([
|
||||
'readNotifications',
|
||||
'readAllNotifications',
|
||||
'readAllMessagingMessages',
|
||||
'readAllMessagingMessagesOfARoom',
|
||||
'readAntenna',
|
||||
'readAllAntennas',
|
||||
].includes(type) && !subscription.sendReadMessage) continue;
|
||||
|
|
|
@ -3,7 +3,7 @@ import { IsNull } from 'typeorm';
|
|||
import { DI } from '@/di-symbols.js';
|
||||
import type { EmojisRepository, BlockingsRepository, NoteReactionsRepository, UsersRepository, NotesRepository } from '@/models/index.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 { IdService } from '@/core/IdService.js';
|
||||
import type { NoteReaction } from '@/models/entities/NoteReaction.js';
|
||||
|
@ -85,7 +85,7 @@ export class ReactionService {
|
|||
}
|
||||
|
||||
@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
|
||||
if (note.userId !== user.id) {
|
||||
const blocked = await this.userBlockingService.checkBlocked(note.userId, user.id);
|
||||
|
@ -177,11 +177,11 @@ export class ReactionService {
|
|||
|
||||
//#region 配信
|
||||
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);
|
||||
if (note.userHost !== null) {
|
||||
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)) {
|
||||
|
@ -189,7 +189,7 @@ export class ReactionService {
|
|||
} else if (note.visibility === 'specified') {
|
||||
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))) {
|
||||
dm.addDirectRecipe(u as IRemoteUser);
|
||||
dm.addDirectRecipe(u as RemoteUser);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -235,11 +235,11 @@ export class ReactionService {
|
|||
|
||||
//#region 配信
|
||||
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);
|
||||
if (note.userHost !== null) {
|
||||
const reactee = await this.usersRepository.findOneBy({ id: note.userId });
|
||||
dm.addDirectRecipe(reactee as IRemoteUser);
|
||||
dm.addDirectRecipe(reactee as RemoteUser);
|
||||
}
|
||||
dm.addFollowersRecipe();
|
||||
dm.execute();
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
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 { IdService } from '@/core/IdService.js';
|
||||
import { Cache } from '@/misc/cache.js';
|
||||
|
@ -34,16 +34,16 @@ export class RelayService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
private async getRelayActor(): Promise<ILocalUser> {
|
||||
private async getRelayActor(): Promise<LocalUser> {
|
||||
const user = await this.usersRepository.findOneBy({
|
||||
host: IsNull(),
|
||||
username: ACTOR_USERNAME,
|
||||
});
|
||||
|
||||
if (user) return user as ILocalUser;
|
||||
if (user) return user as LocalUser;
|
||||
|
||||
const created = await this.createSystemUserService.createSystemUser(ACTOR_USERNAME);
|
||||
return created as ILocalUser;
|
||||
return created as LocalUser;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
@ -56,7 +56,7 @@ export class RelayService {
|
|||
|
||||
const relayActor = await this.getRelayActor();
|
||||
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);
|
||||
|
||||
return relay;
|
||||
|
@ -75,7 +75,7 @@ export class RelayService {
|
|||
const relayActor = await this.getRelayActor();
|
||||
const follow = this.apRendererService.renderFollowRelay(relay, 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);
|
||||
|
||||
await this.relaysRepository.delete(relay.id);
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import type Logger from '@/logger.js';
|
||||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
||||
@Injectable()
|
||||
export class RemoteLoggerService {
|
||||
|
|
|
@ -4,7 +4,7 @@ import chalk from 'chalk';
|
|||
import { IsNull } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.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 Logger from '@/logger.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}`;
|
||||
|
||||
|
@ -82,7 +82,7 @@ export class RemoteUserResolveService {
|
|||
const self = await this.resolveSelf(acctLower);
|
||||
|
||||
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(`recovery missmatch uri for (username=${username}, host=${host}) from ${user.uri} to ${self.href}`);
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import Redis from 'ioredis';
|
|||
import { In } from 'typeorm';
|
||||
import type { Role, RoleAssignment, RoleAssignmentsRepository, RolesRepository, UsersRepository } from '@/models/index.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 { bindThis } from '@/decorators.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
import { Inject, Injectable, OnApplicationShutdown } from '@nestjs/common';
|
||||
import Redis from 'ioredis';
|
||||
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 { QueueService } from '@/core/QueueService.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)) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -162,13 +162,13 @@ export class UserBlockingService implements OnApplicationShutdown {
|
|||
|
||||
// リモートにフォローリクエストをしていたらUndoFollow送信
|
||||
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);
|
||||
}
|
||||
|
||||
// リモートからフォローリクエストを受けていたらReject送信
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -210,13 +210,13 @@ export class UserBlockingService implements OnApplicationShutdown {
|
|||
|
||||
// リモートにフォローをしていたらUndoFollow送信
|
||||
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);
|
||||
}
|
||||
|
||||
// リモートからフォローをされていたらRejectFollow送信
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -236,7 +236,7 @@ export class UserBlockingService implements OnApplicationShutdown {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public async unblock(blocker: CacheableUser, blockee: CacheableUser) {
|
||||
public async unblock(blocker: User, blockee: User) {
|
||||
const blocking = await this.blockingsRepository.findOneBy({
|
||||
blockerId: blocker.id,
|
||||
blockeeId: blockee.id,
|
||||
|
@ -261,7 +261,7 @@ export class UserBlockingService implements OnApplicationShutdown {
|
|||
|
||||
// deliver if remote bloking
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
|||
import Redis from 'ioredis';
|
||||
import type { UsersRepository } from '@/models/index.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 { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
@ -11,10 +11,10 @@ import type { OnApplicationShutdown } from '@nestjs/common';
|
|||
|
||||
@Injectable()
|
||||
export class UserCacheService implements OnApplicationShutdown {
|
||||
public userByIdCache: Cache<CacheableUser>;
|
||||
public localUserByNativeTokenCache: Cache<CacheableLocalUser | null>;
|
||||
public localUserByIdCache: Cache<CacheableLocalUser>;
|
||||
public uriPersonCache: Cache<CacheableUser | null>;
|
||||
public userByIdCache: Cache<User>;
|
||||
public localUserByNativeTokenCache: Cache<LocalUser | null>;
|
||||
public localUserByIdCache: Cache<LocalUser>;
|
||||
public uriPersonCache: Cache<User | null>;
|
||||
|
||||
constructor(
|
||||
@Inject(DI.redisSubscriber)
|
||||
|
@ -27,10 +27,10 @@ export class UserCacheService implements OnApplicationShutdown {
|
|||
) {
|
||||
//this.onMessage = this.onMessage.bind(this);
|
||||
|
||||
this.userByIdCache = new Cache<CacheableUser>(Infinity);
|
||||
this.localUserByNativeTokenCache = new Cache<CacheableLocalUser | null>(Infinity);
|
||||
this.localUserByIdCache = new Cache<CacheableLocalUser>(Infinity);
|
||||
this.uriPersonCache = new Cache<CacheableUser | null>(Infinity);
|
||||
this.userByIdCache = new Cache<User>(Infinity);
|
||||
this.localUserByNativeTokenCache = new Cache<LocalUser | null>(Infinity);
|
||||
this.localUserByIdCache = new Cache<LocalUser>(Infinity);
|
||||
this.uriPersonCache = new Cache<User | null>(Infinity);
|
||||
|
||||
this.redisSubscriber.on('message', this.onMessage);
|
||||
}
|
||||
|
@ -58,7 +58,7 @@ export class UserCacheService implements OnApplicationShutdown {
|
|||
break;
|
||||
}
|
||||
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.set(body.newToken, user);
|
||||
break;
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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 { QueueService } from '@/core/QueueService.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');
|
||||
|
||||
type Local = ILocalUser | {
|
||||
id: ILocalUser['id'];
|
||||
host: ILocalUser['host'];
|
||||
uri: ILocalUser['uri']
|
||||
type Local = LocalUser | {
|
||||
id: LocalUser['id'];
|
||||
host: LocalUser['host'];
|
||||
uri: LocalUser['uri']
|
||||
};
|
||||
type Remote = IRemoteUser | {
|
||||
id: IRemoteUser['id'];
|
||||
host: IRemoteUser['host'];
|
||||
uri: IRemoteUser['uri'];
|
||||
inbox: IRemoteUser['inbox'];
|
||||
type Remote = RemoteUser | {
|
||||
id: RemoteUser['id'];
|
||||
host: RemoteUser['host'];
|
||||
uri: RemoteUser['uri'];
|
||||
inbox: RemoteUser['inbox'];
|
||||
};
|
||||
type Both = Local | Remote;
|
||||
|
||||
|
@ -81,7 +81,7 @@ export class UserFollowingService {
|
|||
|
||||
if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee) && blocked) {
|
||||
// リモートフォローを受けてブロックしていた場合は、エラーにするのではなく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);
|
||||
return;
|
||||
} else if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee) && blocking) {
|
||||
|
@ -130,7 +130,7 @@ export class UserFollowingService {
|
|||
await this.insertFollowingDoc(followee, follower);
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -293,13 +293,13 @@ export class UserFollowingService {
|
|||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
if (this.userEntityService.isLocalUser(followee) && this.userEntityService.isRemoteUser(follower)) {
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
|
@ -388,7 +388,7 @@ export class UserFollowingService {
|
|||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
@ -403,7 +403,7 @@ export class UserFollowingService {
|
|||
},
|
||||
): Promise<void> {
|
||||
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に怒られるので
|
||||
this.queueService.deliver(follower, content, followee.inbox);
|
||||
|
@ -434,7 +434,7 @@ export class UserFollowingService {
|
|||
followee: {
|
||||
id: User['id']; host: User['host']; uri: User['host']; inbox: User['inbox']; sharedInbox: User['sharedInbox'];
|
||||
},
|
||||
follower: CacheableUser,
|
||||
follower: User,
|
||||
): Promise<void> {
|
||||
const request = await this.followRequestsRepository.findOneBy({
|
||||
followeeId: followee.id,
|
||||
|
@ -448,7 +448,7 @@ export class UserFollowingService {
|
|||
await this.insertFollowingDoc(followee, follower);
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -556,7 +556,7 @@ export class UserFollowingService {
|
|||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -35,7 +35,7 @@ export class UserSuspendService {
|
|||
|
||||
if (this.userEntityService.isLocalUser(user)) {
|
||||
// 知り得る全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[] = [];
|
||||
|
||||
|
@ -65,7 +65,7 @@ export class UserSuspendService {
|
|||
|
||||
if (this.userEntityService.isLocalUser(user)) {
|
||||
// 知り得る全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[] = [];
|
||||
|
||||
|
|
|
@ -53,7 +53,7 @@ export class VideoProcessingService {
|
|||
thumbnail: '1',
|
||||
url,
|
||||
})
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { In } from 'typeorm';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import promiseLimit from 'promise-limit';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { CacheableRemoteUser, CacheableUser } from '@/models/entities/User.js';
|
||||
import { concat, toArray, toSingle, unique } from '@/misc/prelude/array.js';
|
||||
import type { RemoteUser, User } from '@/models/entities/User.js';
|
||||
import { concat, unique } from '@/misc/prelude/array.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 type { ApObject } from './type.js';
|
||||
import type { Resolver } from './ApResolverService.js';
|
||||
|
@ -14,8 +12,8 @@ type Visibility = 'public' | 'home' | 'followers' | 'specified';
|
|||
|
||||
type AudienceInfo = {
|
||||
visibility: Visibility,
|
||||
mentionedUsers: CacheableUser[],
|
||||
visibleUsers: CacheableUser[],
|
||||
mentionedUsers: User[],
|
||||
visibleUsers: User[],
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
|
@ -26,16 +24,16 @@ export class ApAudienceService {
|
|||
}
|
||||
|
||||
@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 ccGroups = this.groupingAudience(getApIds(cc), actor);
|
||||
|
||||
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(
|
||||
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) {
|
||||
return {
|
||||
|
@ -69,7 +67,7 @@ export class ApAudienceService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
private groupingAudience(ids: string[], actor: CacheableRemoteUser) {
|
||||
private groupingAudience(ids: string[], actor: RemoteUser) {
|
||||
const groups = {
|
||||
public: [] as string[],
|
||||
followers: [] as string[],
|
||||
|
@ -101,7 +99,7 @@ export class ApAudienceService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
private isFollowers(id: string, actor: CacheableRemoteUser) {
|
||||
private isFollowers(id: string, actor: RemoteUser) {
|
||||
return (
|
||||
id === (actor.followersUri ?? `${actor.uri}/followers`)
|
||||
);
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import escapeRegexp from 'escape-regexp';
|
||||
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 { CacheableRemoteUser, CacheableUser } from '@/models/entities/User.js';
|
||||
import { Cache } from '@/misc/cache.js';
|
||||
import type { UserPublickey } from '@/models/entities/UserPublickey.js';
|
||||
import { UserCacheService } from '@/core/UserCacheService.js';
|
||||
import type { Note } from '@/models/entities/Note.js';
|
||||
import type { MessagingMessage } from '@/models/entities/MessagingMessage.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { RemoteUser, User } from '@/models/entities/User.js';
|
||||
import { getApId } from './type.js';
|
||||
import { ApPersonService } from './models/ApPersonService.js';
|
||||
import type { IObject } from './type.js';
|
||||
|
@ -42,9 +41,6 @@ export class ApDbResolverService {
|
|||
@Inject(DI.usersRepository)
|
||||
private usersRepository: UsersRepository,
|
||||
|
||||
@Inject(DI.messagingMessagesRepository)
|
||||
private messagingMessagesRepository: MessagingMessagesRepository,
|
||||
|
||||
@Inject(DI.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
|
||||
*/
|
||||
@bindThis
|
||||
public async getUserFromApId(value: string | IObject): Promise<CacheableUser | null> {
|
||||
public async getUserFromApId(value: string | IObject): Promise<User | null> {
|
||||
const parsed = this.parseUri(value);
|
||||
|
||||
if (parsed.local) {
|
||||
|
@ -143,7 +122,7 @@ export class ApDbResolverService {
|
|||
*/
|
||||
@bindThis
|
||||
public async getAuthUserFromKeyId(keyId: string): Promise<{
|
||||
user: CacheableRemoteUser;
|
||||
user: RemoteUser;
|
||||
key: UserPublickey;
|
||||
} | null> {
|
||||
const key = await this.publicKeyCache.fetch(keyId, async () => {
|
||||
|
@ -159,7 +138,7 @@ export class ApDbResolverService {
|
|||
if (key == null) return null;
|
||||
|
||||
return {
|
||||
user: await this.userCacheService.findById(key.userId) as CacheableRemoteUser,
|
||||
user: await this.userCacheService.findById(key.userId) as RemoteUser,
|
||||
key,
|
||||
};
|
||||
}
|
||||
|
@ -169,10 +148,10 @@ export class ApDbResolverService {
|
|||
*/
|
||||
@bindThis
|
||||
public async getAuthUserFromApId(uri: string): Promise<{
|
||||
user: CacheableRemoteUser;
|
||||
user: RemoteUser;
|
||||
key: UserPublickey | 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;
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ import { IsNull, Not } from 'typeorm';
|
|||
import { DI } from '@/di-symbols.js';
|
||||
import type { FollowingsRepository, UsersRepository } from '@/models/index.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 { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
@ -18,7 +18,7 @@ interface IFollowersRecipe extends IRecipe {
|
|||
|
||||
interface IDirectRecipe extends IRecipe {
|
||||
type: 'Direct';
|
||||
to: IRemoteUser;
|
||||
to: RemoteUser;
|
||||
}
|
||||
|
||||
const isFollowers = (recipe: any): recipe is IFollowersRecipe =>
|
||||
|
@ -50,7 +50,7 @@ export class ApDeliverManagerService {
|
|||
* @param from Followee
|
||||
*/
|
||||
@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(
|
||||
this.userEntityService,
|
||||
this.followingsRepository,
|
||||
|
@ -68,7 +68,7 @@ export class ApDeliverManagerService {
|
|||
* @param to Target user
|
||||
*/
|
||||
@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(
|
||||
this.userEntityService,
|
||||
this.followingsRepository,
|
||||
|
@ -132,7 +132,7 @@ class DeliverManager {
|
|||
* @param to To
|
||||
*/
|
||||
@bindThis
|
||||
public addDirectRecipe(to: IRemoteUser) {
|
||||
public addDirectRecipe(to: RemoteUser) {
|
||||
const recipe = {
|
||||
type: 'Direct',
|
||||
to,
|
||||
|
|
|
@ -2,7 +2,6 @@ import { Inject, Injectable } from '@nestjs/common';
|
|||
import { In } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import type { CacheableRemoteUser } from '@/models/entities/User.js';
|
||||
import { UserFollowingService } from '@/core/UserFollowingService.js';
|
||||
import { ReactionService } from '@/core/ReactionService.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 { UserEntityService } from '@/core/entities/UserEntityService.js';
|
||||
import { QueueService } from '@/core/QueueService.js';
|
||||
import { MessagingService } from '@/core/MessagingService.js';
|
||||
import type { UsersRepository, NotesRepository, FollowingsRepository, MessagingMessagesRepository, AbuseUserReportsRepository, FollowRequestsRepository } from '@/models/index.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 { UsersRepository, NotesRepository, FollowingsRepository, AbuseUserReportsRepository, FollowRequestsRepository } from '@/models/index.js';
|
||||
import { bindThis } from '@/decorators.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 { ApLoggerService } from './ApLoggerService.js';
|
||||
import { ApDbResolverService } from './ApDbResolverService.js';
|
||||
|
@ -31,8 +31,7 @@ import { ApAudienceService } from './ApAudienceService.js';
|
|||
import { ApPersonService } from './models/ApPersonService.js';
|
||||
import { ApQuestionService } from './models/ApQuestionService.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 { bindThis } from '@/decorators.js';
|
||||
import type { IAccept, IAdd, IAnnounce, IBlock, ICreate, IDelete, IFlag, IFollow, ILike, IObject, IReject, IRemove, IUndo, IUpdate } from './type.js';
|
||||
|
||||
@Injectable()
|
||||
export class ApInboxService {
|
||||
|
@ -51,9 +50,6 @@ export class ApInboxService {
|
|||
@Inject(DI.followingsRepository)
|
||||
private followingsRepository: FollowingsRepository,
|
||||
|
||||
@Inject(DI.messagingMessagesRepository)
|
||||
private messagingMessagesRepository: MessagingMessagesRepository,
|
||||
|
||||
@Inject(DI.abuseUserReportsRepository)
|
||||
private abuseUserReportsRepository: AbuseUserReportsRepository,
|
||||
|
||||
|
@ -81,13 +77,12 @@ export class ApInboxService {
|
|||
private apPersonService: ApPersonService,
|
||||
private apQuestionService: ApQuestionService,
|
||||
private queueService: QueueService,
|
||||
private messagingService: MessagingService,
|
||||
) {
|
||||
this.logger = this.apLoggerService.logger;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async performActivity(actor: CacheableRemoteUser, activity: IObject) {
|
||||
public async performActivity(actor: RemoteUser, activity: IObject) {
|
||||
if (isCollectionOrOrderedCollection(activity)) {
|
||||
const resolver = this.apResolverService.createResolver();
|
||||
for (const item of toArray(isCollection(activity) ? activity.items : activity.orderedItems)) {
|
||||
|
@ -115,7 +110,7 @@ export class ApInboxService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public async performOneActivity(actor: CacheableRemoteUser, activity: IObject): Promise<void> {
|
||||
public async performOneActivity(actor: RemoteUser, activity: IObject): Promise<void> {
|
||||
if (actor.isSuspended) return;
|
||||
|
||||
if (isCreate(activity)) {
|
||||
|
@ -124,8 +119,6 @@ export class ApInboxService {
|
|||
await this.delete(actor, activity);
|
||||
} else if (isUpdate(activity)) {
|
||||
await this.update(actor, activity);
|
||||
} else if (isRead(activity)) {
|
||||
await this.read(actor, activity);
|
||||
} else if (isFollow(activity)) {
|
||||
await this.follow(actor, activity);
|
||||
} else if (isAccept(activity)) {
|
||||
|
@ -152,7 +145,7 @@ export class ApInboxService {
|
|||
}
|
||||
|
||||
@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);
|
||||
|
||||
if (followee == null) {
|
||||
|
@ -168,7 +161,7 @@ export class ApInboxService {
|
|||
}
|
||||
|
||||
@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 note = await this.apNoteService.fetchNote(targetUri);
|
||||
|
@ -186,30 +179,7 @@ export class ApInboxService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
private async read(actor: CacheableRemoteUser, activity: IRead): 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> {
|
||||
private async accept(actor: RemoteUser, activity: IAccept): Promise<string> {
|
||||
const uri = activity.id ?? activity;
|
||||
|
||||
this.logger.info(`Accept: ${uri}`);
|
||||
|
@ -227,7 +197,7 @@ export class ApInboxService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
private async acceptFollow(actor: CacheableRemoteUser, activity: IFollow): Promise<string> {
|
||||
private async acceptFollow(actor: RemoteUser, activity: IFollow): Promise<string> {
|
||||
// ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある
|
||||
|
||||
const follower = await this.apDbResolverService.getUserFromApId(activity.actor);
|
||||
|
@ -251,7 +221,7 @@ export class ApInboxService {
|
|||
}
|
||||
|
||||
@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) {
|
||||
throw new Error('invalid actor');
|
||||
}
|
||||
|
@ -271,7 +241,7 @@ export class ApInboxService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
private async announce(actor: CacheableRemoteUser, activity: IAnnounce): Promise<void> {
|
||||
private async announce(actor: RemoteUser, activity: IAnnounce): Promise<void> {
|
||||
const uri = getApId(activity);
|
||||
|
||||
this.logger.info(`Announce: ${uri}`);
|
||||
|
@ -282,7 +252,7 @@ export class ApInboxService {
|
|||
}
|
||||
|
||||
@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);
|
||||
|
||||
if (actor.isSuspended) {
|
||||
|
@ -342,7 +312,7 @@ export class ApInboxService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
private async block(actor: CacheableRemoteUser, activity: IBlock): Promise<string> {
|
||||
private async block(actor: RemoteUser, activity: IBlock): Promise<string> {
|
||||
// ※ activity.objectにブロック対象があり、それは存在するローカルユーザーのはず
|
||||
|
||||
const blockee = await this.apDbResolverService.getUserFromApId(activity.object);
|
||||
|
@ -360,7 +330,7 @@ export class ApInboxService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
private async create(actor: CacheableRemoteUser, activity: ICreate): Promise<void> {
|
||||
private async create(actor: RemoteUser, activity: ICreate): Promise<void> {
|
||||
const uri = getApId(activity);
|
||||
|
||||
this.logger.info(`Create: ${uri}`);
|
||||
|
@ -396,7 +366,7 @@ export class ApInboxService {
|
|||
}
|
||||
|
||||
@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);
|
||||
|
||||
if (typeof note === 'object') {
|
||||
|
@ -431,7 +401,7 @@ export class ApInboxService {
|
|||
}
|
||||
|
||||
@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) {
|
||||
throw new Error('invalid actor');
|
||||
}
|
||||
|
@ -473,7 +443,7 @@ export class ApInboxService {
|
|||
}
|
||||
|
||||
@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}`);
|
||||
|
||||
if (actor.uri !== uri) {
|
||||
|
@ -482,7 +452,7 @@ export class ApInboxService {
|
|||
|
||||
const user = await this.usersRepository.findOneByOrFail({ id: actor.id });
|
||||
if (user.isDeleted) {
|
||||
this.logger.info('skip: already deleted');
|
||||
return 'skip: already deleted';
|
||||
}
|
||||
|
||||
const job = await this.queueService.createDeleteAccountJob(actor);
|
||||
|
@ -495,7 +465,7 @@ export class ApInboxService {
|
|||
}
|
||||
|
||||
@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}`);
|
||||
|
||||
const unlock = await this.appLockService.getApLock(uri);
|
||||
|
@ -504,16 +474,7 @@ export class ApInboxService {
|
|||
const note = await this.apDbResolverService.getNoteFromApId(uri);
|
||||
|
||||
if (note == null) {
|
||||
const message = await this.apDbResolverService.getMessageFromApId(uri);
|
||||
if (message == null) return 'message not found';
|
||||
|
||||
if (message.userId !== actor.id) {
|
||||
return '投稿を削除しようとしているユーザーは投稿の作成者ではありません';
|
||||
}
|
||||
|
||||
await this.messagingService.deleteMessage(message);
|
||||
|
||||
return 'ok: message deleted';
|
||||
return 'message not found';
|
||||
}
|
||||
|
||||
if (note.userId !== actor.id) {
|
||||
|
@ -528,7 +489,7 @@ export class ApInboxService {
|
|||
}
|
||||
|
||||
@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スキーマと対応させられないので
|
||||
// 対象ユーザーは一番最初のユーザー として あとはコメントとして格納する
|
||||
const uris = getApIds(activity.object);
|
||||
|
@ -553,7 +514,7 @@ export class ApInboxService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
private async reject(actor: CacheableRemoteUser, activity: IReject): Promise<string> {
|
||||
private async reject(actor: RemoteUser, activity: IReject): Promise<string> {
|
||||
const uri = activity.id ?? activity;
|
||||
|
||||
this.logger.info(`Reject: ${uri}`);
|
||||
|
@ -571,7 +532,7 @@ export class ApInboxService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
private async rejectFollow(actor: CacheableRemoteUser, activity: IFollow): Promise<string> {
|
||||
private async rejectFollow(actor: RemoteUser, activity: IFollow): Promise<string> {
|
||||
// ※ activityはこっちから投げたフォローリクエストなので、activity.actorは存在するローカルユーザーである必要がある
|
||||
|
||||
const follower = await this.apDbResolverService.getUserFromApId(activity.actor);
|
||||
|
@ -595,7 +556,7 @@ export class ApInboxService {
|
|||
}
|
||||
|
||||
@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) {
|
||||
throw new Error('invalid actor');
|
||||
}
|
||||
|
@ -615,7 +576,7 @@ export class ApInboxService {
|
|||
}
|
||||
|
||||
@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) {
|
||||
throw new Error('invalid actor');
|
||||
}
|
||||
|
@ -641,7 +602,7 @@ export class ApInboxService {
|
|||
}
|
||||
|
||||
@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);
|
||||
if (follower == null) {
|
||||
return 'skip: follower not found';
|
||||
|
@ -661,7 +622,7 @@ export class ApInboxService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
private async undoAnnounce(actor: CacheableRemoteUser, activity: IAnnounce): Promise<string> {
|
||||
private async undoAnnounce(actor: RemoteUser, activity: IAnnounce): Promise<string> {
|
||||
const uri = getApId(activity);
|
||||
|
||||
const note = await this.notesRepository.findOneBy({
|
||||
|
@ -676,7 +637,7 @@ export class ApInboxService {
|
|||
}
|
||||
|
||||
@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);
|
||||
|
||||
if (blockee == null) {
|
||||
|
@ -692,7 +653,7 @@ export class ApInboxService {
|
|||
}
|
||||
|
||||
@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);
|
||||
if (followee == null) {
|
||||
return 'skip: followee not found';
|
||||
|
@ -726,7 +687,7 @@ export class ApInboxService {
|
|||
}
|
||||
|
||||
@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 note = await this.apNoteService.fetchNote(targetUri);
|
||||
|
@ -741,7 +702,7 @@ export class ApInboxService {
|
|||
}
|
||||
|
||||
@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) {
|
||||
return 'skip: invalid actor';
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import type Logger from '@/logger.js';
|
||||
import { RemoteLoggerService } from '@/core/RemoteLoggerService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
||||
@Injectable()
|
||||
export class ApLoggerService {
|
||||
|
|
|
@ -5,7 +5,7 @@ import { v4 as uuid } from 'uuid';
|
|||
import * as mfm from 'mfm-js';
|
||||
import { DI } from '@/di-symbols.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 { Blocking } from '@/models/entities/Blocking.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 { Emoji } from '@/models/entities/Emoji.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 { UserKeypairStoreService } from '@/core/UserKeypairStoreService.js';
|
||||
import { MfmService } from '@/core/MfmService.js';
|
||||
|
@ -24,7 +23,7 @@ import type { UsersRepository, UserProfilesRepository, NotesRepository, DriveFil
|
|||
import { bindThis } from '@/decorators.js';
|
||||
import { LdSignatureService } from './LdSignatureService.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';
|
||||
|
||||
@Injectable()
|
||||
|
@ -61,7 +60,7 @@ export class ApRendererService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public renderAccept(object: any, user: { id: User['id']; host: null }) {
|
||||
public renderAccept(object: any, user: { id: User['id']; host: null }): IAccept {
|
||||
return {
|
||||
type: 'Accept',
|
||||
actor: `${this.config.url}/users/${user.id}`,
|
||||
|
@ -70,7 +69,7 @@ export class ApRendererService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public renderAdd(user: ILocalUser, target: any, object: any) {
|
||||
public renderAdd(user: LocalUser, target: any, object: any): IAdd {
|
||||
return {
|
||||
type: 'Add',
|
||||
actor: `${this.config.url}/users/${user.id}`,
|
||||
|
@ -80,7 +79,7 @@ export class ApRendererService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public renderAnnounce(object: any, note: Note) {
|
||||
public renderAnnounce(object: any, note: Note): IAnnounce {
|
||||
const attributedTo = `${this.config.url}/users/${note.userId}`;
|
||||
|
||||
let to: string[] = [];
|
||||
|
@ -93,7 +92,7 @@ export class ApRendererService {
|
|||
to = [`${attributedTo}/followers`];
|
||||
cc = ['https://www.w3.org/ns/activitystreams#Public'];
|
||||
} else {
|
||||
return null;
|
||||
throw new Error('renderAnnounce: cannot render non-public note');
|
||||
}
|
||||
|
||||
return {
|
||||
|
@ -113,7 +112,7 @@ export class ApRendererService {
|
|||
* @param block The block to be rendered. The blockee relation must be loaded.
|
||||
*/
|
||||
@bindThis
|
||||
public renderBlock(block: Blocking) {
|
||||
public renderBlock(block: Blocking): IBlock {
|
||||
if (block.blockee?.uri == null) {
|
||||
throw new Error('renderBlock: missing blockee uri');
|
||||
}
|
||||
|
@ -127,14 +126,14 @@ export class ApRendererService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public renderCreate(object: any, note: Note) {
|
||||
public renderCreate(object: IObject, note: Note): ICreate {
|
||||
const activity = {
|
||||
id: `${this.config.url}/notes/${note.id}/activity`,
|
||||
actor: `${this.config.url}/users/${note.userId}`,
|
||||
type: 'Create',
|
||||
published: note.createdAt.toISOString(),
|
||||
object,
|
||||
} as any;
|
||||
} as ICreate;
|
||||
|
||||
if (object.to) activity.to = object.to;
|
||||
if (object.cc) activity.cc = object.cc;
|
||||
|
@ -143,7 +142,7 @@ export class ApRendererService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public renderDelete(object: any, user: { id: User['id']; host: null }) {
|
||||
public renderDelete(object: IObject | string, user: { id: User['id']; host: null }): IDelete {
|
||||
return {
|
||||
type: 'Delete',
|
||||
actor: `${this.config.url}/users/${user.id}`,
|
||||
|
@ -153,7 +152,7 @@ export class ApRendererService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public renderDocument(file: DriveFile) {
|
||||
public renderDocument(file: DriveFile): IApDocument {
|
||||
return {
|
||||
type: 'Document',
|
||||
mediaType: file.type,
|
||||
|
@ -163,12 +162,12 @@ export class ApRendererService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public renderEmoji(emoji: Emoji) {
|
||||
public renderEmoji(emoji: Emoji): IApEmoji {
|
||||
return {
|
||||
id: `${this.config.url}/emojis/${emoji.name}`,
|
||||
type: 'Emoji',
|
||||
name: `:${emoji.name}:`,
|
||||
updated: emoji.updatedAt != null ? emoji.updatedAt.toISOString() : new Date().toISOString,
|
||||
updated: emoji.updatedAt != null ? emoji.updatedAt.toISOString() : new Date().toISOString(),
|
||||
icon: {
|
||||
type: 'Image',
|
||||
mediaType: emoji.type ?? 'image/png',
|
||||
|
@ -179,9 +178,8 @@ export class ApRendererService {
|
|||
}
|
||||
|
||||
// to anonymise reporters, the reporting actor must be a system user
|
||||
// object has to be a uri or array of uris
|
||||
@bindThis
|
||||
public renderFlag(user: ILocalUser, object: [string], content: string) {
|
||||
public renderFlag(user: LocalUser, object: IObject | string, content: string): IFlag {
|
||||
return {
|
||||
type: 'Flag',
|
||||
actor: `${this.config.url}/users/${user.id}`,
|
||||
|
@ -191,15 +189,13 @@ export class ApRendererService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public renderFollowRelay(relay: Relay, relayActor: ILocalUser) {
|
||||
const follow = {
|
||||
public renderFollowRelay(relay: Relay, relayActor: LocalUser): IFollow {
|
||||
return {
|
||||
id: `${this.config.url}/activities/follow-relay/${relay.id}`,
|
||||
type: 'Follow',
|
||||
actor: `${this.config.url}/users/${relayActor.id}`,
|
||||
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'] },
|
||||
followee: { id: User['id']; host: User['host']; uri: User['host'] },
|
||||
requestId?: string,
|
||||
) {
|
||||
const follow = {
|
||||
): IFollow {
|
||||
return {
|
||||
id: requestId ?? `${this.config.url}/follows/${follower.id}/${followee.id}`,
|
||||
type: 'Follow',
|
||||
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,
|
||||
} as any;
|
||||
|
||||
return follow;
|
||||
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!,
|
||||
};
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public renderHashtag(tag: string) {
|
||||
public renderHashtag(tag: string): IApHashtag {
|
||||
return {
|
||||
type: 'Hashtag',
|
||||
href: `${this.config.url}/tags/${encodeURIComponent(tag)}`,
|
||||
|
@ -238,7 +232,7 @@ export class ApRendererService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public renderImage(file: DriveFile) {
|
||||
public renderImage(file: DriveFile): IApImage {
|
||||
return {
|
||||
type: 'Image',
|
||||
url: this.driveFileEntityService.getPublicUrl(file),
|
||||
|
@ -248,7 +242,7 @@ export class ApRendererService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public renderKey(user: ILocalUser, key: UserKeypair, postfix?: string) {
|
||||
public renderKey(user: LocalUser, key: UserKeypair, postfix?: string): IKey {
|
||||
return {
|
||||
id: `${this.config.url}/users/${user.id}${postfix ?? '/publickey'}`,
|
||||
type: 'Key',
|
||||
|
@ -261,7 +255,7 @@ export class ApRendererService {
|
|||
}
|
||||
|
||||
@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 object = {
|
||||
|
@ -271,10 +265,11 @@ export class ApRendererService {
|
|||
object: note.uri ? note.uri : `${this.config.url}/notes/${noteReaction.noteId}`,
|
||||
content: reaction,
|
||||
_misskey_reaction: reaction,
|
||||
} as any;
|
||||
} as ILike;
|
||||
|
||||
if (reaction.startsWith(':')) {
|
||||
const name = reaction.replaceAll(':', '');
|
||||
// TODO: cache
|
||||
const emoji = await this.emojisRepository.findOneBy({
|
||||
name,
|
||||
host: IsNull(),
|
||||
|
@ -287,16 +282,16 @@ export class ApRendererService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public renderMention(mention: User) {
|
||||
public renderMention(mention: User): IApMention {
|
||||
return {
|
||||
type: 'Mention',
|
||||
href: this.userEntityService.isRemoteUser(mention) ? mention.uri : `${this.config.url}/users/${(mention as ILocalUser).id}`,
|
||||
name: this.userEntityService.isRemoteUser(mention) ? `@${mention.username}@${mention.host}` : `@${(mention as ILocalUser).username}`,
|
||||
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 LocalUser).username}`,
|
||||
};
|
||||
}
|
||||
|
||||
@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[]) => {
|
||||
if (!ids || ids.length === 0) return [];
|
||||
const items = await this.driveFilesRepository.findBy({ id: In(ids) });
|
||||
|
@ -409,11 +404,7 @@ export class ApRendererService {
|
|||
totalItems: poll!.votes[i],
|
||||
},
|
||||
})),
|
||||
} : {};
|
||||
|
||||
const asTalk = isTalk ? {
|
||||
_misskey_talk: true,
|
||||
} : {};
|
||||
} as const : {};
|
||||
|
||||
return {
|
||||
id: `${this.config.url}/notes/${note.id}`,
|
||||
|
@ -436,12 +427,11 @@ export class ApRendererService {
|
|||
sensitive: note.cw != null || files.some(file => file.isSensitive),
|
||||
tag,
|
||||
...asPoll,
|
||||
...asTalk,
|
||||
};
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async renderPerson(user: ILocalUser) {
|
||||
public async renderPerson(user: LocalUser) {
|
||||
const id = `${this.config.url}/users/${user.id}`;
|
||||
const isSystem = !!user.username.match(/\./);
|
||||
|
||||
|
@ -518,8 +508,8 @@ export class ApRendererService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public async renderQuestion(user: { id: User['id'] }, note: Note, poll: Poll) {
|
||||
const question = {
|
||||
public renderQuestion(user: { id: User['id'] }, note: Note, poll: Poll): IQuestion {
|
||||
return {
|
||||
type: 'Question',
|
||||
id: `${this.config.url}/questions/${note.id}`,
|
||||
actor: `${this.config.url}/users/${user.id}`,
|
||||
|
@ -533,21 +523,10 @@ export class ApRendererService {
|
|||
},
|
||||
})),
|
||||
};
|
||||
|
||||
return question;
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public renderRead(user: { id: User['id'] }, message: MessagingMessage) {
|
||||
return {
|
||||
type: 'Read',
|
||||
actor: `${this.config.url}/users/${user.id}`,
|
||||
object: message.uri,
|
||||
};
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public renderReject(object: any, user: { id: User['id'] }) {
|
||||
public renderReject(object: any, user: { id: User['id'] }): IReject {
|
||||
return {
|
||||
type: 'Reject',
|
||||
actor: `${this.config.url}/users/${user.id}`,
|
||||
|
@ -556,7 +535,7 @@ export class ApRendererService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public renderRemove(user: { id: User['id'] }, target: any, object: any) {
|
||||
public renderRemove(user: { id: User['id'] }, target: any, object: any): IRemove {
|
||||
return {
|
||||
type: 'Remove',
|
||||
actor: `${this.config.url}/users/${user.id}`,
|
||||
|
@ -566,7 +545,7 @@ export class ApRendererService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public renderTombstone(id: string) {
|
||||
public renderTombstone(id: string): ITombstone {
|
||||
return {
|
||||
id,
|
||||
type: 'Tombstone',
|
||||
|
@ -574,8 +553,7 @@ export class ApRendererService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public renderUndo(object: any, user: { id: User['id'] }) {
|
||||
if (object == null) return null;
|
||||
public renderUndo(object: any, user: { id: User['id'] }): IUndo {
|
||||
const id = typeof object.id === 'string' && object.id.startsWith(this.config.url) ? `${object.id}/undo` : undefined;
|
||||
|
||||
return {
|
||||
|
@ -588,21 +566,19 @@ export class ApRendererService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public renderUpdate(object: any, user: { id: User['id'] }) {
|
||||
const activity = {
|
||||
public renderUpdate(object: any, user: { id: User['id'] }): IUpdate {
|
||||
return {
|
||||
id: `${this.config.url}/users/${user.id}#updates/${new Date().getTime()}`,
|
||||
actor: `${this.config.url}/users/${user.id}`,
|
||||
type: 'Update',
|
||||
to: ['https://www.w3.org/ns/activitystreams#Public'],
|
||||
object,
|
||||
published: new Date().toISOString(),
|
||||
} as any;
|
||||
|
||||
return activity;
|
||||
};
|
||||
}
|
||||
|
||||
@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 {
|
||||
id: `${this.config.url}/users/${user.id}#votes/${vote.id}/activity`,
|
||||
actor: `${this.config.url}/users/${user.id}`,
|
||||
|
@ -621,9 +597,7 @@ export class ApRendererService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
public renderActivity(x: any): IActivity | null {
|
||||
if (x == null) return null;
|
||||
|
||||
public addContext<T extends IObject>(x: T): T & { '@context': any; id: string; } {
|
||||
if (typeof x === 'object' && x.id == null) {
|
||||
x.id = `${this.config.url}/${uuid()}`;
|
||||
}
|
||||
|
@ -653,13 +627,12 @@ export class ApRendererService {
|
|||
'_misskey_quote': 'misskey:_misskey_quote',
|
||||
'_misskey_reaction': 'misskey:_misskey_reaction',
|
||||
'_misskey_votes': 'misskey:_misskey_votes',
|
||||
'_misskey_talk': 'misskey:_misskey_talk',
|
||||
'isCat': 'misskey:isCat',
|
||||
// vcard
|
||||
vcard: 'http://www.w3.org/2006/vcard/ns#',
|
||||
},
|
||||
],
|
||||
}, x);
|
||||
}, x as T & { id: string; });
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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 type { NotesRepository, PollsRepository, NoteReactionsRepository, UsersRepository } from '@/models/index.js';
|
||||
import type { Config } from '@/config.js';
|
||||
|
@ -18,7 +18,7 @@ import type { IObject, ICollection, IOrderedCollection } from './type.js';
|
|||
|
||||
export class Resolver {
|
||||
private history: Set<string>;
|
||||
private user?: ILocalUser;
|
||||
private user?: LocalUser;
|
||||
private logger: Logger;
|
||||
|
||||
constructor(
|
||||
|
@ -38,8 +38,7 @@ export class Resolver {
|
|||
private recursionLimit = 100,
|
||||
) {
|
||||
this.history = new Set();
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
|
||||
this.logger = this.loggerService?.getLogger('ap-resolve'); // なぜか TypeError: Cannot read properties of undefined (reading 'getLogger') と言われる
|
||||
this.logger = this.loggerService.getLogger('ap-resolve');
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
@ -124,17 +123,17 @@ export class Resolver {
|
|||
switch (parsed.type) {
|
||||
case 'notes':
|
||||
return this.notesRepository.findOneByOrFail({ id: parsed.id })
|
||||
.then(note => {
|
||||
.then(async note => {
|
||||
if (parsed.rest === 'activity') {
|
||||
// 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 {
|
||||
return this.apRendererService.renderNote(note);
|
||||
}
|
||||
});
|
||||
case 'users':
|
||||
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':
|
||||
// Polls are indexed by the note they are attached to.
|
||||
return Promise.all([
|
||||
|
@ -143,8 +142,8 @@ export class Resolver {
|
|||
])
|
||||
.then(([note, poll]) => this.apRendererService.renderQuestion({ id: note.userId }, note, poll));
|
||||
case 'likes':
|
||||
return this.noteReactionsRepository.findOneByOrFail({ id: parsed.id }).then(reaction =>
|
||||
this.apRendererService.renderActivity(this.apRendererService.renderLike(reaction, { uri: null }))!);
|
||||
return this.noteReactionsRepository.findOneByOrFail({ id: parsed.id }).then(async reaction =>
|
||||
this.apRendererService.addContext(await this.apRendererService.renderLike(reaction, { uri: null })));
|
||||
case 'follows':
|
||||
// rest should be <followee id>
|
||||
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(
|
||||
[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:
|
||||
throw new Error(`resolveLocal: type ${parsed.type} unhandled`);
|
||||
}
|
||||
|
@ -184,6 +183,7 @@ export class ApResolverService {
|
|||
private httpRequestService: HttpRequestService,
|
||||
private apRendererService: ApRendererService,
|
||||
private apDbResolverService: ApDbResolverService,
|
||||
private loggerService: LoggerService,
|
||||
) {
|
||||
}
|
||||
|
||||
|
@ -202,6 +202,7 @@ export class ApResolverService {
|
|||
this.httpRequestService,
|
||||
this.apRendererService,
|
||||
this.apDbResolverService,
|
||||
this.loggerService,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import * as crypto from 'node:crypto';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import jsonld from 'jsonld';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { HttpRequestService } from '@/core/HttpRequestService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { CONTEXTS } from './misc/contexts.js';
|
||||
|
@ -85,7 +84,9 @@ class LdSignature {
|
|||
@bindThis
|
||||
public async normalize(data: any) {
|
||||
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,
|
||||
});
|
||||
}
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
|||
import { DI } from '@/di-symbols.js';
|
||||
import type { DriveFilesRepository } from '@/models/index.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 { MetaService } from '@/core/MetaService.js';
|
||||
import { truncate } from '@/misc/truncate.js';
|
||||
|
@ -36,7 +36,7 @@ export class ApImageService {
|
|||
* Imageを作成します。
|
||||
*/
|
||||
@bindThis
|
||||
public async createImage(actor: CacheableRemoteUser, value: any): Promise<DriveFile> {
|
||||
public async createImage(actor: RemoteUser, value: any): Promise<DriveFile> {
|
||||
// 投稿者が凍結されていたらスキップ
|
||||
if (actor.isSuspended) {
|
||||
throw new Error('actor has been suspended');
|
||||
|
@ -88,7 +88,7 @@ export class ApImageService {
|
|||
* リモートサーバーからフェッチしてMisskeyに登録しそれを返します。
|
||||
*/
|
||||
@bindThis
|
||||
public async resolveImage(actor: CacheableRemoteUser, value: any): Promise<DriveFile> {
|
||||
public async resolveImage(actor: RemoteUser, value: any): Promise<DriveFile> {
|
||||
// TODO
|
||||
|
||||
// リモートサーバーからフェッチしてきて登録
|
||||
|
|
|
@ -1,15 +1,14 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import promiseLimit from 'promise-limit';
|
||||
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 { 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 { ApResolverService, Resolver } from '../ApResolverService.js';
|
||||
import { ApPersonService } from './ApPersonService.js';
|
||||
import type { IObject, IApMention } from '../type.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
||||
@Injectable()
|
||||
export class ApMentionService {
|
||||
|
@ -26,10 +25,10 @@ export class ApMentionService {
|
|||
public async extractApMentions(tags: IObject | IObject[] | null | undefined, resolver: Resolver) {
|
||||
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(
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { forwardRef, Inject, Injectable } from '@nestjs/common';
|
||||
import promiseLimit from 'promise-limit';
|
||||
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 { CacheableRemoteUser } from '@/models/entities/User.js';
|
||||
import type { RemoteUser } from '@/models/entities/User.js';
|
||||
import type { Note } from '@/models/entities/Note.js';
|
||||
import { toArray, toSingle, unique } from '@/misc/prelude/array.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 { StatusError } from '@/misc/status-error.js';
|
||||
import { UtilityService } from '@/core/UtilityService.js';
|
||||
import { MessagingService } from '@/core/MessagingService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { getOneApId, getApId, getOneApHrefNullable, validPost, isEmoji, getApType } from '../type.js';
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
||||
|
@ -47,9 +46,6 @@ export class ApNoteService {
|
|||
@Inject(DI.emojisRepository)
|
||||
private emojisRepository: EmojisRepository,
|
||||
|
||||
@Inject(DI.messagingMessagesRepository)
|
||||
private messagingMessagesRepository: MessagingMessagesRepository,
|
||||
|
||||
private idService: IdService,
|
||||
private apMfmService: ApMfmService,
|
||||
private apResolverService: ApResolverService,
|
||||
|
@ -64,7 +60,6 @@ export class ApNoteService {
|
|||
private apImageService: ApImageService,
|
||||
private apQuestionService: ApQuestionService,
|
||||
private metaService: MetaService,
|
||||
private messagingService: MessagingService,
|
||||
private appLockService: AppLockService,
|
||||
private pollService: PollService,
|
||||
private noteCreateService: NoteCreateService,
|
||||
|
@ -114,7 +109,7 @@ export class ApNoteService {
|
|||
public async createNote(value: string | IObject, resolver?: Resolver, silent = false): Promise<Note | null> {
|
||||
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 err = this.validateNote(object, entryUri);
|
||||
|
@ -129,7 +124,7 @@ export class ApNoteService {
|
|||
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)}`);
|
||||
|
||||
|
@ -146,7 +141,7 @@ export class ApNoteService {
|
|||
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) {
|
||||
|
@ -165,8 +160,6 @@ export class ApNoteService {
|
|||
}
|
||||
}
|
||||
|
||||
let isMessaging = note._misskey_talk && visibility === 'specified';
|
||||
|
||||
const apMentions = await this.apMentionService.extractApMentions(note.tag, resolver);
|
||||
const apHashtags = await extractApHashtags(note.tag);
|
||||
|
||||
|
@ -193,17 +186,6 @@ export class ApNoteService {
|
|||
return x;
|
||||
}
|
||||
}).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}`);
|
||||
throw err;
|
||||
})
|
||||
|
@ -293,13 +275,6 @@ export class ApNoteService {
|
|||
|
||||
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, {
|
||||
createdAt: note.published ? new Date(note.published) : null,
|
||||
files,
|
||||
|
|
|
@ -1,11 +1,11 @@
|
|||
import { forwardRef, Inject, Injectable } from '@nestjs/common';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import promiseLimit from 'promise-limit';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { ModuleRef } from '@nestjs/core';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { FollowingsRepository, InstancesRepository, UserProfilesRepository, UserPublickeysRepository, UsersRepository } from '@/models/index.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 { truncate } from '@/misc/truncate.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';
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-imports
|
||||
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 summaryLength = 2048;
|
||||
|
@ -197,7 +197,7 @@ export class ApPersonService implements OnModuleInit {
|
|||
* Misskeyに対象のPersonが登録されていればそれを返します。
|
||||
*/
|
||||
@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');
|
||||
|
||||
const cached = this.userCacheService.uriPersonCache.get(uri);
|
||||
|
@ -259,7 +259,7 @@ export class ApPersonService implements OnModuleInit {
|
|||
}
|
||||
|
||||
// Create user
|
||||
let user: IRemoteUser;
|
||||
let user: RemoteUser;
|
||||
try {
|
||||
// Start transaction
|
||||
await this.db.transaction(async transactionalEntityManager => {
|
||||
|
@ -284,7 +284,7 @@ export class ApPersonService implements OnModuleInit {
|
|||
isBot,
|
||||
isCat: (person as any).isCat === true,
|
||||
showTimelineReplies: false,
|
||||
})) as IRemoteUser;
|
||||
})) as RemoteUser;
|
||||
|
||||
await transactionalEntityManager.save(new UserProfile({
|
||||
userId: user.id,
|
||||
|
@ -313,7 +313,7 @@ export class ApPersonService implements OnModuleInit {
|
|||
});
|
||||
|
||||
if (u) {
|
||||
user = u as IRemoteUser;
|
||||
user = u as RemoteUser;
|
||||
} else {
|
||||
throw new Error('already registered');
|
||||
}
|
||||
|
@ -392,7 +392,7 @@ export class ApPersonService implements OnModuleInit {
|
|||
}
|
||||
|
||||
//#region このサーバーに既に登録されているか
|
||||
const exist = await this.usersRepository.findOneBy({ uri }) as IRemoteUser;
|
||||
const exist = await this.usersRepository.findOneBy({ uri }) as RemoteUser;
|
||||
|
||||
if (exist == null) {
|
||||
return;
|
||||
|
@ -500,7 +500,7 @@ export class ApPersonService implements OnModuleInit {
|
|||
* リモートサーバーからフェッチしてMisskeyに登録しそれを返します。
|
||||
*/
|
||||
@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');
|
||||
|
||||
//#region このサーバーに既に登録されていたらそれを返す
|
||||
|
|
|
@ -1,25 +1,25 @@
|
|||
export type obj = { [x: string]: any };
|
||||
export type Obj = { [x: string]: any };
|
||||
export type ApObject = IObject | string | (IObject | string)[];
|
||||
|
||||
export interface IObject {
|
||||
'@context': string | string[] | obj | obj[];
|
||||
'@context'?: string | string[] | Obj | Obj[];
|
||||
type: string | string[];
|
||||
id?: string;
|
||||
name?: string | null;
|
||||
summary?: string;
|
||||
published?: string;
|
||||
cc?: ApObject;
|
||||
to?: ApObject;
|
||||
attributedTo: ApObject;
|
||||
attributedTo?: ApObject;
|
||||
attachment?: any[];
|
||||
inReplyTo?: any;
|
||||
replies?: ICollection;
|
||||
content?: string;
|
||||
name?: string;
|
||||
content?: string | null;
|
||||
startTime?: Date;
|
||||
endTime?: Date;
|
||||
icon?: any;
|
||||
image?: any;
|
||||
url?: ApObject;
|
||||
url?: ApObject | string;
|
||||
href?: string;
|
||||
tag?: IObject | IObject[];
|
||||
sensitive?: boolean;
|
||||
|
@ -113,11 +113,11 @@ export interface IPost extends IObject {
|
|||
_misskey_quote?: string;
|
||||
_misskey_content?: string;
|
||||
quoteUrl?: string;
|
||||
_misskey_talk?: boolean;
|
||||
}
|
||||
|
||||
export interface IQuestion extends IObject {
|
||||
type: 'Note' | 'Question';
|
||||
actor: string;
|
||||
source?: {
|
||||
content: string;
|
||||
mediaType: string;
|
||||
|
@ -200,6 +200,7 @@ export const isPropertyValue = (object: IObject): object is IApPropertyValue =>
|
|||
export interface IApMention extends IObject {
|
||||
type: 'Mention';
|
||||
href: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
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 {
|
||||
type: 'Emoji';
|
||||
updated: Date;
|
||||
name: string;
|
||||
updated: string;
|
||||
}
|
||||
|
||||
export const isEmoji = (object: IObject): object is IApEmoji =>
|
||||
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 {
|
||||
type: 'Create';
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import type Logger from '@/logger.js';
|
||||
import { LoggerService } from '@/core/LoggerService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
||||
@Injectable()
|
||||
export class ChartLoggerService {
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Injectable, Inject } from '@nestjs/common';
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import FederationChart from './charts/federation.js';
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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 { AppLockService } from '@/core/AppLockService.js';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { AntennaNotesRepository, AntennasRepository, UserGroupJoiningsRepository } from '@/models/index.js';
|
||||
import { awaitAll } from '@/misc/prelude/await-all.js';
|
||||
import type { AntennaNotesRepository, AntennasRepository } from '@/models/index.js';
|
||||
import type { Packed } from '@/misc/schema.js';
|
||||
import type { Antenna } from '@/models/entities/Antenna.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
@ -14,9 +13,6 @@ export class AntennaEntityService {
|
|||
|
||||
@Inject(DI.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 hasUnreadNote = (await this.antennaNotesRepository.findOneBy({ antennaId: antenna.id, read: false })) != null;
|
||||
const userGroupJoining = antenna.userGroupJoiningId ? await this.userGroupJoiningsRepository.findOneBy({ id: antenna.userGroupJoiningId }) : null;
|
||||
|
||||
return {
|
||||
id: antenna.id,
|
||||
|
@ -37,7 +32,6 @@ export class AntennaEntityService {
|
|||
excludeKeywords: antenna.excludeKeywords,
|
||||
src: antenna.src,
|
||||
userListId: antenna.userListId,
|
||||
userGroupId: userGroupJoining ? userGroupJoining.userGroupId : null,
|
||||
users: antenna.users,
|
||||
caseSensitive: antenna.caseSensitive,
|
||||
notify: antenna.notify,
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.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 { App } from '@/models/entities/App.js';
|
||||
import type { User } from '@/models/entities/User.js';
|
||||
import { UserEntityService } from './UserEntityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
||||
@Injectable()
|
||||
|
|
|
@ -2,10 +2,8 @@ import { Inject, Injectable } from '@nestjs/common';
|
|||
import { DI } from '@/di-symbols.js';
|
||||
import type { AuthSessionsRepository } from '@/models/index.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 { User } from '@/models/entities/User.js';
|
||||
import { UserEntityService } from './UserEntityService.js';
|
||||
import { AppEntityService } from './AppEntityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.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 { } from '@/models/entities/Blocking.js';
|
||||
import type { User } from '@/models/entities/User.js';
|
||||
|
|
|
@ -4,7 +4,6 @@ import type { ClipsRepository } 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 { Clip } from '@/models/entities/Clip.js';
|
||||
import { UserEntityService } from './UserEntityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
import { forwardRef, Inject, Injectable } from '@nestjs/common';
|
||||
import { DataSource, In } from 'typeorm';
|
||||
import * as mfm from 'mfm-js';
|
||||
import { DataSource } from 'typeorm';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { NotesRepository, DriveFilesRepository } from '@/models/index.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 { deepClone } from '@/misc/clone.js';
|
||||
import { UtilityService } from '../UtilityService.js';
|
||||
import { VideoProcessingService } from '../VideoProcessingService.js';
|
||||
import { UserEntityService } from './UserEntityService.js';
|
||||
import { DriveFolderEntityService } from './DriveFolderEntityService.js';
|
||||
import { VideoProcessingService } from '../VideoProcessingService.js';
|
||||
|
||||
type PackOptions = {
|
||||
detail?: boolean,
|
||||
|
@ -74,14 +73,14 @@ export class DriveFileEntityService {
|
|||
}
|
||||
|
||||
@bindThis
|
||||
private getProxiedUrl(url: string, mode?: 'static' | 'avatar'): string | null {
|
||||
private getProxiedUrl(url: string, mode?: 'static' | 'avatar'): string {
|
||||
return appendQuery(
|
||||
`${this.config.mediaProxy}/${mode ?? 'image'}.webp`,
|
||||
query({
|
||||
url,
|
||||
...(mode ? { [mode]: '1' } : {}),
|
||||
})
|
||||
)
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
@bindThis
|
||||
|
@ -110,7 +109,7 @@ export class DriveFileEntityService {
|
|||
}
|
||||
|
||||
@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) {
|
||||
return this.getProxiedUrl(file.uri, mode);
|
||||
|
|
|
@ -4,9 +4,7 @@ import type { DriveFilesRepository, DriveFoldersRepository } from '@/models/inde
|
|||
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 { DriveFolder } from '@/models/entities/DriveFolder.js';
|
||||
import { UserEntityService } from './UserEntityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
||||
@Injectable()
|
||||
|
|
|
@ -1,50 +1,63 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.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 { } from '@/models/entities/Blocking.js';
|
||||
import type { User } from '@/models/entities/User.js';
|
||||
import type { Emoji } from '@/models/entities/Emoji.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { UserEntityService } from './UserEntityService.js';
|
||||
|
||||
@Injectable()
|
||||
export class EmojiEntityService {
|
||||
constructor(
|
||||
@Inject(DI.emojisRepository)
|
||||
private emojisRepository: EmojisRepository,
|
||||
|
||||
private userEntityService: UserEntityService,
|
||||
) {
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public async pack(
|
||||
public async packSimple(
|
||||
src: Emoji['id'] | Emoji,
|
||||
opts: { omitHost?: boolean; omitId?: boolean; withUrl?: boolean; } = { omitHost: true, omitId: true, withUrl: true },
|
||||
): Promise<Packed<'Emoji'>> {
|
||||
opts = { omitHost: true, omitId: true, withUrl: true, ...opts }
|
||||
|
||||
): Promise<Packed<'EmojiSimple'>> {
|
||||
const emoji = typeof src === 'object' ? src : await this.emojisRepository.findOneByOrFail({ id: src });
|
||||
|
||||
return {
|
||||
id: opts.omitId ? undefined : emoji.id,
|
||||
aliases: emoji.aliases,
|
||||
name: emoji.name,
|
||||
category: emoji.category,
|
||||
host: opts.omitHost ? undefined : emoji.host,
|
||||
// || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
|
||||
url: opts.withUrl ? (emoji.publicUrl || emoji.originalUrl) : undefined,
|
||||
url: emoji.publicUrl || emoji.originalUrl,
|
||||
};
|
||||
}
|
||||
|
||||
@bindThis
|
||||
public packMany(
|
||||
public packSimpleMany(
|
||||
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)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,13 +1,10 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.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 { User } from '@/models/entities/User.js';
|
||||
import type { FlashLike } from '@/models/entities/FlashLike.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
import { UserEntityService } from './UserEntityService.js';
|
||||
import { FlashEntityService } from './FlashEntityService.js';
|
||||
|
||||
@Injectable()
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.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 { User } from '@/models/entities/User.js';
|
||||
import type { FollowRequest } from '@/models/entities/FollowRequest.js';
|
||||
|
|
|
@ -1,12 +1,8 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.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 { User } from '@/models/entities/User.js';
|
||||
import type { GalleryLike } from '@/models/entities/GalleryLike.js';
|
||||
import { UserEntityService } from './UserEntityService.js';
|
||||
import { GalleryPostEntityService } from './GalleryPostEntityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.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 { } from '@/models/entities/Blocking.js';
|
||||
import type { User } from '@/models/entities/User.js';
|
||||
import type { Hashtag } from '@/models/entities/Hashtag.js';
|
||||
import { UserEntityService } from './UserEntityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.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 { } from '@/models/entities/Blocking.js';
|
||||
import type { User } from '@/models/entities/User.js';
|
||||
import type { Instance } from '@/models/entities/Instance.js';
|
||||
import { MetaService } from '@/core/MetaService.js';
|
||||
import { UtilityService } from '../UtilityService.js';
|
||||
|
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -2,9 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
|
|||
import { DI } from '@/di-symbols.js';
|
||||
import type { ModerationLogsRepository } 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 { ModerationLog } from '@/models/entities/ModerationLog.js';
|
||||
import { UserEntityService } from './UserEntityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import { forwardRef, Inject, Injectable } from '@nestjs/common';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DataSource, In } from 'typeorm';
|
||||
import * as mfm from 'mfm-js';
|
||||
import { ModuleRef } from '@nestjs/core';
|
||||
import { DI } from '@/di-symbols.js';
|
||||
import type { Config } from '@/config.js';
|
||||
import type { Packed } from '@/misc/schema.js';
|
||||
import { nyaize } from '@/misc/nyaize.js';
|
||||
import { awaitAll } from '@/misc/prelude/await-all.js';
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.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 { User } from '@/models/entities/User.js';
|
||||
import type { NoteFavorite } from '@/models/entities/NoteFavorite.js';
|
||||
import { UserEntityService } from './UserEntityService.js';
|
||||
import { NoteEntityService } from './NoteEntityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.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 { OnModuleInit } from '@nestjs/common';
|
||||
import type { } from '@/models/entities/Blocking.js';
|
||||
|
|
|
@ -13,13 +13,11 @@ import type { OnModuleInit } from '@nestjs/common';
|
|||
import type { CustomEmojiService } from '../CustomEmojiService.js';
|
||||
import type { UserEntityService } from './UserEntityService.js';
|
||||
import type { NoteEntityService } from './NoteEntityService.js';
|
||||
import type { UserGroupInvitationEntityService } from './UserGroupInvitationEntityService.js';
|
||||
|
||||
@Injectable()
|
||||
export class NotificationEntityService implements OnModuleInit {
|
||||
private userEntityService: UserEntityService;
|
||||
private noteEntityService: NoteEntityService;
|
||||
private userGroupInvitationEntityService: UserGroupInvitationEntityService;
|
||||
private customEmojiService: CustomEmojiService;
|
||||
|
||||
constructor(
|
||||
|
@ -36,7 +34,6 @@ export class NotificationEntityService implements OnModuleInit {
|
|||
|
||||
//private userEntityService: UserEntityService,
|
||||
//private noteEntityService: NoteEntityService,
|
||||
//private userGroupInvitationEntityService: UserGroupInvitationEntityService,
|
||||
//private customEmojiService: CustomEmojiService,
|
||||
) {
|
||||
}
|
||||
|
@ -44,7 +41,6 @@ export class NotificationEntityService implements OnModuleInit {
|
|||
onModuleInit() {
|
||||
this.userEntityService = this.moduleRef.get('UserEntityService');
|
||||
this.noteEntityService = this.moduleRef.get('NoteEntityService');
|
||||
this.userGroupInvitationEntityService = this.moduleRef.get('UserGroupInvitationEntityService');
|
||||
this.customEmojiService = this.moduleRef.get('CustomEmojiService');
|
||||
}
|
||||
|
||||
|
@ -111,9 +107,6 @@ export class NotificationEntityService implements OnModuleInit {
|
|||
_hint_: options._hintForEachNotes_,
|
||||
}),
|
||||
} : {}),
|
||||
...(notification.type === 'groupInvited' ? {
|
||||
invitation: this.userGroupInvitationEntityService.pack(notification.userGroupInvitationId!),
|
||||
} : {}),
|
||||
...(notification.type === 'achievementEarned' ? {
|
||||
achievement: notification.achievement,
|
||||
} : {}),
|
||||
|
|
|
@ -1,12 +1,9 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.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 { User } from '@/models/entities/User.js';
|
||||
import type { PageLike } from '@/models/entities/PageLike.js';
|
||||
import { UserEntityService } from './UserEntityService.js';
|
||||
import { PageEntityService } from './PageEntityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
||||
|
|
|
@ -2,7 +2,6 @@ import { Inject, Injectable } from '@nestjs/common';
|
|||
import { DI } from '@/di-symbols.js';
|
||||
import type { RoleAssignmentsRepository, RolesRepository } from '@/models/index.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 { Role } from '@/models/entities/Role.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
@ -26,14 +25,7 @@ export class RoleEntityService {
|
|||
public async pack(
|
||||
src: Role['id'] | Role,
|
||||
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 assigns = await this.roleAssignmentsRepository.findBy({
|
||||
|
@ -66,9 +58,6 @@ export class RoleEntityService {
|
|||
canEditMembersByModerator: role.canEditMembersByModerator,
|
||||
policies: policies,
|
||||
usersCount: assigns.length,
|
||||
...(opts.detail ? {
|
||||
users: this.userEntityService.packMany(assigns.map(x => x.userId), me),
|
||||
} : {}),
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -76,11 +65,8 @@ export class RoleEntityService {
|
|||
public packMany(
|
||||
roles: any[],
|
||||
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)));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { DI } from '@/di-symbols.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 { User } from '@/models/entities/User.js';
|
||||
import type { Signin } from '@/models/entities/Signin.js';
|
||||
import { UserEntityService } from './UserEntityService.js';
|
||||
import { bindThis } from '@/decorators.js';
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { forwardRef, Inject, Injectable } from '@nestjs/common';
|
||||
import { Inject, Injectable } from '@nestjs/common';
|
||||
import { In, Not } from 'typeorm';
|
||||
import Ajv from 'ajv';
|
||||
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 { Cache } from '@/misc/cache.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 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 { RoleService } from '@/core/RoleService.js';
|
||||
import type { OnModuleInit } from '@nestjs/common';
|
||||
|
@ -32,13 +32,13 @@ type IsMeAndIsUserDetailed<ExpectsMe extends boolean | null, Detailed extends bo
|
|||
|
||||
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(user: User | { host: User['host'] }): boolean {
|
||||
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(user: User | { host: User['host'] }): boolean {
|
||||
return !isLocalUser(user);
|
||||
|
@ -102,12 +102,6 @@ export class UserEntityService implements OnModuleInit {
|
|||
@Inject(DI.announcementReadsRepository)
|
||||
private announcementReadsRepository: AnnouncementReadsRepository,
|
||||
|
||||
@Inject(DI.messagingMessagesRepository)
|
||||
private messagingMessagesRepository: MessagingMessagesRepository,
|
||||
|
||||
@Inject(DI.userGroupJoiningsRepository)
|
||||
private userGroupJoiningsRepository: UserGroupJoiningsRepository,
|
||||
|
||||
@Inject(DI.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
|
||||
public async getHasUnreadAnnouncement(userId: User['id']): Promise<boolean> {
|
||||
const reads = await this.announcementReadsRepository.findBy({
|
||||
|
@ -492,7 +456,6 @@ export class UserEntityService implements OnModuleInit {
|
|||
hasUnreadAnnouncement: this.getHasUnreadAnnouncement(user.id),
|
||||
hasUnreadAntenna: this.getHasUnreadAntenna(user.id),
|
||||
hasUnreadChannel: this.getHasUnreadChannel(user.id),
|
||||
hasUnreadMessagingMessage: this.getHasUnreadMessagingMessage(user.id),
|
||||
hasUnreadNotification: this.getHasUnreadNotification(user.id),
|
||||
hasPendingReceivedFollowRequest: this.getHasPendingReceivedFollowRequest(user.id),
|
||||
mutedWords: profile!.mutedWords,
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue