Merge branch 'develop' into sw-notification-action

This commit is contained in:
tamaina 2021-06-29 22:45:12 +09:00
commit a1c45e85cf
332 changed files with 1379 additions and 3400 deletions

View File

@ -2,6 +2,6 @@
"extension": ["ts","js","cjs","mjs"], "extension": ["ts","js","cjs","mjs"],
"require": ["ts-node/register", "tsconfig-paths/register"], "require": ["ts-node/register", "tsconfig-paths/register"],
"slow": 1000, "slow": 1000,
"timeout": 30000, "timeout": 35000,
"exit": true "exit": true
} }

View File

@ -28,12 +28,7 @@ If your language is not listed in Crowdin, please open an issue.
![Crowdin](https://d322cqt584bo4o.cloudfront.net/misskey/localized.svg) ![Crowdin](https://d322cqt584bo4o.cloudfront.net/misskey/localized.svg)
## Internationalization (i18n)
Misskey uses the Vue.js plugin [Vue I18n](https://github.com/kazupon/vue-i18n).
Documentation of Vue I18n is available at http://kazupon.github.io/vue-i18n/introduction.html .
## Documentation ## Documentation
* Documents for contributors are located in [`/docs`](/docs).
* Documents for instance admins are located in [`/docs`](/docs). * Documents for instance admins are located in [`/docs`](/docs).
* Documents for end users are located in [`/src/docs`](/src/docs). * Documents for end users are located in [`/src/docs`](/src/docs).
@ -41,8 +36,8 @@ Documentation of Vue I18n is available at http://kazupon.github.io/vue-i18n/intr
* Test codes are located in [`/test`](/test). * Test codes are located in [`/test`](/test).
## Continuous integration ## Continuous integration
Misskey uses CircleCI for executing automated tests. Misskey uses GitHub Actions for executing automated tests.
Configuration files are located in [`/.circleci`](/.circleci). Configuration files are located in [`/.github/workflows`](/.github/workflows).
## Adding MisskeyRoom items ## Adding MisskeyRoom items
* Use English for material, object and texture names. * Use English for material, object and texture names.

View File

@ -6,10 +6,6 @@ And is distributed under The GNU Affero General Public License Version 3, you sh
Misskey includes several third-party Open-Source softwares. Misskey includes several third-party Open-Source softwares.
Unicode emoji regular expressions by Twitter, Inc.
License: MIT
https://github.com/twitter/twemoji-parser/blob/master/LICENSE.md
Emoji keywords for Unicode 11 and below by Mu-An Chiou Emoji keywords for Unicode 11 and below by Mu-An Chiou
License: MIT License: MIT
https://github.com/muan/emojilib/blob/master/LICENSE https://github.com/muan/emojilib/blob/master/LICENSE

View File

@ -99,6 +99,11 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
To receive updates of this repo, follow [@repo@misskey.io](https://misskey.io/@repo) on fediverse. To receive updates of this repo, follow [@repo@misskey.io](https://misskey.io/@repo) on fediverse.
Related projects
----------------------------------------------------------------
- [misskey.js](https://github.com/misskey-dev/misskey.js) - Misskey SDK for JavaScript
- [mfm.js](https://github.com/misskey-dev/mfm.js) - MFM parser
:heart: Backers :heart: Backers
---------------------------------------------------------------- ----------------------------------------------------------------
<!-- PATREON_START --> <!-- PATREON_START -->

9
SECURITY.md Normal file
View File

@ -0,0 +1,9 @@
# Reporting Security Issues
If you discover a security issue in Misskey, please report it by sending an
email to [syuilotan@yahoo.co.jp](mailto:syuilotan@yahoo.co.jp).
This will allow us to assess the risk, and make a fix available before we add a
bug report to the GitHub repository.
Thanks for helping make Misskey safe for everyone.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

After

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

View File

@ -4,7 +4,7 @@
import * as fs from 'fs'; import * as fs from 'fs';
import * as gulp from 'gulp'; import * as gulp from 'gulp';
import * as rimraf from 'rimraf'; import rimraf from 'rimraf';
const replace = require('gulp-replace'); const replace = require('gulp-replace');
const terser = require('gulp-terser'); const terser = require('gulp-terser');
const cssnano = require('gulp-cssnano'); const cssnano = require('gulp-cssnano');

View File

@ -259,8 +259,6 @@ monthX: "{month}"
yearX: "{year}" yearX: "{year}"
pages: "الصفحات" pages: "الصفحات"
integration: "دمج" integration: "دمج"
connectSerice: "أوصل"
disconnectSerice: "قطع الاتصال"
enableLocalTimeline: "تفعيل الخيط المحلي" enableLocalTimeline: "تفعيل الخيط المحلي"
enableGlobalTimeline: "تفعيل الخيط الزمني الشامل" enableGlobalTimeline: "تفعيل الخيط الزمني الشامل"
disablingTimelinesInfo: "سيتمكن المسؤولون ومن تعديل دائمًا و من الوصول إلى جميع المخططات الزمنية ، حتى إذا لم يتم تمكينها." disablingTimelinesInfo: "سيتمكن المسؤولون ومن تعديل دائمًا و من الوصول إلى جميع المخططات الزمنية ، حتى إذا لم يتم تمكينها."

View File

@ -269,8 +269,6 @@ monthX: "{month}"
yearX: "{year}" yearX: "{year}"
pages: "Stránky" pages: "Stránky"
integration: "Integrace" integration: "Integrace"
connectSerice: "Připojit"
disconnectSerice: "Odpojit"
enableLocalTimeline: "Povolit lokální čas" enableLocalTimeline: "Povolit lokální čas"
enableGlobalTimeline: "Povolit globální čas" enableGlobalTimeline: "Povolit globální čas"
registration: "Registrace" registration: "Registrace"

View File

@ -279,6 +279,7 @@ emptyDrive: "Drive ist leer"
emptyFolder: "Der Ordner ist leer" emptyFolder: "Der Ordner ist leer"
unableToDelete: "Nicht löschbar" unableToDelete: "Nicht löschbar"
inputNewFileName: "Gib einen neuen Dateinamen ein" inputNewFileName: "Gib einen neuen Dateinamen ein"
inputNewDescription: "Gib eine neue Beschreibung ein"
inputNewFolderName: "Gib einen neuen Ordnernamen ein" inputNewFolderName: "Gib einen neuen Ordnernamen ein"
circularReferenceFolder: "Der Zielordner ist ein Unterorder des Ordners, den du verschieben möchtest." circularReferenceFolder: "Der Zielordner ist ein Unterorder des Ordners, den du verschieben möchtest."
hasChildFilesOrFolders: "Dieser Ordner kann nicht gelöscht werden, da er nicht leer ist." hasChildFilesOrFolders: "Dieser Ordner kann nicht gelöscht werden, da er nicht leer ist."
@ -310,8 +311,8 @@ monthX: "{month}"
yearX: "{year}" yearX: "{year}"
pages: "Seiten" pages: "Seiten"
integration: "Integration" integration: "Integration"
connectSerice: "Verbinden" connectService: "Verbinden"
disconnectSerice: "Trennen" disconnectService: "Trennen"
enableLocalTimeline: "Lokale Chronik aktivieren" enableLocalTimeline: "Lokale Chronik aktivieren"
enableGlobalTimeline: "Globale Chronik aktivieren" enableGlobalTimeline: "Globale Chronik aktivieren"
disablingTimelinesInfo: "Administratoren und Moderatoren haben immer Zugriff auf alle Chroniken, auch wenn diese deaktiviert sind." disablingTimelinesInfo: "Administratoren und Moderatoren haben immer Zugriff auf alle Chroniken, auch wenn diese deaktiviert sind."
@ -325,6 +326,7 @@ driveCapacityPerRemoteAccount: "Drive-Kapazität pro Benutzer anderer Instanzen"
inMb: "In Megabytes" inMb: "In Megabytes"
iconUrl: "Icon-URL" iconUrl: "Icon-URL"
bannerUrl: "Banner-URL" bannerUrl: "Banner-URL"
backgroundImageUrl: "Hintergrundbild-URL"
basicInfo: "Basisdaten" basicInfo: "Basisdaten"
pinnedUsers: "Angepinnte Benutzer" pinnedUsers: "Angepinnte Benutzer"
pinnedUsersDescription: "Gib einen Benutzernamen pro Zeile ein. Diese werden im \"Erkunden\" Tab angezeigt." pinnedUsersDescription: "Gib einen Benutzernamen pro Zeile ein. Diese werden im \"Erkunden\" Tab angezeigt."
@ -546,6 +548,8 @@ disablePlayer: "Video-Player schließen"
expandTweet: "Tweet ausklappen" expandTweet: "Tweet ausklappen"
themeEditor: "Farbthemen-Editor" themeEditor: "Farbthemen-Editor"
description: "Beschreibung" description: "Beschreibung"
describeFile: "Beschreibung hinzufügen"
enterFileDescription: "Beschreibung eingeben"
author: "Autor" author: "Autor"
leaveConfirm: "Es gibt unspeicherte Änderungen. Möchtest du diese verwerfen?" leaveConfirm: "Es gibt unspeicherte Änderungen. Möchtest du diese verwerfen?"
manage: "Verwaltung" manage: "Verwaltung"

View File

@ -279,6 +279,7 @@ emptyDrive: "The drive is empty"
emptyFolder: "This folder is empty" emptyFolder: "This folder is empty"
unableToDelete: "Unable to delete" unableToDelete: "Unable to delete"
inputNewFileName: "Enter a new filename" inputNewFileName: "Enter a new filename"
inputNewDescription: "Enter new caption"
inputNewFolderName: "Enter a new folder name" inputNewFolderName: "Enter a new folder name"
circularReferenceFolder: "The destination folder is a subfolder of the folder you wish to move." circularReferenceFolder: "The destination folder is a subfolder of the folder you wish to move."
hasChildFilesOrFolders: "Since this folder is not empty, it can not be deleted." hasChildFilesOrFolders: "Since this folder is not empty, it can not be deleted."
@ -310,8 +311,8 @@ monthX: "{month}"
yearX: "{year} /" yearX: "{year} /"
pages: "Pages" pages: "Pages"
integration: "Integration" integration: "Integration"
connectSerice: "Connect" connectService: "Connect"
disconnectSerice: "Disconnect" disconnectService: "Disconnect"
enableLocalTimeline: "Enable local timeline" enableLocalTimeline: "Enable local timeline"
enableGlobalTimeline: "Enable global timeline" enableGlobalTimeline: "Enable global timeline"
disablingTimelinesInfo: "Admins and Mods will always have access to all timelines, even if they are not enabled." disablingTimelinesInfo: "Admins and Mods will always have access to all timelines, even if they are not enabled."
@ -325,6 +326,7 @@ driveCapacityPerRemoteAccount: "Drive capacity per remote user"
inMb: "In megabytes" inMb: "In megabytes"
iconUrl: "Icon URL" iconUrl: "Icon URL"
bannerUrl: "Banner image URL" bannerUrl: "Banner image URL"
backgroundImageUrl: "Background image URL"
basicInfo: "Basic info" basicInfo: "Basic info"
pinnedUsers: "Pinned user" pinnedUsers: "Pinned user"
pinnedUsersDescription: "List one username per line. Users listed here will be pinned under \"Explore\" tab." pinnedUsersDescription: "List one username per line. Users listed here will be pinned under \"Explore\" tab."
@ -546,6 +548,8 @@ disablePlayer: "Close video player"
expandTweet: "Expand tweet" expandTweet: "Expand tweet"
themeEditor: "Theme editor" themeEditor: "Theme editor"
description: "Description" description: "Description"
describeFile: "Add caption"
enterFileDescription: "Enter caption"
author: "Author" author: "Author"
leaveConfirm: "There are unsaved changes. Do you want to discard them?" leaveConfirm: "There are unsaved changes. Do you want to discard them?"
manage: "Management" manage: "Management"

View File

@ -309,8 +309,6 @@ monthX: "Mes {month}"
yearX: "Año {year}" yearX: "Año {year}"
pages: "Páginas" pages: "Páginas"
integration: "Integración" integration: "Integración"
connectSerice: "Conectarse"
disconnectSerice: "Desconectarse"
enableLocalTimeline: "Habilitar linea de tiempo local" enableLocalTimeline: "Habilitar linea de tiempo local"
enableGlobalTimeline: "Habilitar linea de tiempo global" enableGlobalTimeline: "Habilitar linea de tiempo global"
disablingTimelinesInfo: "Aunque se desactiven estas lineas de tiempo, por conveniencia el administrador y los moderadores pueden seguir usándolos" disablingTimelinesInfo: "Aunque se desactiven estas lineas de tiempo, por conveniencia el administrador y los moderadores pueden seguir usándolos"

View File

@ -279,6 +279,7 @@ emptyDrive: "Le Drive est vide"
emptyFolder: "Le dossier est vide" emptyFolder: "Le dossier est vide"
unableToDelete: "Suppression impossible" unableToDelete: "Suppression impossible"
inputNewFileName: "Entrez un nouveau nom de fichier" inputNewFileName: "Entrez un nouveau nom de fichier"
inputNewDescription: "Veuillez entrer une nouvelle description"
inputNewFolderName: "Entrez un nouveau nom de dossier" inputNewFolderName: "Entrez un nouveau nom de dossier"
circularReferenceFolder: "Le dossier de destination est un sous-dossier du dossier que vous souhaitez déplacer." circularReferenceFolder: "Le dossier de destination est un sous-dossier du dossier que vous souhaitez déplacer."
hasChildFilesOrFolders: "Impossible de supprimer ce dossier car il n'est pas vide." hasChildFilesOrFolders: "Impossible de supprimer ce dossier car il n'est pas vide."
@ -310,8 +311,8 @@ monthX: "{month}"
yearX: "{year}" yearX: "{year}"
pages: "Pages" pages: "Pages"
integration: "Intégrations" integration: "Intégrations"
connectSerice: "Connecter" connectService: "Connexion"
disconnectSerice: "Déconnecter" disconnectService: "Déconnexion"
enableLocalTimeline: "Activer le fil local" enableLocalTimeline: "Activer le fil local"
enableGlobalTimeline: "Activer le fil global" enableGlobalTimeline: "Activer le fil global"
disablingTimelinesInfo: "Même si vous désactivez ces fils, les administrateur·rice·s et les modérateur·rice·s pourront toujours y accéder." disablingTimelinesInfo: "Même si vous désactivez ces fils, les administrateur·rice·s et les modérateur·rice·s pourront toujours y accéder."
@ -546,6 +547,8 @@ disablePlayer: "Fermer le lecteur vidéo"
expandTweet: "Étendre le tweet" expandTweet: "Étendre le tweet"
themeEditor: "Éditeur de thèmes" themeEditor: "Éditeur de thèmes"
description: "Description" description: "Description"
describeFile: "Ajouter une description d'image"
enterFileDescription: "Saisissez une description"
author: "Auteur·rice" author: "Auteur·rice"
leaveConfirm: "Vous avez des modifications non-sauvegardées. Voulez-vous les ignorer ?" leaveConfirm: "Vous avez des modifications non-sauvegardées. Voulez-vous les ignorer ?"
manage: "Gestion" manage: "Gestion"

View File

@ -1,5 +1,5 @@
--- ---
_lang_: "Bahasa Jepang" _lang_: "Bahasa Indonesia"
headlineMisskey: "Jaringan terhubung melalui note" headlineMisskey: "Jaringan terhubung melalui note"
introMisskey: "Selamat datang! Misskey adalah perangkat mikroblog tercatu bersifat sumber terbuka.\nMulailah menuliskan catatan, bagikan peristiwa terkini, serta ceritakan segala tentangmu.📡\nTunjukkan juga reaksimu pada catatan pengguna lain.👍\nMari jelajahi dunia baru🚀" introMisskey: "Selamat datang! Misskey adalah perangkat mikroblog tercatu bersifat sumber terbuka.\nMulailah menuliskan catatan, bagikan peristiwa terkini, serta ceritakan segala tentangmu.📡\nTunjukkan juga reaksimu pada catatan pengguna lain.👍\nMari jelajahi dunia baru🚀"
monthAndDay: "{day} {month}" monthAndDay: "{day} {month}"
@ -310,8 +310,6 @@ monthX: "{month}"
yearX: "{year}" yearX: "{year}"
pages: "Halaman" pages: "Halaman"
integration: "Integrasi" integration: "Integrasi"
connectSerice: "Sambungkan"
disconnectSerice: "Putuskan"
enableLocalTimeline: "Nyalakan linimasa lokal" enableLocalTimeline: "Nyalakan linimasa lokal"
enableGlobalTimeline: "Nyalakan linimasa global" enableGlobalTimeline: "Nyalakan linimasa global"
disablingTimelinesInfo: "Admin dan Moderator akan selalu memiliki akses ke semua linimasa meskipun linimasa tersebut tidak diaktifkan." disablingTimelinesInfo: "Admin dan Moderator akan selalu memiliki akses ke semua linimasa meskipun linimasa tersebut tidak diaktifkan."
@ -977,9 +975,9 @@ _theme:
infoFg: "Teks informasi" infoFg: "Teks informasi"
infoWarnBg: "Latar belakang peringatan" infoWarnBg: "Latar belakang peringatan"
infoWarnFg: "Teks peringatan" infoWarnFg: "Teks peringatan"
cwBg: "Latar belakang tombol CW" cwBg: "Latar belakang tombol Sembunyikan Konten"
cwFg: "Teks tombol CW" cwFg: "Teks tombol Sembunyikan Konten"
cwHoverBg: "Latar belakang tombol CW (Mengambang)" cwHoverBg: "Latar belakang tombol Sembunyikan Konten (Mengambang)"
toastBg: "Latar belakang pemberitahuan" toastBg: "Latar belakang pemberitahuan"
toastFg: "Teks pemberitahuan" toastFg: "Teks pemberitahuan"
buttonBg: "Latar belakang tombol" buttonBg: "Latar belakang tombol"
@ -1122,7 +1120,7 @@ _widgets:
aiscript: "Konsol AiScript" aiscript: "Konsol AiScript"
_cw: _cw:
hide: "Sembunyikan" hide: "Sembunyikan"
show: "Selebihnya" show: "Lihat konten"
chars: "{count} karakter" chars: "{count} karakter"
files: "{count} berkas" files: "{count} berkas"
_poll: _poll:
@ -1551,7 +1549,7 @@ _pages:
fn: "Fungsi" fn: "Fungsi"
_fn: _fn:
slots: "Slot" slots: "Slot"
slots-info: "Pisahkan setiap slow dengan baris baru" slots-info: "Pisahkan setiap slot dengan baris baru"
arg1: "Keluaran" arg1: "Keluaran"
for: "Ulangi" for: "Ulangi"
_for: _for:

View File

@ -21,6 +21,7 @@ const languages = [
'en-US', 'en-US',
'es-ES', 'es-ES',
'fr-FR', 'fr-FR',
'id-ID',
'ja-JP', 'ja-JP',
'ja-KS', 'ja-KS',
'kab-KAB', 'kab-KAB',

View File

@ -274,6 +274,7 @@ emptyDrive: "Il Drive è vuoto"
emptyFolder: "La cartella è vuota" emptyFolder: "La cartella è vuota"
unableToDelete: "Eliminazione impossibile" unableToDelete: "Eliminazione impossibile"
inputNewFileName: "Inserisci nome del nuovo file" inputNewFileName: "Inserisci nome del nuovo file"
inputNewDescription: "Inserisci una nuova descrizione"
inputNewFolderName: "Inserisci nome della nuova cartella" inputNewFolderName: "Inserisci nome della nuova cartella"
circularReferenceFolder: "La cartella di destinazione è una sottocartella della cartella che vuoi spostare." circularReferenceFolder: "La cartella di destinazione è una sottocartella della cartella che vuoi spostare."
hasChildFilesOrFolders: "Impossibile eliminare la cartella perché non è vuota" hasChildFilesOrFolders: "Impossibile eliminare la cartella perché non è vuota"
@ -305,8 +306,8 @@ monthX: "{month}"
yearX: "{year}" yearX: "{year}"
pages: "Pagine" pages: "Pagine"
integration: "App collegate" integration: "App collegate"
connectSerice: "Connetti" connectService: "Connessione"
disconnectSerice: "Disconnetti" disconnectService: "Disconnessione "
enableLocalTimeline: "Abilita Timeline locale" enableLocalTimeline: "Abilita Timeline locale"
enableGlobalTimeline: "Abilita Timeline federata" enableGlobalTimeline: "Abilita Timeline federata"
disablingTimelinesInfo: "Anche se disabiliti queste timeline, gli amministratori e i moderatori potranno sempre accederci." disablingTimelinesInfo: "Anche se disabiliti queste timeline, gli amministratori e i moderatori potranno sempre accederci."
@ -533,6 +534,8 @@ disablePlayer: "Chiudi lettore video"
expandTweet: "Espandi tweet" expandTweet: "Espandi tweet"
themeEditor: "Editor di temi" themeEditor: "Editor di temi"
description: "Descrizione" description: "Descrizione"
describeFile: "Aggiungi una descrizione d'immagine"
enterFileDescription: "Inserisci descrizione"
author: "Autore" author: "Autore"
leaveConfirm: "Ci sono delle modifiche ancora non salvate. Vuoi cancellarle?" leaveConfirm: "Ci sono delle modifiche ancora non salvate. Vuoi cancellarle?"
manage: "Gestione" manage: "Gestione"

View File

@ -279,6 +279,7 @@ emptyDrive: "ドライブは空です"
emptyFolder: "フォルダーは空です" emptyFolder: "フォルダーは空です"
unableToDelete: "削除できません" unableToDelete: "削除できません"
inputNewFileName: "新しいファイル名を入力してください" inputNewFileName: "新しいファイル名を入力してください"
inputNewDescription: "新しいキャプションを入力してください"
inputNewFolderName: "新しいフォルダ名を入力してください" inputNewFolderName: "新しいフォルダ名を入力してください"
circularReferenceFolder: "移動先のフォルダーは、移動するフォルダーのサブフォルダーです。" circularReferenceFolder: "移動先のフォルダーは、移動するフォルダーのサブフォルダーです。"
hasChildFilesOrFolders: "このフォルダは空でないため、削除できません。" hasChildFilesOrFolders: "このフォルダは空でないため、削除できません。"
@ -310,8 +311,8 @@ monthX: "{month}月"
yearX: "{year}年" yearX: "{year}年"
pages: "ページ" pages: "ページ"
integration: "連携" integration: "連携"
connectSerice: "接続する" connectService: "接続する"
disconnectSerice: "切断する" disconnectService: "切断する"
enableLocalTimeline: "ローカルタイムラインを有効にする" enableLocalTimeline: "ローカルタイムラインを有効にする"
enableGlobalTimeline: "グローバルタイムラインを有効にする" enableGlobalTimeline: "グローバルタイムラインを有効にする"
disablingTimelinesInfo: "これらのタイムラインを無効化しても、利便性のため管理者およびモデレーターは引き続き利用することができます。" disablingTimelinesInfo: "これらのタイムラインを無効化しても、利便性のため管理者およびモデレーターは引き続き利用することができます。"
@ -325,6 +326,7 @@ driveCapacityPerRemoteAccount: "リモートユーザーひとりあたりのド
inMb: "メガバイト単位" inMb: "メガバイト単位"
iconUrl: "アイコン画像のURL (faviconなど)" iconUrl: "アイコン画像のURL (faviconなど)"
bannerUrl: "バナー画像のURL" bannerUrl: "バナー画像のURL"
backgroundImageUrl: "背景画像のURL"
basicInfo: "基本情報" basicInfo: "基本情報"
pinnedUsers: "ピン留めユーザー" pinnedUsers: "ピン留めユーザー"
pinnedUsersDescription: "「みつける」ページなどにピン留めしたいユーザーを改行で区切って記述します。" pinnedUsersDescription: "「みつける」ページなどにピン留めしたいユーザーを改行で区切って記述します。"
@ -546,6 +548,8 @@ disablePlayer: "プレイヤーを閉じる"
expandTweet: "ツイートを展開する" expandTweet: "ツイートを展開する"
themeEditor: "テーマエディター" themeEditor: "テーマエディター"
description: "説明" description: "説明"
describeFile: "キャプションを付ける"
enterFileDescription: "キャプションを入力"
author: "作者" author: "作者"
leaveConfirm: "未保存の変更があります。破棄しますか?" leaveConfirm: "未保存の変更があります。破棄しますか?"
manage: "管理" manage: "管理"

View File

@ -308,8 +308,6 @@ monthX: "{month}月"
yearX: "{year}年" yearX: "{year}年"
pages: "ページ" pages: "ページ"
integration: "連携" integration: "連携"
connectSerice: "つなぐ"
disconnectSerice: "切ってまう"
enableLocalTimeline: "ローカルタイムラインを使えるようにする" enableLocalTimeline: "ローカルタイムラインを使えるようにする"
enableGlobalTimeline: "グローバルタイムラインを使えるようにする" enableGlobalTimeline: "グローバルタイムラインを使えるようにする"
disablingTimelinesInfo: "ここらへんのタイムラインを使えんようにしてしもても、管理者とモデレーターは使えるままになってるで、そうやなかったら不便やからな。" disablingTimelinesInfo: "ここらへんのタイムラインを使えんようにしてしもても、管理者とモデレーターは使えるままになってるで、そうやなかったら不便やからな。"

View File

@ -279,6 +279,7 @@ emptyDrive: "드라이브가 비어 있습니다"
emptyFolder: "폴더가 비어 있습니다" emptyFolder: "폴더가 비어 있습니다"
unableToDelete: "삭제할 수 없습니다" unableToDelete: "삭제할 수 없습니다"
inputNewFileName: "바꿀 파일명을 입력해 주세요" inputNewFileName: "바꿀 파일명을 입력해 주세요"
inputNewDescription: "새 캡션을 입력해 주세요"
inputNewFolderName: "바꿀 폴더명을 입력해 주세요" inputNewFolderName: "바꿀 폴더명을 입력해 주세요"
circularReferenceFolder: "지정한 폴더가 이동할 폴더의 하위 폴더입니다." circularReferenceFolder: "지정한 폴더가 이동할 폴더의 하위 폴더입니다."
hasChildFilesOrFolders: "이 폴더는 비어있지 않기 때문에 삭제할 수 없습니다." hasChildFilesOrFolders: "이 폴더는 비어있지 않기 때문에 삭제할 수 없습니다."
@ -310,8 +311,8 @@ monthX: "{month}월"
yearX: "{year}년" yearX: "{year}년"
pages: "페이지" pages: "페이지"
integration: "연동" integration: "연동"
connectSerice: "접속" connectService: "계정 연동"
disconnectSerice: "연결 끊기" disconnectService: "계정 연동 해제"
enableLocalTimeline: "로컬 타임라인 활성화" enableLocalTimeline: "로컬 타임라인 활성화"
enableGlobalTimeline: "글로벌 타임라인 활성화" enableGlobalTimeline: "글로벌 타임라인 활성화"
disablingTimelinesInfo: "특정 타임라인을 비활성화하더라도 관리자 및 모더레이터는 계속 사용할 수 있습니다." disablingTimelinesInfo: "특정 타임라인을 비활성화하더라도 관리자 및 모더레이터는 계속 사용할 수 있습니다."
@ -325,6 +326,7 @@ driveCapacityPerRemoteAccount: "리모트 유저 한 명당 드라이브 용량"
inMb: "메가바이트 단위" inMb: "메가바이트 단위"
iconUrl: "아이콘 URL" iconUrl: "아이콘 URL"
bannerUrl: "배너 이미지 URL" bannerUrl: "배너 이미지 URL"
backgroundImageUrl: "배경 이미지 URL"
basicInfo: "기본 정보" basicInfo: "기본 정보"
pinnedUsers: "고정된 유저" pinnedUsers: "고정된 유저"
pinnedUsersDescription: "\"발견하기\" 페이지 등에 고정하고 싶은 유저를 한 줄에 한 명씩 적습니다." pinnedUsersDescription: "\"발견하기\" 페이지 등에 고정하고 싶은 유저를 한 줄에 한 명씩 적습니다."
@ -546,6 +548,8 @@ disablePlayer: "플레이어 닫기"
expandTweet: "트윗 확장하기" expandTweet: "트윗 확장하기"
themeEditor: "테마 에디터" themeEditor: "테마 에디터"
description: "설명" description: "설명"
describeFile: "캡션 추가"
enterFileDescription: "캡션 입력"
author: "작성자" author: "작성자"
leaveConfirm: "저장하지 않은 변경사항이 있습니다. 취소하시겠습니까?" leaveConfirm: "저장하지 않은 변경사항이 있습니다. 취소하시겠습니까?"
manage: "관리" manage: "관리"

View File

@ -135,6 +135,7 @@ settingGuide: "Proponowana konfiguracja"
cacheRemoteFiles: "Przechowuj zdalne pliki w pamięci podręcznej" cacheRemoteFiles: "Przechowuj zdalne pliki w pamięci podręcznej"
cacheRemoteFilesDescription: "Gdy ta opcja jest wyłączona, zdalne pliki są ładowane bezpośrednio ze zdalnych instancji. Wyłączenie the opcji zmniejszy użycie powierzchni dyskowej, ale zwiększy transfer, ponieważ miniaturki nie będą generowane." cacheRemoteFilesDescription: "Gdy ta opcja jest wyłączona, zdalne pliki są ładowane bezpośrednio ze zdalnych instancji. Wyłączenie the opcji zmniejszy użycie powierzchni dyskowej, ale zwiększy transfer, ponieważ miniaturki nie będą generowane."
flagAsBot: "To konto jest botem" flagAsBot: "To konto jest botem"
flagAsBotDescription: "Jeżeli ten kanał jest kontrolowany przez jakiś program, ustaw tę opcję. Jeżeli włączona, będzie działać jako flaga informująca innych programistów, aby zapobiegać nieskończonej interakcji z różnymi botami i dostosowywać wewnętrzne systemy Misskey, traktując konto jako bota."
flagAsCat: "To konto jest kotem" flagAsCat: "To konto jest kotem"
flagAsCatDescription: "Przełącz tę opcję, aby konto było oznaczone jako kot." flagAsCatDescription: "Przełącz tę opcję, aby konto było oznaczone jako kot."
autoAcceptFollowed: "Automatycznie przyjmuj prośby o możliwość obserwacji od użytkowników, których obserwujesz" autoAcceptFollowed: "Automatycznie przyjmuj prośby o możliwość obserwacji od użytkowników, których obserwujesz"
@ -182,6 +183,7 @@ clearQueueConfirmTitle: "Czy na pewno chcesz wyczyścić kolejkę?"
clearCachedFiles: "Wyczyść pamięć podręczną" clearCachedFiles: "Wyczyść pamięć podręczną"
clearCachedFilesConfirm: "Czy na pewno chcesz usunąć wszystkie zdalne pliki z pamięci podręcznej?" clearCachedFilesConfirm: "Czy na pewno chcesz usunąć wszystkie zdalne pliki z pamięci podręcznej?"
blockedInstances: "Zablokowane instancje" blockedInstances: "Zablokowane instancje"
blockedInstancesDescription: "Wypisz nazwy hostów instancji, które powinny zostać zablokowane. Wypisane instancje nie będą mogły dłużej komunikować się z tą instancją."
muteAndBlock: "Wycisz / Zablokuj" muteAndBlock: "Wycisz / Zablokuj"
mutedUsers: "Wyciszeni użytkownicy" mutedUsers: "Wyciszeni użytkownicy"
blockedUsers: "Zablokowani użytkownicy" blockedUsers: "Zablokowani użytkownicy"
@ -274,6 +276,7 @@ emptyDrive: "Dysk jest pusty"
emptyFolder: "Ten katalog jest pusty" emptyFolder: "Ten katalog jest pusty"
unableToDelete: "Nie można usunąć" unableToDelete: "Nie można usunąć"
inputNewFileName: "Wprowadź nową nazwę pliku" inputNewFileName: "Wprowadź nową nazwę pliku"
inputNewDescription: "Proszę wpisać nowy napis"
inputNewFolderName: "Wprowadź nową nazwę katalogu" inputNewFolderName: "Wprowadź nową nazwę katalogu"
circularReferenceFolder: "Katalog docelowy jest podkatalogiem katalogu, który chcesz przenieść." circularReferenceFolder: "Katalog docelowy jest podkatalogiem katalogu, który chcesz przenieść."
hasChildFilesOrFolders: "Ponieważ ten katalog nie jest pusty, nie może być usunięty." hasChildFilesOrFolders: "Ponieważ ten katalog nie jest pusty, nie może być usunięty."
@ -305,8 +308,6 @@ monthX: "{month}"
yearX: "{year}" yearX: "{year}"
pages: "Strony" pages: "Strony"
integration: "Integracja" integration: "Integracja"
connectSerice: "Połącz"
disconnectSerice: "Rozłącz"
enableLocalTimeline: "Włącz lokalną oś czasu" enableLocalTimeline: "Włącz lokalną oś czasu"
enableGlobalTimeline: "Włącz globalną oś czasu" enableGlobalTimeline: "Włącz globalną oś czasu"
disablingTimelinesInfo: "Administratorzy i moderatorzy będą zawsze mieć dostęp do wszystkich osi czasu, nawet gdy są one wyłączone." disablingTimelinesInfo: "Administratorzy i moderatorzy będą zawsze mieć dostęp do wszystkich osi czasu, nawet gdy są one wyłączone."
@ -532,6 +533,8 @@ disablePlayer: "Zamknij odtwarzacz wideo"
expandTweet: "Rozwiń tweet" expandTweet: "Rozwiń tweet"
themeEditor: "Edytor motywu" themeEditor: "Edytor motywu"
description: "Opis" description: "Opis"
describeFile: "dodaj podpis"
enterFileDescription: "Wprowadź napis"
author: "Autor" author: "Autor"
leaveConfirm: "Są niezapisane zmiany. Czy chcesz je odrzucić?" leaveConfirm: "Są niezapisane zmiany. Czy chcesz je odrzucić?"
manage: "Zarządzanie" manage: "Zarządzanie"

View File

@ -309,8 +309,6 @@ monthX: "{month} месяц"
yearX: "{year} год" yearX: "{year} год"
pages: "Страницы" pages: "Страницы"
integration: "Интеграция" integration: "Интеграция"
connectSerice: "Соединение"
disconnectSerice: "Отключение"
enableLocalTimeline: "Включить локальную ленту" enableLocalTimeline: "Включить локальную ленту"
enableGlobalTimeline: "Включить глобальную ленту" enableGlobalTimeline: "Включить глобальную ленту"
disablingTimelinesInfo: "У администраторов и модераторов есть доступ ко всем лентам, даже если они отключены." disablingTimelinesInfo: "У администраторов и модераторов есть доступ ко всем лентам, даже если они отключены."

View File

@ -307,8 +307,6 @@ monthX: "{month}"
yearX: "{year}" yearX: "{year}"
pages: "Сторінки" pages: "Сторінки"
integration: "Інтеграція" integration: "Інтеграція"
connectSerice: "Під’єднати"
disconnectSerice: "Відключитися"
enableLocalTimeline: "Увімкнути локальну стрічку" enableLocalTimeline: "Увімкнути локальну стрічку"
enableGlobalTimeline: "Увімкнути глобальну стрічку" enableGlobalTimeline: "Увімкнути глобальну стрічку"
disablingTimelinesInfo: "Адміністратори та модератори завжди мають доступ до всіх стрічок, навіть якщо вони вимкнуті." disablingTimelinesInfo: "Адміністратори та модератори завжди мають доступ до всіх стрічок, навіть якщо вони вимкнуті."

View File

@ -279,6 +279,7 @@ emptyDrive: "驱动器为空"
emptyFolder: "空文件夹" emptyFolder: "空文件夹"
unableToDelete: "无法删除" unableToDelete: "无法删除"
inputNewFileName: "请输入新文件名" inputNewFileName: "请输入新文件名"
inputNewDescription: "请输入新标题"
inputNewFolderName: "请输入新文件名" inputNewFolderName: "请输入新文件名"
circularReferenceFolder: "目标文件夹是您要移动的文件夹的子文件夹。" circularReferenceFolder: "目标文件夹是您要移动的文件夹的子文件夹。"
hasChildFilesOrFolders: "此文件夹不为空,无法删除。" hasChildFilesOrFolders: "此文件夹不为空,无法删除。"
@ -310,8 +311,8 @@ monthX: "{month}月"
yearX: "{year}年" yearX: "{year}年"
pages: "页面" pages: "页面"
integration: "关联" integration: "关联"
connectSerice: "连接" connectService: "连接"
disconnectSerice: "断开连接" disconnectService: "断开连接"
enableLocalTimeline: "启用本地时间线功能" enableLocalTimeline: "启用本地时间线功能"
enableGlobalTimeline: "启用全局时间线" enableGlobalTimeline: "启用全局时间线"
disablingTimelinesInfo: "即使时间线功能被禁用,出于便利性的原因,管理员和数据图表也可以继续使用。" disablingTimelinesInfo: "即使时间线功能被禁用,出于便利性的原因,管理员和数据图表也可以继续使用。"
@ -325,6 +326,7 @@ driveCapacityPerRemoteAccount: "每个远程用户的网盘容量"
inMb: "以兆字节(MegaByte)为单位" inMb: "以兆字节(MegaByte)为单位"
iconUrl: "图标URL" iconUrl: "图标URL"
bannerUrl: "Banner URL" bannerUrl: "Banner URL"
backgroundImageUrl: "背景图URL"
basicInfo: "基本信息" basicInfo: "基本信息"
pinnedUsers: "置顶用户" pinnedUsers: "置顶用户"
pinnedUsersDescription: "在「发现」页面中使用换行标记想要置顶的用户。" pinnedUsersDescription: "在「发现」页面中使用换行标记想要置顶的用户。"
@ -546,6 +548,8 @@ disablePlayer: "关闭播放器"
expandTweet: "展开贴文" expandTweet: "展开贴文"
themeEditor: "主题编辑器" themeEditor: "主题编辑器"
description: "描述" description: "描述"
describeFile: "添加标题"
enterFileDescription: "输入标题"
author: "作者" author: "作者"
leaveConfirm: "存在未保存的更改。要放弃更改吗?" leaveConfirm: "存在未保存的更改。要放弃更改吗?"
manage: "管理" manage: "管理"

View File

@ -1,18 +1,19 @@
--- ---
_lang_: "繁體中文" _lang_: "繁體中文"
headlineMisskey: "貼文連繫網" headlineMisskey: "貼文連繫網"
introMisskey: "歡迎! Misskey是一個開源且去中心化的社群網絡。\n通過「貼文」分享周邊新鮮事,並告訴其他人您的想法!📡\n透過「情感」功能對大家的貼文表達情感👍\n一起來探索這個新的世界吧🚀" introMisskey: "歡迎! Misskey是一個開放原始碼且去中心化的社群網路。\n透過「貼文」分享周邊新鮮事,並告訴其他人您的想法!📡\n透過「情感」功能對大家的貼文表達情感👍\n一起來探索這個新的世界吧🚀"
monthAndDay: "{month}月 {day}日" monthAndDay: "{month}月 {day}日"
search: "搜尋" search: "搜尋"
notifications: "通知" notifications: "通知"
username: "使用者名稱" username: "使用者名稱"
password: "密碼" password: "密碼"
forgotPassword: "忘記密碼"
fetchingAsApObject: "從聯邦宇宙取得中..." fetchingAsApObject: "從聯邦宇宙取得中..."
ok: "OK" ok: "OK"
gotIt: "知道了" gotIt: "知道了"
cancel: "取消" cancel: "取消"
enterUsername: "輸入使用者名稱" enterUsername: "輸入使用者名稱"
renotedBy: "{user} 轉了" renotedBy: "{user} 轉了"
noNotes: "貼文不可用。" noNotes: "貼文不可用。"
noNotifications: "沒有通知" noNotifications: "沒有通知"
instance: "實例" instance: "實例"
@ -92,9 +93,9 @@ followRequestPending: "追隨許可批准中"
enterEmoji: "輸入表情符號" enterEmoji: "輸入表情符號"
renote: "轉發" renote: "轉發"
unrenote: "取消轉發" unrenote: "取消轉發"
renoted: "轉成功" renoted: "轉成功"
cantRenote: "無法轉發此貼文。" cantRenote: "無法轉發此貼文。"
cantReRenote: "無法轉發之前已經轉發過的內容。" cantReRenote: "無法轉傳之前已經轉傳過的內容。"
quote: "引用" quote: "引用"
pinnedNote: "已置頂的貼文" pinnedNote: "已置頂的貼文"
pinned: "置頂" pinned: "置頂"
@ -309,8 +310,6 @@ monthX: "{month}月"
yearX: "{year}年" yearX: "{year}年"
pages: "頁面" pages: "頁面"
integration: "整合" integration: "整合"
connectSerice: "連線"
disconnectSerice: "中斷連線"
enableLocalTimeline: "開啟本地時間軸" enableLocalTimeline: "開啟本地時間軸"
enableGlobalTimeline: "啟用公開時間軸" enableGlobalTimeline: "啟用公開時間軸"
disablingTimelinesInfo: "即使您關閉了時間線功能,管理員和協調人仍可以繼續使用,以方便您。" disablingTimelinesInfo: "即使您關閉了時間線功能,管理員和協調人仍可以繼續使用,以方便您。"
@ -733,6 +732,7 @@ noBotProtectionWarning: "尚未設定Bot防護。"
configure: "設定" configure: "設定"
expiration: "期限" expiration: "期限"
middle: "中" middle: "中"
emailNotConfiguredWarning: "沒有設定電子郵件地址"
_ad: _ad:
back: "返回" back: "返回"
_gallery: _gallery:

View File

@ -0,0 +1,13 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class userProfileDescriptionLength1622679304522 implements MigrationInterface {
name = 'userProfileDescriptionLength1622679304522';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "description" TYPE character varying(2048)`, undefined);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "user_profile" ALTER COLUMN "description" TYPE character varying(1024)`, undefined);
}
}

View File

@ -0,0 +1,12 @@
import {MigrationInterface, QueryRunner} from "typeorm";
export class logMessageLength1622681548499 implements MigrationInterface {
name = 'logMessageLength1622681548499';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "log" ALTER COLUMN "message" TYPE character varying(2048)`, undefined);
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "log" ALTER COLUMN "message" TYPE character varying(1024)`, undefined);
}
}

View File

@ -1,7 +1,7 @@
{ {
"name": "misskey", "name": "misskey",
"author": "syuilo <syuilotan@yahoo.co.jp>", "author": "syuilo <syuilotan@yahoo.co.jp>",
"version": "12.81.2", "version": "12.83.0",
"codename": "indigo", "codename": "indigo",
"repository": { "repository": {
"type": "git", "type": "git",
@ -30,10 +30,9 @@
"format": "gulp format" "format": "gulp format"
}, },
"resolutions": { "resolutions": {
"mfm-js/twemoji-parser": "13.1.x",
"chokidar": "^3.3.1", "chokidar": "^3.3.1",
"constantinople": "^4.0.1", "lodash": "^4.17.21"
"jsonld/rdf-canonize/node-forge": "0.10.0",
"lodash": "^4.17.20"
}, },
"dependencies": { "dependencies": {
"@babel/plugin-transform-runtime": "7.14.3", "@babel/plugin-transform-runtime": "7.14.3",
@ -43,7 +42,7 @@
"@koa/router": "9.0.1", "@koa/router": "9.0.1",
"@sentry/browser": "5.29.2", "@sentry/browser": "5.29.2",
"@sentry/tracing": "5.29.2", "@sentry/tracing": "5.29.2",
"@sinonjs/fake-timers": "7.0.5", "@sinonjs/fake-timers": "7.1.2",
"@syuilo/aiscript": "0.11.1", "@syuilo/aiscript": "0.11.1",
"@types/bcryptjs": "2.4.2", "@types/bcryptjs": "2.4.2",
"@types/bull": "3.15.1", "@types/bull": "3.15.1",
@ -58,23 +57,23 @@
"@types/jsdom": "16.2.10", "@types/jsdom": "16.2.10",
"@types/jsonld": "1.5.5", "@types/jsonld": "1.5.5",
"@types/katex": "0.11.0", "@types/katex": "0.11.0",
"@types/koa": "2.13.1", "@types/koa": "2.13.3",
"@types/koa-bodyparser": "4.3.0", "@types/koa-bodyparser": "4.3.1",
"@types/koa-cors": "0.0.0", "@types/koa-cors": "0.0.0",
"@types/koa-favicon": "2.0.19", "@types/koa-favicon": "2.0.19",
"@types/koa-logger": "3.1.1", "@types/koa-logger": "3.1.1",
"@types/koa-mount": "4.0.0", "@types/koa-mount": "4.0.0",
"@types/koa-send": "4.1.2", "@types/koa-send": "4.1.2",
"@types/koa-views": "2.0.4", "@types/koa-views": "7.0.0",
"@types/koa__cors": "3.0.2", "@types/koa__cors": "3.0.2",
"@types/koa__multer": "2.0.2", "@types/koa__multer": "2.0.2",
"@types/koa__router": "8.0.4", "@types/koa__router": "8.0.4",
"@types/markdown-it": "12.0.1", "@types/markdown-it": "12.0.2",
"@types/matter-js": "0.14.12", "@types/matter-js": "0.14.12",
"@types/mocha": "8.2.2", "@types/mocha": "8.2.2",
"@types/node": "15.3.1", "@types/node": "15.12.2",
"@types/node-fetch": "2.5.10", "@types/node-fetch": "2.5.10",
"@types/nodemailer": "6.4.1", "@types/nodemailer": "6.4.2",
"@types/nprogress": "0.2.0", "@types/nprogress": "0.2.0",
"@types/oauth": "0.9.1", "@types/oauth": "0.9.1",
"@types/parse5": "6.0.0", "@types/parse5": "6.0.0",
@ -85,12 +84,12 @@
"@types/qrcode": "1.4.0", "@types/qrcode": "1.4.0",
"@types/random-seed": "0.3.3", "@types/random-seed": "0.3.3",
"@types/ratelimiter": "3.4.1", "@types/ratelimiter": "3.4.1",
"@types/redis": "2.8.28", "@types/redis": "2.8.29",
"@types/rename": "1.0.3", "@types/rename": "1.0.3",
"@types/request-stats": "3.0.0", "@types/request-stats": "3.0.0",
"@types/rimraf": "3.0.0", "@types/rimraf": "3.0.0",
"@types/seedrandom": "2.4.28", "@types/seedrandom": "2.4.28",
"@types/sharp": "0.28.1", "@types/sharp": "0.28.3",
"@types/sinonjs__fake-timers": "6.0.2", "@types/sinonjs__fake-timers": "6.0.2",
"@types/speakeasy": "2.0.5", "@types/speakeasy": "2.0.5",
"@types/throttle-debounce": "2.1.0", "@types/throttle-debounce": "2.1.0",
@ -102,38 +101,38 @@
"@types/webpack-stream": "3.2.12", "@types/webpack-stream": "3.2.12",
"@types/websocket": "1.0.2", "@types/websocket": "1.0.2",
"@types/ws": "7.4.4", "@types/ws": "7.4.4",
"@typescript-eslint/parser": "4.24.0", "@typescript-eslint/parser": "4.26.1",
"@vue/compiler-sfc": "3.0.11", "@vue/compiler-sfc": "3.1.1",
"abort-controller": "3.0.0", "abort-controller": "3.0.0",
"apexcharts": "3.26.3", "apexcharts": "3.27.1",
"autobind-decorator": "2.4.0", "autobind-decorator": "2.4.0",
"autosize": "4.0.4", "autosize": "4.0.4",
"autwh": "0.1.0", "autwh": "0.1.0",
"aws-sdk": "2.910.0", "aws-sdk": "2.923.0",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"blurhash": "1.1.3", "blurhash": "1.1.3",
"broadcast-channel": "3.6.0", "broadcast-channel": "3.6.0",
"bull": "3.22.6", "bull": "3.22.7",
"cafy": "15.2.1", "cafy": "15.2.1",
"cbor": "7.0.5", "cbor": "7.0.5",
"chalk": "4.1.1", "chalk": "4.1.1",
"chart.js": "2.9.4", "chart.js": "2.9.4",
"cli-highlight": "2.1.11", "cli-highlight": "2.1.11",
"commander": "7.2.0", "commander": "7.2.0",
"concurrently": "6.1.0", "concurrently": "6.2.0",
"content-disposition": "0.5.3", "content-disposition": "0.5.3",
"core-js": "3.12.1", "core-js": "3.14.0",
"crc-32": "1.2.0", "crc-32": "1.2.0",
"css-loader": "5.2.4", "css-loader": "5.2.6",
"cssnano": "5.0.3", "cssnano": "5.0.5",
"dateformat": "4.5.1", "dateformat": "4.5.1",
"diskusage": "1.1.3", "diskusage": "1.1.3",
"escape-regexp": "0.0.1", "escape-regexp": "0.0.1",
"eslint": "7.26.0", "eslint": "7.28.0",
"eslint-plugin-vue": "7.9.0", "eslint-plugin-vue": "7.10.0",
"eventemitter3": "4.0.7", "eventemitter3": "4.0.7",
"feed": "4.2.2", "feed": "4.2.2",
"file-type": "16.4.0", "file-type": "16.5.0",
"fluent-ffmpeg": "2.1.2", "fluent-ffmpeg": "2.1.2",
"glob": "7.1.7", "glob": "7.1.7",
"got": "11.8.2", "got": "11.8.2",
@ -148,12 +147,12 @@
"http-proxy-agent": "4.0.1", "http-proxy-agent": "4.0.1",
"http-signature": "1.3.5", "http-signature": "1.3.5",
"https-proxy-agent": "5.0.0", "https-proxy-agent": "5.0.0",
"idb-keyval": "5.0.5", "idb-keyval": "5.0.6",
"insert-text-at-cursor": "0.3.0", "insert-text-at-cursor": "0.3.0",
"is-root": "2.1.0", "is-root": "2.1.0",
"is-svg": "4.3.1", "is-svg": "4.3.1",
"js-yaml": "4.1.0", "js-yaml": "4.1.0",
"jsdom": "16.5.3", "jsdom": "16.6.0",
"json5": "2.2.0", "json5": "2.2.0",
"json5-loader": "4.0.1", "json5-loader": "4.0.1",
"jsonld": "4.0.1", "jsonld": "4.0.1",
@ -173,23 +172,24 @@
"markdown-it": "12.0.6", "markdown-it": "12.0.6",
"markdown-it-anchor": "7.1.0", "markdown-it-anchor": "7.1.0",
"matter-js": "0.17.1", "matter-js": "0.17.1",
"mfm-js": "0.16.4", "mfm-js": "0.18.0",
"misskey-js": "0.0.4",
"mocha": "8.4.0", "mocha": "8.4.0",
"moji": "0.5.1", "moji": "0.5.1",
"ms": "2.1.3", "ms": "2.1.3",
"multer": "1.4.2", "multer": "1.4.2",
"nested-property": "4.0.0", "nested-property": "4.0.0",
"node-fetch": "2.6.1", "node-fetch": "2.6.1",
"nodemailer": "6.6.0", "nodemailer": "6.6.1",
"object-assign-deep": "0.4.0", "object-assign-deep": "0.4.0",
"os-utils": "0.0.14", "os-utils": "0.0.14",
"parse5": "6.0.1", "parse5": "6.0.1",
"pg": "8.6.0", "pg": "8.6.0",
"portscanner": "2.2.0", "portscanner": "2.2.0",
"postcss": "8.2.15", "postcss": "8.3.0",
"postcss-loader": "5.3.0", "postcss-loader": "5.3.0",
"prismjs": "1.23.0", "prismjs": "1.23.0",
"probe-image-size": "7.1.0", "probe-image-size": "7.2.1",
"promise-limit": "2.7.0", "promise-limit": "2.7.0",
"promise-sequential": "1.1.1", "promise-sequential": "1.1.1",
"pug": "3.0.2", "pug": "3.0.2",
@ -210,35 +210,36 @@
"rimraf": "3.0.2", "rimraf": "3.0.2",
"rndstr": "1.0.0", "rndstr": "1.0.0",
"s-age": "1.1.2", "s-age": "1.1.2",
"sass": "1.32.13", "sass": "1.34.1",
"sass-loader": "11.1.1", "sass-loader": "12.0.0",
"seedrandom": "3.0.5", "seedrandom": "3.0.5",
"sharp": "0.28.2", "sharp": "0.28.3",
"speakeasy": "2.0.0", "speakeasy": "2.0.0",
"stringz": "2.1.0", "stringz": "2.1.0",
"style-loader": "2.0.0", "style-loader": "2.0.0",
"summaly": "2.4.0", "summaly": "2.4.0",
"syslog-pro": "1.0.0", "syslog-pro": "1.0.0",
"systeminformation": "5.6.22", "systeminformation": "5.7.4",
"syuilo-password-strength": "0.0.1", "syuilo-password-strength": "0.0.1",
"textarea-caret": "3.1.0", "textarea-caret": "3.1.0",
"three": "0.117.1", "three": "0.117.1",
"throttle-debounce": "3.0.1", "throttle-debounce": "3.0.1",
"tinycolor2": "1.4.2", "tinycolor2": "1.4.2",
"tmp": "0.2.1", "tmp": "0.2.1",
"ts-loader": "9.2.1", "ts-loader": "9.2.3",
"ts-node": "9.1.1", "ts-node": "10.0.0",
"tsc-alias": "1.2.11", "tsc-alias": "1.3.2",
"tsconfig-paths": "3.9.0", "tsconfig-paths": "3.9.0",
"tslint": "6.1.3", "tslint": "6.1.3",
"tslint-sonarts": "1.9.0", "tslint-sonarts": "1.9.0",
"twemoji-parser": "13.1.0",
"typeorm": "0.2.32", "typeorm": "0.2.32",
"typescript": "4.2.4", "typescript": "4.3.2",
"ulid": "2.3.0", "ulid": "2.3.0",
"uuid": "8.3.2", "uuid": "8.3.2",
"v-debounce": "0.1.2", "v-debounce": "0.1.2",
"vanilla-tilt": "1.7.0", "vanilla-tilt": "1.7.0",
"vue": "3.0.11", "vue": "3.1.1",
"vue-color": "2.8.1", "vue-color": "2.8.1",
"vue-json-pretty": "1.7.1", "vue-json-pretty": "1.7.1",
"vue-loader": "16.1.2", "vue-loader": "16.1.2",
@ -248,10 +249,10 @@
"vue-svg-loader": "0.17.0-beta.2", "vue-svg-loader": "0.17.0-beta.2",
"vuedraggable": "4.0.1", "vuedraggable": "4.0.1",
"web-push": "3.4.4", "web-push": "3.4.4",
"webpack": "5.37.1", "webpack": "5.38.1",
"webpack-cli": "4.7.0", "webpack-cli": "4.7.2",
"websocket": "1.0.34", "websocket": "1.0.34",
"ws": "7.4.5", "ws": "7.4.6",
"xev": "2.0.1" "xev": "2.0.1"
}, },
"devDependencies": { "devDependencies": {

View File

@ -1,11 +1,11 @@
<script lang="ts"> <script lang="ts">
import { defineComponent, h, TransitionGroup } from 'vue'; import { defineComponent, h, PropType, TransitionGroup } from 'vue';
import MkAd from '@client/components/global/ad.vue'; import MkAd from '@client/components/global/ad.vue';
export default defineComponent({ export default defineComponent({
props: { props: {
items: { items: {
type: Array, type: Array as PropType<{ id: string; createdAt: string; _shouldInsertAd_: boolean; }[]>,
required: true, required: true,
}, },
direction: { direction: {

View File

@ -87,6 +87,10 @@ export default defineComponent({
text: this.file.isSensitive ? this.$ts.unmarkAsSensitive : this.$ts.markAsSensitive, text: this.file.isSensitive ? this.$ts.unmarkAsSensitive : this.$ts.markAsSensitive,
icon: this.file.isSensitive ? 'fas fa-eye' : 'fas fa-eye-slash', icon: this.file.isSensitive ? 'fas fa-eye' : 'fas fa-eye-slash',
action: this.toggleSensitive action: this.toggleSensitive
}, {
text: this.$ts.describeFile,
icon: 'fas fa-i-cursor',
action: this.describe
}, null, { }, null, {
text: this.$ts.copyUrl, text: this.$ts.copyUrl,
icon: 'fas fa-link', icon: 'fas fa-link',
@ -150,6 +154,26 @@ export default defineComponent({
}); });
}, },
describe() {
os.popup(import('@client/components/media-caption.vue'), {
title: this.$ts.describeFile,
input: {
placeholder: this.$ts.inputNewDescription,
default: this.file.comment !== null ? this.file.comment : '',
},
image: this.file
}, {
done: result => {
if (!result || result.canceled) return;
let comment = result.result;
os.api('drive/files/update', {
fileId: this.file.id,
comment: comment.length == 0 ? null : comment
});
}
}, 'closed');
},
toggleSensitive() { toggleSensitive() {
os.api('drive/files/update', { os.api('drive/files/update', {
fileId: this.file.id, fileId: this.file.id,

View File

@ -139,7 +139,7 @@ export default defineComponent({
}); });
} }
this.connection = os.stream.useSharedConnection('drive'); this.connection = os.stream.useChannel('drive');
this.connection.on('fileCreated', this.onStreamDriveFileCreated); this.connection.on('fileCreated', this.onStreamDriveFileCreated);
this.connection.on('fileUpdated', this.onStreamDriveFileUpdated); this.connection.on('fileUpdated', this.onStreamDriveFileUpdated);
@ -301,7 +301,7 @@ export default defineComponent({
} }
}).then(({ canceled, result: url }) => { }).then(({ canceled, result: url }) => {
if (canceled) return; if (canceled) return;
os.api('drive/files/upload_from_url', { os.api('drive/files/upload-from-url', {
url: url, url: url,
folderId: this.folder ? this.folder.id : undefined folderId: this.folder ? this.folder.id : undefined
}); });

View File

@ -1,7 +1,5 @@
<template> <template>
<div class="xfbouadm" v-if="meta" :style="{ backgroundImage: `url(${ meta.backgroundImageUrl })` }"> <div class="xfbouadm" v-if="meta" :style="{ backgroundImage: `url(${ meta.backgroundImageUrl })` }"></div>
</div>
</template> </template>
<script lang="ts"> <script lang="ts">

View File

@ -71,7 +71,7 @@ export default defineComponent({
}, },
mounted() { mounted() {
this.connection = os.stream.useSharedConnection('main'); this.connection = os.stream.useChannel('main');
this.connection.on('follow', this.onFollowChange); this.connection.on('follow', this.onFollowChange);
this.connection.on('unfollow', this.onFollowChange); this.connection.on('unfollow', this.onFollowChange);

View File

@ -2,7 +2,7 @@
<MkModal ref="modal" @click="$refs.modal.close()" @closed="$emit('closed')"> <MkModal ref="modal" @click="$refs.modal.close()" @closed="$emit('closed')">
<div class="xubzgfga"> <div class="xubzgfga">
<header>{{ image.name }}</header> <header>{{ image.name }}</header>
<img :src="image.url" :alt="image.name" :title="image.name" @click="$refs.modal.close()"/> <img :src="image.url" :alt="image.comment" :title="image.comment" @click="$refs.modal.close()"/>
<footer> <footer>
<span>{{ image.type }}</span> <span>{{ image.type }}</span>
<span>{{ bytes(image.size) }}</span> <span>{{ bytes(image.size) }}</span>

View File

@ -87,8 +87,6 @@ export default defineComponent({
> .icon { > .icon {
padding-left: 2px; padding-left: 2px;
font-size: .9em; font-size: .9em;
font-weight: 400;
font-style: normal;
} }
} }
</style> </style>

View File

@ -0,0 +1,238 @@
<template>
<MkModal ref="modal" @click="done(true)" @closed="$emit('closed')">
<div class="container">
<div class="fullwidth top-caption">
<div class="mk-dialog">
<header v-if="title"><Mfm :text="title"/></header>
<textarea autofocus v-model="inputValue" :placeholder="input.placeholder" @keydown="onInputKeydown"></textarea>
<div class="buttons" v-if="(showOkButton || showCancelButton)">
<MkButton inline @click="ok" primary>{{ $ts.ok }}</MkButton>
<MkButton inline @click="cancel" >{{ $ts.cancel }}</MkButton>
</div>
</div>
</div>
<div class="hdrwpsaf fullwidth">
<header>{{ image.name }}</header>
<img :src="image.url" :alt="image.comment" :title="image.comment" @click="$refs.modal.close()"/>
<footer>
<span>{{ image.type }}</span>
<span>{{ bytes(image.size) }}</span>
<span v-if="image.properties && image.properties.width">{{ number(image.properties.width) }}px × {{ number(image.properties.height) }}px</span>
</footer>
</div>
</div>
</MkModal>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import MkModal from '@client/components/ui/modal.vue';
import MkButton from '@client/components/ui/button.vue';
import bytes from '@client/filters/bytes';
import number from '@client/filters/number';
export default defineComponent({
components: {
MkModal,
MkButton,
},
props: {
image: {
type: Object,
required: true,
},
title: {
type: String,
required: false
},
input: {
required: true
},
showOkButton: {
type: Boolean,
default: true
},
showCancelButton: {
type: Boolean,
default: true
},
cancelableByBgClick: {
type: Boolean,
default: true
},
},
emits: ['done', 'closed'],
data() {
return {
inputValue: this.input.default ? this.input.default : null
};
},
mounted() {
document.addEventListener('keydown', this.onKeydown);
},
beforeUnmount() {
document.removeEventListener('keydown', this.onKeydown);
},
methods: {
bytes,
number,
done(canceled, result?) {
this.$emit('done', { canceled, result });
this.$refs.modal.close();
},
async ok() {
if (!this.showOkButton) return;
const result = this.inputValue;
this.done(false, result);
},
cancel() {
this.done(true);
},
onBgClick() {
if (this.cancelableByBgClick) {
this.cancel();
}
},
onKeydown(e) {
if (e.which === 27) { // ESC
this.cancel();
}
},
onInputKeydown(e) {
if (e.which === 13) { // Enter
if (e.ctrlKey) {
e.preventDefault();
e.stopPropagation();
this.ok();
}
}
}
}
});
</script>
<style lang="scss" scoped>
.container {
display: flex;
width: 100%;
height: 100%;
flex-direction: row;
}
@media (max-width: 850px) {
.container {
flex-direction: column;
}
.top-caption {
padding-bottom: 8px;
}
}
.fullwidth {
width: 100%;
margin: auto;
}
.mk-dialog {
position: relative;
padding: 32px;
min-width: 320px;
max-width: 480px;
box-sizing: border-box;
text-align: center;
background: var(--panel);
border-radius: var(--radius);
margin: auto;
> header {
margin: 0 0 8px 0;
font-weight: bold;
font-size: 20px;
}
> .buttons {
margin-top: 16px;
> * {
margin: 0 8px;
}
}
> textarea {
display: block;
box-sizing: border-box;
padding: 0 24px;
margin: 0;
width: 100%;
font-size: 16px;
border: none;
border-radius: 0;
background: transparent;
color: var(--fg);
font-family: inherit;
max-width: 100%;
min-width: 100%;
min-height: 90px;
&:focus {
outline: none;
}
&:disabled {
opacity: 0.5;
}
}
}
.hdrwpsaf {
display: flex;
flex-direction: column;
height: 100%;
> header,
> footer {
align-self: center;
display: inline-block;
padding: 6px 9px;
font-size: 90%;
background: rgba(0, 0, 0, 0.5);
border-radius: 6px;
color: #fff;
}
> header {
margin-bottom: 8px;
opacity: 0.9;
}
> img {
display: block;
flex: 1;
min-height: 0;
object-fit: contain;
width: 100%;
cursor: zoom-out;
image-orientation: from-image;
}
> footer {
margin-top: 8px;
opacity: 0.8;
> span + span {
margin-left: 0.5em;
padding-left: 0.5em;
border-left: solid 1px rgba(255, 255, 255, 0.5);
}
}
}
</style>

View File

@ -1,6 +1,6 @@
<template> <template>
<div class="qjewsnkg" v-if="hide" @click="hide = false"> <div class="qjewsnkg" v-if="hide" @click="hide = false">
<ImgWithBlurhash class="bg" :hash="image.blurhash" :title="image.name"/> <ImgWithBlurhash class="bg" :hash="image.blurhash" :title="image.comment" :alt="image.comment"/>
<div class="text"> <div class="text">
<div> <div>
<b><i class="fas fa-exclamation-triangle"></i> {{ $ts.sensitive }}</b> <b><i class="fas fa-exclamation-triangle"></i> {{ $ts.sensitive }}</b>
@ -14,7 +14,7 @@
:title="image.name" :title="image.name"
@click.prevent="onClick" @click.prevent="onClick"
> >
<ImgWithBlurhash :hash="image.blurhash" :src="url" :alt="image.name" :title="image.name" :cover="false"/> <ImgWithBlurhash :hash="image.blurhash" :src="url" :alt="image.comment" :title="image.comment" :cover="false"/>
<div class="gif" v-if="image.type === 'image/gif'">GIF</div> <div class="gif" v-if="image.type === 'image/gif'">GIF</div>
</a> </a>
<i class="fas fa-eye-slash" @click="hide = true"></i> <i class="fas fa-eye-slash" @click="hide = true"></i>

View File

@ -12,10 +12,10 @@
<XNotification v-else :notification="notification" :with-time="true" :full="true" class="_panel notification" :key="notification.id"/> <XNotification v-else :notification="notification" :with-time="true" :full="true" class="_panel notification" :key="notification.id"/>
</XList> </XList>
<button class="_buttonPrimary" v-appear="$store.state.enableInfiniteScroll ? fetchMore : null" @click="fetchMore" v-show="more" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }"> <MkButton primary style="margin: var(--margin) auto;" v-appear="$store.state.enableInfiniteScroll ? fetchMore : null" @click="fetchMore" v-show="more" :disabled="moreFetching" :style="{ cursor: moreFetching ? 'wait' : 'pointer' }">
<template v-if="!moreFetching">{{ $ts.loadMore }}</template> <template v-if="!moreFetching">{{ $ts.loadMore }}</template>
<template v-if="moreFetching"><MkLoading inline/></template> <template v-if="moreFetching"><MkLoading inline/></template>
</button> </MkButton>
</div> </div>
</transition> </transition>
</template> </template>
@ -29,12 +29,14 @@ import XList from './date-separated-list.vue';
import XNote from './note.vue'; import XNote from './note.vue';
import { notificationTypes } from '../../types'; import { notificationTypes } from '../../types';
import * as os from '@client/os'; import * as os from '@client/os';
import MkButton from '@client/components/ui/button.vue';
export default defineComponent({ export default defineComponent({
components: { components: {
XNotification, XNotification,
XList, XList,
XNote, XNote,
MkButton,
}, },
mixins: [ mixins: [
@ -88,7 +90,7 @@ export default defineComponent({
}, },
mounted() { mounted() {
this.connection = os.stream.useSharedConnection('main'); this.connection = os.stream.useChannel('main');
this.connection.on('notification', this.onNotification); this.connection.on('notification', this.onNotification);
this.connection.on('readAllNotifications', () => { this.connection.on('readAllNotifications', () => {

View File

@ -89,6 +89,27 @@ export default defineComponent({
file.name = result; file.name = result;
}); });
}, },
async describe(file) {
os.popup(import("@client/components/media-caption.vue"), {
title: this.$ts.describeFile,
input: {
placeholder: this.$ts.inputNewDescription,
default: file.comment !== null ? file.comment : "",
},
image: file
}, {
done: result => {
if (!result || result.canceled) return;
let comment = result.result;
os.api('drive/files/update', {
fileId: file.id,
comment: comment.length == 0 ? null : comment
});
}
}, 'closed');
},
showFileMenu(file, ev: MouseEvent) { showFileMenu(file, ev: MouseEvent) {
if (this.menu) return; if (this.menu) return;
this.menu = os.modalMenu([{ this.menu = os.modalMenu([{
@ -99,6 +120,10 @@ export default defineComponent({
text: file.isSensitive ? this.$ts.unmarkAsSensitive : this.$ts.markAsSensitive, text: file.isSensitive ? this.$ts.unmarkAsSensitive : this.$ts.markAsSensitive,
icon: file.isSensitive ? 'fas fa-eye-slash' : 'fas fa-eye', icon: file.isSensitive ? 'fas fa-eye-slash' : 'fas fa-eye',
action: () => { this.toggleSensitive(file) } action: () => { this.toggleSensitive(file) }
}, {
text: this.$ts.describeFile,
icon: 'fas fa-i-cursor',
action: () => { this.describe(file) }
}, { }, {
text: this.$ts.attachCancel, text: this.$ts.attachCancel,
icon: 'fas fa-times-circle', icon: 'fas fa-times-circle',

View File

@ -92,33 +92,33 @@ export default defineComponent({
this.query = { this.query = {
antennaId: this.antenna antennaId: this.antenna
}; };
this.connection = os.stream.connectToChannel('antenna', { this.connection = os.stream.useChannel('antenna', {
antennaId: this.antenna antennaId: this.antenna
}); });
this.connection.on('note', prepend); this.connection.on('note', prepend);
} else if (this.src == 'home') { } else if (this.src == 'home') {
endpoint = 'notes/timeline'; endpoint = 'notes/timeline';
this.connection = os.stream.useSharedConnection('homeTimeline'); this.connection = os.stream.useChannel('homeTimeline');
this.connection.on('note', prepend); this.connection.on('note', prepend);
this.connection2 = os.stream.useSharedConnection('main'); this.connection2 = os.stream.useChannel('main');
this.connection2.on('follow', onChangeFollowing); this.connection2.on('follow', onChangeFollowing);
this.connection2.on('unfollow', onChangeFollowing); this.connection2.on('unfollow', onChangeFollowing);
} else if (this.src == 'local') { } else if (this.src == 'local') {
endpoint = 'notes/local-timeline'; endpoint = 'notes/local-timeline';
this.connection = os.stream.useSharedConnection('localTimeline'); this.connection = os.stream.useChannel('localTimeline');
this.connection.on('note', prepend); this.connection.on('note', prepend);
} else if (this.src == 'social') { } else if (this.src == 'social') {
endpoint = 'notes/hybrid-timeline'; endpoint = 'notes/hybrid-timeline';
this.connection = os.stream.useSharedConnection('hybridTimeline'); this.connection = os.stream.useChannel('hybridTimeline');
this.connection.on('note', prepend); this.connection.on('note', prepend);
} else if (this.src == 'global') { } else if (this.src == 'global') {
endpoint = 'notes/global-timeline'; endpoint = 'notes/global-timeline';
this.connection = os.stream.useSharedConnection('globalTimeline'); this.connection = os.stream.useChannel('globalTimeline');
this.connection.on('note', prepend); this.connection.on('note', prepend);
} else if (this.src == 'mentions') { } else if (this.src == 'mentions') {
endpoint = 'notes/mentions'; endpoint = 'notes/mentions';
this.connection = os.stream.useSharedConnection('main'); this.connection = os.stream.useChannel('main');
this.connection.on('mention', prepend); this.connection.on('mention', prepend);
} else if (this.src == 'directs') { } else if (this.src == 'directs') {
endpoint = 'notes/mentions'; endpoint = 'notes/mentions';
@ -130,14 +130,14 @@ export default defineComponent({
prepend(note); prepend(note);
} }
}; };
this.connection = os.stream.useSharedConnection('main'); this.connection = os.stream.useChannel('main');
this.connection.on('mention', onNote); this.connection.on('mention', onNote);
} else if (this.src == 'list') { } else if (this.src == 'list') {
endpoint = 'notes/user-list-timeline'; endpoint = 'notes/user-list-timeline';
this.query = { this.query = {
listId: this.list listId: this.list
}; };
this.connection = os.stream.connectToChannel('userList', { this.connection = os.stream.useChannel('userList', {
listId: this.list listId: this.list
}); });
this.connection.on('note', prepend); this.connection.on('note', prepend);
@ -148,7 +148,7 @@ export default defineComponent({
this.query = { this.query = {
channelId: this.channel channelId: this.channel
}; };
this.connection = os.stream.connectToChannel('channel', { this.connection = os.stream.useChannel('channel', {
channelId: this.channel channelId: this.channel
}); });
this.connection.on('note', prepend); this.connection.on('note', prepend);

View File

@ -224,8 +224,6 @@ fetchInstance().then(() => {
initializeSw(); initializeSw();
}); });
stream.init($i);
const app = createApp(await ( const app = createApp(await (
window.location.search === '?zen' ? import('@client/ui/zen.vue') : window.location.search === '?zen' ? import('@client/ui/zen.vue') :
!$i ? import('@client/ui/visitor.vue') : !$i ? import('@client/ui/visitor.vue') :
@ -357,7 +355,7 @@ if ($i) {
} }
} }
const main = stream.useSharedConnection('main', 'System'); const main = stream.useChannel('main', null, 'System');
// 自分の情報が更新されたとき // 自分の情報が更新されたとき
main.on('meUpdated', i => { main.on('meUpdated', i => {
@ -419,10 +417,6 @@ if ($i) {
sound.play('channel'); sound.play('channel');
}); });
main.on('readAllAnnouncements', () => {
updateAccount({ hasUnreadAnnouncement: false });
});
// トークンが再生成されたとき // トークンが再生成されたとき
// このままではMisskeyが利用できないので強制的にサインアウトさせる // このままではMisskeyが利用できないので強制的にサインアウトさせる
main.on('myTokenRegenerated', () => { main.on('myTokenRegenerated', () => {

View File

@ -1,26 +1,14 @@
import { computed, reactive } from 'vue'; import { computed, reactive } from 'vue';
import * as Misskey from 'misskey-js';
import { api } from './os'; import { api } from './os';
// TODO: 他のタブと永続化されたstateを同期 // TODO: 他のタブと永続化されたstateを同期
export type Instance = {
emojis: {
category: string;
}[];
ads: {
id: string;
ratio: number;
place: string;
url: string;
imageUrl: string;
}[];
};
const data = localStorage.getItem('instance'); const data = localStorage.getItem('instance');
// TODO: instanceをリアクティブにするかは再考の余地あり // TODO: instanceをリアクティブにするかは再考の余地あり
export const instance: Instance = reactive(data ? JSON.parse(data) : { export const instance: Misskey.entities.InstanceMetadata = reactive(data ? JSON.parse(data) : {
// TODO: set default values // TODO: set default values
}); });

View File

@ -3,16 +3,16 @@
import { Component, defineAsyncComponent, markRaw, reactive, Ref, ref } from 'vue'; import { Component, defineAsyncComponent, markRaw, reactive, Ref, ref } from 'vue';
import { EventEmitter } from 'eventemitter3'; import { EventEmitter } from 'eventemitter3';
import insertTextAtCursor from 'insert-text-at-cursor'; import insertTextAtCursor from 'insert-text-at-cursor';
import * as Misskey from 'misskey-js';
import * as Sentry from '@sentry/browser'; import * as Sentry from '@sentry/browser';
import Stream from '@client/scripts/stream'; import { apiUrl, debug, url } from '@client/config';
import { apiUrl, debug } from '@client/config';
import MkPostFormDialog from '@client/components/post-form-dialog.vue'; import MkPostFormDialog from '@client/components/post-form-dialog.vue';
import MkWaitingDialog from '@client/components/waiting-dialog.vue'; import MkWaitingDialog from '@client/components/waiting-dialog.vue';
import { resolve } from '@client/router'; import { resolve } from '@client/router';
import { $i } from '@client/account'; import { $i } from '@client/account';
import { defaultStore } from '@client/store'; import { defaultStore } from '@client/store';
export const stream = markRaw(new Stream()); export const stream = markRaw(new Misskey.Stream(url, $i));
export const pendingApiRequestsCount = ref(0); export const pendingApiRequestsCount = ref(0);
let apiRequestsCount = 0; // for debug let apiRequestsCount = 0; // for debug
@ -20,7 +20,11 @@ export const apiRequests = ref([]); // for debug
export const windows = new Map(); export const windows = new Map();
export function api(endpoint: string, data: Record<string, any> = {}, token?: string | null | undefined) { const apiClient = new Misskey.api.APIClient({
origin: url,
});
export const api = ((endpoint: string, data: Record<string, any> = {}, token?: string | null | undefined) => {
pendingApiRequestsCount.value++; pendingApiRequestsCount.value++;
const onFinally = () => { const onFinally = () => {
@ -56,7 +60,7 @@ export function api(endpoint: string, data: Record<string, any> = {}, token?: st
if (res.status === 200) { if (res.status === 200) {
resolve(body); resolve(body);
if (debug) { if (debug) {
log!.res = markRaw(body); log!.res = markRaw(JSON.parse(JSON.stringify(body)));
log!.state = 'success'; log!.state = 'success';
} }
} else if (res.status === 204) { } else if (res.status === 204) {
@ -90,17 +94,15 @@ export function api(endpoint: string, data: Record<string, any> = {}, token?: st
promise.then(onFinally, onFinally); promise.then(onFinally, onFinally);
return promise; return promise;
} }) as typeof apiClient.request;
export function apiWithDialog( export const apiWithDialog = ((
endpoint: string, endpoint: string,
data: Record<string, any> = {}, data: Record<string, any> = {},
token?: string | null | undefined, token?: string | null | undefined,
onSuccess?: (res: any) => void, ) => {
onFailure?: (e: Error) => void,
) {
const promise = api(endpoint, data, token); const promise = api(endpoint, data, token);
promiseDialog(promise, onSuccess, onFailure ? onFailure : (e) => { promiseDialog(promise, null, (e) => {
dialog({ dialog({
type: 'error', type: 'error',
text: e.message + '\n' + (e as any).id, text: e.message + '\n' + (e as any).id,
@ -108,7 +110,7 @@ export function apiWithDialog(
}); });
return promise; return promise;
} }) as typeof api;
export function promiseDialog<T extends Promise<any>>( export function promiseDialog<T extends Promise<any>>(
promise: T, promise: T,

View File

@ -90,7 +90,7 @@ export default defineComponent({
stats: null, stats: null,
serverInfo: null, serverInfo: null,
connection: null, connection: null,
queueConnection: os.stream.useSharedConnection('queueStats'), queueConnection: os.stream.useChannel('queueStats'),
memUsage: 0, memUsage: 0,
chartCpuMem: null, chartCpuMem: null,
chartNet: null, chartNet: null,
@ -121,7 +121,7 @@ export default defineComponent({
os.api('admin/server-info', {}).then(res => { os.api('admin/server-info', {}).then(res => {
this.serverInfo = res; this.serverInfo = res;
this.connection = os.stream.useSharedConnection('serverStats'); this.connection = os.stream.useChannel('serverStats');
this.connection.on('stats', this.onStats); this.connection.on('stats', this.onStats);
this.connection.on('statsLog', this.onStatsLog); this.connection.on('statsLog', this.onStatsLog);
this.connection.send('requestLog', { this.connection.send('requestLog', {

View File

@ -92,6 +92,7 @@ export default defineComponent({
version, version,
url, url,
stats: null, stats: null,
meta: null,
fetchStats: () => os.api('stats', {}), fetchStats: () => os.api('stats', {}),
fetchServerInfo: () => os.api('admin/server-info', {}), fetchServerInfo: () => os.api('admin/server-info', {}),
fetchJobs: () => os.api('admin/queue/deliver-delayed', {}), fetchJobs: () => os.api('admin/queue/deliver-delayed', {}),

View File

@ -35,7 +35,7 @@ export default defineComponent({
title: this.$ts.jobQueue, title: this.$ts.jobQueue,
icon: 'fas fa-clipboard-list', icon: 'fas fa-clipboard-list',
}, },
connection: os.stream.useSharedConnection('queueStats'), connection: os.stream.useChannel('queueStats'),
} }
}, },

View File

@ -19,6 +19,11 @@
<span>{{ $ts.bannerUrl }}</span> <span>{{ $ts.bannerUrl }}</span>
</FormInput> </FormInput>
<FormInput v-model:value="backgroundImageUrl">
<template #prefix><i class="fas fa-link"></i></template>
<span>{{ $ts.backgroundImageUrl }}</span>
</FormInput>
<FormInput v-model:value="tosUrl"> <FormInput v-model:value="tosUrl">
<template #prefix><i class="fas fa-link"></i></template> <template #prefix><i class="fas fa-link"></i></template>
<span>{{ $ts.tosUrl }}</span> <span>{{ $ts.tosUrl }}</span>
@ -88,6 +93,7 @@ export default defineComponent({
maintainerEmail: null, maintainerEmail: null,
iconUrl: null, iconUrl: null,
bannerUrl: null, bannerUrl: null,
backgroundImageUrl: null,
maxNoteTextLength: 0, maxNoteTextLength: 0,
enableLocalTimeline: false, enableLocalTimeline: false,
enableGlobalTimeline: false, enableGlobalTimeline: false,
@ -106,6 +112,7 @@ export default defineComponent({
this.tosUrl = meta.tosUrl; this.tosUrl = meta.tosUrl;
this.iconUrl = meta.iconUrl; this.iconUrl = meta.iconUrl;
this.bannerUrl = meta.bannerUrl; this.bannerUrl = meta.bannerUrl;
this.backgroundImageUrl = meta.backgroundImageUrl;
this.maintainerName = meta.maintainerName; this.maintainerName = meta.maintainerName;
this.maintainerEmail = meta.maintainerEmail; this.maintainerEmail = meta.maintainerEmail;
this.maxNoteTextLength = meta.maxNoteTextLength; this.maxNoteTextLength = meta.maxNoteTextLength;
@ -120,6 +127,7 @@ export default defineComponent({
tosUrl: this.tosUrl, tosUrl: this.tosUrl,
iconUrl: this.iconUrl, iconUrl: this.iconUrl,
bannerUrl: this.bannerUrl, bannerUrl: this.bannerUrl,
backgroundImageUrl: this.backgroundImageUrl,
maintainerName: this.maintainerName, maintainerName: this.maintainerName,
maintainerEmail: this.maintainerEmail, maintainerEmail: this.maintainerEmail,
maxNoteTextLength: this.maxNoteTextLength, maxNoteTextLength: this.maxNoteTextLength,

View File

@ -63,7 +63,7 @@ export default defineComponent({
}, },
mounted() { mounted() {
this.connection = os.stream.useSharedConnection('messagingIndex'); this.connection = os.stream.useChannel('messagingIndex');
this.connection.on('message', this.onMessage); this.connection.on('message', this.onMessage);
this.connection.on('read', this.onRead); this.connection.on('read', this.onRead);

View File

@ -141,7 +141,7 @@ const Component = defineComponent({
this.group = group; this.group = group;
} }
this.connection = os.stream.connectToChannel('messaging', { this.connection = os.stream.useChannel('messaging', {
otherparty: this.user ? this.user.id : undefined, otherparty: this.user ? this.user.id : undefined,
group: this.group ? this.group.id : undefined, group: this.group ? this.group.id : undefined,
}); });

View File

@ -350,6 +350,9 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@use "sass:math";
.xqnhankfuuilcwvhgsopeqncafzsquya { .xqnhankfuuilcwvhgsopeqncafzsquya {
text-align: center; text-align: center;
@ -388,11 +391,11 @@ export default defineComponent({
font-size: 0.8em; font-size: 0.8em;
&:first-child { &:first-child {
margin-left: -($gap / 2); margin-left: -(math.div($gap, 2));
} }
&:last-child { &:last-child {
margin-right: -($gap / 2); margin-right: -(math.div($gap, 2));
} }
} }
} }
@ -413,11 +416,11 @@ export default defineComponent({
font-size: 12px; font-size: 12px;
&:first-child { &:first-child {
margin-top: -($gap / 2); margin-top: -(math.div($gap, 2));
} }
&:last-child { &:last-child {
margin-bottom: -($gap / 2); margin-bottom: -(math.div($gap, 2));
} }
} }
} }

View File

@ -61,7 +61,7 @@ export default defineComponent({
if (this.connection) { if (this.connection) {
this.connection.dispose(); this.connection.dispose();
} }
this.connection = os.stream.connectToChannel('gamesReversiGame', { this.connection = os.stream.useChannel('gamesReversiGame', {
gameId: this.game.id gameId: this.game.id
}); });
this.connection.on('started', this.onStarted); this.connection.on('started', this.onStarted);

View File

@ -92,7 +92,7 @@ export default defineComponent({
mounted() { mounted() {
if (this.$i) { if (this.$i) {
this.connection = os.stream.useSharedConnection('gamesReversi'); this.connection = os.stream.useChannel('gamesReversi');
this.connection.on('invited', this.onInvited); this.connection.on('invited', this.onInvited);

View File

@ -220,6 +220,9 @@ export default defineComponent({
</script> </script>
<style lang="scss" scoped> <style lang="scss" scoped>
@use "sass:math";
.uawsfosz { .uawsfosz {
> div { > div {
padding: 24px; padding: 24px;
@ -227,12 +230,12 @@ export default defineComponent({
> .meter { > .meter {
$size: 12px; $size: 12px;
background: rgba(0, 0, 0, 0.1); background: rgba(0, 0, 0, 0.1);
border-radius: ($size / 2); border-radius: math.div($size, 2);
overflow: hidden; overflow: hidden;
> div { > div {
height: $size; height: $size;
border-radius: ($size / 2); border-radius: math.div($size, 2);
} }
} }
} }

View File

@ -4,8 +4,8 @@
<div class="_formLabel"><i class="fab fa-twitter"></i> Twitter</div> <div class="_formLabel"><i class="fab fa-twitter"></i> Twitter</div>
<div class="_formPanel" style="padding: 16px;"> <div class="_formPanel" style="padding: 16px;">
<p v-if="integrations.twitter">{{ $ts.connectedTo }}: <a :href="`https://twitter.com/${integrations.twitter.screenName}`" rel="nofollow noopener" target="_blank">@{{ integrations.twitter.screenName }}</a></p> <p v-if="integrations.twitter">{{ $ts.connectedTo }}: <a :href="`https://twitter.com/${integrations.twitter.screenName}`" rel="nofollow noopener" target="_blank">@{{ integrations.twitter.screenName }}</a></p>
<MkButton v-if="integrations.twitter" @click="disconnectTwitter" danger>{{ $ts.disconnectSerice }}</MkButton> <MkButton v-if="integrations.twitter" @click="disconnectTwitter" danger>{{ $ts.disconnectService }}</MkButton>
<MkButton v-else @click="connectTwitter" primary>{{ $ts.connectSerice }}</MkButton> <MkButton v-else @click="connectTwitter" primary>{{ $ts.connectService }}</MkButton>
</div> </div>
</div> </div>
@ -13,8 +13,8 @@
<div class="_formLabel"><i class="fab fa-discord"></i> Discord</div> <div class="_formLabel"><i class="fab fa-discord"></i> Discord</div>
<div class="_formPanel" style="padding: 16px;"> <div class="_formPanel" style="padding: 16px;">
<p v-if="integrations.discord">{{ $ts.connectedTo }}: <a :href="`https://discord.com/users/${integrations.discord.id}`" rel="nofollow noopener" target="_blank">@{{ integrations.discord.username }}#{{ integrations.discord.discriminator }}</a></p> <p v-if="integrations.discord">{{ $ts.connectedTo }}: <a :href="`https://discord.com/users/${integrations.discord.id}`" rel="nofollow noopener" target="_blank">@{{ integrations.discord.username }}#{{ integrations.discord.discriminator }}</a></p>
<MkButton v-if="integrations.discord" @click="disconnectDiscord" danger>{{ $ts.disconnectSerice }}</MkButton> <MkButton v-if="integrations.discord" @click="disconnectDiscord" danger>{{ $ts.disconnectService }}</MkButton>
<MkButton v-else @click="connectDiscord" primary>{{ $ts.connectSerice }}</MkButton> <MkButton v-else @click="connectDiscord" primary>{{ $ts.connectService }}</MkButton>
</div> </div>
</div> </div>
@ -22,8 +22,8 @@
<div class="_formLabel"><i class="fab fa-github"></i> GitHub</div> <div class="_formLabel"><i class="fab fa-github"></i> GitHub</div>
<div class="_formPanel" style="padding: 16px;"> <div class="_formPanel" style="padding: 16px;">
<p v-if="integrations.github">{{ $ts.connectedTo }}: <a :href="`https://github.com/${integrations.github.login}`" rel="nofollow noopener" target="_blank">@{{ integrations.github.login }}</a></p> <p v-if="integrations.github">{{ $ts.connectedTo }}: <a :href="`https://github.com/${integrations.github.login}`" rel="nofollow noopener" target="_blank">@{{ integrations.github.login }}</a></p>
<MkButton v-if="integrations.github" @click="disconnectGithub" danger>{{ $ts.disconnectSerice }}</MkButton> <MkButton v-if="integrations.github" @click="disconnectGithub" danger>{{ $ts.disconnectService }}</MkButton>
<MkButton v-else @click="connectGithub" primary>{{ $ts.connectSerice }}</MkButton> <MkButton v-else @click="connectGithub" primary>{{ $ts.connectService }}</MkButton>
</div> </div>
</div> </div>
</FormBase> </FormBase>

View File

@ -71,7 +71,7 @@
<FormButton primary v-else @click="wallpaper = null">{{ $ts.removeWallpaper }}</FormButton> <FormButton primary v-else @click="wallpaper = null">{{ $ts.removeWallpaper }}</FormButton>
<FormGroup> <FormGroup>
<FormLink to="https://assets.msky.cafe/theme/list" external><template #icon><i class="fas fa-globe"></i></template>{{ $ts._theme.explore }}</FormLink> <FormLink to="https://assets.misskey.io/theme/list" external><template #icon><i class="fas fa-globe"></i></template>{{ $ts._theme.explore }}</FormLink>
<FormLink to="/settings/theme/install"><template #icon><i class="fas fa-download"></i></template>{{ $ts._theme.install }}</FormLink> <FormLink to="/settings/theme/install"><template #icon><i class="fas fa-download"></i></template>{{ $ts._theme.install }}</FormLink>
</FormGroup> </FormGroup>

View File

@ -3,6 +3,11 @@ import * as url from '../../prelude/url';
export function getStaticImageUrl(baseUrl: string): string { export function getStaticImageUrl(baseUrl: string): string {
const u = new URL(baseUrl); const u = new URL(baseUrl);
if (u.href.startsWith(`${instanceUrl}/proxy/`)) {
// もう既にproxyっぽそうだったらsearchParams付けるだけ
u.searchParams.set('static', '1');
return u.href;
}
const dummy = `${u.host}${u.pathname}`; // 拡張子がないとキャッシュしてくれないCDNがあるので const dummy = `${u.host}${u.pathname}`; // 拡張子がないとキャッシュしてくれないCDNがあるので
return `${instanceUrl}/proxy/${dummy}?${url.query({ return `${instanceUrl}/proxy/${dummy}?${url.query({
url: u.href, url: u.href,

View File

@ -47,7 +47,7 @@ export function selectFile(src: any, label: string | null, multiple = false) {
const marker = Math.random().toString(); // TODO: UUIDとか使う const marker = Math.random().toString(); // TODO: UUIDとか使う
const connection = os.stream.useSharedConnection('main'); const connection = os.stream.useChannel('main');
connection.on('urlUploadFinished', data => { connection.on('urlUploadFinished', data => {
if (data.marker === marker) { if (data.marker === marker) {
res(multiple ? [data.file] : data.file); res(multiple ? [data.file] : data.file);
@ -55,7 +55,7 @@ export function selectFile(src: any, label: string | null, multiple = false) {
} }
}); });
os.api('drive/files/upload_from_url', { os.api('drive/files/upload-from-url', {
url: url, url: url,
marker marker
}); });

View File

@ -1,312 +0,0 @@
import autobind from 'autobind-decorator';
import { EventEmitter } from 'eventemitter3';
import ReconnectingWebsocket from 'reconnecting-websocket';
import { markRaw } from 'vue';
import { debug, wsUrl } from '@client/config';
import { query as urlQuery } from '../../prelude/url';
/**
* Misskey stream connection
*/
export default class Stream extends EventEmitter {
private stream: ReconnectingWebsocket;
public state: 'initializing' | 'reconnecting' | 'connected' = 'initializing';
private sharedConnectionPools: Pool[] = [];
private sharedConnections: SharedConnection[] = [];
private nonSharedConnections: NonSharedConnection[] = [];
@autobind
public init(user): void {
const query = urlQuery({
i: user?.token,
_t: Date.now(),
});
this.stream = new ReconnectingWebsocket(`${wsUrl}?${query}`, '', { minReconnectionDelay: 1 }); // https://github.com/pladaria/reconnecting-websocket/issues/91
this.stream.addEventListener('open', this.onOpen);
this.stream.addEventListener('close', this.onClose);
this.stream.addEventListener('message', this.onMessage);
}
@autobind
public useSharedConnection(channel: string, name?: string): SharedConnection {
let pool = this.sharedConnectionPools.find(p => p.channel === channel);
if (pool == null) {
pool = new Pool(this, channel);
this.sharedConnectionPools.push(pool);
}
const connection = markRaw(new SharedConnection(this, channel, pool, name));
this.sharedConnections.push(connection);
return connection;
}
@autobind
public removeSharedConnection(connection: SharedConnection) {
this.sharedConnections = this.sharedConnections.filter(c => c !== connection);
}
@autobind
public removeSharedConnectionPool(pool: Pool) {
this.sharedConnectionPools = this.sharedConnectionPools.filter(p => p !== pool);
}
@autobind
public connectToChannel(channel: string, params?: any): NonSharedConnection {
const connection = markRaw(new NonSharedConnection(this, channel, params));
this.nonSharedConnections.push(connection);
return connection;
}
@autobind
public disconnectToChannel(connection: NonSharedConnection) {
this.nonSharedConnections = this.nonSharedConnections.filter(c => c !== connection);
}
/**
* Callback of when open connection
*/
@autobind
private onOpen() {
const isReconnect = this.state === 'reconnecting';
this.state = 'connected';
this.emit('_connected_');
// チャンネル再接続
if (isReconnect) {
for (const p of this.sharedConnectionPools)
p.connect();
for (const c of this.nonSharedConnections)
c.connect();
}
}
/**
* Callback of when close connection
*/
@autobind
private onClose() {
if (this.state === 'connected') {
this.state = 'reconnecting';
this.emit('_disconnected_');
}
}
/**
* Callback of when received a message from connection
*/
@autobind
private onMessage(message) {
const { type, body } = JSON.parse(message.data);
if (type === 'channel') {
const id = body.id;
let connections: Connection[];
connections = this.sharedConnections.filter(c => c.id === id);
if (connections.length === 0) {
connections = [this.nonSharedConnections.find(c => c.id === id)];
}
for (const c of connections.filter(c => c != null)) {
c.emit(body.type, Object.freeze(body.body));
if (debug) c.inCount++;
}
} else {
this.emit(type, Object.freeze(body));
}
}
/**
* Send a message to connection
*/
@autobind
public send(typeOrPayload, payload?) {
const data = payload === undefined ? typeOrPayload : {
type: typeOrPayload,
body: payload
};
this.stream.send(JSON.stringify(data));
}
/**
* Close this connection
*/
@autobind
public close() {
this.stream.removeEventListener('open', this.onOpen);
this.stream.removeEventListener('message', this.onMessage);
}
}
let idCounter = 0;
class Pool {
public channel: string;
public id: string;
protected stream: Stream;
public users = 0;
private disposeTimerId: any;
private isConnected = false;
constructor(stream: Stream, channel: string) {
this.channel = channel;
this.stream = stream;
this.id = (++idCounter).toString();
this.stream.on('_disconnected_', this.onStreamDisconnected);
}
@autobind
private onStreamDisconnected() {
this.isConnected = false;
}
@autobind
public inc() {
if (this.users === 0 && !this.isConnected) {
this.connect();
}
this.users++;
// タイマー解除
if (this.disposeTimerId) {
clearTimeout(this.disposeTimerId);
this.disposeTimerId = null;
}
}
@autobind
public dec() {
this.users--;
// そのコネクションの利用者が誰もいなくなったら
if (this.users === 0) {
// また直ぐに再利用される可能性があるので、一定時間待ち、
// 新たな利用者が現れなければコネクションを切断する
this.disposeTimerId = setTimeout(() => {
this.disconnect();
}, 3000);
}
}
@autobind
public connect() {
if (this.isConnected) return;
this.isConnected = true;
this.stream.send('connect', {
channel: this.channel,
id: this.id
});
}
@autobind
private disconnect() {
this.stream.off('_disconnected_', this.onStreamDisconnected);
this.stream.send('disconnect', { id: this.id });
this.stream.removeSharedConnectionPool(this);
}
}
abstract class Connection extends EventEmitter {
public channel: string;
protected stream: Stream;
public abstract id: string;
public name?: string; // for debug
public inCount: number = 0; // for debug
public outCount: number = 0; // for debug
constructor(stream: Stream, channel: string, name?: string) {
super();
this.stream = stream;
this.channel = channel;
this.name = name;
}
@autobind
public send(id: string, typeOrPayload, payload?) {
const type = payload === undefined ? typeOrPayload.type : typeOrPayload;
const body = payload === undefined ? typeOrPayload.body : payload;
this.stream.send('ch', {
id: id,
type: type,
body: body
});
if (debug) this.outCount++;
}
public abstract dispose(): void;
}
class SharedConnection extends Connection {
private pool: Pool;
public get id(): string {
return this.pool.id;
}
constructor(stream: Stream, channel: string, pool: Pool, name?: string) {
super(stream, channel, name);
this.pool = pool;
this.pool.inc();
}
@autobind
public send(typeOrPayload, payload?) {
super.send(this.pool.id, typeOrPayload, payload);
}
@autobind
public dispose() {
this.pool.dec();
this.removeAllListeners();
this.stream.removeSharedConnection(this);
}
}
class NonSharedConnection extends Connection {
public id: string;
protected params: any;
constructor(stream: Stream, channel: string, params?: any) {
super(stream, channel);
this.params = params;
this.id = (++idCounter).toString();
this.connect();
}
@autobind
public connect() {
this.stream.send('connect', {
channel: this.channel,
id: this.id,
params: this.params
});
}
@autobind
public send(typeOrPayload, payload?) {
super.send(this.id, typeOrPayload, payload);
}
@autobind
public dispose() {
this.removeAllListeners();
this.stream.send('disconnect', { id: this.id });
this.stream.disconnectToChannel(this);
}
}

View File

@ -146,6 +146,7 @@ hr {
width: 100%; width: 100%;
height: 100%; height: 100%;
background: var(--modalBg); background: var(--modalBg);
-webkit-backdrop-filter: var(--modalBgFilter);
backdrop-filter: var(--modalBgFilter); backdrop-filter: var(--modalBgFilter);
} }

View File

@ -48,7 +48,7 @@ export default defineComponent({
}; };
if ($i) { if ($i) {
const connection = stream.useSharedConnection('main', 'UI'); const connection = stream.useChannel('main', null, 'UI');
connection.on('notification', onNotification); connection.on('notification', onNotification);
//#region Listen message from SW //#region Listen message from SW

View File

@ -121,33 +121,33 @@ export default defineComponent({
this.query = { this.query = {
antennaId: this.antenna antennaId: this.antenna
}; };
this.connection = os.stream.connectToChannel('antenna', { this.connection = os.stream.useChannel('antenna', {
antennaId: this.antenna antennaId: this.antenna
}); });
this.connection.on('note', prepend); this.connection.on('note', prepend);
} else if (this.src == 'home') { } else if (this.src == 'home') {
endpoint = 'notes/timeline'; endpoint = 'notes/timeline';
this.connection = os.stream.useSharedConnection('homeTimeline'); this.connection = os.stream.useChannel('homeTimeline');
this.connection.on('note', prepend); this.connection.on('note', prepend);
this.connection2 = os.stream.useSharedConnection('main'); this.connection2 = os.stream.useChannel('main');
this.connection2.on('follow', onChangeFollowing); this.connection2.on('follow', onChangeFollowing);
this.connection2.on('unfollow', onChangeFollowing); this.connection2.on('unfollow', onChangeFollowing);
} else if (this.src == 'local') { } else if (this.src == 'local') {
endpoint = 'notes/local-timeline'; endpoint = 'notes/local-timeline';
this.connection = os.stream.useSharedConnection('localTimeline'); this.connection = os.stream.useChannel('localTimeline');
this.connection.on('note', prepend); this.connection.on('note', prepend);
} else if (this.src == 'social') { } else if (this.src == 'social') {
endpoint = 'notes/hybrid-timeline'; endpoint = 'notes/hybrid-timeline';
this.connection = os.stream.useSharedConnection('hybridTimeline'); this.connection = os.stream.useChannel('hybridTimeline');
this.connection.on('note', prepend); this.connection.on('note', prepend);
} else if (this.src == 'global') { } else if (this.src == 'global') {
endpoint = 'notes/global-timeline'; endpoint = 'notes/global-timeline';
this.connection = os.stream.useSharedConnection('globalTimeline'); this.connection = os.stream.useChannel('globalTimeline');
this.connection.on('note', prepend); this.connection.on('note', prepend);
} else if (this.src == 'mentions') { } else if (this.src == 'mentions') {
endpoint = 'notes/mentions'; endpoint = 'notes/mentions';
this.connection = os.stream.useSharedConnection('main'); this.connection = os.stream.useChannel('main');
this.connection.on('mention', prepend); this.connection.on('mention', prepend);
} else if (this.src == 'directs') { } else if (this.src == 'directs') {
endpoint = 'notes/mentions'; endpoint = 'notes/mentions';
@ -159,14 +159,14 @@ export default defineComponent({
prepend(note); prepend(note);
} }
}; };
this.connection = os.stream.useSharedConnection('main'); this.connection = os.stream.useChannel('main');
this.connection.on('mention', onNote); this.connection.on('mention', onNote);
} else if (this.src == 'list') { } else if (this.src == 'list') {
endpoint = 'notes/user-list-timeline'; endpoint = 'notes/user-list-timeline';
this.query = { this.query = {
listId: this.list listId: this.list
}; };
this.connection = os.stream.connectToChannel('userList', { this.connection = os.stream.useChannel('userList', {
listId: this.list listId: this.list
}); });
this.connection.on('note', prepend); this.connection.on('note', prepend);
@ -178,7 +178,7 @@ export default defineComponent({
this.query = { this.query = {
channelId: this.channel channelId: this.channel
}; };
this.connection = os.stream.connectToChannel('channel', { this.connection = os.stream.useChannel('channel', {
channelId: this.channel channelId: this.channel
}); });
this.connection.on('note', prepend); this.connection.on('note', prepend);

View File

@ -241,7 +241,6 @@ export default defineComponent({
> .text { > .text {
display: none; display: none;
} }
} }
} }
@ -309,7 +308,7 @@ export default defineComponent({
> .indicator { > .indicator {
position: absolute; position: absolute;
top: 0; top: 0;
left: 20px; left: 0;
color: var(--navIndicator); color: var(--navIndicator);
font-size: 8px; font-size: 8px;
animation: blink 1s infinite; animation: blink 1s infinite;

View File

@ -65,7 +65,7 @@ export default defineComponent({
extends: widget, extends: widget,
data() { data() {
return { return {
connection: os.stream.useSharedConnection('queueStats'), connection: os.stream.useChannel('queueStats'),
inbox: { inbox: {
activeSincePrevTick: 0, activeSincePrevTick: 0,
active: 0, active: 0,

View File

@ -48,7 +48,7 @@ export default defineComponent({
}; };
}, },
mounted() { mounted() {
this.connection = os.stream.useSharedConnection('main'); this.connection = os.stream.useChannel('main');
this.connection.on('driveFileCreated', this.onDriveFileCreated); this.connection.on('driveFileCreated', this.onDriveFileCreated);

View File

@ -63,7 +63,7 @@ export default defineComponent({
os.api('server-info', {}).then(res => { os.api('server-info', {}).then(res => {
this.meta = res; this.meta = res;
}); });
this.connection = os.stream.useSharedConnection('serverStats'); this.connection = os.stream.useChannel('serverStats');
}, },
unmounted() { unmounted() {
this.connection.dispose(); this.connection.dispose();

View File

@ -1,25 +1,25 @@
# ストリーミングAPI # API de Streaming
ストリーミングAPIを使うと、リアルタイムで様々な情報(例えばタイムラインに新しい投稿が流れてきた、メッセージが届いた、フォローされた、など)を受け取ったり、様々な操作を行ったりすることができます。 Usando la API de streaming, se puede recibir en tiempo real toda clase de información (por ejemplo, los posts nuevos que pasaron por la linea de tiempo, los mensajes recibidos, las notificaciones de seguimiento, etc.) y manejar varias operaciones en estas.
## ストリームに接続する ## Conectarse a streams
ストリーミングAPIを利用するには、まずMisskeyサーバーに**websocket**接続する必要があります。 Para usar la API de streaming, primero hay que conectar un **websocket** al servidor de Misskey
以下のURLに、`i`というパラメータ名で認証情報を含めて、websocket接続してください。例: Conecte el websocket a la URL mencionada abajo, incluyendo la información de autenticación en el parámetro `i`Ej:
``` ```
%WS_URL%/streaming?i=xxxxxxxxxxxxxxx %WS_URL%/streaming?i=xxxxxxxxxxxxxxx
``` ```
認証情報は、自分のAPIキーや、アプリケーションからストリームに接続する際はユーザーのアクセストークンのことを指します。 La información de autenticación hace referencia a tu propia clave de la API, o al token de acceso del usuario cuando se conecta al stream desde la aplicación
<div class="ui info"> <div class="ui info">
<p><i class="fas fa-info-circle"></i> 認証情報の取得については、<a href="./api">こちらのドキュメント</a>をご確認ください。</p> <p><i class="fas fa-info-circle"></i> Para obtener la información de la autenticación, consulte <a href="./api">Este documento</a></p>
</div> </div>
--- ---
認証情報は省略することもできますが、その場合非ログインでの利用ということになり、受信できる情報や可能な操作は限られます。例: La información de autenticación puede omitirse, pero en ese caso de uso sin un login, se restringirá la información que puede ser recibida y las operaciones posibles,Ej:
``` ```
%WS_URL%/streaming %WS_URL%/streaming
@ -50,7 +50,7 @@ MisskeyのストリーミングAPIにはチャンネルという概念があり
} }
``` ```
ここで、 Aquí
* `channel`には接続したいチャンネル名を設定します。チャンネルの種類については後述します。 * `channel`には接続したいチャンネル名を設定します。チャンネルの種類については後述します。
* `id`にはそのチャンネルとやり取りするための任意のIDを設定します。ストリームでは様々なメッセージが流れるので、そのメッセージがどのチャンネルからのものなのか識別する必要があるからです。このIDは、UUIDや、乱数のようなもので構いません。 * `id`にはそのチャンネルとやり取りするための任意のIDを設定します。ストリームでは様々なメッセージが流れるので、そのメッセージがどのチャンネルからのものなのか識別する必要があるからです。このIDは、UUIDや、乱数のようなもので構いません。
* `params`はチャンネルに接続する際のパラメータです。チャンネルによって接続時に必要とされるパラメータは異なります。パラメータ不要のチャンネルに接続する際は、このプロパティは省略可能です。 * `params`はチャンネルに接続する際のパラメータです。チャンネルによって接続時に必要とされるパラメータは異なります。パラメータ不要のチャンネルに接続する際は、このプロパティは省略可能です。
@ -76,7 +76,7 @@ MisskeyのストリーミングAPIにはチャンネルという概念があり
} }
``` ```
ここで、 Aquí
* `id`には前述したそのチャンネルに接続する際に設定したIDが設定されています。これで、このメッセージがどのチャンネルからのものなのか知ることができます。 * `id`には前述したそのチャンネルに接続する際に設定したIDが設定されています。これで、このメッセージがどのチャンネルからのものなのか知ることができます。
* `type`にはメッセージの種類が設定されます。チャンネルによって、どのような種類のメッセージが流れてくるかは異なります。 * `type`にはメッセージの種類が設定されます。チャンネルによって、どのような種類のメッセージが流れてくるかは異なります。
* `body`にはメッセージの内容が設定されます。チャンネルによって、どのような内容のメッセージが流れてくるかは異なります。 * `body`にはメッセージの内容が設定されます。チャンネルによって、どのような内容のメッセージが流れてくるかは異なります。
@ -98,7 +98,7 @@ MisskeyのストリーミングAPIにはチャンネルという概念があり
} }
``` ```
ここで、 Aquí
* `id`には前述したそのチャンネルに接続する際に設定したIDを設定します。これで、このメッセージがどのチャンネルに向けたものなのか識別させることができます。 * `id`には前述したそのチャンネルに接続する際に設定したIDを設定します。これで、このメッセージがどのチャンネルに向けたものなのか識別させることができます。
* `type`にはメッセージの種類を設定します。チャンネルによって、どのような種類のメッセージを受け付けるかは異なります。 * `type`にはメッセージの種類を設定します。チャンネルによって、どのような種類のメッセージを受け付けるかは異なります。
* `body`にはメッセージの内容を設定します。チャンネルによって、どのような内容のメッセージを受け付けるかは異なります。 * `body`にはメッセージの内容を設定します。チャンネルによって、どのような内容のメッセージを受け付けるかは異なります。
@ -115,7 +115,7 @@ MisskeyのストリーミングAPIにはチャンネルという概念があり
} }
``` ```
ここで、 Aquí
* `id`には前述したそのチャンネルに接続する際に設定したIDを設定します。 * `id`には前述したそのチャンネルに接続する際に設定したIDを設定します。
## ストリームを経由してAPIリクエストする ## ストリームを経由してAPIリクエストする
@ -136,7 +136,7 @@ MisskeyのストリーミングAPIにはチャンネルという概念があり
} }
``` ```
ここで、 Aquí
* `id`には、APIのレスポンスを識別するための、APIリクエストごとの一意なIDを設定する必要があります。UUIDや、簡単な乱数のようなもので構いません。 * `id`には、APIのレスポンスを識別するための、APIリクエストごとの一意なIDを設定する必要があります。UUIDや、簡単な乱数のようなもので構いません。
* `endpoint`には、あなたがリクエストしたいAPIのエンドポイントを指定します。 * `endpoint`には、あなたがリクエストしたいAPIのエンドポイントを指定します。
* `data`には、エンドポイントのパラメータを含めます。 * `data`には、エンドポイントのパラメータを含めます。
@ -158,7 +158,7 @@ APIへリクエストすると、レスポンスがストリームから次の
} }
``` ```
ここで、 Aquí
* `xxxxxxxxxxxxxxxx`の部分には、リクエストの際に設定された`id`が含まれています。これにより、どのリクエストに対するレスポンスなのか判別することができます。 * `xxxxxxxxxxxxxxxx`の部分には、リクエストの際に設定された`id`が含まれています。これにより、どのリクエストに対するレスポンスなのか判別することができます。
* `body`には、レスポンスが含まれています。 * `body`には、レスポンスが含まれています。
@ -185,7 +185,7 @@ Misskeyは投稿のキャプチャと呼ばれる仕組みを提供していま
} }
``` ```
ここで、 Aquí
* `id`にキャプチャしたい投稿の`id`を設定します。 * `id`にキャプチャしたい投稿の`id`を設定します。
このメッセージを送信すると、Misskeyにキャプチャを要請したことになり、以後、その投稿に関するイベントが流れてくるようになります。 このメッセージを送信すると、Misskeyにキャプチャを要請したことになり、以後、その投稿に関するイベントが流れてくるようになります。
@ -206,7 +206,7 @@ Misskeyは投稿のキャプチャと呼ばれる仕組みを提供していま
} }
``` ```
ここで、 Aquí
* `body`内の`id`に、イベントを発生させた投稿のIDが設定されます。 * `body`内の`id`に、イベントを発生させた投稿のIDが設定されます。
* `body`内の`type`に、イベントの種類が設定されます。 * `body`内の`type`に、イベントの種類が設定されます。
* `body`内の`body`に、イベントの詳細が設定されます。 * `body`内の`body`に、イベントの詳細が設定されます。
@ -219,7 +219,7 @@ Misskeyは投稿のキャプチャと呼ばれる仕組みを提供していま
* `reaction`に、リアクションの種類が設定されます。 * `reaction`に、リアクションの種類が設定されます。
* `userId`に、リアクションを行ったユーザーのIDが設定されます。 * `userId`に、リアクションを行ったユーザーのIDが設定されます。
: Ej:
```json ```json
{ {
type: 'noteUpdated', type: 'noteUpdated',
@ -239,7 +239,7 @@ Misskeyは投稿のキャプチャと呼ばれる仕組みを提供していま
* `deletedAt`に、削除日時が設定されます。 * `deletedAt`に、削除日時が設定されます。
: Ej:
```json ```json
{ {
type: 'noteUpdated', type: 'noteUpdated',
@ -259,7 +259,7 @@ Misskeyは投稿のキャプチャと呼ばれる仕組みを提供していま
* `choice`に、選択肢IDが設定されます。 * `choice`に、選択肢IDが設定されます。
* `userId`に、投票を行ったユーザーのIDが設定されます。 * `userId`に、投票を行ったユーザーのIDが設定されます。
: Ej:
```json ```json
{ {
type: 'noteUpdated', type: 'noteUpdated',
@ -289,7 +289,7 @@ Misskeyは投稿のキャプチャと呼ばれる仕組みを提供していま
} }
``` ```
ここで、 Aquí
* `id`にキャプチャを解除したい投稿の`id`を設定します。 * `id`にキャプチャを解除したい投稿の`id`を設定します。
このメッセージを送信すると、以後、その投稿に関するイベントは流れてこないようになります。 このメッセージを送信すると、以後、その投稿に関するイベントは流れてこないようになります。

View File

@ -1,12 +1,12 @@
# Tema # Tema
テーマを設定して、Misskeyクライアントの見た目を変更できます。 Eligiendo un tema, se puede cambiar la apariencia del cliente de Misskey
## テーマの設定 ## Configuración del tema
設定 > テーマ Configuración > Tema
## テーマを作成する ## Crear tema
テーマコードはJSON5で記述されたテーマオブジェクトです。 テーマは以下のようなオブジェクトです。 El código del tema se guarda como un archivo JSON5. Un ejemplo de tema se puede ver aquí:
``` js ``` js
{ {
id: '17587283-dd92-4a2c-a22c-be0637c9e22a', id: '17587283-dd92-4a2c-a22c-be0637c9e22a',
@ -33,36 +33,36 @@
``` ```
* `id` ... テーマの一意なID。UUIDをおすすめします。 * `id` ... Clave única del tema.Se recomienda un código UUID
* `name` ... テーマ名 * `name` ... Nombre del tema
* `author` ... テーマの作者 * `author` ... Autor del tema
* `desc` ... テーマの説明(オプション) * `desc` ... Descripción del tema (opcional)
* `base` ... 明るいテーマか、暗いテーマか * `base` ... Si es un tema claro u oscuro
* `light`にすると明るいテーマになり、`dark`にすると暗いテーマになります。 * Si se elige `light`, será un tema claro. Si se elige `dark`, será un tema oscuro.
* テーマはここで設定されたベーステーマを継承します。 * Aquí el tema hereda los valores por defecto del tema base elegido
* `props` ... テーマのスタイル定義。これから説明します。 * `props` ... Definición del estilo del tema. Esto se explica en lo siguiente.
### テーマのスタイル定義 ### Definición del estilo del tema
`props`下にはテーマのスタイルを定義します。 キーがCSSの変数名になり、バリューで中身を指定します。 なお、この`props`オブジェクトはベーステーマから継承されます。 ベーステーマは、このテーマの`base`が`light`なら[_light.json5](https://github.com/misskey-dev/misskey/blob/develop/src/client/themes/_light.json5)で、`dark`なら[_dark.json5](https://github.com/misskey-dev/misskey/blob/develop/src/client/themes/_dark.json5)です。 つまり、このテーマ内の`props`に`panel`というキーが無くても、そこにはベーステーマの`panel`があると見なされます。 Debajo de `props`, se define el estilo del tema. La clave es el nombre de las variables del CSS, y con los valores estos se configuran. Incluso más, este objeto `props` hereda los valores por defecto del tema base. El tema base es [_light.json5](https://github.com/misskey-dev/misskey/blob/develop/src/client/themes/_light.json5) si el `base` de este tema es `light`, y [_dark.json5](https://github.com/misskey-dev/misskey/blob/develop/src/client/themes/_dark.json5) si si el `base` de este tema es `dark` Resumiendo, aunque no haya una clave `panel` en el `props` del tema, se considera el <0>panel</0> del tema base.
#### バリューで使える構文 #### Sintaxis de las variables
* 16進数で表された色 * Los colores en base hexadecimal
* : `#00ff00` * Ej: `#00ff00`
* `rgb(r, g, b)`形式で表された色 * Los colores con la sintaxis `rgb(r, g, b)`
* : `rgb(0, 255, 0)` * Ej: `rgb(0, 255, 0)`
* `rgb(r, g, b, a)`形式で表された透明度を含む色 * Los colores con la sintaxis `rgb(r, g, b, a)` con un grado de transparencia
* : `rgba(0, 255, 0, 0.5)` * Ej: `rgba(0, 255, 0, 0.5)`
* 他のキーの値の参照 * Referencias a valores de otras claves
* `@{キー名}`と書くと他のキーの値の参照になります。`{キー名}`は参照したいキーの名前に置き換えます。 * Escribiendo `@{nombre de clave}` se hace referencia al valor de la otra clave.Reemplace `{nombre de clave}` por el nombre de la clave al cual quiera hacer referencia.
* : `@panel` * Ej: `@panel`
* 定数(後述)の参照 * Referencia a una constante (ver más abajo)
* `${定数名}`と書くと定数の参照になります。`{定数名}`は参照したい定数の名前に置き換えます。 * Escribiendo `${nombre de la constante}` se hace referencia a la constante.Reemplace `{nombre de la constante}` por la constante al cual quiera hacer referencia.
* : `$main` * Ej: `$main`
* 関数(後述) * Funciones (ver más abajo)
* `:{関数名}<{引数}<{色}` * `:{nombre de la función}<{parámetros}<{color}`
#### Constante #### Constantes
「CSS変数として出力はしたくないが、他のCSS変数の値として使いまわしたい」値があるときは、定数を使うと便利です。 キー名を`$`で始めると、そのキーはCSS変数として出力されません。 Cuando hay un valor que no se quiere generar como variable CSS pero sí se quiere reutilizar como valor de otra variable CSS, es conveniente usar constantes. Cuando a un nombre de clave se le añade como prefijo `$`, esa clave no será generada como una variable CSS.
#### funciones #### funciones
wip wip

View File

@ -1,15 +1,15 @@
# タイムラインの比較 # Comparación de las lineas de tiempo
https://docs.google.com/spreadsheets/d/1lxQ2ugKrhz58Bg96HTDK_2F98BUritkMyIiBkOByjHA/edit?usp=sharing https://docs.google.com/spreadsheets/d/1lxQ2ugKrhz58Bg96HTDK_2F98BUritkMyIiBkOByjHA/edit?usp=sharing
## Inicio ## Inicio
自分のフォローしているユーザーの投稿 Los posts de los usuarios que uno sigue
## Local ## Local
全てのローカルユーザーの「ホーム」指定されていない投稿 Todos los posts de los usuarios locales que no estén marcados como "Solo inicio"
## Social ## Social
自分のフォローしているユーザーの投稿と、全てのローカルユーザーの「ホーム」指定されていない投稿 Los posts de los usuarios que uno sigue más todos los posts de los usuarios locales que no estén marcados como "Solo inicio"
## Global ## Global
全てのローカルユーザーの「ホーム」指定されていない投稿と、サーバーに届いた全てのリモートユーザーの「ホーム」指定されていない投稿 Todos los posts de los usuarios locales que no estén marcados como "Solo inicio" más todos los posts de los usuarios remotos recibidos por el servidor que no estén marcados como "Solo inicio"

View File

@ -7,11 +7,11 @@ API를 사용하려면 먼저 액세스 토큰을 취득해야 합니다. 이
## 액세스 토큰 가져오기 ## 액세스 토큰 가져오기
기본적으로 API는 요청 시에 액세스 토큰이 필요합니다. API에 요청하는 것이 자기 자신인지, 불특정한 유저에게 사용하는 애플리케이션인지에 따라 취득 절차가 달라집니다. 기본적으로 API는 요청 시에 액세스 토큰이 필요합니다. API에 요청하는 것이 자기 자신인지, 불특정한 유저에게 사용하는 애플리케이션인지에 따라 취득 절차가 달라집니다.
* 전자의 경우: [ 「자기 자신의 액세스 토큰을 수동으로 발급하기」](#自分自身のアクセストークンを手動発行する)로 진행 * 전자의 경우: [「자기 자신의 액세스 토큰을 수동으로 발급하기」](#自分自身のアクセストークンを手動発行する)로 진행
* 후자의 경우: [「애플리케이션 사용자에게 액세스 토큰 발급을 요청하기」](#アプリケーション利用者にアクセストークンの発行をリクエストする)로 진행 * 후자의 경우: [「애플리케이션 사용자에게 액세스 토큰 발급을 요청하기」](#アプリケーション利用者にアクセストークンの発行をリクエストする)로 진행
### 자기 자신의 액세스 토큰을 수동으로 발급하기 ### 자기 자신의 액세스 토큰을 수동으로 발급하기
「설정 &#062 API」에서 자신의 액세스 토큰을 발급할 수 있습니다. 「설정 > API」에서 자신의 액세스 토큰을 발급할 수 있습니다.
[「API 사용 방법」으로 이동](#APIの使い方) [「API 사용 방법」으로 이동](#APIの使い方)

View File

@ -1,2 +1,2 @@
# MFM # MFM
MFM은 Misskey Flavored Markdown의 약자로, Misskey의 다양한 장소에서 사용할 수 있는 전용 마크업 언어입니다. MFM로 사용 가능한 구문은 [MFM 치트 시트 ](/mfm-cheat-sheet)에서 확인할 수 있습니다. MFM은 Misskey Flavored Markdown의 약자로, Misskey의 다양한 장소에서 사용할 수 있는 전용 마크업 언어입니다. MFM로 사용 가능한 구문은 [MFM 치트 시트](/mfm-cheat-sheet)에서 확인할 수 있습니다.

View File

@ -93,6 +93,9 @@
{ "category": "face", "char": "🥱", "name": "yawning", "keywords": ["face", "tired", "yawning"] }, { "category": "face", "char": "🥱", "name": "yawning", "keywords": ["face", "tired", "yawning"] },
{ "category": "face", "char": "😴", "name": "sleeping", "keywords": ["face", "tired", "sleepy", "night", "zzz"] }, { "category": "face", "char": "😴", "name": "sleeping", "keywords": ["face", "tired", "sleepy", "night", "zzz"] },
{ "category": "face", "char": "💤", "name": "zzz", "keywords": ["sleepy", "tired", "dream"] }, { "category": "face", "char": "💤", "name": "zzz", "keywords": ["sleepy", "tired", "dream"] },
{ "category": "face", "char": "\uD83D\uDE36\u200D\uD83C\uDF2B\uFE0F", "name": "face_in_clouds", "keywords": [] },
{ "category": "face", "char": "\uD83D\uDE2E\u200D\uD83D\uDCA8", "name": "face_exhaling", "keywords": [] },
{ "category": "face", "char": "\uD83D\uDE35\u200D\uD83D\uDCAB", "name": "face_with_spiral_eyes", "keywords": [] },
{ "category": "face", "char": "💩", "name": "poop", "keywords": ["hankey", "shitface", "fail", "turd", "shit"] }, { "category": "face", "char": "💩", "name": "poop", "keywords": ["hankey", "shitface", "fail", "turd", "shit"] },
{ "category": "face", "char": "😈", "name": "smiling_imp", "keywords": ["devil", "horns"] }, { "category": "face", "char": "😈", "name": "smiling_imp", "keywords": ["devil", "horns"] },
{ "category": "face", "char": "👿", "name": "imp", "keywords": ["devil", "angry", "horns"] }, { "category": "face", "char": "👿", "name": "imp", "keywords": ["devil", "angry", "horns"] },
@ -1219,6 +1222,8 @@
{ "category": "symbols", "char": "💘", "name": "cupid", "keywords": ["love", "like", "heart", "affection", "valentines"] }, { "category": "symbols", "char": "💘", "name": "cupid", "keywords": ["love", "like", "heart", "affection", "valentines"] },
{ "category": "symbols", "char": "💝", "name": "gift_heart", "keywords": ["love", "valentines"] }, { "category": "symbols", "char": "💝", "name": "gift_heart", "keywords": ["love", "valentines"] },
{ "category": "symbols", "char": "💟", "name": "heart_decoration", "keywords": ["purple-square", "love", "like"] }, { "category": "symbols", "char": "💟", "name": "heart_decoration", "keywords": ["purple-square", "love", "like"] },
{ "category": "symbols", "char": "\u2764\uFE0F\u200D\uD83D\uDD25", "name": "heart_on_fire", "keywords": [] },
{ "category": "symbols", "char": "\u2764\uFE0F\u200D\uD83E\uDE79", "name": "mending_heart", "keywords": [] },
{ "category": "symbols", "char": "☮", "name": "peace_symbol", "keywords": ["hippie"] }, { "category": "symbols", "char": "☮", "name": "peace_symbol", "keywords": ["hippie"] },
{ "category": "symbols", "char": "✝", "name": "latin_cross", "keywords": ["christianity"] }, { "category": "symbols", "char": "✝", "name": "latin_cross", "keywords": ["christianity"] },
{ "category": "symbols", "char": "☪", "name": "star_and_crescent", "keywords": ["islam"] }, { "category": "symbols", "char": "☪", "name": "star_and_crescent", "keywords": ["islam"] },

View File

@ -56,6 +56,8 @@ const forceExitAfter = timeout => () => {
* @param {string} signalOrEvent The exit signal or event name received on the process. * @param {string} signalOrEvent The exit signal or event name received on the process.
*/ */
async function shutdownHandler(signalOrEvent) { async function shutdownHandler(signalOrEvent) {
if (process.env.NODE_ENV === 'test') return process.exit(0);
console.warn(`Shutting down: received [${signalOrEvent}] signal`); console.warn(`Shutting down: received [${signalOrEvent}] signal`);
for (const listener of shutdownListeners) { for (const listener of shutdownListeners) {

File diff suppressed because one or more lines are too long

View File

@ -5,6 +5,8 @@ import { Note } from '../models/entities/note';
import { Cache } from './cache'; import { Cache } from './cache';
import { isSelfHost, toPunyNullable } from './convert-host'; import { isSelfHost, toPunyNullable } from './convert-host';
import { decodeReaction } from './reaction-lib'; import { decodeReaction } from './reaction-lib';
import config from '@/config';
import { query } from '@/prelude/url';
const cache = new Cache<Emoji | null>(1000 * 60 * 60 * 12); const cache = new Cache<Emoji | null>(1000 * 60 * 60 * 12);
@ -59,9 +61,12 @@ export async function populateEmoji(emojiName: string, noteUserHost: string | nu
if (emoji == null) return null; if (emoji == null) return null;
const isLocal = emoji.host == null;
const url = isLocal ? emoji.url : `${config.url}/proxy/image.png?${query({url: emoji.url})}`;
return { return {
name: emojiName, name: emojiName,
url: emoji.url, url,
}; };
} }

View File

@ -41,14 +41,12 @@ export const packedBlockingSchema = {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
format: 'id', format: 'id',
description: 'The unique identifier for this blocking.',
example: 'xxxxxxxxxx', example: 'xxxxxxxxxx',
}, },
createdAt: { createdAt: {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
format: 'date-time', format: 'date-time',
description: 'The date that the blocking was created.'
}, },
blockeeId: { blockeeId: {
type: 'string' as const, type: 'string' as const,
@ -59,7 +57,6 @@ export const packedBlockingSchema = {
type: 'object' as const, type: 'object' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
ref: 'User', ref: 'User',
description: 'The blockee.'
}, },
} }
}; };

View File

@ -51,14 +51,12 @@ export const packedChannelSchema = {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
format: 'id', format: 'id',
description: 'The unique identifier for this Channel.',
example: 'xxxxxxxxxx', example: 'xxxxxxxxxx',
}, },
createdAt: { createdAt: {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
format: 'date-time', format: 'date-time',
description: 'The date that the Channel was created.'
}, },
lastNotedAt: { lastNotedAt: {
type: 'string' as const, type: 'string' as const,
@ -68,7 +66,6 @@ export const packedChannelSchema = {
name: { name: {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
description: 'The name of the Channel.'
}, },
description: { description: {
type: 'string' as const, type: 'string' as const,

View File

@ -39,14 +39,12 @@ export const packedClipSchema = {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
format: 'id', format: 'id',
description: 'The unique identifier for this Clip.',
example: 'xxxxxxxxxx', example: 'xxxxxxxxxx',
}, },
createdAt: { createdAt: {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
format: 'date-time', format: 'date-time',
description: 'The date that the Clip was created.'
}, },
userId: { userId: {
type: 'string' as const, type: 'string' as const,
@ -61,17 +59,14 @@ export const packedClipSchema = {
name: { name: {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
description: 'The name of the Clip.'
}, },
description: { description: {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: true as const, optional: false as const, nullable: true as const,
description: 'The description of the Clip.'
}, },
isPublic: { isPublic: {
type: 'boolean' as const, type: 'boolean' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
description: 'Whether this Clip is public.',
}, },
}, },
}; };

View File

@ -59,6 +59,7 @@ export class DriveFileRepository extends Repository<DriveFile> {
const { sum } = await this const { sum } = await this
.createQueryBuilder('file') .createQueryBuilder('file')
.where('file.userId = :id', { id: id }) .where('file.userId = :id', { id: id })
.andWhere('file.isLink = FALSE')
.select('SUM(file.size)', 'sum') .select('SUM(file.size)', 'sum')
.getRawOne(); .getRawOne();
@ -69,6 +70,7 @@ export class DriveFileRepository extends Repository<DriveFile> {
const { sum } = await this const { sum } = await this
.createQueryBuilder('file') .createQueryBuilder('file')
.where('file.userHost = :host', { host: toPuny(host) }) .where('file.userHost = :host', { host: toPuny(host) })
.andWhere('file.isLink = FALSE')
.select('SUM(file.size)', 'sum') .select('SUM(file.size)', 'sum')
.getRawOne(); .getRawOne();
@ -79,6 +81,7 @@ export class DriveFileRepository extends Repository<DriveFile> {
const { sum } = await this const { sum } = await this
.createQueryBuilder('file') .createQueryBuilder('file')
.where('file.userHost IS NULL') .where('file.userHost IS NULL')
.andWhere('file.isLink = FALSE')
.select('SUM(file.size)', 'sum') .select('SUM(file.size)', 'sum')
.getRawOne(); .getRawOne();
@ -89,6 +92,7 @@ export class DriveFileRepository extends Repository<DriveFile> {
const { sum } = await this const { sum } = await this
.createQueryBuilder('file') .createQueryBuilder('file')
.where('file.userHost IS NOT NULL') .where('file.userHost IS NOT NULL')
.andWhere('file.isLink = FALSE')
.select('SUM(file.size)', 'sum') .select('SUM(file.size)', 'sum')
.getRawOne(); .getRawOne();
@ -150,44 +154,37 @@ export const packedDriveFileSchema = {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
format: 'id', format: 'id',
description: 'The unique identifier for this Drive file.',
example: 'xxxxxxxxxx', example: 'xxxxxxxxxx',
}, },
createdAt: { createdAt: {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
format: 'date-time', format: 'date-time',
description: 'The date that the Drive file was created on Misskey.'
}, },
name: { name: {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
description: 'The file name with extension.',
example: 'lenna.jpg' example: 'lenna.jpg'
}, },
type: { type: {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
description: 'The MIME type of this Drive file.',
example: 'image/jpeg' example: 'image/jpeg'
}, },
md5: { md5: {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
format: 'md5', format: 'md5',
description: 'The MD5 hash of this Drive file.',
example: '15eca7fba0480996e2245f5185bf39f2' example: '15eca7fba0480996e2245f5185bf39f2'
}, },
size: { size: {
type: 'number' as const, type: 'number' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
description: 'The size of this Drive file. (bytes)',
example: 51469 example: 51469
}, },
isSensitive: { isSensitive: {
type: 'boolean' as const, type: 'boolean' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
description: 'Whether this Drive file is sensitive.',
}, },
blurhash: { blurhash: {
type: 'string' as const, type: 'string' as const,
@ -218,13 +215,11 @@ export const packedDriveFileSchema = {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: true as const, optional: false as const, nullable: true as const,
format: 'url', format: 'url',
description: 'The URL of this Drive file.',
}, },
thumbnailUrl: { thumbnailUrl: {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: true as const, optional: false as const, nullable: true as const,
format: 'url', format: 'url',
description: 'The thumbnail URL of this Drive file.',
}, },
comment: { comment: {
type: 'string' as const, type: 'string' as const,
@ -234,26 +229,22 @@ export const packedDriveFileSchema = {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: true as const, optional: false as const, nullable: true as const,
format: 'id', format: 'id',
description: 'The parent folder ID of this Drive file.',
example: 'xxxxxxxxxx', example: 'xxxxxxxxxx',
}, },
folder: { folder: {
type: 'object' as const, type: 'object' as const,
optional: true as const, nullable: true as const, optional: true as const, nullable: true as const,
description: 'The parent folder of this Drive file.',
ref: 'DriveFolder' ref: 'DriveFolder'
}, },
userId: { userId: {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: true as const, optional: false as const, nullable: true as const,
format: 'id', format: 'id',
description: 'Owner ID of this Drive file.',
example: 'xxxxxxxxxx', example: 'xxxxxxxxxx',
}, },
user: { user: {
type: 'object' as const, type: 'object' as const,
optional: true as const, nullable: true as const, optional: true as const, nullable: true as const,
description: 'Owner of this Drive file.',
ref: 'User' ref: 'User'
} }
}, },

View File

@ -59,35 +59,29 @@ export const packedDriveFolderSchema = {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
format: 'id', format: 'id',
description: 'The unique identifier for this Drive folder.',
example: 'xxxxxxxxxx', example: 'xxxxxxxxxx',
}, },
createdAt: { createdAt: {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
format: 'date-time', format: 'date-time',
description: 'The date that the Drive folder was created.'
}, },
name: { name: {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
description: 'The folder name.',
}, },
foldersCount: { foldersCount: {
type: 'number' as const, type: 'number' as const,
optional: true as const, nullable: false as const, optional: true as const, nullable: false as const,
description: 'The count of child folders.',
}, },
filesCount: { filesCount: {
type: 'number' as const, type: 'number' as const,
optional: true as const, nullable: false as const, optional: true as const, nullable: false as const,
description: 'The count of child files.',
}, },
parentId: { parentId: {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: true as const, optional: false as const, nullable: true as const,
format: 'id', format: 'id',
description: 'The parent folder ID of this folder.',
example: 'xxxxxxxxxx', example: 'xxxxxxxxxx',
}, },
parent: { parent: {

View File

@ -95,14 +95,12 @@ export const packedFollowingSchema = {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
format: 'id', format: 'id',
description: 'The unique identifier for this following.',
example: 'xxxxxxxxxx', example: 'xxxxxxxxxx',
}, },
createdAt: { createdAt: {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
format: 'date-time', format: 'date-time',
description: 'The date that the following was created.'
}, },
followeeId: { followeeId: {
type: 'string' as const, type: 'string' as const,
@ -113,7 +111,6 @@ export const packedFollowingSchema = {
type: 'object' as const, type: 'object' as const,
optional: true as const, nullable: false as const, optional: true as const, nullable: false as const,
ref: 'User', ref: 'User',
description: 'The followee.'
}, },
followerId: { followerId: {
type: 'string' as const, type: 'string' as const,
@ -124,7 +121,6 @@ export const packedFollowingSchema = {
type: 'object' as const, type: 'object' as const,
optional: true as const, nullable: false as const, optional: true as const, nullable: false as const,
ref: 'User', ref: 'User',
description: 'The follower.'
}, },
} }
}; };

View File

@ -34,38 +34,31 @@ export const packedHashtagSchema = {
tag: { tag: {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
description: 'The hashtag name. No # prefixed.',
example: 'misskey', example: 'misskey',
}, },
mentionedUsersCount: { mentionedUsersCount: {
type: 'number' as const, type: 'number' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
description: 'Number of all users using this hashtag.'
}, },
mentionedLocalUsersCount: { mentionedLocalUsersCount: {
type: 'number' as const, type: 'number' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
description: 'Number of local users using this hashtag.'
}, },
mentionedRemoteUsersCount: { mentionedRemoteUsersCount: {
type: 'number' as const, type: 'number' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
description: 'Number of remote users using this hashtag.'
}, },
attachedUsersCount: { attachedUsersCount: {
type: 'number' as const, type: 'number' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
description: 'Number of all users who attached this hashtag to profile.'
}, },
attachedLocalUsersCount: { attachedLocalUsersCount: {
type: 'number' as const, type: 'number' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
description: 'Number of local users who attached this hashtag to profile.'
}, },
attachedRemoteUsersCount: { attachedRemoteUsersCount: {
type: 'number' as const, type: 'number' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
description: 'Number of remote users who attached this hashtag to profile.'
}, },
} }
}; };

View File

@ -53,14 +53,12 @@ export const packedMessagingMessageSchema = {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
format: 'id', format: 'id',
description: 'The unique identifier for this MessagingMessage.',
example: 'xxxxxxxxxx', example: 'xxxxxxxxxx',
}, },
createdAt: { createdAt: {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
format: 'date-time', format: 'date-time',
description: 'The date that the MessagingMessage was created.'
}, },
userId: { userId: {
type: 'string' as const, type: 'string' as const,

View File

@ -41,14 +41,12 @@ export const packedMutingSchema = {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
format: 'id', format: 'id',
description: 'The unique identifier for this muting.',
example: 'xxxxxxxxxx', example: 'xxxxxxxxxx',
}, },
createdAt: { createdAt: {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
format: 'date-time', format: 'date-time',
description: 'The date that the muting was created.'
}, },
muteeId: { muteeId: {
type: 'string' as const, type: 'string' as const,
@ -59,7 +57,6 @@ export const packedMutingSchema = {
type: 'object' as const, type: 'object' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
ref: 'User', ref: 'User',
description: 'The mutee.'
}, },
} }
}; };

View File

@ -35,14 +35,12 @@ export const packedNoteFavoriteSchema = {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
format: 'id', format: 'id',
description: 'The unique identifier for this favorite.',
example: 'xxxxxxxxxx', example: 'xxxxxxxxxx',
}, },
createdAt: { createdAt: {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
format: 'date-time', format: 'date-time',
description: 'The date that the favorite was created.'
}, },
note: { note: {
type: 'object' as const, type: 'object' as const,

View File

@ -32,25 +32,21 @@ export const packedNoteReactionSchema = {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
format: 'id', format: 'id',
description: 'The unique identifier for this reaction.',
example: 'xxxxxxxxxx', example: 'xxxxxxxxxx',
}, },
createdAt: { createdAt: {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
format: 'date-time', format: 'date-time',
description: 'The date that the reaction was created.'
}, },
user: { user: {
type: 'object' as const, type: 'object' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
ref: 'User', ref: 'User',
description: 'User who performed this reaction.'
}, },
type: { type: {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
description: 'The reaction type.'
}, },
}, },
}; };

View File

@ -281,14 +281,12 @@ export const packedNoteSchema = {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
format: 'id', format: 'id',
description: 'The unique identifier for this Note.',
example: 'xxxxxxxxxx', example: 'xxxxxxxxxx',
}, },
createdAt: { createdAt: {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
format: 'date-time', format: 'date-time',
description: 'The date that the Note was created on Misskey.'
}, },
text: { text: {
type: 'string' as const, type: 'string' as const,
@ -426,7 +424,6 @@ export const packedNoteSchema = {
reactions: { reactions: {
type: 'object' as const, type: 'object' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
description: 'Key is either Unicode emoji or custom emoji, value is count of that emoji reaction.',
}, },
renoteCount: { renoteCount: {
type: 'number' as const, type: 'number' as const,
@ -439,18 +436,15 @@ export const packedNoteSchema = {
uri: { uri: {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: true as const, optional: false as const, nullable: true as const,
description: 'The URI of a note. it will be null when the note is local.',
}, },
url: { url: {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: true as const, optional: false as const, nullable: true as const,
description: 'The human readable url of a note. it will be null when the note is local.',
}, },
myReaction: { myReaction: {
type: 'object' as const, type: 'object' as const,
optional: true as const, nullable: true as const, optional: true as const, nullable: true as const,
description: 'Key is either Unicode emoji or custom emoji, value is count of that emoji reaction.',
}, },
}, },
}; };

View File

@ -117,20 +117,17 @@ export const packedNotificationSchema = {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
format: 'id', format: 'id',
description: 'The unique identifier for this notification.',
example: 'xxxxxxxxxx', example: 'xxxxxxxxxx',
}, },
createdAt: { createdAt: {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
format: 'date-time', format: 'date-time',
description: 'The date that the notification was created.'
}, },
type: { type: {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
enum: ['follow', 'followRequestAccepted', 'receiveFollowRequest', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote'], enum: ['follow', 'followRequestAccepted', 'receiveFollowRequest', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote'],
description: 'The type of the notification.'
}, },
userId: { userId: {
type: 'string' as const, type: 'string' as const,

View File

@ -34,19 +34,16 @@ export const packedUserGroupSchema = {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
format: 'id', format: 'id',
description: 'The unique identifier for this UserGroup.',
example: 'xxxxxxxxxx', example: 'xxxxxxxxxx',
}, },
createdAt: { createdAt: {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
format: 'date-time', format: 'date-time',
description: 'The date that the UserGroup was created.'
}, },
name: { name: {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
description: 'The name of the UserGroup.'
}, },
ownerId: { ownerId: {
type: 'string' as const, type: 'string' as const,

View File

@ -33,19 +33,16 @@ export const packedUserListSchema = {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
format: 'id', format: 'id',
description: 'The unique identifier for this UserList.',
example: 'xxxxxxxxxx', example: 'xxxxxxxxxx',
}, },
createdAt: { createdAt: {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
format: 'date-time', format: 'date-time',
description: 'The date that the UserList was created.'
}, },
name: { name: {
type: 'string' as const, type: 'string' as const,
optional: false as const, nullable: false as const, optional: false as const, nullable: false as const,
description: 'The name of the UserList.'
}, },
userIds: { userIds: {
type: 'array' as const, type: 'array' as const,

View File

@ -342,19 +342,16 @@ export const packedUserSchema = {
type: 'string' as const, type: 'string' as const,
nullable: false as const, optional: false as const, nullable: false as const, optional: false as const,
format: 'id', format: 'id',
description: 'The unique identifier for this User.',
example: 'xxxxxxxxxx', example: 'xxxxxxxxxx',
}, },
name: { name: {
type: 'string' as const, type: 'string' as const,
nullable: true as const, optional: false as const, nullable: true as const, optional: false as const,
description: 'The name of the user, as theyve defined it.',
example: '藍' example: '藍'
}, },
username: { username: {
type: 'string' as const, type: 'string' as const,
nullable: false as const, optional: false as const, nullable: false as const, optional: false as const,
description: 'The screen name, handle, or alias that this user identifies themselves with.',
example: 'ai' example: 'ai'
}, },
host: { host: {
@ -379,24 +376,20 @@ export const packedUserSchema = {
isAdmin: { isAdmin: {
type: 'boolean' as const, type: 'boolean' as const,
nullable: false as const, optional: false as const, nullable: false as const, optional: false as const,
description: 'Whether this account is the admin.',
default: false default: false
}, },
isModerator: { isModerator: {
type: 'boolean' as const, type: 'boolean' as const,
nullable: false as const, optional: false as const, nullable: false as const, optional: false as const,
description: 'Whether this account is a moderator.',
default: false default: false
}, },
isBot: { isBot: {
type: 'boolean' as const, type: 'boolean' as const,
nullable: false as const, optional: true as const, nullable: false as const, optional: true as const,
description: 'Whether this account is a bot.'
}, },
isCat: { isCat: {
type: 'boolean' as const, type: 'boolean' as const,
nullable: false as const, optional: true as const, nullable: false as const, optional: true as const,
description: 'Whether this account is a cat.'
}, },
emojis: { emojis: {
type: 'array' as const, type: 'array' as const,
@ -438,7 +431,6 @@ export const packedUserSchema = {
type: 'string' as const, type: 'string' as const,
nullable: false as const, optional: true as const, nullable: false as const, optional: true as const,
format: 'date-time', format: 'date-time',
description: 'The date that the user account was created on Misskey.'
}, },
updatedAt: { updatedAt: {
type: 'string' as const, type: 'string' as const,
@ -471,7 +463,6 @@ export const packedUserSchema = {
description: { description: {
type: 'string' as const, type: 'string' as const,
nullable: true as const, optional: true as const, nullable: true as const, optional: true as const,
description: 'The user-defined UTF-8 string describing their account.',
example: 'Hi masters, I am Ai!' example: 'Hi masters, I am Ai!'
}, },
location: { location: {
@ -505,17 +496,14 @@ export const packedUserSchema = {
followersCount: { followersCount: {
type: 'number' as const, type: 'number' as const,
nullable: false as const, optional: true as const, nullable: false as const, optional: true as const,
description: 'The number of followers this account currently has.'
}, },
followingCount: { followingCount: {
type: 'number' as const, type: 'number' as const,
nullable: false as const, optional: true as const, nullable: false as const, optional: true as const,
description: 'The number of users this account is following.'
}, },
notesCount: { notesCount: {
type: 'number' as const, type: 'number' as const,
nullable: false as const, optional: true as const, nullable: false as const, optional: true as const,
description: 'The number of Notes (including renotes) issued by the user.'
}, },
pinnedNoteIds: { pinnedNoteIds: {
type: 'array' as const, type: 'array' as const,

View File

@ -1,12 +1,12 @@
import Resolver from '../../resolver'; import Resolver from '../../resolver';
import { IRemoteUser } from '../../../../models/entities/user'; import { IRemoteUser } from '../../../../models/entities/user';
import acceptFollow from './follow'; import acceptFollow from './follow';
import { IAccept, IFollow } from '../../type'; import { IAccept, isFollow, getApType } from '../../type';
import { apLogger } from '../../logger'; import { apLogger } from '../../logger';
const logger = apLogger; const logger = apLogger;
export default async (actor: IRemoteUser, activity: IAccept): Promise<void> => { export default async (actor: IRemoteUser, activity: IAccept): Promise<string> => {
const uri = activity.id || activity; const uri = activity.id || activity;
logger.info(`Accept: ${uri}`); logger.info(`Accept: ${uri}`);
@ -18,13 +18,7 @@ export default async (actor: IRemoteUser, activity: IAccept): Promise<void> => {
throw e; throw e;
}); });
switch (object.type) { if (isFollow(object)) return await acceptFollow(actor, object);
case 'Follow':
acceptFollow(actor, object as IFollow);
break;
default: return `skip: Unknown Accept type: ${getApType(object)}`;
logger.warn(`Unknown accept type: ${object.type}`);
break;
}
}; };

View File

@ -1,7 +1,7 @@
import Resolver from '../../resolver'; import Resolver from '../../resolver';
import { IRemoteUser } from '../../../../models/entities/user'; import { IRemoteUser } from '../../../../models/entities/user';
import createNote from './note'; import createNote from './note';
import { ICreate, getApId, validPost } from '../../type'; import { ICreate, getApId, isPost, getApType } from '../../type';
import { apLogger } from '../../logger'; import { apLogger } from '../../logger';
import { toArray, concat, unique } from '../../../../prelude/array'; import { toArray, concat, unique } from '../../../../prelude/array';
@ -35,9 +35,9 @@ export default async (actor: IRemoteUser, activity: ICreate): Promise<void> => {
throw e; throw e;
}); });
if (validPost.includes(object.type)) { if (isPost(object)) {
createNote(resolver, actor, object, false, activity); createNote(resolver, actor, object, false, activity);
} else { } else {
logger.warn(`Unknown type: ${object.type}`); logger.warn(`Unknown type: ${getApType(object)}`);
} }
}; };

View File

@ -1,12 +1,12 @@
import Resolver from '../../resolver'; import Resolver from '../../resolver';
import { IRemoteUser } from '../../../../models/entities/user'; import { IRemoteUser } from '../../../../models/entities/user';
import rejectFollow from './follow'; import rejectFollow from './follow';
import { IReject, IFollow } from '../../type'; import { IReject, isFollow, getApType } from '../../type';
import { apLogger } from '../../logger'; import { apLogger } from '../../logger';
const logger = apLogger; const logger = apLogger;
export default async (actor: IRemoteUser, activity: IReject): Promise<void> => { export default async (actor: IRemoteUser, activity: IReject): Promise<string> => {
const uri = activity.id || activity; const uri = activity.id || activity;
logger.info(`Reject: ${uri}`); logger.info(`Reject: ${uri}`);
@ -18,13 +18,7 @@ export default async (actor: IRemoteUser, activity: IReject): Promise<void> => {
throw e; throw e;
}); });
switch (object.type) { if (isFollow(object)) return await rejectFollow(actor, object);
case 'Follow':
rejectFollow(actor, object as IFollow);
break;
default: return `skip: Unknown Reject type: ${getApType(object)}`;
logger.warn(`Unknown reject type: ${object.type}`);
break;
}
}; };

View File

@ -3,14 +3,15 @@ import { IRemoteUser } from '../../../../models/entities/user';
import { IAnnounce, getApId } from '../../type'; import { IAnnounce, getApId } from '../../type';
import deleteNote from '../../../../services/note/delete'; import deleteNote from '../../../../services/note/delete';
export const undoAnnounce = async (actor: IRemoteUser, activity: IAnnounce): Promise<void> => { export const undoAnnounce = async (actor: IRemoteUser, activity: IAnnounce): Promise<string> => {
const uri = getApId(activity); const uri = getApId(activity);
const note = await Notes.findOne({ const note = await Notes.findOne({
uri uri
}); });
if (!note) return; if (!note) return 'skip: no such Announce';
await deleteNote(actor, note); await deleteNote(actor, note);
return 'ok: deleted';
}; };

View File

@ -1,5 +1,5 @@
import { IRemoteUser } from '../../../../models/entities/user'; import { IRemoteUser } from '../../../../models/entities/user';
import { IUndo, IFollow, IBlock, ILike, IAnnounce } from '../../type'; import { IUndo, isFollow, isBlock, isLike, isAnnounce, getApType } from '../../type';
import unfollow from './follow'; import unfollow from './follow';
import unblock from './block'; import unblock from './block';
import undoLike from './like'; import undoLike from './like';
@ -9,7 +9,7 @@ import { apLogger } from '../../logger';
const logger = apLogger; const logger = apLogger;
export default async (actor: IRemoteUser, activity: IUndo): Promise<void> => { export default async (actor: IRemoteUser, activity: IUndo): Promise<string> => {
if ('actor' in activity && actor.uri !== activity.actor) { if ('actor' in activity && actor.uri !== activity.actor) {
throw new Error('invalid actor'); throw new Error('invalid actor');
} }
@ -25,20 +25,10 @@ export default async (actor: IRemoteUser, activity: IUndo): Promise<void> => {
throw e; throw e;
}); });
switch (object.type) { if (isFollow(object)) return await unfollow(actor, object);
case 'Follow': if (isBlock(object)) return await unblock(actor, object);
unfollow(actor, object as IFollow); if (isLike(object)) return await undoLike(actor, object);
break; if (isAnnounce(object)) return await undoAnnounce(actor, object);
case 'Block':
unblock(actor, object as IBlock); return `skip: unknown object type ${getApType(object)}`;
break;
case 'Like':
case 'EmojiReaction':
case 'EmojiReact':
undoLike(actor, object as ILike);
break;
case 'Announce':
undoAnnounce(actor, object as IAnnounce);
break;
}
}; };

View File

@ -1,5 +1,5 @@
import { IRemoteUser } from '../../../../models/entities/user'; import { IRemoteUser } from '../../../../models/entities/user';
import { IUpdate, validActor } from '../../type'; import { getApType, IUpdate, isActor } from '../../type';
import { apLogger } from '../../logger'; import { apLogger } from '../../logger';
import { updateQuestion } from '../../models/question'; import { updateQuestion } from '../../models/question';
import Resolver from '../../resolver'; import Resolver from '../../resolver';
@ -22,13 +22,13 @@ export default async (actor: IRemoteUser, activity: IUpdate): Promise<string> =>
throw e; throw e;
}); });
if (validActor.includes(object.type)) { if (isActor(object)) {
await updatePerson(actor.uri!, resolver, object); await updatePerson(actor.uri!, resolver, object);
return `ok: Person updated`; return `ok: Person updated`;
} else if (object.type === 'Question') { } else if (getApType(object) === 'Question') {
await updateQuestion(object).catch(e => console.log(e)); await updateQuestion(object).catch(e => console.log(e));
return `ok: Question updated`; return `ok: Question updated`;
} else { } else {
return `skip: Unknown type: ${object.type}`; return `skip: Unknown type: ${getApType(object)}`;
} }
}; };

View File

@ -28,7 +28,7 @@ export async function createImage(actor: IRemoteUser, value: any): Promise<Drive
const instance = await fetchMeta(); const instance = await fetchMeta();
const cache = instance.cacheRemoteFiles; const cache = instance.cacheRemoteFiles;
let file = await uploadFromUrl(image.url, actor, null, image.url, image.sensitive, false, !cache); let file = await uploadFromUrl(image.url, actor, null, image.url, image.sensitive, false, !cache, image.name);
if (file.isLink) { if (file.isLink) {
// URLが異なっている場合、同じ画像が以前に異なるURLで登録されていたということなので、 // URLが異なっている場合、同じ画像が以前に異なるURLで登録されていたということなので、

View File

@ -17,7 +17,7 @@ import { deliverQuestionUpdate } from '../../../services/note/polls/update';
import { extractDbHost, toPuny } from '@/misc/convert-host'; import { extractDbHost, toPuny } from '@/misc/convert-host';
import { Emojis, Polls, MessagingMessages } from '../../../models'; import { Emojis, Polls, MessagingMessages } from '../../../models';
import { Note } from '../../../models/entities/note'; import { Note } from '../../../models/entities/note';
import { IObject, getOneApId, getApId, getOneApHrefNullable, validPost, IPost, isEmoji } from '../type'; import { IObject, getOneApId, getApId, getOneApHrefNullable, validPost, IPost, isEmoji, getApType } from '../type';
import { Emoji } from '../../../models/entities/emoji'; import { Emoji } from '../../../models/entities/emoji';
import { genId } from '@/misc/gen-id'; import { genId } from '@/misc/gen-id';
import { fetchMeta } from '@/misc/fetch-meta'; import { fetchMeta } from '@/misc/fetch-meta';
@ -36,8 +36,8 @@ export function validateNote(object: any, uri: string) {
return new Error('invalid Note: object is null'); return new Error('invalid Note: object is null');
} }
if (!validPost.includes(object.type)) { if (!validPost.includes(getApType(object))) {
return new Error(`invalid Note: invalid object type ${object.type}`); return new Error(`invalid Note: invalid object type ${getApType(object)}`);
} }
if (object.id && extractDbHost(object.id) !== expectHost) { if (object.id && extractDbHost(object.id) !== expectHost) {

View File

@ -4,7 +4,7 @@ import * as promiseLimit from 'promise-limit';
import config from '@/config'; import config from '@/config';
import Resolver from '../resolver'; import Resolver from '../resolver';
import { resolveImage } from './image'; import { resolveImage } from './image';
import { isCollectionOrOrderedCollection, isCollection, IPerson, getApId, getOneApHrefNullable, IObject, isPropertyValue, IApPropertyValue } from '../type'; import { isCollectionOrOrderedCollection, isCollection, IPerson, getApId, getOneApHrefNullable, IObject, isPropertyValue, IApPropertyValue, getApType } from '../type';
import { fromHtml } from '../../../mfm/from-html'; import { fromHtml } from '../../../mfm/from-html';
import { htmlToMfm } from '../misc/html-to-mfm'; import { htmlToMfm } from '../misc/html-to-mfm';
import { resolveNote, extractEmojis } from './note'; import { resolveNote, extractEmojis } from './note';
@ -137,7 +137,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
const tags = extractApHashtags(person.tag).map(tag => normalizeForSearch(tag)).splice(0, 32); const tags = extractApHashtags(person.tag).map(tag => normalizeForSearch(tag)).splice(0, 32);
const isBot = object.type === 'Service'; const isBot = getApType(object) === 'Service';
const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/); const bday = person['vcard:bday']?.match(/^\d{4}-\d{2}-\d{2}/);
@ -337,7 +337,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
emojis: emojiNames, emojis: emojiNames,
name: person.name, name: person.name,
tags, tags,
isBot: object.type === 'Service', isBot: getApType(object) === 'Service',
isCat: (person as any).isCat === true, isCat: (person as any).isCat === true,
isLocked: !!person.manuallyApprovesFollowers, isLocked: !!person.manuallyApprovesFollowers,
isExplorable: !!person.discoverable, isExplorable: !!person.discoverable,
@ -476,7 +476,7 @@ export async function updateFeatured(userId: User['id']) {
// Resolve and regist Notes // Resolve and regist Notes
const limit = promiseLimit<Note | null>(2); const limit = promiseLimit<Note | null>(2);
const featuredNotes = await Promise.all(items const featuredNotes = await Promise.all(items
.filter(item => item.type === 'Note') .filter(item => getApType(item) === 'Note') // TODO: Noteでなくてもいいかも
.slice(0, 5) .slice(0, 5)
.map(item => limit(() => resolveNote(item, resolver)))); .map(item => limit(() => resolveNote(item, resolver))));

View File

@ -4,5 +4,6 @@ import { DriveFiles } from '../../../models';
export default (file: DriveFile) => ({ export default (file: DriveFile) => ({
type: 'Document', type: 'Document',
mediaType: file.type, mediaType: file.type,
url: DriveFiles.getPublicUrl(file) url: DriveFiles.getPublicUrl(file),
name: file.comment,
}); });

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