Merge branch 'develop'

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

View file

@ -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"
}

View file

@ -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:

View file

@ -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

View file

@ -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
View file

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

View file

@ -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

View file

@ -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

View file

@ -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 }}

View file

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

View file

@ -8,6 +8,28 @@
You should also include the user name that made the change.
-->
## 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

View file

@ -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

View file

@ -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

View file

@ -5,9 +5,9 @@ Also, the later tasks are more indefinite and are subject to change as developme
## (1) Improve maintainability \<current phase\>
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 ✔️

View file

@ -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
View file

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

View file

@ -103,6 +103,8 @@ renoted: "Renote getätigt."
cantRenote: "Renote dieses Beitrags nicht möglich."
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:

View file

@ -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:

View file

@ -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"

View file

@ -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}"

View file

@ -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: "ロールの編集"

View file

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

View file

@ -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:

View file

@ -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:

View file

@ -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"

View file

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

View file

@ -83,7 +83,14 @@ module.exports = {
// A map from regular expressions to module names or to arrays of module names that allow to stub out resources with a single module
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,

View file

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

View file

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

View file

@ -11,7 +11,9 @@
"watch:swc": "swc src -d built -D -w",
"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"
}
}

View file

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

View file

@ -32,7 +32,7 @@ export class AccountUpdateService {
// フォロワーがリモートユーザーかつ投稿者がローカルユーザーならUpdateを配信
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);
}

View file

@ -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);

View file

@ -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)

View file

@ -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,

View file

@ -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),
});
}

View file

@ -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);
}
}
}

View file

@ -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';

View file

@ -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';

View file

@ -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);

View file

@ -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),

View file

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

View file

@ -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;
}

View file

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

View file

@ -1,9 +1,8 @@
import { URL } from 'node:url';
import { 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;
},

View file

@ -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

View file

@ -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);

View file

@ -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);

View file

@ -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 });
}
}

View file

@ -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);
}

View file

@ -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;
}
}

View file

@ -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;

View file

@ -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();

View file

@ -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);

View file

@ -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 {

View file

@ -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}`);

View file

@ -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';

View file

@ -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);
}
}

View file

@ -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;

View file

@ -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);
}

View file

@ -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[] = [];

View file

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

View file

@ -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`)
);

View file

@ -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;

View file

@ -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,

View file

@ -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';
}

View file

@ -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 {

View file

@ -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

View file

@ -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,
);
}
}

View file

@ -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,
});
}

View file

@ -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
// リモートサーバーからフェッチしてきて登録

View file

@ -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;
}

View file

@ -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,

View file

@ -1,11 +1,11 @@
import { forwardRef, Inject, Injectable } from '@nestjs/common';
import { Inject, Injectable } from '@nestjs/common';
import promiseLimit from 'promise-limit';
import { 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 このサーバーに既に登録されていたらそれを返す

View file

@ -1,25 +1,25 @@
export type obj = { [x: string]: any };
export type Obj = { [x: string]: any };
export type ApObject = IObject | string | (IObject | string)[];
export 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';
}

View file

@ -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 {

View file

@ -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';

View file

@ -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';

View file

@ -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,

View file

@ -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()

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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);

View file

@ -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()

View file

@ -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)));
}
}

View file

@ -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()

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

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

View file

@ -2,9 +2,7 @@ import { Inject, Injectable } from '@nestjs/common';
import { DI } from '@/di-symbols.js';
import 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';

View file

@ -1,9 +1,8 @@
import { forwardRef, Inject, Injectable } from '@nestjs/common';
import { Inject, Injectable } from '@nestjs/common';
import { DataSource, In } from 'typeorm';
import * 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';

View file

@ -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';

View file

@ -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';

View file

@ -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,
} : {}),

View file

@ -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';

View file

@ -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)));
}
}

View file

@ -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';

View file

@ -1,4 +1,4 @@
import { forwardRef, Inject, Injectable } from '@nestjs/common';
import { Inject, Injectable } from '@nestjs/common';
import { In, Not } from 'typeorm';
import 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