Compare commits

..

2 Commits

Author SHA1 Message Date
syuilo
f9e2cd4088 Merge branch 'develop' into multi-server 2025-03-14 15:44:18 +09:00
syuilo
35a3acab42 init 2025-03-14 14:47:43 +09:00
424 changed files with 4231 additions and 4685 deletions

View File

@@ -1,51 +0,0 @@
name: release-tag
on:
push:
branches:
- 'main'
jobs:
release-image:
runs-on: ubuntu-latest
env:
DOCKER_ORG: sendnrw
DOCKER_LATEST: latest
RUNNER_TOOL_CACHE: /toolcache
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up QEMU
uses: docker/setup-qemu-action@v2
- name: Set up Docker BuildX
uses: docker/setup-buildx-action@v2
with: # replace it with your local IP
config-inline: |
[registry."git.send.nrw"]
http = true
insecure = true
- name: Login to DockerHub
uses: docker/login-action@v2
with:
registry: git.send.nrw # replace it with your local IP
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}
- name: Get Meta
id: meta
run: |
echo REPO_NAME=$(echo ${GITHUB_REPOSITORY} | awk -F"/" '{print $2}') >> $GITHUB_OUTPUT
echo REPO_VERSION=$(git describe --tags --always | sed 's/^v//') >> $GITHUB_OUTPUT
- name: Build and push
uses: docker/build-push-action@v4
with:
context: .
file: ./Dockerfile
platforms: |
linux/amd64
push: true
tags: | # replace it with your local IP and tags
git.send.nrw/${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ steps.meta.outputs.REPO_VERSION }}
git.send.nrw/${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ env.DOCKER_LATEST }}

View File

@@ -24,6 +24,9 @@ updates:
aws-sdk: aws-sdk:
patterns: patterns:
- "@aws-sdk/*" - "@aws-sdk/*"
bull-board:
patterns:
- "@bull-board/*"
nestjs: nestjs:
patterns: patterns:
- "@nestjs/*" - "@nestjs/*"

View File

@@ -50,8 +50,8 @@ jobs:
- run: pnpm i --frozen-lockfile - run: pnpm i --frozen-lockfile
- name: Check pnpm-lock.yaml - name: Check pnpm-lock.yaml
run: git diff --exit-code pnpm-lock.yaml run: git diff --exit-code pnpm-lock.yaml
- name: Build dependent packages - name: Build misskey-js
run: pnpm -F misskey-js -F misskey-bubble-game -F misskey-reversi build run: pnpm --filter misskey-js build
- name: Build storybook - name: Build storybook
run: pnpm --filter frontend build-storybook run: pnpm --filter frontend build-storybook
- name: Publish to Chromatic - name: Publish to Chromatic

View File

@@ -1,46 +1,23 @@
## 2025.3.2 ## 2025.3.2
### General ### General
- セキュリティを強化するため、ジョブキューのダッシュボード(bull-board)統合が削除されました。 -
- Misskeyネイティブでダッシュボードを実装予定です
### Client ### Client
- Feat: 設定の管理が強化されました - Feat: 設定の管理が強化されました
- 内部処理が一新され、安定性とパフォーマンスが向上しました - 自動でバックアップされるように
- 全てのクライアント設定がエクスポート(バックアップ)/インポート対象に含まれるようになりました - 任意の設定項目をデバイス間で同期できるように(実験的)
- プラグイン、テーマ、クライアントに追加されたすべてのアカウント情報も含まれるようになりました
- 自動で設定データをサーバーにバックアップできるように
- 設定→設定のプロファイル→自動バックアップ で有効にできます
- 新しいデバイスからログインしたり、ブラウザから設定データが消えてしまったときに自動で復元されます(復元をスキップすることも可能)
- 任意の設定項目をデバイス間で同期できるように
- 設定項目の「...」メニュー→「デバイス間で同期」
- 同期をオンにした際にサーバーに保存された値とローカルの値が競合する場合はどちらを優先するか選択できます
- 任意の設定項目を初期値にリセットできるように
- 設定項目の「...」メニュー→「初期値にリセット」
- アカウントごとに設定値が分離される設定とそうでないクライアント設定が混在していた(かつ分離するかどうかを設定不可だった)のを、基本的に一律でクライアント全体に適用されるようにし、個別でアカウントごとに異なる設定を行えるように
- 設定項目の「...」メニュー→「アカウントで上書き」をオンにすることで、設定値をそのアカウントでだけ適用するようにできます
- ログアウトすると設定データもブラウザから消去されるようになりプライバシーが向上しました
- 再度ログインすればサーバーのバックアップから設定データを復元可能です
- エクスポートした設定データを他のサーバーでインポートして適用すること(設定の持ち運び)が可能になりました
- Feat: 画面を重ねて表示するオプションを実装(実験的)
- 設定 → その他 → 実験的機能 → Enable stacking router view
- Enhance: プラグインの管理が強化されました - Enhance: プラグインの管理が強化されました
- インストール/アンインストール/設定の変更時にリロード不要になりました - インストール/アンインストール/設定の変更時にリロード不要になりました
- Enhance: ログアウト時、ブラウザに保存されたWebクライアントのデータを全て消去するように - Enhance: ログアウト時、ブラウザに保存されたWebクライアントのデータを全て消去するように
- Enhance: CWの注釈テキストが入力されていない場合, Postボタンを非アクティブに - Enhance: CWの注釈テキストが入力されていない場合, Postボタンを非アクティブに
- Enhance: CWを無効にした場合, 注釈テキストが最大入力文字数を超えていても投稿できるように - Enhance: CWを無効にした場合, 注釈テキストが最大入力文字数を超えていても投稿できるように
- Enhance: テーマ設定画面のデザインを改善 - Enhance: テーマ設定画面のデザインを改善
- Enhance: 投稿フォームの設定メニューを改良
- 投稿フォームをリセットできるように
- 文字数カウントを復活
- Enhance: 2段階認証時のリカバリーコードのファイル名にサーバーURLを含めるように
- Fix: テーマ切り替え時に一部の色が変わらない問題を修正 - Fix: テーマ切り替え時に一部の色が変わらない問題を修正
### Server ### Server
- Fix: プロフィール追加情報で無効なURLに入力された場合に照会エラーを出るのを修正 - Fix: プロフィール追加情報で無効なURLに入力された場合に照会エラーを出るのを修正
- Fix: ActivityPubリクエストURLチェック実装は仕様に従っていないのを修正 - Fix: ActivityPubリクエストURLチェック実装は仕様に従っていないのを修正
- Fix: 連合無しモードでも外部から照会可能だった問題を修正
- Fix: テスト用WebHookのペイロードの`emojis`パラメータが実際のものと異なる問題を修正
## 2025.3.1 ## 2025.3.1

View File

@@ -273,6 +273,7 @@ niraxは、Misskeyで使用しているオリジナルのフロントエンド
query?: Record<string, string>; query?: Record<string, string>;
loginRequired?: boolean; loginRequired?: boolean;
hash?: string; hash?: string;
globalCacheKey?: string;
children?: RouteDef[]; children?: RouteDef[];
} }
``` ```

View File

@@ -6,7 +6,7 @@
**🌎 **Misskey** is an open source, federated social media platform that's free forever! 🚀** **🌎 **Misskey** is an open source, federated social media platform that's free forever! 🚀**
[Learn more](https://misskey-hub.net/) [Learn more](https://misskey-hub.net/)
--- ---
<a href="https://misskey-hub.net/servers/"> <a href="https://misskey-hub.net/servers/">

View File

@@ -1013,7 +1013,6 @@ flip: "اقلب"
lastNDays: "آخر {n} أيام" lastNDays: "آخر {n} أيام"
surrender: "ألغِ" surrender: "ألغِ"
postForm: "أنشئ ملاحظة" postForm: "أنشئ ملاحظة"
information: "عن"
_delivery: _delivery:
stop: "مُعلّق" stop: "مُعلّق"
_initialAccountSetting: _initialAccountSetting:

View File

@@ -853,7 +853,6 @@ renotes: "রিনোট"
sourceCode: "সোর্স কোড" sourceCode: "সোর্স কোড"
flip: "উল্টান" flip: "উল্টান"
postForm: "নোট লিখুন" postForm: "নোট লিখুন"
information: "আপনার সম্পর্কে"
_delivery: _delivery:
stop: "স্থগিত করা হয়েছে" stop: "স্থগিত করা হয়েছে"
_type: _type:

View File

@@ -698,7 +698,6 @@ userSaysSomethingAbout: "{name} està parlant sobre \"{word}\""
makeActive: "Activar" makeActive: "Activar"
display: "Veure" display: "Veure"
copy: "Copiar" copy: "Copiar"
copiedToClipboard: "Copiat al porta papers"
metrics: "Mètriques" metrics: "Mètriques"
overview: "Visió General" overview: "Visió General"
logs: "Registres" logs: "Registres"
@@ -1140,7 +1139,7 @@ channelArchiveConfirmDescription: "Un Canal arxivat no apareixerà a la llista d
thisChannelArchived: "Aquest Canal ha sigut arxivat." thisChannelArchived: "Aquest Canal ha sigut arxivat."
displayOfNote: "Mostrar notes" displayOfNote: "Mostrar notes"
initialAccountSetting: "Configuració del perfil" initialAccountSetting: "Configuració del perfil"
youFollowing: "Segueixes " youFollowing: "Seguint"
preventAiLearning: "Descartar l'ús d'aprenentatge automàtic (IA Generativa)" preventAiLearning: "Descartar l'ús d'aprenentatge automàtic (IA Generativa)"
preventAiLearningDescription: "Demanar els indexadors no fer servir els texts, imatges, etc. en cap conjunt de dades per alimentar l'aprenentatge automàtic (IA Predictiva/ Generativa). Això s'aconsegueix afegint la etiqueta \"noai\" com a resposta HTML al contingut corresponent. Prevenir aquest ús totalment pot ser que no sigui aconseguit, ja que molts indexadors poden obviar aquesta etiqueta." preventAiLearningDescription: "Demanar els indexadors no fer servir els texts, imatges, etc. en cap conjunt de dades per alimentar l'aprenentatge automàtic (IA Predictiva/ Generativa). Això s'aconsegueix afegint la etiqueta \"noai\" com a resposta HTML al contingut corresponent. Prevenir aquest ús totalment pot ser que no sigui aconseguit, ja que molts indexadors poden obviar aquesta etiqueta."
options: "Opcions" options: "Opcions"
@@ -1191,7 +1190,7 @@ pastAnnouncements: "Informes passats"
youHaveUnreadAnnouncements: "Tens informes per llegir." youHaveUnreadAnnouncements: "Tens informes per llegir."
useSecurityKey: "Segueix les instruccions del teu navegador O dispositiu per fer servir el teu passkey." useSecurityKey: "Segueix les instruccions del teu navegador O dispositiu per fer servir el teu passkey."
replies: "Respostes" replies: "Respostes"
renotes: "Impulsos" renotes: "Impulsar"
loadReplies: "Mostrar les respostes" loadReplies: "Mostrar les respostes"
loadConversation: "Mostrar la conversació " loadConversation: "Mostrar la conversació "
pinnedList: "Llista fixada" pinnedList: "Llista fixada"
@@ -1327,19 +1326,7 @@ restore: "Restaurar "
syncBetweenDevices: "Sincronització entre dispositius" syncBetweenDevices: "Sincronització entre dispositius"
preferenceSyncConflictTitle: "Els valors de la configuració ja existeixen al dispositiu" preferenceSyncConflictTitle: "Els valors de la configuració ja existeixen al dispositiu"
preferenceSyncConflictText: "Un element de la configuració amb sincronització activada desa els seus valors al servidor, però s'ha trobat un valor a la configuració desat al servidor per aquest element de la configuració. Quin valor us sobreescriure?" preferenceSyncConflictText: "Un element de la configuració amb sincronització activada desa els seus valors al servidor, però s'ha trobat un valor a la configuració desat al servidor per aquest element de la configuració. Quin valor us sobreescriure?"
preferenceSyncConflictChoiceServer: "Valors de configuració del servidor"
preferenceSyncConflictChoiceDevice: "Punts d'ajustos del dispositiu "
preferenceSyncConflictChoiceCancel: "Cancel·lar l'activació de la sincronització "
paste: "Pegar"
emojiPalette: "Calaix d'emojis"
postForm: "Formulari de publicació" postForm: "Formulari de publicació"
textCount: "Nombre de caràcters "
information: "Informació"
_emojiPalette:
palettes: "Calaixos d'emojis"
enableSyncBetweenDevicesForPalettes: "Activa la sincronització dels calaixos d'emojis entre dispositius"
paletteForMain: "Calaix d'emojis principal"
paletteForReaction: "Calaix d'emojis per reaccions"
_settings: _settings:
driveBanner: "Pots gestionar i configurar el Disc, comprovar el seu ús i establir una configuració per a la càrrega d'arxius." driveBanner: "Pots gestionar i configurar el Disc, comprovar el seu ús i establir una configuració per a la càrrega d'arxius."
pluginBanner: "Els complements poden fer-se servir per ampliar les funcionalitats del client. Els complements poden instal·lar-se, configurar-se individualment i gestionar-se." pluginBanner: "Els complements poden fer-se servir per ampliar les funcionalitats del client. Els complements poden instal·lar-se, configurar-se individualment i gestionar-se."
@@ -1357,9 +1344,6 @@ _settings:
preferencesBanner: "Pots configurar el comportament general del client segons les teves preferències." preferencesBanner: "Pots configurar el comportament general del client segons les teves preferències."
appearanceBanner: "Pots configurar les preferències relacionades amb la visualització i l'aspecte del client segons el teu parer." appearanceBanner: "Pots configurar les preferències relacionades amb la visualització i l'aspecte del client segons el teu parer."
soundsBanner: "Configuració dels sons que reproduirà el client." soundsBanner: "Configuració dels sons que reproduirà el client."
timelineAndNote: "Línia de temps i nota"
makeEveryTextElementsSelectable: "Fes que tots els elements del text siguin seleccionables"
makeEveryTextElementsSelectable_description: "L'activació pot reduir la usabilitat en determinades ocasions."
_preferencesProfile: _preferencesProfile:
profileName: "Nom del perfil" profileName: "Nom del perfil"
profileNameDescription: "Estableix un nom que identifiqui aquest dispositiu." profileNameDescription: "Estableix un nom que identifiqui aquest dispositiu."
@@ -2072,7 +2056,7 @@ _theme:
hashtag: "Etiqueta" hashtag: "Etiqueta"
mention: "Menció" mention: "Menció"
mentionMe: "Mencions (jo)" mentionMe: "Mencions (jo)"
renote: "Impulsar" renote: "Renotar"
modalBg: "Fons del modal" modalBg: "Fons del modal"
divider: "Divisor" divider: "Divisor"
scrollbarHandle: "Maneta de la barra de desplaçament" scrollbarHandle: "Maneta de la barra de desplaçament"
@@ -2514,7 +2498,7 @@ _notification:
follow: "Segueix-me" follow: "Segueix-me"
mention: "Menció" mention: "Menció"
reply: "Respostes" reply: "Respostes"
renote: "Impulsos" renote: "Impulsar"
quote: "Citar" quote: "Citar"
reaction: "Reaccions" reaction: "Reaccions"
pollEnded: "Enquesta terminada" pollEnded: "Enquesta terminada"
@@ -2524,13 +2508,12 @@ _notification:
achievementEarned: "Assoliment desbloquejat" achievementEarned: "Assoliment desbloquejat"
exportCompleted: "Exportació completada" exportCompleted: "Exportació completada"
login: "Iniciar sessió" login: "Iniciar sessió"
createToken: "Creació de tokens d'accés "
test: "Prova la notificació" test: "Prova la notificació"
app: "Notificacions d'aplicacions" app: "Notificacions d'aplicacions"
_actions: _actions:
followBack: "També et segueix" followBack: "També et segueix"
reply: "Respondre" reply: "Respondre"
renote: "Impulsar" renote: "Impulsos"
_deck: _deck:
alwaysShowMainColumn: "Mostrar sempre la columna principal" alwaysShowMainColumn: "Mostrar sempre la columna principal"
columnAlign: "Alinea les columnes" columnAlign: "Alinea les columnes"
@@ -2674,8 +2657,10 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "Assegura't que qui distribueix aquest recurs és fiable abans d'instal·lar-ho." checkVendorBeforeInstall: "Assegura't que qui distribueix aquest recurs és fiable abans d'instal·lar-ho."
_plugin: _plugin:
title: "Vols instal·lar aquest afegit?" title: "Vols instal·lar aquest afegit?"
metaTitle: "Informació de l'afegit "
_theme: _theme:
title: "Vols instal·lar aquest tema?" title: "Vols instal·lar aquest tema?"
metaTitle: "Informació del tema"
_meta: _meta:
base: "Paleta de colors base" base: "Paleta de colors base"
_vendorInfo: _vendorInfo:

View File

@@ -169,9 +169,6 @@ addAccount: "Přidat účet"
reloadAccountsList: "Obnovit list účtů" reloadAccountsList: "Obnovit list účtů"
loginFailed: "Přihlášení se nezdařilo." loginFailed: "Přihlášení se nezdařilo."
showOnRemote: "Více na původním profilu" showOnRemote: "Více na původním profilu"
continueOnRemote: "Pokračujte na původní profil"
chooseServerOnMisskeyHub: "Vyberete si server z Misskey Hubu"
inputHostName: "Zadejte doménu"
general: "Obecně" general: "Obecně"
wallpaper: "Obrázek na pozadí" wallpaper: "Obrázek na pozadí"
setWallpaper: "Nastavení obrázku na pozadí" setWallpaper: "Nastavení obrázku na pozadí"
@@ -196,7 +193,6 @@ perHour: "za hodinu"
perDay: "za den" perDay: "za den"
stopActivityDelivery: "Přestat zasílat aktivitu" stopActivityDelivery: "Přestat zasílat aktivitu"
blockThisInstance: "Blokovat tuto instanci" blockThisInstance: "Blokovat tuto instanci"
silenceThisInstance: "Utišit tuto instanci"
operations: "Operace" operations: "Operace"
software: "Software" software: "Software"
version: "Verze" version: "Verze"
@@ -1103,7 +1099,6 @@ flip: "Otočit"
lastNDays: "Posledních {n} dnů" lastNDays: "Posledních {n} dnů"
surrender: "Zrušit" surrender: "Zrušit"
postForm: "Formulář pro odeslání" postForm: "Formulář pro odeslání"
information: "Informace"
_delivery: _delivery:
stop: "Suspendováno" stop: "Suspendováno"
_type: _type:

View File

@@ -49,7 +49,7 @@ pin: "An dein Profil anheften"
unpin: "Von deinem Profil lösen" unpin: "Von deinem Profil lösen"
copyContent: "Inhalt kopieren" copyContent: "Inhalt kopieren"
copyLink: "Link kopieren" copyLink: "Link kopieren"
copyRemoteLink: "Remote-Link kopieren" copyRemoteLink: "Renote-Link kopieren"
copyLinkRenote: "Renote-Link kopieren" copyLinkRenote: "Renote-Link kopieren"
delete: "Löschen" delete: "Löschen"
deleteAndEdit: "Löschen und Bearbeiten" deleteAndEdit: "Löschen und Bearbeiten"
@@ -1309,7 +1309,6 @@ availableRoles: "Verfügbare Rollen"
federationSpecified: "Dieser Server arbeitet mit Whitelist-Föderation. Er kann nicht mit anderen als den vom Administrator angegebenen Servern interagieren." federationSpecified: "Dieser Server arbeitet mit Whitelist-Föderation. Er kann nicht mit anderen als den vom Administrator angegebenen Servern interagieren."
federationDisabled: "Föderation ist auf diesem Server deaktiviert. Es ist nicht möglich, mit Benutzern auf anderen Servern zu interagieren." federationDisabled: "Föderation ist auf diesem Server deaktiviert. Es ist nicht möglich, mit Benutzern auf anderen Servern zu interagieren."
postForm: "Notizfenster" postForm: "Notizfenster"
information: "Über"
_settings: _settings:
webhook: "Webhook" webhook: "Webhook"
_accountSettings: _accountSettings:
@@ -2525,8 +2524,10 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "Überprüfe vor Installation die Vertrauenswürdigkeit des Vertreibers." checkVendorBeforeInstall: "Überprüfe vor Installation die Vertrauenswürdigkeit des Vertreibers."
_plugin: _plugin:
title: "Möchtest du dieses Plugin installieren?" title: "Möchtest du dieses Plugin installieren?"
metaTitle: "Plugininformation"
_theme: _theme:
title: "Möchten du dieses Farbschema installieren?" title: "Möchten du dieses Farbschema installieren?"
metaTitle: "Farbschemainfo"
_meta: _meta:
base: "Farbschemavorlage" base: "Farbschemavorlage"
_vendorInfo: _vendorInfo:

View File

@@ -289,7 +289,6 @@ icon: "Εικονίδιο"
replies: "Απάντηση" replies: "Απάντηση"
renotes: "Κοινοποίηση σημειώματος" renotes: "Κοινοποίηση σημειώματος"
postForm: "Φόρμα δημοσίευσης" postForm: "Φόρμα δημοσίευσης"
information: "Πληροφορίες"
_email: _email:
_follow: _follow:
title: "Έχετε ένα νέο ακόλουθο" title: "Έχετε ένα νέο ακόλουθο"

View File

@@ -698,7 +698,6 @@ userSaysSomethingAbout: "{name} said something about \"{word}\""
makeActive: "Activate" makeActive: "Activate"
display: "Display" display: "Display"
copy: "Copy" copy: "Copy"
copiedToClipboard: "Copied to clipboard"
metrics: "Metrics" metrics: "Metrics"
overview: "Overview" overview: "Overview"
logs: "Logs" logs: "Logs"
@@ -1317,29 +1316,14 @@ unmarkAsSensitiveConfirm: "Do you want to remove the sensitive designation for t
preferences: "Preferences" preferences: "Preferences"
accessibility: "Accessibility" accessibility: "Accessibility"
preferencesProfile: "Preferences profile" preferencesProfile: "Preferences profile"
copyPreferenceId: "Copy the preference ID" copyPreferenceId: "Copy the proference ID"
resetToDefaultValue: "Revert to default" resetToDefaultValue: "Revert to default"
overrideByAccount: "Override by the account" overrideByAccount: "Override by the account"
untitled: "Untitled" untitled: "Untitled"
noName: "No name" noName: "No name"
skip: "Skip" skip: "Skip"
restore: "Restore" restore: "Restore"
syncBetweenDevices: "Sync between devices"
preferenceSyncConflictTitle: "The configured value exists on the server."
preferenceSyncConflictText: "The sync enabled settings will save their values to the server. However, there are existing values on the server. Which set of values would you like to overwrite?"
preferenceSyncConflictChoiceServer: "Configured value on server"
preferenceSyncConflictChoiceDevice: "Configured value on device"
preferenceSyncConflictChoiceCancel: "Cancel enabling sync"
paste: "Paste"
emojiPalette: "Emoji palette"
postForm: "Posting form" postForm: "Posting form"
textCount: "Character count"
information: "About"
_emojiPalette:
palettes: "Palette"
enableSyncBetweenDevicesForPalettes: "Enable palette sync between devices"
paletteForMain: "Main palette"
paletteForReaction: "Reaction palette"
_settings: _settings:
driveBanner: "You can manage and configure the drive, check usage, and configure file upload settings." driveBanner: "You can manage and configure the drive, check usage, and configure file upload settings."
pluginBanner: "You can extend client features with plugins. You can install plugins, configure and manage individually." pluginBanner: "You can extend client features with plugins. You can install plugins, configure and manage individually."
@@ -1357,9 +1341,6 @@ _settings:
preferencesBanner: "You can configure the overall behavior of the client according to your preferences." preferencesBanner: "You can configure the overall behavior of the client according to your preferences."
appearanceBanner: "You can configure the appearance and display settings for the client according to your preferences." appearanceBanner: "You can configure the appearance and display settings for the client according to your preferences."
soundsBanner: "You can configure the sound settings for playback in the client." soundsBanner: "You can configure the sound settings for playback in the client."
timelineAndNote: "Timeline and note"
makeEveryTextElementsSelectable: "Make all text elements selectable"
makeEveryTextElementsSelectable_description: "Enabling this may reduce usability in some situations."
_preferencesProfile: _preferencesProfile:
profileName: "Profile name" profileName: "Profile name"
profileNameDescription: "Set a name that identifies this device." profileNameDescription: "Set a name that identifies this device."
@@ -2524,7 +2505,6 @@ _notification:
achievementEarned: "Achievement unlocked" achievementEarned: "Achievement unlocked"
exportCompleted: "The export has been completed" exportCompleted: "The export has been completed"
login: "Sign In" login: "Sign In"
createToken: "Create access token"
test: "Notification test" test: "Notification test"
app: "Notifications from linked apps" app: "Notifications from linked apps"
_actions: _actions:
@@ -2552,7 +2532,6 @@ _deck:
useSimpleUiForNonRootPages: "Use simple UI for navigated pages" useSimpleUiForNonRootPages: "Use simple UI for navigated pages"
usedAsMinWidthWhenFlexible: "Minimum width will be used for this when the \"Auto-adjust width\" option is enabled" usedAsMinWidthWhenFlexible: "Minimum width will be used for this when the \"Auto-adjust width\" option is enabled"
flexible: "Auto-adjust width" flexible: "Auto-adjust width"
enableSyncBetweenDevicesForProfiles: "Enable profile information sync between devices"
_columns: _columns:
main: "Main" main: "Main"
widgets: "Widgets" widgets: "Widgets"
@@ -2674,8 +2653,10 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "Make sure the distributor of this resource is trustworthy before installation." checkVendorBeforeInstall: "Make sure the distributor of this resource is trustworthy before installation."
_plugin: _plugin:
title: "Do you want to install this plugin?" title: "Do you want to install this plugin?"
metaTitle: "Plugin information"
_theme: _theme:
title: "Do you want to install this theme?" title: "Do you want to install this theme?"
metaTitle: "Theme information"
_meta: _meta:
base: "Base color scheme" base: "Base color scheme"
_vendorInfo: _vendorInfo:
@@ -2876,8 +2857,8 @@ _selfXssPrevention:
description2: "If you do not understand exactly what you are trying to paste, %cstop working right now and close this window." description2: "If you do not understand exactly what you are trying to paste, %cstop working right now and close this window."
description3: "For more information, please refer to this. {link}" description3: "For more information, please refer to this. {link}"
_followRequest: _followRequest:
recieved: "Received request" recieved: "Received application"
sent: "Sent request" sent: "Sent application"
_remoteLookupErrors: _remoteLookupErrors:
_federationNotAllowed: _federationNotAllowed:
title: "Unable to communicate with this server" title: "Unable to communicate with this server"

View File

@@ -1300,7 +1300,6 @@ target: "Para"
federationSpecified: "Este servidor opera en una federación de listas blancas. No puede interactuar con otros servidores que no sean los especificados por el administrador." federationSpecified: "Este servidor opera en una federación de listas blancas. No puede interactuar con otros servidores que no sean los especificados por el administrador."
federationDisabled: "La federación está desactivada en este servidor. No puede interactuar con usuarios de otros servidores" federationDisabled: "La federación está desactivada en este servidor. No puede interactuar con usuarios de otros servidores"
postForm: "Formulario" postForm: "Formulario"
information: "Información"
_settings: _settings:
webhook: "Webhook" webhook: "Webhook"
_accountSettings: _accountSettings:
@@ -2522,8 +2521,10 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "Asegúrate de que el distribuidor de este recurso es de confianza antes de proceder a la instalación." checkVendorBeforeInstall: "Asegúrate de que el distribuidor de este recurso es de confianza antes de proceder a la instalación."
_plugin: _plugin:
title: "¿Quieres instalar este plugin?" title: "¿Quieres instalar este plugin?"
metaTitle: "Información del plugin"
_theme: _theme:
title: "¿Quieres instalar este tema?" title: "¿Quieres instalar este tema?"
metaTitle: "Información del tema"
_meta: _meta:
base: "Esquema de color base" base: "Esquema de color base"
_vendorInfo: _vendorInfo:

View File

@@ -1278,7 +1278,6 @@ lockdown: "Verrouiller"
pleaseSelectAccount: "Sélectionner un compte" pleaseSelectAccount: "Sélectionner un compte"
availableRoles: "Rôles disponibles" availableRoles: "Rôles disponibles"
postForm: "Formulaire de publication" postForm: "Formulaire de publication"
information: "Informations"
_abuseUserReport: _abuseUserReport:
forward: "Transférer" forward: "Transférer"
forwardDescription: "Transférer le signalement vers une instance distante en tant qu'anonyme." forwardDescription: "Transférer le signalement vers une instance distante en tant qu'anonyme."
@@ -2296,8 +2295,10 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "Veuillez confirmer que le distributeur est fiable avant l'installation." checkVendorBeforeInstall: "Veuillez confirmer que le distributeur est fiable avant l'installation."
_plugin: _plugin:
title: "Voulez-vous installer cette extension ?" title: "Voulez-vous installer cette extension ?"
metaTitle: "Informations sur l'extension"
_theme: _theme:
title: "Voulez-vous installer ce thème ?" title: "Voulez-vous installer ce thème ?"
metaTitle: "Informations sur le thème"
_meta: _meta:
base: "Palette de couleurs de base" base: "Palette de couleurs de base"
_vendorInfo: _vendorInfo:

View File

@@ -1262,7 +1262,6 @@ modified: "Diubah"
thereAreNChanges: "Ada {n} perubahan" thereAreNChanges: "Ada {n} perubahan"
prohibitedWordsForNameOfUser: "Kata yang dilarang untuk nama pengguna" prohibitedWordsForNameOfUser: "Kata yang dilarang untuk nama pengguna"
postForm: "Buat catatan" postForm: "Buat catatan"
information: "Informasi"
_settings: _settings:
webhook: "Webhook" webhook: "Webhook"
_abuseUserReport: _abuseUserReport:
@@ -2493,8 +2492,10 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "Pastikan sumber dari sumber daya ini terpercaya sebelum melakukan pemasangan." checkVendorBeforeInstall: "Pastikan sumber dari sumber daya ini terpercaya sebelum melakukan pemasangan."
_plugin: _plugin:
title: "Apakah kamu ingin memasang plugin ini?" title: "Apakah kamu ingin memasang plugin ini?"
metaTitle: "Informasi plugin"
_theme: _theme:
title: "Apakah kamu ingin memasang tema ini?" title: "Apakah kamu ingin memasang tema ini?"
metaTitle: "Informasi tema"
_meta: _meta:
base: "Skema warna dasar" base: "Skema warna dasar"
_vendorInfo: _vendorInfo:

32
locales/index.d.ts vendored
View File

@@ -2810,10 +2810,6 @@ export interface Locale extends ILocale {
* コピー * コピー
*/ */
"copy": string; "copy": string;
/**
* クリップボードにコピーされました
*/
"copiedToClipboard": string;
/** /**
* メトリクス * メトリクス
*/ */
@@ -5350,14 +5346,6 @@ export interface Locale extends ILocale {
* 投稿フォーム * 投稿フォーム
*/ */
"postForm": string; "postForm": string;
/**
* 文字数
*/
"textCount": string;
/**
* 情報
*/
"information": string;
"_emojiPalette": { "_emojiPalette": {
/** /**
* パレット * パレット
@@ -5445,14 +5433,6 @@ export interface Locale extends ILocale {
* タイムラインとノート * タイムラインとノート
*/ */
"timelineAndNote": string; "timelineAndNote": string;
/**
* 全てのテキスト要素を選択可能にする
*/
"makeEveryTextElementsSelectable": string;
/**
* 有効にすると、一部のシチュエーションでのユーザビリティが低下する場合があります。
*/
"makeEveryTextElementsSelectable_description": string;
}; };
"_preferencesProfile": { "_preferencesProfile": {
/** /**
@@ -9797,10 +9777,6 @@ export interface Locale extends ILocale {
* ログイン * ログイン
*/ */
"login": string; "login": string;
/**
* アクセストークンの作成
*/
"createToken": string;
/** /**
* 通知のテスト * 通知のテスト
*/ */
@@ -10363,12 +10339,20 @@ export interface Locale extends ILocale {
* このプラグインをインストールしますか? * このプラグインをインストールしますか?
*/ */
"title": string; "title": string;
/**
* プラグイン情報
*/
"metaTitle": string;
}; };
"_theme": { "_theme": {
/** /**
* このテーマをインストールしますか? * このテーマをインストールしますか?
*/ */
"title": string; "title": string;
/**
* テーマ情報
*/
"metaTitle": string;
}; };
"_meta": { "_meta": {
/** /**

View File

@@ -698,7 +698,6 @@ userSaysSomethingAbout: "{name} ha Notato a riguardo di \"{word}\""
makeActive: "Attiva" makeActive: "Attiva"
display: "Visualizza" display: "Visualizza"
copy: "Copia" copy: "Copia"
copiedToClipboard: "Copiato negli appunti"
metrics: "Statistiche" metrics: "Statistiche"
overview: "Anteprima" overview: "Anteprima"
logs: "Log" logs: "Log"
@@ -974,7 +973,7 @@ check: "Verifica"
driveCapOverrideLabel: "Modificare la capienza del Drive per questo profilo" driveCapOverrideLabel: "Modificare la capienza del Drive per questo profilo"
driveCapOverrideCaption: "Se viene specificato meno di 0, viene annullato." driveCapOverrideCaption: "Se viene specificato meno di 0, viene annullato."
requireAdminForView: "Per visualizzarli, è necessario aver effettuato l'accesso con un profilo amministratore." requireAdminForView: "Per visualizzarli, è necessario aver effettuato l'accesso con un profilo amministratore."
isSystemAccount: "Si tratta di un profilo creato e gestito automaticamente dal sistema." isSystemAccount: "Questi profili vengono creati e gestiti automaticamente dal sistema"
typeToConfirm: "Digita {x} per continuare" typeToConfirm: "Digita {x} per continuare"
deleteAccount: "Eliminazione profilo" deleteAccount: "Eliminazione profilo"
document: "Documentazione" document: "Documentazione"
@@ -1324,22 +1323,7 @@ untitled: "Senza titolo"
noName: "Senza nome" noName: "Senza nome"
skip: "Salta" skip: "Salta"
restore: "Ripristina" restore: "Ripristina"
syncBetweenDevices: "Sincronizzazione tra i dispositivi"
preferenceSyncConflictTitle: "Sul server esiste già il valore impostato"
preferenceSyncConflictText: "Le impostazione sincronizzata salverà il valore sul server. Però, bada che esiste già un valore sul server. Quale vorresti sovrascrivere?"
preferenceSyncConflictChoiceServer: "Valore del server"
preferenceSyncConflictChoiceDevice: "Valore del dispositivo"
preferenceSyncConflictChoiceCancel: "Annulla la sincronizzazione"
paste: "Incolla"
emojiPalette: "Tavolozza emoji"
postForm: "Finestra di pubblicazione" postForm: "Finestra di pubblicazione"
textCount: "Il numero di caratteri"
information: "Informazioni"
_emojiPalette:
palettes: "Tavolozza"
enableSyncBetweenDevicesForPalettes: "Attiva la sincronizzazione tra dispositivi"
paletteForMain: "Tavolozza principale"
paletteForReaction: "Tavolozza per reazioni"
_settings: _settings:
driveBanner: "Permette di gestire e configurare il Drive, controllare il consumo di spazio e configurare il caricamento dei file." driveBanner: "Permette di gestire e configurare il Drive, controllare il consumo di spazio e configurare il caricamento dei file."
pluginBanner: "Consentono di migliorare le funzionalità. Le estensioni si possono configurare e gestire singolarmente." pluginBanner: "Consentono di migliorare le funzionalità. Le estensioni si possono configurare e gestire singolarmente."
@@ -1357,9 +1341,6 @@ _settings:
preferencesBanner: "Puoi personalizzare il comportamento del tuo dispositivo." preferencesBanner: "Puoi personalizzare il comportamento del tuo dispositivo."
appearanceBanner: "Puoi personalizzare l'aspetto nel dispositivo, in base alle tue preferenze." appearanceBanner: "Puoi personalizzare l'aspetto nel dispositivo, in base alle tue preferenze."
soundsBanner: "Puoi personalizzare i suoni emessi dagli eventi sul tuo dispositivo." soundsBanner: "Puoi personalizzare i suoni emessi dagli eventi sul tuo dispositivo."
timelineAndNote: "Note e Timeline"
makeEveryTextElementsSelectable: "Imposta ogni elemento come selezionabile"
makeEveryTextElementsSelectable_description: "Potrebbe ridurre l'usabilità in alcune situazioni."
_preferencesProfile: _preferencesProfile:
profileName: "Nome del profilo" profileName: "Nome del profilo"
profileNameDescription: "Impostare il nome che indentifica questo dispositivo." profileNameDescription: "Impostare il nome che indentifica questo dispositivo."
@@ -2524,7 +2505,6 @@ _notification:
achievementEarned: "Risultato raggiunto" achievementEarned: "Risultato raggiunto"
exportCompleted: "Esportazione completata" exportCompleted: "Esportazione completata"
login: "Accessi" login: "Accessi"
createToken: "Creare un token di accesso"
test: "Notifiche di test" test: "Notifiche di test"
app: "Notifiche da applicazioni" app: "Notifiche da applicazioni"
_actions: _actions:
@@ -2552,7 +2532,6 @@ _deck:
useSimpleUiForNonRootPages: "Visualizza sotto pagine con interfaccia web semplice" useSimpleUiForNonRootPages: "Visualizza sotto pagine con interfaccia web semplice"
usedAsMinWidthWhenFlexible: "Se \"larghezza flessibile\" è abilitato, questa diventa la larghezza minima" usedAsMinWidthWhenFlexible: "Se \"larghezza flessibile\" è abilitato, questa diventa la larghezza minima"
flexible: "Larghezza flessibile" flexible: "Larghezza flessibile"
enableSyncBetweenDevicesForProfiles: "Abilita la sincronizzazione delle informazioni profilo tra dispositivi"
_columns: _columns:
main: "Principale" main: "Principale"
widgets: "Riquadri" widgets: "Riquadri"
@@ -2674,8 +2653,10 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "Prima di installare, assicurati che la fonte sia affidabile." checkVendorBeforeInstall: "Prima di installare, assicurati che la fonte sia affidabile."
_plugin: _plugin:
title: "Vuoi davvero installare questo componente aggiuntivo?" title: "Vuoi davvero installare questo componente aggiuntivo?"
metaTitle: "Informazioni sul componente aggiuntivo"
_theme: _theme:
title: "Vuoi davvero installare questa variazione grafica?" title: "Vuoi davvero installare questa variazione grafica?"
metaTitle: "Informazioni sulla variazione grafica"
_meta: _meta:
base: "Combinazione base di colori" base: "Combinazione base di colori"
_vendorInfo: _vendorInfo:

View File

@@ -698,7 +698,6 @@ userSaysSomethingAbout: "{name}が「{word}」について何かを言いまし
makeActive: "アクティブにする" makeActive: "アクティブにする"
display: "表示" display: "表示"
copy: "コピー" copy: "コピー"
copiedToClipboard: "クリップボードにコピーされました"
metrics: "メトリクス" metrics: "メトリクス"
overview: "概要" overview: "概要"
logs: "ログ" logs: "ログ"
@@ -1333,8 +1332,6 @@ preferenceSyncConflictChoiceCancel: "同期の有効化をキャンセル"
paste: "ペースト" paste: "ペースト"
emojiPalette: "絵文字パレット" emojiPalette: "絵文字パレット"
postForm: "投稿フォーム" postForm: "投稿フォーム"
textCount: "文字数"
information: "情報"
_emojiPalette: _emojiPalette:
palettes: "パレット" palettes: "パレット"
@@ -1360,8 +1357,6 @@ _settings:
appearanceBanner: "好みに応じた、クライアントの見た目・表示方法に関する設定が行えます。" appearanceBanner: "好みに応じた、クライアントの見た目・表示方法に関する設定が行えます。"
soundsBanner: "クライアントで再生するサウンドの設定が行えます。" soundsBanner: "クライアントで再生するサウンドの設定が行えます。"
timelineAndNote: "タイムラインとノート" timelineAndNote: "タイムラインとノート"
makeEveryTextElementsSelectable: "全てのテキスト要素を選択可能にする"
makeEveryTextElementsSelectable_description: "有効にすると、一部のシチュエーションでのユーザビリティが低下する場合があります。"
_preferencesProfile: _preferencesProfile:
profileName: "プロファイル名" profileName: "プロファイル名"
@@ -2589,7 +2584,6 @@ _notification:
achievementEarned: "実績の獲得" achievementEarned: "実績の獲得"
exportCompleted: "エクスポートが完了した" exportCompleted: "エクスポートが完了した"
login: "ログイン" login: "ログイン"
createToken: "アクセストークンの作成"
test: "通知のテスト" test: "通知のテスト"
app: "連携アプリからの通知" app: "連携アプリからの通知"
@@ -2750,8 +2744,10 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "配布元が信頼できるかを確認した上でインストールしてください。" checkVendorBeforeInstall: "配布元が信頼できるかを確認した上でインストールしてください。"
_plugin: _plugin:
title: "このプラグインをインストールしますか?" title: "このプラグインをインストールしますか?"
metaTitle: "プラグイン情報"
_theme: _theme:
title: "このテーマをインストールしますか?" title: "このテーマをインストールしますか?"
metaTitle: "テーマ情報"
_meta: _meta:
base: "基本のカラースキーム" base: "基本のカラースキーム"
_vendorInfo: _vendorInfo:

View File

@@ -1312,7 +1312,6 @@ federationDisabled: "このサーバーは連合が無効化されてるで。
confirmOnReact: "ツッコむときに確認とる" confirmOnReact: "ツッコむときに確認とる"
reactAreYouSure: "\" {emoji} \" でツッコむ?" reactAreYouSure: "\" {emoji} \" でツッコむ?"
postForm: "投稿フォーム" postForm: "投稿フォーム"
information: "情報"
_settings: _settings:
webhook: "Webhook" webhook: "Webhook"
_accountSettings: _accountSettings:
@@ -2611,8 +2610,10 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "配ってるとこが信頼できるか確認した上でインストールしてな。" checkVendorBeforeInstall: "配ってるとこが信頼できるか確認した上でインストールしてな。"
_plugin: _plugin:
title: "このプラグイン、インストールする?" title: "このプラグイン、インストールする?"
metaTitle: "プラグイン情報"
_theme: _theme:
title: "このテーマインストールする?" title: "このテーマインストールする?"
metaTitle: "テーマ情報"
_meta: _meta:
base: "" base: ""
_vendorInfo: _vendorInfo:

View File

@@ -655,7 +655,6 @@ replies: "답하기"
renotes: "리노트" renotes: "리노트"
attach: "옇기" attach: "옇기"
surrender: "아이예" surrender: "아이예"
information: "정보"
_delivery: _delivery:
stop: "고만 보내예" stop: "고만 보내예"
_type: _type:

View File

@@ -1330,7 +1330,6 @@ preferenceSyncConflictChoiceDevice: "장치 설정값"
paste: "붙여넣기" paste: "붙여넣기"
emojiPalette: "이모지 팔레트" emojiPalette: "이모지 팔레트"
postForm: "글 입력란" postForm: "글 입력란"
information: "정보"
_emojiPalette: _emojiPalette:
palettes: "팔레트" palettes: "팔레트"
paletteForMain: "메인으로 사용할 팔레트" paletteForMain: "메인으로 사용할 팔레트"
@@ -2651,8 +2650,10 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "제공자를 신뢰할 수 있는 경우에만 설치하십시오." checkVendorBeforeInstall: "제공자를 신뢰할 수 있는 경우에만 설치하십시오."
_plugin: _plugin:
title: "이 플러그인을 설치하시겠습니까?" title: "이 플러그인을 설치하시겠습니까?"
metaTitle: "플러그인 정보"
_theme: _theme:
title: "이 테마를 설치하시겠습니까?" title: "이 테마를 설치하시겠습니까?"
metaTitle: "테마 정보"
_meta: _meta:
base: "기본 컬러 스키마" base: "기본 컬러 스키마"
_vendorInfo: _vendorInfo:

View File

@@ -394,7 +394,6 @@ searchByGoogle: "ຄົ້ນຫາ"
file: "ໄຟລ໌" file: "ໄຟລ໌"
replies: "ຕອບ​ກັບ" replies: "ຕອບ​ກັບ"
renotes: "Renote" renotes: "Renote"
information: "ກ່ຽວກັບ"
_delivery: _delivery:
stop: "ໂຈະ" stop: "ໂຈະ"
_type: _type:

View File

@@ -462,7 +462,6 @@ loggedInAsBot: "Momenteel als bot ingelogd"
icon: "Avatar" icon: "Avatar"
replies: "Antwoord" replies: "Antwoord"
renotes: "Herdelen" renotes: "Herdelen"
information: "Over"
_delivery: _delivery:
stop: "Opgeschort" stop: "Opgeschort"
_type: _type:

View File

@@ -463,7 +463,6 @@ icon: "Avatar"
replies: "Svar" replies: "Svar"
renotes: "Renote" renotes: "Renote"
surrender: "Avbryt" surrender: "Avbryt"
information: "Informasjon"
_delivery: _delivery:
stop: "Suspendert" stop: "Suspendert"
_initialAccountSetting: _initialAccountSetting:

View File

@@ -1045,7 +1045,6 @@ lastNDays: "W ciągu ostatnich {n} dni"
surrender: "Odrzuć" surrender: "Odrzuć"
gameRetry: "Spróbuj ponownie" gameRetry: "Spróbuj ponownie"
postForm: "Formularz tworzenia wpisu" postForm: "Formularz tworzenia wpisu"
information: "Informacje"
_delivery: _delivery:
stop: "Zawieszono" stop: "Zawieszono"
_type: _type:

View File

@@ -1302,7 +1302,6 @@ pleaseSelectAccount: "Selecione uma conta"
availableRoles: "Cargos disponíveis" availableRoles: "Cargos disponíveis"
acknowledgeNotesAndEnable: "Ative após compreender as precauções." acknowledgeNotesAndEnable: "Ative após compreender as precauções."
postForm: "Campo de postagem" postForm: "Campo de postagem"
information: "Informações"
_settings: _settings:
webhook: "Webhook" webhook: "Webhook"
_accountSettings: _accountSettings:
@@ -2599,8 +2598,10 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "Tenha certeza de que o distribuidor desse recurso é confiável antes da instalação." checkVendorBeforeInstall: "Tenha certeza de que o distribuidor desse recurso é confiável antes da instalação."
_plugin: _plugin:
title: "Deseja instalar esse plugin?" title: "Deseja instalar esse plugin?"
metaTitle: "Informações do plugin"
_theme: _theme:
title: "Deseja instalar esse tema?" title: "Deseja instalar esse tema?"
metaTitle: "Informações do tema"
_meta: _meta:
base: "Paleta de cores base" base: "Paleta de cores base"
_vendorInfo: _vendorInfo:

View File

@@ -646,7 +646,6 @@ show: "Arată"
icon: "Avatar" icon: "Avatar"
replies: "Răspunde" replies: "Răspunde"
renotes: "Re-notează" renotes: "Re-notează"
information: "Despre"
_delivery: _delivery:
stop: "Suspendat" stop: "Suspendat"
_type: _type:

View File

@@ -1182,7 +1182,6 @@ alwaysConfirmFollow: "Всегда подтверждать подписку"
inquiry: "Связаться" inquiry: "Связаться"
messageToFollower: "Сообщение подписчикам" messageToFollower: "Сообщение подписчикам"
postForm: "Форма отправки" postForm: "Форма отправки"
information: "Описание"
_settings: _settings:
webhook: "Вебхук" webhook: "Вебхук"
_delivery: _delivery:

View File

@@ -918,7 +918,6 @@ sourceCode: "Zdrojový kód"
flip: "Preklopiť" flip: "Preklopiť"
lastNDays: "Posledných {n} dní" lastNDays: "Posledných {n} dní"
postForm: "Napísať poznámku" postForm: "Napísať poznámku"
information: "Informácie"
_delivery: _delivery:
stop: "Zmrazené" stop: "Zmrazené"
_type: _type:

View File

@@ -562,7 +562,6 @@ inquiry: "Kontakt"
tryAgain: "Försök igen senare" tryAgain: "Försök igen senare"
signinWithPasskey: "Logga in med nyckel" signinWithPasskey: "Logga in med nyckel"
unknownWebAuthnKey: "Okänd nyckel" unknownWebAuthnKey: "Okänd nyckel"
information: "Om"
_delivery: _delivery:
stop: "Suspenderad" stop: "Suspenderad"
_type: _type:

View File

@@ -1293,7 +1293,6 @@ prohibitedWordsForNameOfUserDescription: "หากมีสตริงใด
yourNameContainsProhibitedWords: "ชื่อของคุณนั้นมีคำที่ต้องห้าม" yourNameContainsProhibitedWords: "ชื่อของคุณนั้นมีคำที่ต้องห้าม"
yourNameContainsProhibitedWordsDescription: "ถ้าหากคุณต้องการใช้ชื่อนี้ กรุณาติดต่อผู้ดูแลระบบของเซิร์ฟเวอร์นะค่ะ" yourNameContainsProhibitedWordsDescription: "ถ้าหากคุณต้องการใช้ชื่อนี้ กรุณาติดต่อผู้ดูแลระบบของเซิร์ฟเวอร์นะค่ะ"
postForm: "แบบฟอร์มการโพสต์" postForm: "แบบฟอร์มการโพสต์"
information: "เกี่ยวกับ"
_settings: _settings:
webhook: "Webhook" webhook: "Webhook"
_abuseUserReport: _abuseUserReport:
@@ -2573,8 +2572,10 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "โปรดตรวจสอบให้แน่ใจว่าแหล่งแจกหน่ายมีความน่าเชื่อถือก่อนทำการติดตั้ง" checkVendorBeforeInstall: "โปรดตรวจสอบให้แน่ใจว่าแหล่งแจกหน่ายมีความน่าเชื่อถือก่อนทำการติดตั้ง"
_plugin: _plugin:
title: "ต้องการติดตั้งปลั๊กอินนี้ใช่ไหม?" title: "ต้องการติดตั้งปลั๊กอินนี้ใช่ไหม?"
metaTitle: "ข้อมูลส่วนเสริม"
_theme: _theme:
title: "ต้องการติดตั้งธีมนี้ใช่ไหม?" title: "ต้องการติดตั้งธีมนี้ใช่ไหม?"
metaTitle: "ข้อมูลธีม"
_meta: _meta:
base: "โทนสีพื้นฐาน" base: "โทนสีพื้นฐาน"
_vendorInfo: _vendorInfo:

View File

@@ -910,7 +910,6 @@ sourceCode: "Вихідний код"
flip: "Перевернути" flip: "Перевернути"
lastNDays: "Останні {n} днів" lastNDays: "Останні {n} днів"
postForm: "Створення нотатки" postForm: "Створення нотатки"
information: "Інформація"
_delivery: _delivery:
stop: "Призупинено" stop: "Призупинено"
_type: _type:

View File

@@ -841,7 +841,6 @@ icon: "Avatar"
replies: "Javob berish" replies: "Javob berish"
renotes: "Qayta qayd etish" renotes: "Qayta qayd etish"
flip: "Teskari" flip: "Teskari"
information: "Haqida"
_delivery: _delivery:
stop: "To'xtatilgan" stop: "To'xtatilgan"
_type: _type:

View File

@@ -1120,7 +1120,6 @@ cwNotationRequired: "Nếu \"Ẩn nội dung\" được bật thì cần phải
lastNDays: "{n} ngày trước" lastNDays: "{n} ngày trước"
surrender: "Từ chối" surrender: "Từ chối"
postForm: "Mẫu đăng" postForm: "Mẫu đăng"
information: "Giới thiệu"
_delivery: _delivery:
stop: "Đã vô hiệu hóa" stop: "Đã vô hiệu hóa"
_type: _type:

View File

@@ -698,7 +698,6 @@ userSaysSomethingAbout: "{name} 说了关于「{word}」的什么"
makeActive: "启用" makeActive: "启用"
display: "显示" display: "显示"
copy: "复制" copy: "复制"
copiedToClipboard: "已复制到剪贴板"
metrics: "指标" metrics: "指标"
overview: "概览" overview: "概览"
logs: "日志" logs: "日志"
@@ -747,7 +746,7 @@ confirmToUnclipAlreadyClippedNote: "本帖已包含在便签 \"{name}\" 里。
public: "公开" public: "公开"
private: "私密" private: "私密"
i18nInfo: "Misskey 已经被志愿者们翻译成了各种语言。如果你也有兴趣,可以通过 {link} 帮助翻译。" i18nInfo: "Misskey 已经被志愿者们翻译成了各种语言。如果你也有兴趣,可以通过 {link} 帮助翻译。"
manageAccessTokens: "管理访问令牌" manageAccessTokens: "管理 Access Tokens"
accountInfo: "账户信息" accountInfo: "账户信息"
notesCount: "帖子数量" notesCount: "帖子数量"
repliesCount: "回复数量" repliesCount: "回复数量"
@@ -1333,8 +1332,6 @@ preferenceSyncConflictChoiceCancel: "取消同步"
paste: "粘贴" paste: "粘贴"
emojiPalette: "表情符号调色板" emojiPalette: "表情符号调色板"
postForm: "投稿窗口" postForm: "投稿窗口"
textCount: "字数"
information: "关于"
_emojiPalette: _emojiPalette:
palettes: "调色板" palettes: "调色板"
enableSyncBetweenDevicesForPalettes: "启用调色板的设备间同步" enableSyncBetweenDevicesForPalettes: "启用调色板的设备间同步"
@@ -1342,24 +1339,13 @@ _emojiPalette:
paletteForReaction: "回应用调色板" paletteForReaction: "回应用调色板"
_settings: _settings:
driveBanner: "可在此管理和设置网盘、确认使用量及配置上传文件的设置。" driveBanner: "可在此管理和设置网盘、确认使用量及配置上传文件的设置。"
pluginBanner: "使用插件可以扩展客户端的功能。可以在此安装、单独管理插件。"
notificationsBanner: "可在此设置从服务器接收的通知的种类和范围,以及推送通知的设置。"
api: "API" api: "API"
webhook: "Webhook" webhook: "Webhook"
serviceConnection: "连接服务"
serviceConnectionBanner: "可在此管理用于连接外部应用或服务的访问令牌及 Webhook。"
accountData: "账户数据"
accountDataBanner: "可在此导入或导出帐户数据的存档。"
muteAndBlockBanner: "可在此设置隐藏内容,或限制指定用户能进行的操作。"
accessibilityBanner: "可在此设置客户端的显示及动态效果等辅助设置。"
privacyBanner: "可在此设置如内容可见性、可发现性、批准关注请求等账户隐私设置。" privacyBanner: "可在此设置如内容可见性、可发现性、批准关注请求等账户隐私设置。"
securityBanner: "可在此设置如密码、登入方式、验证器、Passkey 等账户安全性设置。" securityBanner: "可在此设置如密码、登入方式、验证器、Passkey 等账户安全性设置。"
preferencesBanner: "可在此设置客户端的整体运作行为。" preferencesBanner: "可在此设置客户端的整体运作行为。"
appearanceBanner: "可在此设置客户端的外观及显示方式。" appearanceBanner: "可在此设置客户端的外观及显示方式。"
soundsBanner: "可在此设置客户端播放的声音。" soundsBanner: "可在此设置客户端播放的声音。"
timelineAndNote: "时间线和帖子"
makeEveryTextElementsSelectable: "使所有的文字均可选择"
makeEveryTextElementsSelectable_description: "若开启,在某些情况下可能降低用户体验。"
_preferencesProfile: _preferencesProfile:
profileName: "配置名" profileName: "配置名"
profileNameDescription: "请指定用于识别此设备的名称" profileNameDescription: "请指定用于识别此设备的名称"
@@ -2524,7 +2510,6 @@ _notification:
achievementEarned: "取得的成就" achievementEarned: "取得的成就"
exportCompleted: "已完成导出" exportCompleted: "已完成导出"
login: "登录" login: "登录"
createToken: "创建访问令牌"
test: "测试通知" test: "测试通知"
app: "关联应用的通知" app: "关联应用的通知"
_actions: _actions:
@@ -2674,8 +2659,10 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "请在安装前确保来源可靠" checkVendorBeforeInstall: "请在安装前确保来源可靠"
_plugin: _plugin:
title: "要安装此插件吗?" title: "要安装此插件吗?"
metaTitle: "插件信息"
_theme: _theme:
title: "要安装此主题吗?" title: "要安装此主题吗?"
metaTitle: "主题信息"
_meta: _meta:
base: "基本配色方案" base: "基本配色方案"
_vendorInfo: _vendorInfo:

View File

@@ -698,7 +698,6 @@ userSaysSomethingAbout: "{name} 說了一些關於「{word}」的話"
makeActive: "啟用" makeActive: "啟用"
display: "檢視" display: "檢視"
copy: "複製" copy: "複製"
copiedToClipboard: "已複製到剪貼簿"
metrics: "指標" metrics: "指標"
overview: "概覽" overview: "概覽"
logs: "日誌" logs: "日誌"
@@ -1333,8 +1332,6 @@ preferenceSyncConflictChoiceCancel: "取消啟用同步"
paste: "貼上" paste: "貼上"
emojiPalette: "表情符號調色盤" emojiPalette: "表情符號調色盤"
postForm: "發文視窗" postForm: "發文視窗"
textCount: "字數"
information: "關於"
_emojiPalette: _emojiPalette:
palettes: "調色盤" palettes: "調色盤"
enableSyncBetweenDevicesForPalettes: "啟用裝置與裝置之間的調色盤同步化" enableSyncBetweenDevicesForPalettes: "啟用裝置與裝置之間的調色盤同步化"
@@ -1358,8 +1355,6 @@ _settings:
appearanceBanner: "您可以根據喜好設定與用戶端外觀和顯示方式相關的設定。" appearanceBanner: "您可以根據喜好設定與用戶端外觀和顯示方式相關的設定。"
soundsBanner: "您可以調整用戶端播放的聲音設定。" soundsBanner: "您可以調整用戶端播放的聲音設定。"
timelineAndNote: "時間軸及貼文" timelineAndNote: "時間軸及貼文"
makeEveryTextElementsSelectable: "允許選取所有文字"
makeEveryTextElementsSelectable_description: "啟用此功能後,可能會在某些情境下降低可用性。"
_preferencesProfile: _preferencesProfile:
profileName: "設定檔案名稱" profileName: "設定檔案名稱"
profileNameDescription: "設定一個名稱來識別此裝置。" profileNameDescription: "設定一個名稱來識別此裝置。"
@@ -2524,7 +2519,6 @@ _notification:
achievementEarned: "獲得成就" achievementEarned: "獲得成就"
exportCompleted: "已完成匯出。" exportCompleted: "已完成匯出。"
login: "登入" login: "登入"
createToken: "建立存取權杖"
test: "通知測試" test: "通知測試"
app: "應用程式通知" app: "應用程式通知"
_actions: _actions:
@@ -2674,8 +2668,10 @@ _externalResourceInstaller:
checkVendorBeforeInstall: "安裝前請確認提供者是可信賴的。" checkVendorBeforeInstall: "安裝前請確認提供者是可信賴的。"
_plugin: _plugin:
title: "要安裝此外掛嘛?" title: "要安裝此外掛嘛?"
metaTitle: "外掛資訊"
_theme: _theme:
title: "要安裝此佈景主題嗎?" title: "要安裝此佈景主題嗎?"
metaTitle: "佈景主題資訊"
_meta: _meta:
base: "基本配色方案" base: "基本配色方案"
_vendorInfo: _vendorInfo:

View File

@@ -1,6 +1,6 @@
{ {
"name": "misskey", "name": "misskey",
"version": "2025.3.2-beta.9", "version": "2025.3.2-beta.0",
"codename": "nasubi", "codename": "nasubi",
"repository": { "repository": {
"type": "git", "type": "git",

View File

@@ -37,17 +37,17 @@
}, },
"optionalDependencies": { "optionalDependencies": {
"@swc/core-android-arm64": "1.3.11", "@swc/core-android-arm64": "1.3.11",
"@swc/core-darwin-arm64": "1.11.11", "@swc/core-darwin-arm64": "1.10.16",
"@swc/core-darwin-x64": "1.11.11", "@swc/core-darwin-x64": "1.10.16",
"@swc/core-freebsd-x64": "1.3.11", "@swc/core-freebsd-x64": "1.3.11",
"@swc/core-linux-arm-gnueabihf": "1.11.11", "@swc/core-linux-arm-gnueabihf": "1.10.16",
"@swc/core-linux-arm64-gnu": "1.11.11", "@swc/core-linux-arm64-gnu": "1.10.16",
"@swc/core-linux-arm64-musl": "1.11.11", "@swc/core-linux-arm64-musl": "1.10.16",
"@swc/core-linux-x64-gnu": "1.11.11", "@swc/core-linux-x64-gnu": "1.10.16",
"@swc/core-linux-x64-musl": "1.11.11", "@swc/core-linux-x64-musl": "1.10.16",
"@swc/core-win32-arm64-msvc": "1.11.11", "@swc/core-win32-arm64-msvc": "1.10.16",
"@swc/core-win32-ia32-msvc": "1.11.11", "@swc/core-win32-ia32-msvc": "1.10.16",
"@swc/core-win32-x64-msvc": "1.11.11", "@swc/core-win32-x64-msvc": "1.10.16",
"@tensorflow/tfjs": "4.22.0", "@tensorflow/tfjs": "4.22.0",
"@tensorflow/tfjs-node": "4.22.0", "@tensorflow/tfjs-node": "4.22.0",
"bufferutil": "4.0.9", "bufferutil": "4.0.9",
@@ -67,23 +67,26 @@
"utf-8-validate": "6.0.5" "utf-8-validate": "6.0.5"
}, },
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "3.772.0", "@aws-sdk/client-s3": "3.749.0",
"@aws-sdk/lib-storage": "3.772.0", "@aws-sdk/lib-storage": "3.749.0",
"@bull-board/api": "6.7.7",
"@bull-board/fastify": "6.7.7",
"@bull-board/ui": "6.7.7",
"@discordapp/twemoji": "15.1.0", "@discordapp/twemoji": "15.1.0",
"@fastify/accepts": "5.0.2", "@fastify/accepts": "5.0.2",
"@fastify/cookie": "11.0.2", "@fastify/cookie": "11.0.2",
"@fastify/cors": "10.1.0", "@fastify/cors": "10.0.2",
"@fastify/express": "4.0.2", "@fastify/express": "4.0.2",
"@fastify/http-proxy": "10.0.2", "@fastify/http-proxy": "10.0.2",
"@fastify/multipart": "9.0.3", "@fastify/multipart": "9.0.3",
"@fastify/static": "8.1.1", "@fastify/static": "8.1.0",
"@fastify/view": "10.0.2", "@fastify/view": "10.0.2",
"@misskey-dev/sharp-read-bmp": "1.2.0", "@misskey-dev/sharp-read-bmp": "1.2.0",
"@misskey-dev/summaly": "5.2.0", "@misskey-dev/summaly": "5.2.0",
"@napi-rs/canvas": "0.1.68", "@napi-rs/canvas": "0.1.67",
"@nestjs/common": "11.0.12", "@nestjs/common": "11.0.9",
"@nestjs/core": "11.0.12", "@nestjs/core": "11.0.9",
"@nestjs/testing": "11.0.12", "@nestjs/testing": "11.0.9",
"@peertube/http-signature": "1.7.0", "@peertube/http-signature": "1.7.0",
"@sentry/node": "8.55.0", "@sentry/node": "8.55.0",
"@sentry/profiling-node": "8.55.0", "@sentry/profiling-node": "8.55.0",
@@ -91,7 +94,7 @@
"@sinonjs/fake-timers": "11.3.1", "@sinonjs/fake-timers": "11.3.1",
"@smithy/node-http-handler": "2.5.0", "@smithy/node-http-handler": "2.5.0",
"@swc/cli": "0.6.0", "@swc/cli": "0.6.0",
"@swc/core": "1.11.11", "@swc/core": "1.10.16",
"@twemoji/parser": "15.1.1", "@twemoji/parser": "15.1.1",
"accepts": "1.3.8", "accepts": "1.3.8",
"ajv": "8.17.1", "ajv": "8.17.1",
@@ -100,7 +103,7 @@
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"blurhash": "2.0.5", "blurhash": "2.0.5",
"body-parser": "1.20.3", "body-parser": "1.20.3",
"bullmq": "5.44.1", "bullmq": "5.41.1",
"cacheable-lookup": "7.0.0", "cacheable-lookup": "7.0.0",
"cbor": "9.0.2", "cbor": "9.0.2",
"chalk": "5.4.1", "chalk": "5.4.1",
@@ -122,7 +125,7 @@
"hpagent": "1.2.0", "hpagent": "1.2.0",
"htmlescape": "1.1.1", "htmlescape": "1.1.1",
"http-link-header": "1.1.3", "http-link-header": "1.1.3",
"ioredis": "5.6.0", "ioredis": "5.5.0",
"ip-cidr": "4.0.2", "ip-cidr": "4.0.2",
"ipaddr.js": "2.2.0", "ipaddr.js": "2.2.0",
"is-svg": "5.1.0", "is-svg": "5.1.0",
@@ -131,26 +134,26 @@
"json5": "2.2.3", "json5": "2.2.3",
"jsonld": "8.3.3", "jsonld": "8.3.3",
"jsrsasign": "11.1.0", "jsrsasign": "11.1.0",
"juice": "11.0.1", "juice": "11.0.0",
"meilisearch": "0.49.0", "meilisearch": "0.48.2",
"mfm-js": "0.24.0", "mfm-js": "0.24.0",
"microformats-parser": "2.0.2", "microformats-parser": "2.0.2",
"mime-types": "2.1.35", "mime-types": "2.1.35",
"misskey-js": "workspace:*", "misskey-js": "workspace:*",
"misskey-reversi": "workspace:*", "misskey-reversi": "workspace:*",
"ms": "3.0.0-canary.1", "ms": "3.0.0-canary.1",
"nanoid": "5.1.5", "nanoid": "5.1.0",
"nested-property": "4.0.0", "nested-property": "4.0.0",
"node-fetch": "3.3.2", "node-fetch": "3.3.2",
"nodemailer": "6.10.0", "nodemailer": "6.10.0",
"nsfwjs": "4.2.0", "nsfwjs": "4.2.0",
"oauth": "0.10.2", "oauth": "0.10.0",
"oauth2orize": "1.12.0", "oauth2orize": "1.12.0",
"oauth2orize-pkce": "0.1.2", "oauth2orize-pkce": "0.1.2",
"os-utils": "0.0.14", "os-utils": "0.0.14",
"otpauth": "9.3.6", "otpauth": "9.3.6",
"parse5": "7.2.1", "parse5": "7.2.1",
"pg": "8.14.1", "pg": "8.13.3",
"pkce-challenge": "4.1.0", "pkce-challenge": "4.1.0",
"probe-image-size": "7.2.3", "probe-image-size": "7.2.3",
"promise-limit": "2.7.0", "promise-limit": "2.7.0",
@@ -163,8 +166,8 @@
"reflect-metadata": "0.2.2", "reflect-metadata": "0.2.2",
"rename": "1.0.4", "rename": "1.0.4",
"rss-parser": "3.13.0", "rss-parser": "3.13.0",
"rxjs": "7.8.2", "rxjs": "7.8.1",
"sanitize-html": "2.15.0", "sanitize-html": "2.14.0",
"secure-json-parse": "3.0.2", "secure-json-parse": "3.0.2",
"sharp": "0.33.5", "sharp": "0.33.5",
"slacc": "0.0.10", "slacc": "0.0.10",
@@ -173,14 +176,14 @@
"systeminformation": "5.25.11", "systeminformation": "5.25.11",
"tinycolor2": "1.6.0", "tinycolor2": "1.6.0",
"tmp": "0.2.3", "tmp": "0.2.3",
"tsc-alias": "1.8.11", "tsc-alias": "1.8.10",
"tsconfig-paths": "4.2.0", "tsconfig-paths": "4.2.0",
"typeorm": "0.3.21", "typeorm": "0.3.20",
"typescript": "5.8.2", "typescript": "5.7.3",
"ulid": "2.4.0", "ulid": "2.3.0",
"vary": "1.1.2", "vary": "1.1.2",
"web-push": "3.6.7", "web-push": "3.6.7",
"ws": "8.18.1", "ws": "8.18.0",
"xev": "3.0.2" "xev": "3.0.2"
}, },
"devDependencies": { "devDependencies": {
@@ -204,7 +207,7 @@
"@types/jsrsasign": "10.5.15", "@types/jsrsasign": "10.5.15",
"@types/mime-types": "2.1.4", "@types/mime-types": "2.1.4",
"@types/ms": "0.7.34", "@types/ms": "0.7.34",
"@types/node": "22.13.10", "@types/node": "22.13.4",
"@types/nodemailer": "6.4.17", "@types/nodemailer": "6.4.17",
"@types/oauth": "0.9.6", "@types/oauth": "0.9.6",
"@types/oauth2orize": "1.11.5", "@types/oauth2orize": "1.11.5",
@@ -223,9 +226,9 @@
"@types/tmp": "0.2.6", "@types/tmp": "0.2.6",
"@types/vary": "1.1.3", "@types/vary": "1.1.3",
"@types/web-push": "3.6.4", "@types/web-push": "3.6.4",
"@types/ws": "8.18.0", "@types/ws": "8.5.14",
"@typescript-eslint/eslint-plugin": "8.27.0", "@typescript-eslint/eslint-plugin": "8.24.0",
"@typescript-eslint/parser": "8.27.0", "@typescript-eslint/parser": "8.24.0",
"aws-sdk-client-mock": "4.1.0", "aws-sdk-client-mock": "4.1.0",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"eslint-plugin-import": "2.31.0", "eslint-plugin-import": "2.31.0",

View File

@@ -7,16 +7,42 @@ import { Injectable } from '@nestjs/common';
import { MiAbuseUserReport, MiNote, MiUser, MiWebhook } from '@/models/_.js'; import { MiAbuseUserReport, MiNote, MiUser, MiWebhook } from '@/models/_.js';
import { bindThis } from '@/decorators.js'; import { bindThis } from '@/decorators.js';
import { MiSystemWebhook, type SystemWebhookEventType } from '@/models/SystemWebhook.js'; import { MiSystemWebhook, type SystemWebhookEventType } from '@/models/SystemWebhook.js';
import { type AbuseReportPayload, SystemWebhookPayload, SystemWebhookService } from '@/core/SystemWebhookService.js'; import { AbuseReportPayload, SystemWebhookPayload, SystemWebhookService } from '@/core/SystemWebhookService.js';
import { type Packed } from '@/misc/json-schema.js'; import { Packed } from '@/misc/json-schema.js';
import { type WebhookEventTypes } from '@/models/Webhook.js'; import { type WebhookEventTypes } from '@/models/Webhook.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
import { type UserWebhookPayload, UserWebhookService } from '@/core/UserWebhookService.js'; import { type UserWebhookPayload, UserWebhookService } from '@/core/UserWebhookService.js';
import { QueueService } from '@/core/QueueService.js'; import { QueueService } from '@/core/QueueService.js';
import { ModeratorInactivityRemainingTime } from '@/queue/processors/CheckModeratorsActivityProcessorService.js'; import { ModeratorInactivityRemainingTime } from '@/queue/processors/CheckModeratorsActivityProcessorService.js';
const oneDayMillis = 24 * 60 * 60 * 1000; const oneDayMillis = 24 * 60 * 60 * 1000;
function generateAbuseReport(override?: Partial<MiAbuseUserReport>): AbuseReportPayload {
const result: MiAbuseUserReport = {
id: 'dummy-abuse-report1',
targetUserId: 'dummy-target-user',
targetUser: null,
reporterId: 'dummy-reporter-user',
reporter: null,
assigneeId: null,
assignee: null,
resolved: false,
forwarded: false,
comment: 'This is a dummy report for testing purposes.',
targetUserHost: null,
reporterHost: null,
resolvedAs: null,
moderationNote: 'foo',
...override,
};
return {
...result,
targetUser: result.targetUser ? toPackedUserLite(result.targetUser) : null,
reporter: result.reporter ? toPackedUserLite(result.reporter) : null,
assignee: result.assignee ? toPackedUserLite(result.assignee) : null,
};
}
function generateDummyUser(override?: Partial<MiUser>): MiUser { function generateDummyUser(override?: Partial<MiUser>): MiUser {
return { return {
id: 'dummy-user-1', id: 'dummy-user-1',
@@ -108,6 +134,124 @@ function generateDummyNote(override?: Partial<MiNote>): MiNote {
}; };
} }
function toPackedNote(note: MiNote, detail = true, override?: Packed<'Note'>): Packed<'Note'> {
return {
id: note.id,
createdAt: new Date().toISOString(),
deletedAt: null,
text: note.text,
cw: note.cw,
userId: note.userId,
user: toPackedUserLite(note.user ?? generateDummyUser()),
replyId: note.replyId,
renoteId: note.renoteId,
isHidden: false,
visibility: note.visibility,
mentions: note.mentions,
visibleUserIds: note.visibleUserIds,
fileIds: note.fileIds,
files: [],
tags: note.tags,
poll: null,
emojis: note.emojis,
channelId: note.channelId,
channel: note.channel,
localOnly: note.localOnly,
reactionAcceptance: note.reactionAcceptance,
reactionEmojis: {},
reactions: {},
reactionCount: 0,
renoteCount: note.renoteCount,
repliesCount: note.repliesCount,
uri: note.uri ?? undefined,
url: note.url ?? undefined,
reactionAndUserPairCache: note.reactionAndUserPairCache,
...(detail ? {
clippedCount: note.clippedCount,
reply: note.reply ? toPackedNote(note.reply, false) : null,
renote: note.renote ? toPackedNote(note.renote, true) : null,
myReaction: null,
} : {}),
...override,
};
}
function toPackedUserLite(user: MiUser, override?: Packed<'UserLite'>): Packed<'UserLite'> {
return {
id: user.id,
name: user.name,
username: user.username,
host: user.host,
avatarUrl: user.avatarUrl,
avatarBlurhash: user.avatarBlurhash,
avatarDecorations: user.avatarDecorations.map(it => ({
id: it.id,
angle: it.angle,
flipH: it.flipH,
url: 'https://example.com/dummy-image001.png',
offsetX: it.offsetX,
offsetY: it.offsetY,
})),
isBot: user.isBot,
isCat: user.isCat,
emojis: user.emojis,
onlineStatus: 'active',
badgeRoles: [],
...override,
};
}
function toPackedUserDetailedNotMe(user: MiUser, override?: Packed<'UserDetailedNotMe'>): Packed<'UserDetailedNotMe'> {
return {
...toPackedUserLite(user),
url: null,
uri: null,
movedTo: null,
alsoKnownAs: [],
createdAt: new Date().toISOString(),
updatedAt: user.updatedAt?.toISOString() ?? null,
lastFetchedAt: user.lastFetchedAt?.toISOString() ?? null,
bannerUrl: user.bannerUrl,
bannerBlurhash: user.bannerBlurhash,
isLocked: user.isLocked,
isSilenced: false,
isSuspended: user.isSuspended,
description: null,
location: null,
birthday: null,
lang: null,
fields: [],
verifiedLinks: [],
followersCount: user.followersCount,
followingCount: user.followingCount,
notesCount: user.notesCount,
pinnedNoteIds: [],
pinnedNotes: [],
pinnedPageId: null,
pinnedPage: null,
publicReactions: true,
followersVisibility: 'public',
followingVisibility: 'public',
twoFactorEnabled: false,
usePasswordLessLogin: false,
securityKeys: false,
roles: [],
memo: null,
moderationNote: undefined,
isFollowing: false,
isFollowed: false,
hasPendingFollowRequestFromYou: false,
hasPendingFollowRequestToYou: false,
isBlocking: false,
isBlocked: false,
isMuted: false,
isRenoteMuted: false,
notify: 'none',
withReplies: true,
...override,
};
}
const dummyUser1 = generateDummyUser(); const dummyUser1 = generateDummyUser();
const dummyUser2 = generateDummyUser({ const dummyUser2 = generateDummyUser({
id: 'dummy-user-2', id: 'dummy-user-2',
@@ -140,7 +284,6 @@ export class WebhookTestService {
}; };
constructor( constructor(
private customEmojiService: CustomEmojiService,
private userWebhookService: UserWebhookService, private userWebhookService: UserWebhookService,
private systemWebhookService: SystemWebhookService, private systemWebhookService: SystemWebhookService,
private queueService: QueueService, private queueService: QueueService,
@@ -211,31 +354,31 @@ export class WebhookTestService {
switch (params.type) { switch (params.type) {
case 'note': { case 'note': {
send('note', { note: await this.toPackedNote(dummyNote1) }); send('note', { note: toPackedNote(dummyNote1) });
break; break;
} }
case 'reply': { case 'reply': {
send('reply', { note: await this.toPackedNote(dummyReply1) }); send('reply', { note: toPackedNote(dummyReply1) });
break; break;
} }
case 'renote': { case 'renote': {
send('renote', { note: await this.toPackedNote(dummyRenote1) }); send('renote', { note: toPackedNote(dummyRenote1) });
break; break;
} }
case 'mention': { case 'mention': {
send('mention', { note: await this.toPackedNote(dummyMention1) }); send('mention', { note: toPackedNote(dummyMention1) });
break; break;
} }
case 'follow': { case 'follow': {
send('follow', { user: await this.toPackedUserDetailedNotMe(dummyUser1) }); send('follow', { user: toPackedUserDetailedNotMe(dummyUser1) });
break; break;
} }
case 'followed': { case 'followed': {
send('followed', { user: await this.toPackedUserLite(dummyUser2) }); send('followed', { user: toPackedUserLite(dummyUser2) });
break; break;
} }
case 'unfollow': { case 'unfollow': {
send('unfollow', { user: await this.toPackedUserDetailedNotMe(dummyUser3) }); send('unfollow', { user: toPackedUserDetailedNotMe(dummyUser3) });
break; break;
} }
// まだ実装されていない (#9485) // まだ実装されていない (#9485)
@@ -284,7 +427,7 @@ export class WebhookTestService {
switch (params.type) { switch (params.type) {
case 'abuseReport': { case 'abuseReport': {
send('abuseReport', await this.generateAbuseReport({ send('abuseReport', generateAbuseReport({
targetUserId: dummyUser1.id, targetUserId: dummyUser1.id,
targetUser: dummyUser1, targetUser: dummyUser1,
reporterId: dummyUser2.id, reporterId: dummyUser2.id,
@@ -293,7 +436,7 @@ export class WebhookTestService {
break; break;
} }
case 'abuseReportResolved': { case 'abuseReportResolved': {
send('abuseReportResolved', await this.generateAbuseReport({ send('abuseReportResolved', generateAbuseReport({
targetUserId: dummyUser1.id, targetUserId: dummyUser1.id,
targetUser: dummyUser1, targetUser: dummyUser1,
reporterId: dummyUser2.id, reporterId: dummyUser2.id,
@@ -305,7 +448,7 @@ export class WebhookTestService {
break; break;
} }
case 'userCreated': { case 'userCreated': {
send('userCreated', await this.toPackedUserLite(dummyUser1)); send('userCreated', toPackedUserLite(dummyUser1));
break; break;
} }
case 'inactiveModeratorsWarning': { case 'inactiveModeratorsWarning': {
@@ -331,153 +474,4 @@ export class WebhookTestService {
} }
} }
} }
@bindThis
private async generateAbuseReport(override?: Partial<MiAbuseUserReport>): Promise<AbuseReportPayload> {
const result: MiAbuseUserReport = {
id: 'dummy-abuse-report1',
targetUserId: 'dummy-target-user',
targetUser: null,
reporterId: 'dummy-reporter-user',
reporter: null,
assigneeId: null,
assignee: null,
resolved: false,
forwarded: false,
comment: 'This is a dummy report for testing purposes.',
targetUserHost: null,
reporterHost: null,
resolvedAs: null,
moderationNote: 'foo',
...override,
};
return {
...result,
targetUser: result.targetUser ? await this.toPackedUserLite(result.targetUser) : null,
reporter: result.reporter ? await this.toPackedUserLite(result.reporter) : null,
assignee: result.assignee ? await this.toPackedUserLite(result.assignee) : null,
};
}
@bindThis
private async toPackedNote(note: MiNote, detail = true, override?: Packed<'Note'>): Promise<Packed<'Note'>> {
return {
id: note.id,
createdAt: new Date().toISOString(),
deletedAt: null,
text: note.text,
cw: note.cw,
userId: note.userId,
user: await this.toPackedUserLite(note.user ?? generateDummyUser()),
replyId: note.replyId,
renoteId: note.renoteId,
isHidden: false,
visibility: note.visibility,
mentions: note.mentions,
visibleUserIds: note.visibleUserIds,
fileIds: note.fileIds,
files: [],
tags: note.tags,
poll: null,
emojis: await this.customEmojiService.populateEmojis(note.emojis, note.userHost),
channelId: note.channelId,
channel: note.channel,
localOnly: note.localOnly,
reactionAcceptance: note.reactionAcceptance,
reactionEmojis: {},
reactions: {},
reactionCount: 0,
renoteCount: note.renoteCount,
repliesCount: note.repliesCount,
uri: note.uri ?? undefined,
url: note.url ?? undefined,
reactionAndUserPairCache: note.reactionAndUserPairCache,
...(detail ? {
clippedCount: note.clippedCount,
reply: note.reply ? await this.toPackedNote(note.reply, false) : null,
renote: note.renote ? await this.toPackedNote(note.renote, true) : null,
myReaction: null,
} : {}),
...override,
};
}
@bindThis
private async toPackedUserLite(user: MiUser, override?: Packed<'UserLite'>): Promise<Packed<'UserLite'>> {
return {
id: user.id,
name: user.name,
username: user.username,
host: user.host,
avatarUrl: user.avatarUrl,
avatarBlurhash: user.avatarBlurhash,
avatarDecorations: user.avatarDecorations.map(it => ({
id: it.id,
angle: it.angle,
flipH: it.flipH,
url: 'https://example.com/dummy-image001.png',
offsetX: it.offsetX,
offsetY: it.offsetY,
})),
isBot: user.isBot,
isCat: user.isCat,
emojis: await this.customEmojiService.populateEmojis(user.emojis, user.host),
onlineStatus: 'active',
badgeRoles: [],
...override,
};
}
@bindThis
private async toPackedUserDetailedNotMe(user: MiUser, override?: Packed<'UserDetailedNotMe'>): Promise<Packed<'UserDetailedNotMe'>> {
return {
...await this.toPackedUserLite(user),
url: null,
uri: null,
movedTo: null,
alsoKnownAs: [],
createdAt: new Date().toISOString(),
updatedAt: user.updatedAt?.toISOString() ?? null,
lastFetchedAt: user.lastFetchedAt?.toISOString() ?? null,
bannerUrl: user.bannerUrl,
bannerBlurhash: user.bannerBlurhash,
isLocked: user.isLocked,
isSilenced: false,
isSuspended: user.isSuspended,
description: null,
location: null,
birthday: null,
lang: null,
fields: [],
verifiedLinks: [],
followersCount: user.followersCount,
followingCount: user.followingCount,
notesCount: user.notesCount,
pinnedNoteIds: [],
pinnedNotes: [],
pinnedPageId: null,
pinnedPage: null,
publicReactions: true,
followersVisibility: 'public',
followingVisibility: 'public',
twoFactorEnabled: false,
usePasswordLessLogin: false,
securityKeys: false,
roles: [],
memo: null,
moderationNote: undefined,
isFollowing: false,
isFollowed: false,
hasPendingFollowRequestFromYou: false,
hasPendingFollowRequestToYou: false,
isBlocking: false,
isBlocked: false,
isMuted: false,
isRenoteMuted: false,
notify: 'none',
withReplies: true,
...override,
};
}
} }

View File

@@ -4,7 +4,6 @@
*/ */
import * as fs from 'node:fs/promises'; import * as fs from 'node:fs/promises';
import { WritableStream } from 'node:stream/web';
import type { PathLike } from 'node:fs'; import type { PathLike } from 'node:fs';
/** /**

View File

@@ -166,7 +166,6 @@ export interface Schema extends OfSchema {
readonly maximum?: number; readonly maximum?: number;
readonly minimum?: number; readonly minimum?: number;
readonly pattern?: string; readonly pattern?: string;
readonly additionalProperties?: Schema | boolean;
} }
type RequiredPropertyNames<s extends Obj> = { type RequiredPropertyNames<s extends Obj> = {
@@ -218,13 +217,6 @@ type ObjectSchemaTypeDef<p extends Schema> =
: :
p['anyOf'] extends ReadonlyArray<Schema> ? never : // see CONTRIBUTING.md p['anyOf'] extends ReadonlyArray<Schema> ? never : // see CONTRIBUTING.md
p['allOf'] extends ReadonlyArray<Schema> ? UnionToIntersection<UnionSchemaType<p['allOf']>> : p['allOf'] extends ReadonlyArray<Schema> ? UnionToIntersection<UnionSchemaType<p['allOf']>> :
p['additionalProperties'] extends true ? Record<string, any> :
p['additionalProperties'] extends Schema ?
p['additionalProperties'] extends infer AdditionalProperties ?
AdditionalProperties extends Schema ?
Record<string, SchemaType<AdditionalProperties>> :
never :
never :
any; any;
type ObjectSchemaType<p extends Schema> = NullOrUndefined<p, ObjectSchemaTypeDef<p>>; type ObjectSchemaType<p extends Schema> = NullOrUndefined<p, ObjectSchemaTypeDef<p>>;

View File

@@ -6,7 +6,6 @@
import * as fs from 'node:fs'; import * as fs from 'node:fs';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { ZipReader } from 'slacc'; import { ZipReader } from 'slacc';
import { IsNull } from 'typeorm';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { EmojisRepository, DriveFilesRepository } from '@/models/_.js'; import type { EmojisRepository, DriveFilesRepository } from '@/models/_.js';
import type Logger from '@/logger.js'; import type Logger from '@/logger.js';
@@ -87,7 +86,6 @@ export class ImportCustomEmojisProcessorService {
const emojiPath = outputPath + '/' + record.fileName; const emojiPath = outputPath + '/' + record.fileName;
await this.emojisRepository.delete({ await this.emojisRepository.delete({
name: emojiInfo.name, name: emojiInfo.name,
host: IsNull(),
}); });
try { try {

View File

@@ -13,7 +13,7 @@ import accepts from 'accepts';
import vary from 'vary'; import vary from 'vary';
import secureJson from 'secure-json-parse'; import secureJson from 'secure-json-parse';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { FollowingsRepository, NotesRepository, EmojisRepository, NoteReactionsRepository, UserProfilesRepository, UserNotePiningsRepository, UsersRepository, FollowRequestsRepository, MiMeta } from '@/models/_.js'; import type { FollowingsRepository, NotesRepository, EmojisRepository, NoteReactionsRepository, UserProfilesRepository, UserNotePiningsRepository, UsersRepository, FollowRequestsRepository } from '@/models/_.js';
import * as url from '@/misc/prelude/url.js'; import * as url from '@/misc/prelude/url.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import { ApRendererService } from '@/core/activitypub/ApRendererService.js'; import { ApRendererService } from '@/core/activitypub/ApRendererService.js';
@@ -42,9 +42,6 @@ export class ActivityPubServerService {
@Inject(DI.config) @Inject(DI.config)
private config: Config, private config: Config,
@Inject(DI.meta)
private meta: MiMeta,
@Inject(DI.usersRepository) @Inject(DI.usersRepository)
private usersRepository: UsersRepository, private usersRepository: UsersRepository,
@@ -105,11 +102,6 @@ export class ActivityPubServerService {
@bindThis @bindThis
private inbox(request: FastifyRequest, reply: FastifyReply) { private inbox(request: FastifyRequest, reply: FastifyReply) {
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
let signature; let signature;
try { try {
@@ -181,11 +173,6 @@ export class ActivityPubServerService {
request: FastifyRequest<{ Params: { user: string; }; Querystring: { cursor?: string; page?: string; }; }>, request: FastifyRequest<{ Params: { user: string; }; Querystring: { cursor?: string; page?: string; }; }>,
reply: FastifyReply, reply: FastifyReply,
) { ) {
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
const userId = request.params.user; const userId = request.params.user;
const cursor = request.query.cursor; const cursor = request.query.cursor;
@@ -278,11 +265,6 @@ export class ActivityPubServerService {
request: FastifyRequest<{ Params: { user: string; }; Querystring: { cursor?: string; page?: string; }; }>, request: FastifyRequest<{ Params: { user: string; }; Querystring: { cursor?: string; page?: string; }; }>,
reply: FastifyReply, reply: FastifyReply,
) { ) {
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
const userId = request.params.user; const userId = request.params.user;
const cursor = request.query.cursor; const cursor = request.query.cursor;
@@ -372,11 +354,6 @@ export class ActivityPubServerService {
@bindThis @bindThis
private async featured(request: FastifyRequest<{ Params: { user: string; }; }>, reply: FastifyReply) { private async featured(request: FastifyRequest<{ Params: { user: string; }; }>, reply: FastifyReply) {
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
const userId = request.params.user; const userId = request.params.user;
const user = await this.usersRepository.findOneBy({ const user = await this.usersRepository.findOneBy({
@@ -421,11 +398,6 @@ export class ActivityPubServerService {
}>, }>,
reply: FastifyReply, reply: FastifyReply,
) { ) {
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
const userId = request.params.user; const userId = request.params.user;
const sinceId = request.query.since_id; const sinceId = request.query.since_id;
@@ -510,11 +482,6 @@ export class ActivityPubServerService {
@bindThis @bindThis
private async userInfo(request: FastifyRequest, reply: FastifyReply, user: MiUser | null) { private async userInfo(request: FastifyRequest, reply: FastifyReply, user: MiUser | null) {
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
if (user == null) { if (user == null) {
reply.code(404); reply.code(404);
return; return;
@@ -597,11 +564,6 @@ export class ActivityPubServerService {
fastify.get<{ Params: { note: string; } }>('/notes/:note', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => { fastify.get<{ Params: { note: string; } }>('/notes/:note', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => {
vary(reply.raw, 'Accept'); vary(reply.raw, 'Accept');
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
const note = await this.notesRepository.findOneBy({ const note = await this.notesRepository.findOneBy({
id: request.params.note, id: request.params.note,
visibility: In(['public', 'home']), visibility: In(['public', 'home']),
@@ -632,11 +594,6 @@ export class ActivityPubServerService {
fastify.get<{ Params: { note: string; } }>('/notes/:note/activity', async (request, reply) => { fastify.get<{ Params: { note: string; } }>('/notes/:note/activity', async (request, reply) => {
vary(reply.raw, 'Accept'); vary(reply.raw, 'Accept');
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
const note = await this.notesRepository.findOneBy({ const note = await this.notesRepository.findOneBy({
id: request.params.note, id: request.params.note,
userHost: IsNull(), userHost: IsNull(),
@@ -677,11 +634,6 @@ export class ActivityPubServerService {
// publickey // publickey
fastify.get<{ Params: { user: string; } }>('/users/:user/publickey', async (request, reply) => { fastify.get<{ Params: { user: string; } }>('/users/:user/publickey', async (request, reply) => {
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
const userId = request.params.user; const userId = request.params.user;
const user = await this.usersRepository.findOneBy({ const user = await this.usersRepository.findOneBy({
@@ -709,11 +661,6 @@ export class ActivityPubServerService {
fastify.get<{ Params: { user: string; } }>('/users/:user', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => { fastify.get<{ Params: { user: string; } }>('/users/:user', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => {
vary(reply.raw, 'Accept'); vary(reply.raw, 'Accept');
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
const userId = request.params.user; const userId = request.params.user;
const user = await this.usersRepository.findOneBy({ const user = await this.usersRepository.findOneBy({
@@ -727,11 +674,6 @@ export class ActivityPubServerService {
fastify.get<{ Params: { acct: string; } }>('/@:acct', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => { fastify.get<{ Params: { acct: string; } }>('/@:acct', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => {
vary(reply.raw, 'Accept'); vary(reply.raw, 'Accept');
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
const acct = Acct.parse(request.params.acct); const acct = Acct.parse(request.params.acct);
const user = await this.usersRepository.findOneBy({ const user = await this.usersRepository.findOneBy({
@@ -746,11 +688,6 @@ export class ActivityPubServerService {
// emoji // emoji
fastify.get<{ Params: { emoji: string; } }>('/emojis/:emoji', async (request, reply) => { fastify.get<{ Params: { emoji: string; } }>('/emojis/:emoji', async (request, reply) => {
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
const emoji = await this.emojisRepository.findOneBy({ const emoji = await this.emojisRepository.findOneBy({
host: IsNull(), host: IsNull(),
name: request.params.emoji, name: request.params.emoji,
@@ -768,11 +705,6 @@ export class ActivityPubServerService {
// like // like
fastify.get<{ Params: { like: string; } }>('/likes/:like', async (request, reply) => { fastify.get<{ Params: { like: string; } }>('/likes/:like', async (request, reply) => {
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
const reaction = await this.noteReactionsRepository.findOneBy({ id: request.params.like }); const reaction = await this.noteReactionsRepository.findOneBy({ id: request.params.like });
if (reaction == null) { if (reaction == null) {
@@ -794,11 +726,6 @@ export class ActivityPubServerService {
// follow // follow
fastify.get<{ Params: { follower: string; followee: string; } }>('/follows/:follower/:followee', async (request, reply) => { fastify.get<{ Params: { follower: string; followee: string; } }>('/follows/:follower/:followee', async (request, reply) => {
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
// This may be used before the follow is completed, so we do not // This may be used before the follow is completed, so we do not
// check if the following exists. // check if the following exists.
@@ -825,11 +752,6 @@ export class ActivityPubServerService {
// follow // follow
fastify.get<{ Params: { followRequestId: string; } }>('/follows/:followRequestId', async (request, reply) => { fastify.get<{ Params: { followRequestId: string; } }>('/follows/:followRequestId', async (request, reply) => {
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
// This may be used before the follow is completed, so we do not // This may be used before the follow is completed, so we do not
// check if the following exists and only check if the follow request exists. // check if the following exists and only check if the follow request exists.

View File

@@ -8,7 +8,7 @@ import { IsNull } from 'typeorm';
import vary from 'vary'; import vary from 'vary';
import fastifyAccepts from '@fastify/accepts'; import fastifyAccepts from '@fastify/accepts';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import type { MiMeta, UsersRepository } from '@/models/_.js'; import type { UsersRepository } from '@/models/_.js';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
import { escapeAttribute, escapeValue } from '@/misc/prelude/xml.js'; import { escapeAttribute, escapeValue } from '@/misc/prelude/xml.js';
import type { MiUser } from '@/models/User.js'; import type { MiUser } from '@/models/User.js';
@@ -26,9 +26,6 @@ export class WellKnownServerService {
@Inject(DI.config) @Inject(DI.config)
private config: Config, private config: Config,
@Inject(DI.meta)
private meta: MiMeta,
@Inject(DI.usersRepository) @Inject(DI.usersRepository)
private usersRepository: UsersRepository, private usersRepository: UsersRepository,
@@ -69,11 +66,6 @@ export class WellKnownServerService {
}); });
fastify.get('/.well-known/host-meta', async (request, reply) => { fastify.get('/.well-known/host-meta', async (request, reply) => {
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
reply.header('Content-Type', xrd); reply.header('Content-Type', xrd);
return XRD({ element: 'Link', attributes: { return XRD({ element: 'Link', attributes: {
rel: 'lrdd', rel: 'lrdd',
@@ -83,11 +75,6 @@ export class WellKnownServerService {
}); });
fastify.get('/.well-known/host-meta.json', async (request, reply) => { fastify.get('/.well-known/host-meta.json', async (request, reply) => {
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
reply.header('Content-Type', 'application/json'); reply.header('Content-Type', 'application/json');
return { return {
links: [{ links: [{
@@ -99,11 +86,6 @@ export class WellKnownServerService {
}); });
fastify.get('/.well-known/nodeinfo', async (request, reply) => { fastify.get('/.well-known/nodeinfo', async (request, reply) => {
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
return { links: this.nodeinfoServerService.getLinks() }; return { links: this.nodeinfoServerService.getLinks() };
}); });
@@ -117,11 +99,6 @@ fastify.get('/.well-known/change-password', async (request, reply) => {
*/ */
fastify.get<{ Querystring: { resource: string } }>(webFingerPath, async (request, reply) => { fastify.get<{ Querystring: { resource: string } }>(webFingerPath, async (request, reply) => {
if (this.meta.federation === 'none') {
reply.code(403);
return;
}
const fromId = (id: MiUser['id']): FindOptionsWhere<MiUser> => ({ const fromId = (id: MiUser['id']): FindOptionsWhere<MiUser> => ({
id, id,
host: IsNull(), host: IsNull(),

View File

@@ -391,10 +391,10 @@ export class ApiCallService implements OnApplicationShutdown {
} }
} }
if (ep.meta.requiredRolePolicy != null && (this.meta.rootUserId !== user!.id)) { if (ep.meta.requireRolePolicy != null && (this.meta.rootUserId !== user!.id)) {
const myRoles = await this.roleService.getUserRoles(user!.id); const myRoles = await this.roleService.getUserRoles(user!.id);
const policies = await this.roleService.getUserPolicies(user!.id); const policies = await this.roleService.getUserPolicies(user!.id);
if (!policies[ep.meta.requiredRolePolicy] && !myRoles.some(r => r.isAdministrator)) { if (!policies[ep.meta.requireRolePolicy] && !myRoles.some(r => r.isAdministrator)) {
throw new ApiError({ throw new ApiError({
message: 'You are not assigned to a required role.', message: 'You are not assigned to a required role.',
code: 'ROLE_PERMISSION_DENIED', code: 'ROLE_PERMISSION_DENIED',

View File

@@ -6,6 +6,7 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import cors from '@fastify/cors'; import cors from '@fastify/cors';
import multipart from '@fastify/multipart'; import multipart from '@fastify/multipart';
import fastifyCookie from '@fastify/cookie';
import { ModuleRef } from '@nestjs/core'; import { ModuleRef } from '@nestjs/core';
import { AuthenticationResponseJSON } from '@simplewebauthn/types'; import { AuthenticationResponseJSON } from '@simplewebauthn/types';
import type { Config } from '@/config.js'; import type { Config } from '@/config.js';
@@ -56,6 +57,8 @@ export class ApiServerService {
}, },
}); });
fastify.register(fastifyCookie, {});
// Prevent cache // Prevent cache
fastify.addHook('onRequest', (request, reply, done) => { fastify.addHook('onRequest', (request, reply, done) => {
reply.header('Cache-Control', 'private, max-age=0, must-revalidate'); reply.header('Cache-Control', 'private, max-age=0, must-revalidate');

View File

@@ -39,7 +39,7 @@ interface IEndpointMetaBase {
*/ */
readonly requireAdmin?: boolean; readonly requireAdmin?: boolean;
readonly requiredRolePolicy?: KeyOf<'RolePolicies'>; readonly requireRolePolicy?: KeyOf<'RolePolicies'>;
/** /**
* 引っ越し済みのユーザーによるリクエストを禁止するか * 引っ越し済みのユーザーによるリクエストを禁止するか

View File

@@ -12,7 +12,7 @@ export const meta = {
tags: ['admin'], tags: ['admin'],
requireCredential: true, requireCredential: true,
requiredRolePolicy: 'canManageAvatarDecorations', requireRolePolicy: 'canManageAvatarDecorations',
kind: 'write:admin:avatar-decorations', kind: 'write:admin:avatar-decorations',
res: { res: {

View File

@@ -13,7 +13,7 @@ export const meta = {
tags: ['admin'], tags: ['admin'],
requireCredential: true, requireCredential: true,
requiredRolePolicy: 'canManageAvatarDecorations', requireRolePolicy: 'canManageAvatarDecorations',
kind: 'write:admin:avatar-decorations', kind: 'write:admin:avatar-decorations',
errors: { errors: {
}, },

View File

@@ -13,7 +13,7 @@ export const meta = {
tags: ['admin'], tags: ['admin'],
requireCredential: true, requireCredential: true,
requiredRolePolicy: 'canManageAvatarDecorations', requireRolePolicy: 'canManageAvatarDecorations',
kind: 'read:admin:avatar-decorations', kind: 'read:admin:avatar-decorations',
res: { res: {

View File

@@ -13,7 +13,7 @@ export const meta = {
tags: ['admin'], tags: ['admin'],
requireCredential: true, requireCredential: true,
requiredRolePolicy: 'canManageAvatarDecorations', requireRolePolicy: 'canManageAvatarDecorations',
kind: 'write:admin:avatar-decorations', kind: 'write:admin:avatar-decorations',
errors: { errors: {

View File

@@ -11,7 +11,7 @@ export const meta = {
tags: ['admin'], tags: ['admin'],
requireCredential: true, requireCredential: true,
requiredRolePolicy: 'canManageCustomEmojis', requireRolePolicy: 'canManageCustomEmojis',
kind: 'write:admin:emoji', kind: 'write:admin:emoji',
} as const; } as const;

View File

@@ -16,7 +16,7 @@ export const meta = {
tags: ['admin'], tags: ['admin'],
requireCredential: true, requireCredential: true,
requiredRolePolicy: 'canManageCustomEmojis', requireRolePolicy: 'canManageCustomEmojis',
kind: 'write:admin:emoji', kind: 'write:admin:emoji',
errors: { errors: {

View File

@@ -17,7 +17,7 @@ export const meta = {
tags: ['admin'], tags: ['admin'],
requireCredential: true, requireCredential: true,
requiredRolePolicy: 'canManageCustomEmojis', requireRolePolicy: 'canManageCustomEmojis',
kind: 'write:admin:emoji', kind: 'write:admin:emoji',
errors: { errors: {

View File

@@ -11,7 +11,7 @@ export const meta = {
tags: ['admin'], tags: ['admin'],
requireCredential: true, requireCredential: true,
requiredRolePolicy: 'canManageCustomEmojis', requireRolePolicy: 'canManageCustomEmojis',
kind: 'write:admin:emoji', kind: 'write:admin:emoji',
} as const; } as const;

View File

@@ -11,7 +11,7 @@ export const meta = {
tags: ['admin'], tags: ['admin'],
requireCredential: true, requireCredential: true,
requiredRolePolicy: 'canManageCustomEmojis', requireRolePolicy: 'canManageCustomEmojis',
kind: 'write:admin:emoji', kind: 'write:admin:emoji',
errors: { errors: {

View File

@@ -10,7 +10,7 @@ import { QueueService } from '@/core/QueueService.js';
export const meta = { export const meta = {
secure: true, secure: true,
requireCredential: true, requireCredential: true,
requiredRolePolicy: 'canManageCustomEmojis', requireRolePolicy: 'canManageCustomEmojis',
} as const; } as const;
export const paramDef = { export const paramDef = {

View File

@@ -16,7 +16,7 @@ export const meta = {
tags: ['admin'], tags: ['admin'],
requireCredential: true, requireCredential: true,
requiredRolePolicy: 'canManageCustomEmojis', requireRolePolicy: 'canManageCustomEmojis',
kind: 'read:admin:emoji', kind: 'read:admin:emoji',
res: { res: {

View File

@@ -16,7 +16,7 @@ export const meta = {
tags: ['admin'], tags: ['admin'],
requireCredential: true, requireCredential: true,
requiredRolePolicy: 'canManageCustomEmojis', requireRolePolicy: 'canManageCustomEmojis',
kind: 'read:admin:emoji', kind: 'read:admin:emoji',
res: { res: {

View File

@@ -11,7 +11,7 @@ export const meta = {
tags: ['admin'], tags: ['admin'],
requireCredential: true, requireCredential: true,
requiredRolePolicy: 'canManageCustomEmojis', requireRolePolicy: 'canManageCustomEmojis',
kind: 'write:admin:emoji', kind: 'write:admin:emoji',
} as const; } as const;

View File

@@ -11,7 +11,7 @@ export const meta = {
tags: ['admin'], tags: ['admin'],
requireCredential: true, requireCredential: true,
requiredRolePolicy: 'canManageCustomEmojis', requireRolePolicy: 'canManageCustomEmojis',
kind: 'write:admin:emoji', kind: 'write:admin:emoji',
} as const; } as const;

View File

@@ -11,7 +11,7 @@ export const meta = {
tags: ['admin'], tags: ['admin'],
requireCredential: true, requireCredential: true,
requiredRolePolicy: 'canManageCustomEmojis', requireRolePolicy: 'canManageCustomEmojis',
kind: 'write:admin:emoji', kind: 'write:admin:emoji',
} as const; } as const;

View File

@@ -11,7 +11,7 @@ export const meta = {
tags: ['admin'], tags: ['admin'],
requireCredential: true, requireCredential: true,
requiredRolePolicy: 'canManageCustomEmojis', requireRolePolicy: 'canManageCustomEmojis',
kind: 'write:admin:emoji', kind: 'write:admin:emoji',
} as const; } as const;

View File

@@ -14,7 +14,7 @@ export const meta = {
tags: ['admin'], tags: ['admin'],
requireCredential: true, requireCredential: true,
requiredRolePolicy: 'canManageCustomEmojis', requireRolePolicy: 'canManageCustomEmojis',
kind: 'write:admin:emoji', kind: 'write:admin:emoji',
errors: { errors: {

View File

@@ -16,7 +16,7 @@ import { ApiError } from '../../error.js';
export const meta = { export const meta = {
secure: true, secure: true,
requireCredential: true, requireCredential: true,
requiredRolePolicy: 'canImportAntennas', requireRolePolicy: 'canImportAntennas',
prohibitMoved: true, prohibitMoved: true,
limit: { limit: {

View File

@@ -15,7 +15,7 @@ import { ApiError } from '../../error.js';
export const meta = { export const meta = {
secure: true, secure: true,
requireCredential: true, requireCredential: true,
requiredRolePolicy: 'canImportBlocking', requireRolePolicy: 'canImportBlocking',
prohibitMoved: true, prohibitMoved: true,
limit: { limit: {

View File

@@ -15,7 +15,7 @@ import { ApiError } from '../../error.js';
export const meta = { export const meta = {
secure: true, secure: true,
requireCredential: true, requireCredential: true,
requiredRolePolicy: 'canImportFollowing', requireRolePolicy: 'canImportFollowing',
prohibitMoved: true, prohibitMoved: true,
limit: { limit: {
duration: ms('1hour'), duration: ms('1hour'),

View File

@@ -15,7 +15,7 @@ import { ApiError } from '../../error.js';
export const meta = { export const meta = {
secure: true, secure: true,
requireCredential: true, requireCredential: true,
requiredRolePolicy: 'canImportMuting', requireRolePolicy: 'canImportMuting',
prohibitMoved: true, prohibitMoved: true,
limit: { limit: {

View File

@@ -15,7 +15,7 @@ import { ApiError } from '../../error.js';
export const meta = { export const meta = {
secure: true, secure: true,
requireCredential: true, requireCredential: true,
requiredRolePolicy: 'canImportUserLists', requireRolePolicy: 'canImportUserLists',
prohibitMoved: true, prohibitMoved: true,
limit: { limit: {
duration: ms('1hour'), duration: ms('1hour'),

View File

@@ -18,7 +18,7 @@ export const meta = {
tags: ['meta'], tags: ['meta'],
requireCredential: true, requireCredential: true,
requiredRolePolicy: 'canInvite', requireRolePolicy: 'canInvite',
kind: 'write:invite-codes', kind: 'write:invite-codes',
errors: { errors: {

View File

@@ -14,7 +14,7 @@ export const meta = {
tags: ['meta'], tags: ['meta'],
requireCredential: true, requireCredential: true,
requiredRolePolicy: 'canInvite', requireRolePolicy: 'canInvite',
kind: 'write:invite-codes', kind: 'write:invite-codes',
errors: { errors: {

View File

@@ -15,7 +15,7 @@ export const meta = {
tags: ['meta'], tags: ['meta'],
requireCredential: true, requireCredential: true,
requiredRolePolicy: 'canInvite', requireRolePolicy: 'canInvite',
kind: 'read:invite-codes', kind: 'read:invite-codes',
res: { res: {

View File

@@ -14,7 +14,7 @@ export const meta = {
tags: ['meta'], tags: ['meta'],
requireCredential: true, requireCredential: true,
requiredRolePolicy: 'canInvite', requireRolePolicy: 'canInvite',
kind: 'read:invite-codes', kind: 'read:invite-codes',
res: { res: {

View File

@@ -12,7 +12,7 @@ export const meta = {
tags: ['admin'], tags: ['admin'],
requireCredential: true, requireCredential: true,
requiredRolePolicy: 'canManageCustomEmojis', requireRolePolicy: 'canManageCustomEmojis',
kind: 'read:admin:emoji', kind: 'read:admin:emoji',
res: { res: {

View File

@@ -7,12 +7,16 @@ import { randomUUID } from 'node:crypto';
import { dirname } from 'node:path'; import { dirname } from 'node:path';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { createBullBoard } from '@bull-board/api';
import { BullMQAdapter } from '@bull-board/api/bullMQAdapter.js';
import { FastifyAdapter as BullBoardFastifyAdapter } from '@bull-board/fastify';
import ms from 'ms'; import ms from 'ms';
import sharp from 'sharp'; import sharp from 'sharp';
import pug from 'pug'; import pug from 'pug';
import { In, IsNull } from 'typeorm'; import { In, IsNull } from 'typeorm';
import fastifyStatic from '@fastify/static'; import fastifyStatic from '@fastify/static';
import fastifyView from '@fastify/view'; import fastifyView from '@fastify/view';
import fastifyCookie from '@fastify/cookie';
import fastifyProxy from '@fastify/http-proxy'; import fastifyProxy from '@fastify/http-proxy';
import vary from 'vary'; import vary from 'vary';
import htmlSafeJsonStringify from 'htmlescape'; import htmlSafeJsonStringify from 'htmlescape';
@@ -217,6 +221,64 @@ export class ClientServerService {
@bindThis @bindThis
public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) { public createServer(fastify: FastifyInstance, options: FastifyPluginOptions, done: (err?: Error) => void) {
fastify.register(fastifyCookie, {});
//#region Bull Dashboard
const bullBoardPath = '/queue';
// Authenticate
fastify.addHook('onRequest', async (request, reply) => {
if (request.routeOptions.url == null) {
reply.code(404).send('Not found');
return;
}
// %71ueueとかでリクエストされたら困るため
const url = decodeURI(request.routeOptions.url);
if (url === bullBoardPath || url.startsWith(bullBoardPath + '/')) {
if (!url.startsWith(bullBoardPath + '/static/')) {
reply.header('Cache-Control', 'private, max-age=0, must-revalidate');
}
const token = request.cookies.token;
if (token == null) {
reply.code(401).send('Login required');
return;
}
const user = await this.usersRepository.findOneBy({ token });
if (user == null) {
reply.code(403).send('No such user');
return;
}
const isAdministrator = await this.roleService.isAdministrator(user);
if (!isAdministrator) {
reply.code(403).send('Access denied');
return;
}
}
});
const bullBoardServerAdapter = new BullBoardFastifyAdapter();
createBullBoard({
queues: [
this.systemQueue,
this.endedPollNotificationQueue,
this.deliverQueue,
this.inboxQueue,
this.dbQueue,
this.relationshipQueue,
this.objectStorageQueue,
this.userWebhookDeliverQueue,
this.systemWebhookDeliverQueue,
].map(q => new BullMQAdapter(q)),
serverAdapter: bullBoardServerAdapter,
});
bullBoardServerAdapter.setBasePath(bullBoardPath);
(fastify.register as any)(bullBoardServerAdapter.registerPlugin(), { prefix: bullBoardPath });
//#endregion
fastify.register(fastifyView, { fastify.register(fastifyView, {
root: _dirname + '/views', root: _dirname + '/views',
engine: { engine: {

View File

@@ -6,7 +6,7 @@
process.env.NODE_ENV = 'test'; process.env.NODE_ENV = 'test';
import * as assert from 'assert'; import * as assert from 'assert';
import { channel, clip, galleryPost, page, play, post, signup, simpleGet, uploadFile } from '../utils.js'; import { channel, clip, cookie, galleryPost, page, play, post, signup, simpleGet, uploadFile } from '../utils.js';
import type { SimpleGetResponse } from '../utils.js'; import type { SimpleGetResponse } from '../utils.js';
import type * as misskey from 'misskey-js'; import type * as misskey from 'misskey-js';
@@ -156,20 +156,20 @@ describe('Webリソース', () => {
describe(' has entry such ', () => { describe(' has entry such ', () => {
beforeEach(() => { beforeEach(() => {
post(alice, { text: '**a**' }); post(alice, { text: "**a**" })
}); });
test('MFMを含まない。', async () => { test('MFMを含まない。', async () => {
const content = await simpleGet(path(alice.username), '*/*', undefined, res => res.text()); const content = await simpleGet(path(alice.username), "*/*", undefined, res => res.text());
const _body: unknown = content.body; const _body: unknown = content.body;
// JSONフィードのときは改めて文字列化する // JSONフィードのときは改めて文字列化する
const body: string = typeof (_body) === 'object' ? JSON.stringify(_body) : _body as string; const body: string = typeof (_body) === "object" ? JSON.stringify(_body) : _body as string;
if (body.includes('**a**')) { if (body.includes("**a**")) {
throw new Error('MFM shouldn\'t be included'); throw new Error("MFM shouldn't be included");
} }
}); });
}); })
}); });
describe.each([{ path: '/api/foo' }])('$path', ({ path }) => { describe.each([{ path: '/api/foo' }])('$path', ({ path }) => {
@@ -180,6 +180,24 @@ describe('Webリソース', () => {
})); }));
}); });
describe.each([{ path: '/queue' }])('$path', ({ path }) => {
test('はログインしないとGETできない。', async () => await notOk({
path,
status: 401,
}));
test('はadminでなければGETできない。', async () => await notOk({
path,
cookie: cookie(bob),
status: 403,
}));
test('はadminならGETできる。', async () => await ok({
path,
cookie: cookie(alice),
}));
});
describe.each([{ path: '/streaming' }])('$path', ({ path }) => { describe.each([{ path: '/streaming' }])('$path', ({ path }) => {
test('はGETできない。', async () => await notOk({ test('はGETできない。', async () => await notOk({
path, path,

View File

@@ -14,7 +14,6 @@ import { MiSystemWebhook, MiUser, MiWebhook, UserProfilesRepository, UsersReposi
import { IdService } from '@/core/IdService.js'; import { IdService } from '@/core/IdService.js';
import { DI } from '@/di-symbols.js'; import { DI } from '@/di-symbols.js';
import { QueueService } from '@/core/QueueService.js'; import { QueueService } from '@/core/QueueService.js';
import { CustomEmojiService } from '@/core/CustomEmojiService.js';
describe('WebhookTestService', () => { describe('WebhookTestService', () => {
let app: TestingModule; let app: TestingModule;
@@ -57,11 +56,6 @@ describe('WebhookTestService', () => {
providers: [ providers: [
WebhookTestService, WebhookTestService,
IdService, IdService,
{
provide: CustomEmojiService, useFactory: () => ({
populateEmojis: jest.fn(),
}),
},
{ {
provide: QueueService, useFactory: () => ({ provide: QueueService, useFactory: () => ({
systemWebhookDeliver: jest.fn(), systemWebhookDeliver: jest.fn(),

View File

@@ -35,7 +35,7 @@ export type SystemWebhookPayload = {
createdAt: string; createdAt: string;
type: string; type: string;
body: any; body: any;
}; }
const config = loadConfig(); const config = loadConfig();
export const port = config.port; export const port = config.port;
@@ -45,6 +45,10 @@ export const host = new URL(config.url).host;
export const WEBHOOK_HOST = 'http://localhost:15080'; export const WEBHOOK_HOST = 'http://localhost:15080';
export const WEBHOOK_PORT = 15080; export const WEBHOOK_PORT = 15080;
export const cookie = (me: UserToken): string => {
return `token=${me.token};`;
};
export type ApiRequest<E extends keyof misskey.Endpoints, P extends misskey.Endpoints[E]['req'] = misskey.Endpoints[E]['req']> = { export type ApiRequest<E extends keyof misskey.Endpoints, P extends misskey.Endpoints[E]['req'] = misskey.Endpoints[E]['req']> = {
endpoint: E, endpoint: E,
parameters: P, parameters: P,

View File

@@ -16,7 +16,7 @@
"@rollup/pluginutils": "5.1.4", "@rollup/pluginutils": "5.1.4",
"@tabler/icons-webfont": "3.31.0", "@tabler/icons-webfont": "3.31.0",
"@twemoji/parser": "15.1.1", "@twemoji/parser": "15.1.1",
"@vitejs/plugin-vue": "5.2.3", "@vitejs/plugin-vue": "5.2.1",
"@vue/compiler-sfc": "3.5.13", "@vue/compiler-sfc": "3.5.13",
"astring": "1.9.0", "astring": "1.9.0",
"buraha": "0.0.1", "buraha": "0.0.1",
@@ -25,16 +25,16 @@
"misskey-js": "workspace:*", "misskey-js": "workspace:*",
"frontend-shared": "workspace:*", "frontend-shared": "workspace:*",
"punycode.js": "2.3.1", "punycode.js": "2.3.1",
"rollup": "4.36.0", "rollup": "4.34.9",
"sass": "1.86.0", "sass": "1.85.1",
"shiki": "3.2.1", "shiki": "3.1.0",
"tinycolor2": "1.6.0", "tinycolor2": "1.6.0",
"tsc-alias": "1.8.11", "tsc-alias": "1.8.11",
"tsconfig-paths": "4.2.0", "tsconfig-paths": "4.2.0",
"typescript": "5.8.2", "typescript": "5.8.2",
"uuid": "11.1.0", "uuid": "11.1.0",
"json5": "2.2.3", "json5": "2.2.3",
"vite": "6.2.2", "vite": "6.2.1",
"vue": "3.5.13" "vue": "3.5.13"
}, },
"devDependencies": { "devDependencies": {
@@ -42,26 +42,26 @@
"@testing-library/vue": "8.1.0", "@testing-library/vue": "8.1.0",
"@types/estree": "1.0.6", "@types/estree": "1.0.6",
"@types/micromatch": "4.0.9", "@types/micromatch": "4.0.9",
"@types/node": "22.13.11", "@types/node": "22.13.9",
"@types/punycode.js": "npm:@types/punycode@2.1.4", "@types/punycode.js": "npm:@types/punycode@2.1.4",
"@types/tinycolor2": "1.4.6", "@types/tinycolor2": "1.4.6",
"@types/ws": "8.18.0", "@types/ws": "8.18.0",
"@typescript-eslint/eslint-plugin": "8.27.0", "@typescript-eslint/eslint-plugin": "8.26.0",
"@typescript-eslint/parser": "8.27.0", "@typescript-eslint/parser": "8.26.0",
"@vitest/coverage-v8": "3.0.9", "@vitest/coverage-v8": "3.0.8",
"@vue/runtime-core": "3.5.13", "@vue/runtime-core": "3.5.13",
"acorn": "8.14.1", "acorn": "8.14.1",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"eslint-plugin-import": "2.31.0", "eslint-plugin-import": "2.31.0",
"eslint-plugin-vue": "10.0.0", "eslint-plugin-vue": "10.0.0",
"fast-glob": "3.3.3", "fast-glob": "3.3.3",
"happy-dom": "17.4.4", "happy-dom": "17.3.0",
"intersection-observer": "0.12.2", "intersection-observer": "0.12.2",
"micromatch": "4.0.8", "micromatch": "4.0.8",
"msw": "2.7.3", "msw": "2.7.3",
"nodemon": "3.1.9", "nodemon": "3.1.9",
"prettier": "3.5.3", "prettier": "3.5.3",
"start-server-and-test": "2.0.11", "start-server-and-test": "2.0.10",
"vite-plugin-turbosnap": "1.0.3", "vite-plugin-turbosnap": "1.0.3",
"vue-component-type-helpers": "2.2.8", "vue-component-type-helpers": "2.2.8",
"vue-eslint-parser": "10.1.1", "vue-eslint-parser": "10.1.1",

View File

@@ -33,11 +33,13 @@ const canvasPromise = new Promise<WorkerMultiDispatch | HTMLCanvasElement>(resol
Math.min(navigator.hardwareConcurrency - 1, 4), Math.min(navigator.hardwareConcurrency - 1, 4),
); );
resolve(workers); resolve(workers);
if (_DEV_) console.log('WebGL2 in worker is supported!');
} else { } else {
const canvas = document.createElement('canvas'); const canvas = document.createElement('canvas');
canvas.width = 64; canvas.width = 64;
canvas.height = 64; canvas.height = 64;
resolve(canvas); resolve(canvas);
if (_DEV_) console.log('WebGL2 in worker is not supported...');
} }
testWorker.terminate(); testWorker.terminate();
}); });

View File

@@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<script lang="ts" setup> <script lang="ts" setup>
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import { watch, ref } from 'vue'; import { inject, watch, ref } from 'vue';
import XReaction from '@/components/EmReactionsViewer.reaction.vue'; import XReaction from '@/components/EmReactionsViewer.reaction.vue';
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
@@ -22,6 +22,12 @@ const props = withDefaults(defineProps<{
maxNumber: Infinity, maxNumber: Infinity,
}); });
const mock = inject<boolean>('mock', false);
const emit = defineEmits<{
(ev: 'mockUpdateMyReaction', emoji: string, delta: number): void;
}>();
const initialReactions = new Set(Object.keys(props.note.reactions)); const initialReactions = new Set(Object.keys(props.note.reactions));
const reactions = ref<[string, number][]>([]); const reactions = ref<[string, number][]>([]);
@@ -32,8 +38,12 @@ if (props.note.myReaction && !Object.keys(reactions.value).includes(props.note.m
} }
function onMockToggleReaction(emoji: string, count: number) { function onMockToggleReaction(emoji: string, count: number) {
if (!mock) return;
const i = reactions.value.findIndex((item) => item[0] === emoji); const i = reactions.value.findIndex((item) => item[0] === emoji);
if (i < 0) return; if (i < 0) return;
emit('mockUpdateMyReaction', emoji, (count - reactions.value[i][1]));
} }
watch([() => props.note.reactions, () => props.maxNumber], ([newSource, maxNumber]) => { watch([() => props.note.reactions, () => props.maxNumber], ([newSource, maxNumber]) => {

View File

@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only
<template> <template>
<div> <div>
<div class="_fullinfo"> <div class="_fullinfo">
<img :src="notFoundImageUrl" draggable="false"/> <img :src="notFoundImageUrl" class="_ghost"/>
<div>{{ i18n.ts.notFoundDescription }}</div> <div>{{ i18n.ts.notFoundDescription }}</div>
</div> </div>
</div> </div>
@@ -20,5 +20,5 @@ import { i18n } from '@/i18n.js';
const serverMetadata = inject(DI.serverMetadata)!; const serverMetadata = inject(DI.serverMetadata)!;
const notFoundImageUrl = computed(() => serverMetadata.notFoundImageUrl ?? DEFAULT_NOT_FOUND_IMAGE_URL); const notFoundImageUrl = computed(() => serverMetadata?.notFoundImageUrl ?? DEFAULT_NOT_FOUND_IMAGE_URL);
</script> </script>

View File

@@ -12,7 +12,6 @@ const siteName = document.querySelector<HTMLMetaElement>('meta[property="og:site
export const host = address.host; export const host = address.host;
export const hostname = address.hostname; export const hostname = address.hostname;
export const url = address.origin; export const url = address.origin;
export const port = address.port;
export const apiUrl = location.origin + '/api'; export const apiUrl = location.origin + '/api';
export const wsOrigin = location.origin; export const wsOrigin = location.origin;
export const lang = localStorage.getItem('lang') ?? 'en-US'; export const lang = localStorage.getItem('lang') ?? 'en-US';

View File

@@ -109,6 +109,12 @@ export const ROLE_POLICIES = [
'canImportUserLists', 'canImportUserLists',
] as const; ] as const;
// なんか動かない
//export const CURRENT_STICKY_TOP = Symbol('CURRENT_STICKY_TOP');
//export const CURRENT_STICKY_BOTTOM = Symbol('CURRENT_STICKY_BOTTOM');
export const CURRENT_STICKY_TOP = 'CURRENT_STICKY_TOP';
export const CURRENT_STICKY_BOTTOM = 'CURRENT_STICKY_BOTTOM';
export const DEFAULT_SERVER_ERROR_IMAGE_URL = 'https://xn--931a.moe/assets/error.jpg'; export const DEFAULT_SERVER_ERROR_IMAGE_URL = 'https://xn--931a.moe/assets/error.jpg';
export const DEFAULT_NOT_FOUND_IMAGE_URL = 'https://xn--931a.moe/assets/not-found.jpg'; export const DEFAULT_NOT_FOUND_IMAGE_URL = 'https://xn--931a.moe/assets/not-found.jpg';
export const DEFAULT_INFO_IMAGE_URL = 'https://xn--931a.moe/assets/info.jpg'; export const DEFAULT_INFO_IMAGE_URL = 'https://xn--931a.moe/assets/info.jpg';

View File

@@ -21,10 +21,10 @@
"lint": "pnpm typecheck && pnpm eslint" "lint": "pnpm typecheck && pnpm eslint"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "22.13.11", "@types/node": "22.13.9",
"@typescript-eslint/eslint-plugin": "8.27.0", "@typescript-eslint/eslint-plugin": "8.26.0",
"@typescript-eslint/parser": "8.27.0", "@typescript-eslint/parser": "8.26.0",
"esbuild": "0.25.1", "esbuild": "0.25.0",
"eslint-plugin-vue": "10.0.0", "eslint-plugin-vue": "10.0.0",
"nodemon": "3.1.9", "nodemon": "3.1.9",
"typescript": "5.8.2", "typescript": "5.8.2",

View File

@@ -131,7 +131,7 @@ export function imageDataUrl(options?: {
alpha?: number, alpha?: number,
} }
}, seed?: string): string { }, seed?: string): string {
const canvas = window.document.createElement('canvas'); const canvas = document.createElement('canvas');
canvas.width = options?.size?.width ?? 100; canvas.width = options?.size?.width ?? 100;
canvas.height = options?.size?.height ?? 100; canvas.height = options?.size?.height ?? 100;

View File

@@ -23,9 +23,9 @@ let misskeyOS = null;
function loadTheme(applyTheme: typeof import('../src/theme')['applyTheme']) { function loadTheme(applyTheme: typeof import('../src/theme')['applyTheme']) {
unobserve(); unobserve();
const theme = themes[window.document.documentElement.dataset.misskeyTheme]; const theme = themes[document.documentElement.dataset.misskeyTheme];
if (theme) { if (theme) {
applyTheme(themes[window.document.documentElement.dataset.misskeyTheme]); applyTheme(themes[document.documentElement.dataset.misskeyTheme]);
} else { } else {
applyTheme(themes['l-light']); applyTheme(themes['l-light']);
} }
@@ -42,7 +42,7 @@ function loadTheme(applyTheme: typeof import('../src/theme')['applyTheme']) {
} }
} }
}); });
observer.observe(window.document.documentElement, { observer.observe(document.documentElement, {
attributes: true, attributes: true,
attributeFilter: ['data-misskey-theme'], attributeFilter: ['data-misskey-theme'],
}); });
@@ -64,12 +64,12 @@ initialize({
initLocalStorage(); initLocalStorage();
queueMicrotask(() => { queueMicrotask(() => {
Promise.all([ Promise.all([
import('../src/components/index.js'), import('../src/components'),
import('../src/directives/index.js'), import('../src/directives'),
import('../src/widgets/index.js'), import('../src/widgets'),
import('../src/theme.js'), import('../src/theme'),
import('../src/preferences.js'), import('../src/preferences'),
import('../src/os.js'), import('../src/os'),
]).then(([{ default: components }, { default: directives }, { default: widgets }, { applyTheme }, { prefer }, os]) => { ]).then(([{ default: components }, { default: directives }, { default: widgets }, { applyTheme }, { prefer }, os]) => {
setup((app) => { setup((app) => {
moduleInitialized = true; moduleInitialized = true;

View File

@@ -50,46 +50,9 @@ export default [
// defineExposeが誤検知されてしまう // defineExposeが誤検知されてしまう
'@typescript-eslint/no-unused-expressions': 'off', '@typescript-eslint/no-unused-expressions': 'off',
'import/consistent-type-specifier-style': ['error', 'prefer-top-level'], 'import/consistent-type-specifier-style': ['error', 'prefer-top-level'],
// window ... グローバルスコープと衝突し、予期せぬ結果を招くため // window の禁止理由: グローバルスコープと衝突し、予期せぬ結果を招くため
// e ... error や event など、複数のキーワードの頭文字であり分かりにくいため // e の禁止理由: error や event など、複数のキーワードの頭文字であり分かりにくいため
// close ... window.closeと衝突 or 紛らわしい 'id-denylist': ['error', 'window', 'e'],
// open ... window.openと衝突 or 紛らわしい
// fetch ... window.fetchと衝突 or 紛らわしい
// location ... window.locationと衝突 or 紛らわしい
// document ... window.documentと衝突 or 紛らわしい
// history ... window.historyと衝突 or 紛らわしい
'id-denylist': ['warn', 'window', 'e', 'close', 'open', 'fetch', 'location', 'document', 'history'],
'no-restricted-globals': [
'error',
{
'name': 'open',
'message': 'Use `window.open`.',
},
{
'name': 'close',
'message': 'Use `window.close`.',
},
{
'name': 'fetch',
'message': 'Use `window.fetch`.',
},
{
'name': 'location',
'message': 'Use `window.location`.',
},
{
'name': 'document',
'message': 'Use `window.document`.',
},
{
'name': 'history',
'message': 'Use `window.history`.',
},
{
'name': 'name',
'message': 'Use `window.name`. もしくは name という変数名を定義し忘れている',
},
],
'no-shadow': ['warn'], 'no-shadow': ['warn'],
'vue/attributes-order': ['error', { 'vue/attributes-order': ['error', {
alphabetical: false, alphabetical: false,

View File

@@ -28,7 +28,7 @@
"@syuilo/aiscript": "0.19.0", "@syuilo/aiscript": "0.19.0",
"@tabler/icons-webfont": "3.31.0", "@tabler/icons-webfont": "3.31.0",
"@twemoji/parser": "15.1.1", "@twemoji/parser": "15.1.1",
"@vitejs/plugin-vue": "5.2.3", "@vitejs/plugin-vue": "5.2.1",
"@vue/compiler-sfc": "3.5.13", "@vue/compiler-sfc": "3.5.13",
"aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.15", "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.15",
"analytics": "0.8.16", "analytics": "0.8.16",
@@ -38,7 +38,7 @@
"canvas-confetti": "1.9.3", "canvas-confetti": "1.9.3",
"chart.js": "4.4.8", "chart.js": "4.4.8",
"chartjs-adapter-date-fns": "3.0.0", "chartjs-adapter-date-fns": "3.0.0",
"chartjs-chart-matrix": "2.1.1", "chartjs-chart-matrix": "2.0.1",
"chartjs-plugin-gradient": "0.6.1", "chartjs-plugin-gradient": "0.6.1",
"chartjs-plugin-zoom": "2.2.0", "chartjs-plugin-zoom": "2.2.0",
"chromatic": "11.27.0", "chromatic": "11.27.0",
@@ -60,10 +60,10 @@
"misskey-reversi": "workspace:*", "misskey-reversi": "workspace:*",
"photoswipe": "5.4.4", "photoswipe": "5.4.4",
"punycode.js": "2.3.1", "punycode.js": "2.3.1",
"rollup": "4.36.0", "rollup": "4.34.9",
"sanitize-html": "2.15.0", "sanitize-html": "2.14.0",
"sass": "1.86.0", "sass": "1.85.1",
"shiki": "3.2.1", "shiki": "3.1.0",
"strict-event-emitter-types": "2.0.0", "strict-event-emitter-types": "2.0.0",
"textarea-caret": "3.1.0", "textarea-caret": "3.1.0",
"three": "0.174.0", "three": "0.174.0",
@@ -74,54 +74,54 @@
"typescript": "5.8.2", "typescript": "5.8.2",
"uuid": "11.1.0", "uuid": "11.1.0",
"v-code-diff": "1.13.1", "v-code-diff": "1.13.1",
"vite": "6.2.2", "vite": "6.2.1",
"vue": "3.5.13", "vue": "3.5.13",
"vuedraggable": "next", "vuedraggable": "next",
"wanakana": "5.3.1" "wanakana": "5.3.1"
}, },
"devDependencies": { "devDependencies": {
"@misskey-dev/summaly": "5.2.0", "@misskey-dev/summaly": "5.2.0",
"@storybook/addon-actions": "8.6.7", "@storybook/addon-actions": "8.6.4",
"@storybook/addon-essentials": "8.6.7", "@storybook/addon-essentials": "8.6.4",
"@storybook/addon-interactions": "8.6.7", "@storybook/addon-interactions": "8.6.4",
"@storybook/addon-links": "8.6.7", "@storybook/addon-links": "8.6.4",
"@storybook/addon-mdx-gfm": "8.6.7", "@storybook/addon-mdx-gfm": "8.6.4",
"@storybook/addon-storysource": "8.6.7", "@storybook/addon-storysource": "8.6.4",
"@storybook/blocks": "8.6.7", "@storybook/blocks": "8.6.4",
"@storybook/components": "8.6.7", "@storybook/components": "8.6.4",
"@storybook/core-events": "8.6.7", "@storybook/core-events": "8.6.4",
"@storybook/manager-api": "8.6.7", "@storybook/manager-api": "8.6.4",
"@storybook/preview-api": "8.6.7", "@storybook/preview-api": "8.6.4",
"@storybook/react": "8.6.7", "@storybook/react": "8.6.4",
"@storybook/react-vite": "8.6.7", "@storybook/react-vite": "8.6.4",
"@storybook/test": "8.6.7", "@storybook/test": "8.6.4",
"@storybook/theming": "8.6.7", "@storybook/theming": "8.6.4",
"@storybook/types": "8.6.7", "@storybook/types": "8.6.4",
"@storybook/vue3": "8.6.7", "@storybook/vue3": "8.6.4",
"@storybook/vue3-vite": "8.6.7", "@storybook/vue3-vite": "8.6.4",
"@testing-library/vue": "8.1.0", "@testing-library/vue": "8.1.0",
"@types/canvas-confetti": "1.9.0", "@types/canvas-confetti": "1.9.0",
"@types/estree": "1.0.6", "@types/estree": "1.0.6",
"@types/matter-js": "0.19.8", "@types/matter-js": "0.19.8",
"@types/micromatch": "4.0.9", "@types/micromatch": "4.0.9",
"@types/node": "22.13.11", "@types/node": "22.13.9",
"@types/punycode.js": "npm:@types/punycode@2.1.4", "@types/punycode.js": "npm:@types/punycode@2.1.4",
"@types/sanitize-html": "2.13.0", "@types/sanitize-html": "2.13.0",
"@types/seedrandom": "3.0.8", "@types/seedrandom": "3.0.8",
"@types/throttle-debounce": "5.0.2", "@types/throttle-debounce": "5.0.2",
"@types/tinycolor2": "1.4.6", "@types/tinycolor2": "1.4.6",
"@types/ws": "8.18.0", "@types/ws": "8.18.0",
"@typescript-eslint/eslint-plugin": "8.27.0", "@typescript-eslint/eslint-plugin": "8.26.0",
"@typescript-eslint/parser": "8.27.0", "@typescript-eslint/parser": "8.26.0",
"@vitest/coverage-v8": "3.0.9", "@vitest/coverage-v8": "3.0.8",
"@vue/runtime-core": "3.5.13", "@vue/runtime-core": "3.5.13",
"acorn": "8.14.1", "acorn": "8.14.1",
"cross-env": "7.0.3", "cross-env": "7.0.3",
"cypress": "14.2.0", "cypress": "14.1.0",
"eslint-plugin-import": "2.31.0", "eslint-plugin-import": "2.31.0",
"eslint-plugin-vue": "10.0.0", "eslint-plugin-vue": "10.0.0",
"fast-glob": "3.3.3", "fast-glob": "3.3.3",
"happy-dom": "17.4.4", "happy-dom": "17.3.0",
"intersection-observer": "0.12.2", "intersection-observer": "0.12.2",
"micromatch": "4.0.8", "micromatch": "4.0.8",
"msw": "2.7.3", "msw": "2.7.3",
@@ -131,12 +131,12 @@
"react": "19.0.0", "react": "19.0.0",
"react-dom": "19.0.0", "react-dom": "19.0.0",
"seedrandom": "3.0.5", "seedrandom": "3.0.5",
"start-server-and-test": "2.0.11", "start-server-and-test": "2.0.10",
"storybook": "8.6.7", "storybook": "8.6.4",
"storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme", "storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme",
"vite-node": "3.0.9", "vite-node": "3.0.8",
"vite-plugin-turbosnap": "1.0.3", "vite-plugin-turbosnap": "1.0.3",
"vitest": "3.0.9", "vitest": "3.0.8",
"vitest-fetch-mock": "0.4.5", "vitest-fetch-mock": "0.4.5",
"vue-component-type-helpers": "2.2.8", "vue-component-type-helpers": "2.2.8",
"vue-eslint-parser": "10.1.1", "vue-eslint-parser": "10.1.1",

View File

@@ -12,9 +12,9 @@ import '@/style.scss';
import { mainBoot } from '@/boot/main-boot.js'; import { mainBoot } from '@/boot/main-boot.js';
import { subBoot } from '@/boot/sub-boot.js'; import { subBoot } from '@/boot/sub-boot.js';
const subBootPaths = ['/share', '/auth', '/miauth', '/oauth', '/signup-complete', '/install-extensions']; const subBootPaths = ['/share', '/auth', '/miauth', '/oauth', '/signup-complete'];
if (subBootPaths.some(i => window.location.pathname === i || window.location.pathname.startsWith(i + '/'))) { if (subBootPaths.some(i => location.pathname === i || location.pathname.startsWith(i + '/'))) {
subBoot(); subBoot();
} else { } else {
mainBoot(); mainBoot();

View File

@@ -191,7 +191,7 @@ export async function login(token: AccountWithToken['token'], redirect?: string)
// 他のタブは再読み込みするだけ // 他のタブは再読み込みするだけ
reloadChannel.postMessage(null); reloadChannel.postMessage(null);
// このページはredirectで指定された先に移動 // このページはredirectで指定された先に移動
window.location.href = redirect; location.href = redirect;
return; return;
} }

View File

@@ -26,6 +26,8 @@ import { deckStore } from '@/ui/deck/deck-store.js';
import { analytics, initAnalytics } from '@/analytics.js'; import { analytics, initAnalytics } from '@/analytics.js';
import { miLocalStorage } from '@/local-storage.js'; import { miLocalStorage } from '@/local-storage.js';
import { fetchCustomEmojis } from '@/custom-emojis.js'; import { fetchCustomEmojis } from '@/custom-emojis.js';
import { setupRouter } from '@/router/main.js';
import { createMainRouter } from '@/router/definition.js';
import { prefer } from '@/preferences.js'; import { prefer } from '@/preferences.js';
import { $i } from '@/i.js'; import { $i } from '@/i.js';
@@ -95,28 +97,28 @@ export async function common(createVue: () => App<Element>) {
//#endregion //#endregion
// タッチデバイスでCSSの:hoverを機能させる // タッチデバイスでCSSの:hoverを機能させる
window.document.addEventListener('touchend', () => {}, { passive: true }); document.addEventListener('touchend', () => {}, { passive: true });
// URLに#pswpを含む場合は取り除く // URLに#pswpを含む場合は取り除く
if (window.location.hash === '#pswp') { if (location.hash === '#pswp') {
window.history.replaceState(null, '', window.location.href.replace('#pswp', '')); history.replaceState(null, '', location.href.replace('#pswp', ''));
} }
// 一斉リロード // 一斉リロード
reloadChannel.addEventListener('message', path => { reloadChannel.addEventListener('message', path => {
if (path !== null) window.location.href = path; if (path !== null) location.href = path;
else window.location.reload(); else location.reload();
}); });
// If mobile, insert the viewport meta tag // If mobile, insert the viewport meta tag
if (['smartphone', 'tablet'].includes(deviceKind)) { if (['smartphone', 'tablet'].includes(deviceKind)) {
const viewport = window.document.getElementsByName('viewport').item(0); const viewport = document.getElementsByName('viewport').item(0);
viewport.setAttribute('content', viewport.setAttribute('content',
`${viewport.getAttribute('content')}, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover`); `${viewport.getAttribute('content')}, minimum-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover`);
} }
//#region Set lang attr //#region Set lang attr
const html = window.document.documentElement; const html = document.documentElement;
html.setAttribute('lang', lang); html.setAttribute('lang', lang);
//#endregion //#endregion
@@ -130,11 +132,11 @@ export async function common(createVue: () => App<Element>) {
}); });
//#region loginId //#region loginId
const params = new URLSearchParams(window.location.search); const params = new URLSearchParams(location.search);
const loginId = params.get('loginId'); const loginId = params.get('loginId');
if (loginId) { if (loginId) {
const target = getUrlWithoutLoginId(window.location.href); const target = getUrlWithoutLoginId(location.href);
if (!$i || $i.id !== loginId) { if (!$i || $i.id !== loginId) {
const account = await getAccountFromId(loginId); const account = await getAccountFromId(loginId);
@@ -143,7 +145,7 @@ export async function common(createVue: () => App<Element>) {
} }
} }
window.history.replaceState({ misskey: 'loginId' }, '', target); history.replaceState({ misskey: 'loginId' }, '', target);
} }
//#endregion //#endregion
@@ -155,7 +157,7 @@ export async function common(createVue: () => App<Element>) {
); );
}, { immediate: miLocalStorage.getItem('theme') == null }); }, { immediate: miLocalStorage.getItem('theme') == null });
window.document.documentElement.dataset.colorScheme = store.s.darkMode ? 'dark' : 'light'; document.documentElement.dataset.colorScheme = store.s.darkMode ? 'dark' : 'light';
const darkTheme = prefer.model('darkTheme'); const darkTheme = prefer.model('darkTheme');
const lightTheme = prefer.model('lightTheme'); const lightTheme = prefer.model('lightTheme');
@@ -201,20 +203,20 @@ export async function common(createVue: () => App<Element>) {
}, { immediate: true }); }, { immediate: true });
watch(prefer.r.useBlurEffectForModal, v => { watch(prefer.r.useBlurEffectForModal, v => {
window.document.documentElement.style.setProperty('--MI-modalBgFilter', v ? 'blur(4px)' : 'none'); document.documentElement.style.setProperty('--MI-modalBgFilter', v ? 'blur(4px)' : 'none');
}, { immediate: true }); }, { immediate: true });
watch(prefer.r.useBlurEffect, v => { watch(prefer.r.useBlurEffect, v => {
if (v) { if (v) {
window.document.documentElement.style.removeProperty('--MI-blur'); document.documentElement.style.removeProperty('--MI-blur');
} else { } else {
window.document.documentElement.style.setProperty('--MI-blur', 'none'); document.documentElement.style.setProperty('--MI-blur', 'none');
} }
}, { immediate: true }); }, { immediate: true });
// Keep screen on // Keep screen on
const onVisibilityChange = () => window.document.addEventListener('visibilitychange', () => { const onVisibilityChange = () => document.addEventListener('visibilitychange', () => {
if (window.document.visibilityState === 'visible') { if (document.visibilityState === 'visible') {
navigator.wakeLock.request('screen'); navigator.wakeLock.request('screen');
} }
}); });
@@ -224,7 +226,7 @@ export async function common(createVue: () => App<Element>) {
.catch(() => { .catch(() => {
// On WebKit-based browsers, user activation is required to send wake lock request // On WebKit-based browsers, user activation is required to send wake lock request
// https://webkit.org/blog/13862/the-user-activation-api/ // https://webkit.org/blog/13862/the-user-activation-api/
window.document.addEventListener( document.addEventListener(
'click', 'click',
() => navigator.wakeLock.request('screen').then(onVisibilityChange), () => navigator.wakeLock.request('screen').then(onVisibilityChange),
{ once: true }, { once: true },
@@ -232,10 +234,6 @@ export async function common(createVue: () => App<Element>) {
}); });
} }
if (prefer.s.makeEveryTextElementsSelectable) {
window.document.documentElement.classList.add('forceSelectableAll');
}
//#region Fetch user //#region Fetch user
if ($i && $i.token) { if ($i && $i.token) {
if (_DEV_) { if (_DEV_) {
@@ -265,6 +263,8 @@ export async function common(createVue: () => App<Element>) {
const app = createVue(); const app = createVue();
setupRouter(app, createMainRouter);
if (_DEV_) { if (_DEV_) {
app.config.performance = true; app.config.performance = true;
} }
@@ -278,16 +278,16 @@ export async function common(createVue: () => App<Element>) {
const rootEl = ((): HTMLElement => { const rootEl = ((): HTMLElement => {
const MISSKEY_MOUNT_DIV_ID = 'misskey_app'; const MISSKEY_MOUNT_DIV_ID = 'misskey_app';
const currentRoot = window.document.getElementById(MISSKEY_MOUNT_DIV_ID); const currentRoot = document.getElementById(MISSKEY_MOUNT_DIV_ID);
if (currentRoot) { if (currentRoot) {
console.warn('multiple import detected'); console.warn('multiple import detected');
return currentRoot; return currentRoot;
} }
const root = window.document.createElement('div'); const root = document.createElement('div');
root.id = MISSKEY_MOUNT_DIV_ID; root.id = MISSKEY_MOUNT_DIV_ID;
window.document.body.appendChild(root); document.body.appendChild(root);
return root; return root;
})(); })();
@@ -300,26 +300,24 @@ export async function common(createVue: () => App<Element>) {
removeSplash(); removeSplash();
//#region Self-XSS 対策メッセージ //#region Self-XSS 対策メッセージ
if (!_DEV_) { console.log(
console.log( `%c${i18n.ts._selfXssPrevention.warning}`,
`%c${i18n.ts._selfXssPrevention.warning}`, 'color: #f00; background-color: #ff0; font-size: 36px; padding: 4px;',
'color: #f00; background-color: #ff0; font-size: 36px; padding: 4px;', );
); console.log(
console.log( `%c${i18n.ts._selfXssPrevention.title}`,
`%c${i18n.ts._selfXssPrevention.title}`, 'color: #f00; font-weight: 900; font-family: "Hiragino Sans W9", "Hiragino Kaku Gothic ProN", sans-serif; font-size: 24px;',
'color: #f00; font-weight: 900; font-family: "Hiragino Sans W9", "Hiragino Kaku Gothic ProN", sans-serif; font-size: 24px;', );
); console.log(
console.log( `%c${i18n.ts._selfXssPrevention.description1}`,
`%c${i18n.ts._selfXssPrevention.description1}`, 'font-size: 16px; font-weight: 700;',
'font-size: 16px; font-weight: 700;', );
); console.log(
console.log( `%c${i18n.ts._selfXssPrevention.description2}`,
`%c${i18n.ts._selfXssPrevention.description2}`, 'font-size: 16px;',
'font-size: 16px;', 'font-size: 20px; font-weight: 700; color: #f00;',
'font-size: 20px; font-weight: 700; color: #f00;', );
); console.log(i18n.tsx._selfXssPrevention.description3({ link: 'https://misskey-hub.net/docs/for-users/resources/self-xss/' }));
console.log(i18n.tsx._selfXssPrevention.description3({ link: 'https://misskey-hub.net/docs/for-users/resources/self-xss/' }));
}
//#endregion //#endregion
return { return {
@@ -330,7 +328,7 @@ export async function common(createVue: () => App<Element>) {
} }
function removeSplash() { function removeSplash() {
const splash = window.document.getElementById('splash'); const splash = document.getElementById('splash');
if (splash) { if (splash) {
splash.style.opacity = '0'; splash.style.opacity = '0';
splash.style.pointerEvents = 'none'; splash.style.pointerEvents = 'none';

View File

@@ -24,7 +24,7 @@ import { miLocalStorage } from '@/local-storage.js';
import { claimAchievement, claimedAchievements } from '@/utility/achievements.js'; import { claimAchievement, claimedAchievements } from '@/utility/achievements.js';
import { initializeSw } from '@/utility/initialize-sw.js'; import { initializeSw } from '@/utility/initialize-sw.js';
import { emojiPicker } from '@/utility/emoji-picker.js'; import { emojiPicker } from '@/utility/emoji-picker.js';
import { mainRouter } from '@/router.js'; import { mainRouter } from '@/router/main.js';
import { makeHotkey } from '@/utility/hotkey.js'; import { makeHotkey } from '@/utility/hotkey.js';
import { addCustomEmoji, removeCustomEmojis, updateCustomEmojis } from '@/custom-emojis.js'; import { addCustomEmoji, removeCustomEmojis, updateCustomEmojis } from '@/custom-emojis.js';
import { prefer } from '@/preferences.js'; import { prefer } from '@/preferences.js';
@@ -43,7 +43,7 @@ export async function mainBoot() {
if (!$i) uiStyle = 'visitor'; if (!$i) uiStyle = 'visitor';
if (searchParams.has('zen')) uiStyle = 'zen'; if (searchParams.has('zen')) uiStyle = 'zen';
if (uiStyle === 'deck' && prefer.s['deck.useSimpleUiForNonRootPages'] && window.location.pathname !== '/') uiStyle = 'zen'; if (uiStyle === 'deck' && prefer.s['deck.useSimpleUiForNonRootPages'] && location.pathname !== '/') uiStyle = 'zen';
if (searchParams.has('ui')) uiStyle = searchParams.get('ui'); if (searchParams.has('ui')) uiStyle = searchParams.get('ui');
@@ -201,8 +201,6 @@ export async function mainBoot() {
prefer.commit('sound.on.noteMy', store.s.sound_noteMy as any); prefer.commit('sound.on.noteMy', store.s.sound_noteMy as any);
prefer.commit('sound.on.notification', store.s.sound_notification as any); prefer.commit('sound.on.notification', store.s.sound_notification as any);
prefer.commit('sound.on.reaction', store.s.sound_reaction as any); prefer.commit('sound.on.reaction', store.s.sound_reaction as any);
prefer.commit('defaultNoteVisibility', store.s.defaultNoteVisibility);
prefer.commit('defaultNoteLocalOnly', store.s.defaultNoteLocalOnly);
window.setTimeout(() => { window.setTimeout(() => {
unisonReload(); unisonReload();
@@ -216,7 +214,7 @@ export async function mainBoot() {
let reloadDialogShowing = false; let reloadDialogShowing = false;
stream.on('_disconnected_', async () => { stream.on('_disconnected_', async () => {
if (prefer.s.serverDisconnectedBehavior === 'reload') { if (prefer.s.serverDisconnectedBehavior === 'reload') {
window.location.reload(); location.reload();
} else if (prefer.s.serverDisconnectedBehavior === 'dialog') { } else if (prefer.s.serverDisconnectedBehavior === 'dialog') {
if (reloadDialogShowing) return; if (reloadDialogShowing) return;
reloadDialogShowing = true; reloadDialogShowing = true;
@@ -227,7 +225,7 @@ export async function mainBoot() {
}); });
reloadDialogShowing = false; reloadDialogShowing = false;
if (!canceled) { if (!canceled) {
window.location.reload(); location.reload();
} }
} }
}); });
@@ -398,7 +396,7 @@ export async function mainBoot() {
let lastVisibilityChangedAt = Date.now(); let lastVisibilityChangedAt = Date.now();
function claimPlainLucky() { function claimPlainLucky() {
if (window.document.visibilityState !== 'visible') { if (document.visibilityState !== 'visible') {
if (justPlainLuckyTimer != null) window.clearTimeout(justPlainLuckyTimer); if (justPlainLuckyTimer != null) window.clearTimeout(justPlainLuckyTimer);
return; return;
} }
@@ -413,7 +411,7 @@ export async function mainBoot() {
window.addEventListener('visibilitychange', () => { window.addEventListener('visibilitychange', () => {
const now = Date.now(); const now = Date.now();
if (window.document.visibilityState === 'visible') { if (document.visibilityState === 'visible') {
// タブを高速で切り替えたら取得処理が何度も走るのを防ぐ // タブを高速で切り替えたら取得処理が何度も走るのを防ぐ
if ((now - lastVisibilityChangedAt) < 1000 * 10) { if ((now - lastVisibilityChangedAt) < 1000 * 10) {
justPlainLuckyTimer = window.setTimeout(claimPlainLucky, 1000 * 10); justPlainLuckyTimer = window.setTimeout(claimPlainLucky, 1000 * 10);
@@ -458,7 +456,7 @@ export async function mainBoot() {
const latestDonationInfoShownAt = miLocalStorage.getItem('latestDonationInfoShownAt'); const latestDonationInfoShownAt = miLocalStorage.getItem('latestDonationInfoShownAt');
const neverShowDonationInfo = miLocalStorage.getItem('neverShowDonationInfo'); const neverShowDonationInfo = miLocalStorage.getItem('neverShowDonationInfo');
if (neverShowDonationInfo !== 'true' && (createdAt.getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 3))) && !window.location.pathname.startsWith('/miauth')) { if (neverShowDonationInfo !== 'true' && (createdAt.getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 3))) && !location.pathname.startsWith('/miauth')) {
if (latestDonationInfoShownAt == null || (new Date(latestDonationInfoShownAt).getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 30)))) { if (latestDonationInfoShownAt == null || (new Date(latestDonationInfoShownAt).getTime() < (Date.now() - (1000 * 60 * 60 * 24 * 30)))) {
const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkDonation.vue')), {}, { const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkDonation.vue')), {}, {
closed: () => dispose(), closed: () => dispose(),
@@ -554,7 +552,7 @@ export async function mainBoot() {
mainRouter.push('/search'); mainRouter.push('/search');
}, },
} as const satisfies Keymap; } as const satisfies Keymap;
window.document.addEventListener('keydown', makeHotkey(keymap), { passive: false }); document.addEventListener('keydown', makeHotkey(keymap), { passive: false });
initializeSw(); initializeSw();
} }

View File

@@ -88,9 +88,9 @@ import { i18n } from '@/i18n.js';
import { dateString } from '@/filters/date.js'; import { dateString } from '@/filters/date.js';
import MkFolder from '@/components/MkFolder.vue'; import MkFolder from '@/components/MkFolder.vue';
import RouterView from '@/components/global/RouterView.vue'; import RouterView from '@/components/global/RouterView.vue';
import { useRouterFactory } from '@/router/supplier';
import MkTextarea from '@/components/MkTextarea.vue'; import MkTextarea from '@/components/MkTextarea.vue';
import { copyToClipboard } from '@/utility/copy-to-clipboard.js'; import { copyToClipboard } from '@/utility/copy-to-clipboard.js';
import { createRouter } from '@/router.js';
const props = defineProps<{ const props = defineProps<{
report: Misskey.entities.AdminAbuseUserReportsResponse[number]; report: Misskey.entities.AdminAbuseUserReportsResponse[number];
@@ -100,9 +100,10 @@ const emit = defineEmits<{
(ev: 'resolved', reportId: string): void; (ev: 'resolved', reportId: string): void;
}>(); }>();
const targetRouter = createRouter(`/admin/user/${props.report.targetUserId}`); const routerFactory = useRouterFactory();
const targetRouter = routerFactory(`/admin/user/${props.report.targetUserId}`);
targetRouter.init(); targetRouter.init();
const reporterRouter = createRouter(`/admin/user/${props.report.reporterId}`); const reporterRouter = routerFactory(`/admin/user/${props.report.reporterId}`);
reporterRouter.init(); reporterRouter.init();
const moderationNote = ref(props.report.moderationNote ?? ''); const moderationNote = ref(props.report.moderationNote ?? '');
@@ -134,7 +135,7 @@ function forward() {
function showMenu(ev: MouseEvent) { function showMenu(ev: MouseEvent) {
os.popupMenu([{ os.popupMenu([{
icon: 'ti ti-hash', icon: 'ti ti-id',
text: 'Copy ID', text: 'Copy ID',
action: () => { action: () => {
copyToClipboard(props.report.id); copyToClipboard(props.report.id);

View File

@@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, useTemplateRef } from 'vue'; import { ref, shallowRef } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import MkWindow from '@/components/MkWindow.vue'; import MkWindow from '@/components/MkWindow.vue';
import MkTextarea from '@/components/MkTextarea.vue'; import MkTextarea from '@/components/MkTextarea.vue';
@@ -47,7 +47,7 @@ const emit = defineEmits<{
(ev: 'closed'): void; (ev: 'closed'): void;
}>(); }>();
const uiWindow = useTemplateRef('uiWindow'); const uiWindow = shallowRef<InstanceType<typeof MkWindow>>();
const comment = ref(props.initialComment ?? ''); const comment = ref(props.initialComment ?? '');
function send() { function send() {

View File

@@ -192,7 +192,7 @@ function tick() {
tick(); tick();
function calcColors() { function calcColors() {
const computedStyle = getComputedStyle(window.document.documentElement); const computedStyle = getComputedStyle(document.documentElement);
const dark = tinycolor(computedStyle.getPropertyValue('--MI_THEME-bg')).isDark(); const dark = tinycolor(computedStyle.getPropertyValue('--MI_THEME-bg')).isDark();
const accent = tinycolor(computedStyle.getPropertyValue('--MI_THEME-accent')).toHexString(); const accent = tinycolor(computedStyle.getPropertyValue('--MI_THEME-accent')).toHexString();
majorGraduationColor.value = dark ? 'rgba(255, 255, 255, 0.3)' : 'rgba(0, 0, 0, 0.3)'; majorGraduationColor.value = dark ? 'rgba(255, 255, 255, 0.3)' : 'rgba(0, 0, 0, 0.3)';

View File

@@ -4,14 +4,14 @@ SPDX-License-Identifier: AGPL-3.0-only
--> -->
<template> <template>
<canvas ref="canvasEl" style="display: block; width: 100%; height: 100%; pointer-events: none;"></canvas> <canvas ref="canvasEl" style="width: 100%; height: 100%; pointer-events: none;"></canvas>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, onUnmounted, useTemplateRef } from 'vue'; import { onMounted, onUnmounted, shallowRef } from 'vue';
import isChromatic from 'chromatic/isChromatic'; import isChromatic from 'chromatic/isChromatic';
const canvasEl = useTemplateRef('canvasEl'); const canvasEl = shallowRef<HTMLCanvasElement>();
const props = withDefaults(defineProps<{ const props = withDefaults(defineProps<{
scale?: number; scale?: number;

View File

@@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, useTemplateRef } from 'vue'; import { onMounted, shallowRef } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import * as os from '@/os.js'; import * as os from '@/os.js';
import { misskeyApi } from '@/utility/misskey-api.js'; import { misskeyApi } from '@/utility/misskey-api.js';
@@ -37,8 +37,8 @@ const props = withDefaults(defineProps<{
}>(), { }>(), {
}); });
const rootEl = useTemplateRef('rootEl'); const rootEl = shallowRef<HTMLDivElement>();
const modal = useTemplateRef('modal'); const modal = shallowRef<InstanceType<typeof MkModal>>();
async function ok() { async function ok() {
if (props.announcement.needConfirmationToRead) { if (props.announcement.needConfirmationToRead) {

View File

@@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { useTemplateRef } from 'vue'; import { shallowRef } from 'vue';
import * as Misskey from 'misskey-js'; import * as Misskey from 'misskey-js';
import MkModalWindow from '@/components/MkModalWindow.vue'; import MkModalWindow from '@/components/MkModalWindow.vue';
import XAntennaEditor from '@/components/MkAntennaEditor.vue'; import XAntennaEditor from '@/components/MkAntennaEditor.vue';
@@ -40,7 +40,7 @@ const emit = defineEmits<{
(ev: 'closed'): void, (ev: 'closed'): void,
}>(); }>();
const dialog = useTemplateRef('dialog'); const dialog = shallowRef<InstanceType<typeof MkModalWindow>>();
function onAntennaCreated(newAntenna: Misskey.entities.Antenna) { function onAntennaCreated(newAntenna: Misskey.entities.Antenna) {
emit('created', newAntenna); emit('created', newAntenna);

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