Compare commits

...

40 Commits

Author SHA1 Message Date
723d3e6871 Merge branch 'develop' 2019-04-24 15:23:48 +09:00
f063dee12a 11.3.0 2019-04-24 15:23:31 +09:00
db4b315d3d Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-04-24 15:21:58 +09:00
64d00b08a3 Improve announcement
* お知らせにMFMを使えるように
* お知らせに画像を添付できるように
2019-04-24 15:21:39 +09:00
74736a941e Update README.md [AUTOGEN] (#4778) 2019-04-24 14:56:23 +09:00
c40366f20a Update README.md [AUTOGEN] (#4777) 2019-04-24 14:55:49 +09:00
d73e52ac2a Create find-by-hash.ts 2019-04-24 14:54:19 +09:00
9fac8a611f Fix response 2019-04-24 14:54:15 +09:00
9c774a50f8 Update attached-notes.ts 2019-04-24 14:54:06 +09:00
4f20ee1909 Rename theme 2019-04-24 14:44:28 +09:00
1440c5bc65 Fix bug 2019-04-24 14:39:59 +09:00
5829b2a7c1 Update CHANGELOG.md 2019-04-24 10:07:27 +09:00
e3c4bc18d0 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-04-24 10:02:49 +09:00
8555ec2f72 Fix #4767 2019-04-24 10:02:31 +09:00
8d7cefbbbe New Crowdin translations (#4771)
* New translations ja-JP.yml (French)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Chinese Simplified)
2019-04-24 09:54:43 +09:00
5aa5896b22 Cache meta 2019-04-24 08:11:19 +09:00
8a55bdd89d Increase reaction column length 2019-04-24 03:39:11 +09:00
6444ef6444 Merge branch 'develop' of https://github.com/syuilo/misskey into develop 2019-04-24 03:26:10 +09:00
701ab6cc68 Refactor 2019-04-24 03:26:02 +09:00
3c6ce7a943 Update README.md [AUTOGEN] (#4773) 2019-04-24 00:34:48 +09:00
1e54afd217 Update README.md [AUTOGEN] (#4772) 2019-04-24 00:33:55 +09:00
4e90fd3a06 メンテナ名を必須でなくす
Close #4753
2019-04-23 22:48:53 +09:00
0463c6bb0f Refactor API (#4770)
* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* wip

* Update description.ts

* wip
2019-04-23 22:35:26 +09:00
f31f986d66 Fix #4768 2019-04-23 02:50:59 +09:00
c203c8302b Add @rinsuki into collaborators (#4762) 2019-04-22 18:19:20 +09:00
dea4a7b389 Merge branch 'develop' 2019-04-22 00:50:14 +09:00
0caf9b0706 11.2.2 2019-04-22 00:49:36 +09:00
5f655c9fd7 Update dependencies 🚀 2019-04-22 00:43:15 +09:00
44aed53998 New Crowdin translations (#4736)
* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Polish)

* New translations ja-JP.yml (French)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Czech)

* New translations ja-JP.yml (Dutch)

* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (Norwegian)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (Japanese, Kansai)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (Chinese Simplified)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Czech)
2019-04-22 00:39:00 +09:00
f9ff621d4a 2段階認証を有効にするとログインできない問題を修正 2019-04-22 00:38:37 +09:00
23a5a75778 Docker: Remove quotes in docker_example.env (#4765)
Docker does not parse quotes in env_file.
As follows if enclosed in quotation marks.

docker_example.env:
POSTGRES_PASSWORD="password"

Environment value in Misskey container:
$ echo $POSTGRES_PASSWORD
"password"

Some users may see this behavior as unexpected.

Dockerはenv_fileのクォーテーションマークを解析しません。
もし値がクォーテーションマークで囲まれていた場合は以下のような動作になります。

docker_example.env:
POSTGRES_PASSWORD="password"

Misskeyのコンテナ内の環境変数:
$ echo $POSTGRES_PASSWORD
"password"

この動作を予期しない動作のように思うユーザーがいるかもしれません。
2019-04-21 23:58:15 +09:00
a78475844a Fix: リモートユーザーの修復処理が自動的に実行されない など for v11 (#4764)
* Fix #4761

* Fix: updatePersonができない
2019-04-21 23:57:44 +09:00
575cf2935e Merge branch 'develop' 2019-04-21 06:52:28 +09:00
ae43aa1bc0 11.2.1 2019-04-21 06:52:01 +09:00
0ba71c6a47 Fix #4752 (#4759) 2019-04-21 02:30:18 +09:00
643f7d1803 Update CHANGELOG.md 2019-04-20 05:19:28 +09:00
5d06da4db2 Update CHANGELOG.md 2019-04-20 05:13:38 +09:00
2934b09361 Fix bug 2019-04-19 21:37:36 +09:00
97341d3fa0 Update README.md [AUTOGEN] (#4748) 2019-04-19 15:37:47 +09:00
a95546ede0 Fix bug 2019-04-19 01:00:17 +09:00
159 changed files with 2026 additions and 1005 deletions

View File

@ -1,5 +1,4 @@
# db settings
POSTGRES_PASSWORD="example-misskey-pass"
POSTGRES_USER="example-misskey-user"
POSTGRES_DB="misskey"
POSTGRES_PASSWORD=example-misskey-pass
POSTGRES_USER=example-misskey-user
POSTGRES_DB=misskey

4
.github/CODEOWNERS vendored
View File

@ -1,7 +1,7 @@
# PATH OWNERS
/.autogen/ @acid-chicken
/.circleci/ @syuilo @acid-chicken
/.config/ @syuilo @AyaMorisawa @mei23 @acid-chicken
/.config/ @syuilo @AyaMorisawa @mei23 @acid-chicken @rinsuki
# /.config/mongo_initdb_example.js @khws4v1
/.github/ @syuilo @AyaMorisawa @acid-chicken
/.vscode/ @acid-chicken
@ -12,7 +12,7 @@
# /docs/*.fr.md @BoFFire
# /docs/docker.*.md @khws4v1
/locales/ @syuilo
/src/ @syuilo @AyaMorisawa @mei23 @acid-chicken
/src/ @syuilo @AyaMorisawa @mei23 @acid-chicken @rinsuki
# /src/crypto_key.cc @akihikodaki
# /src/crypto_key.d.ts @akihikodaki
/.dockerignore @syuilo # @khws4v1

View File

@ -5,6 +5,60 @@ If you encounter any problems with updating, please try the following:
1. `npm run clean` or `npm run cleanall`
2. Retry update (Don't forget `npm i`)
How to migrate to v11 from v10
------------------------------
1. v11をインストールしたい場所に syuilo/misskey をクローン
2. config を設定する
* PostgreSQL(`db`)の設定とは別に、v10からMongoDBの設定をコピペしてくる(例は下にあります)
* `id`の設定を`meid`または`objectid`にする
``` yml
db:
host: localhost
port: 5432
db: misskey
user: x
pass: x
mongodb:
user: x
pass: x
host: localhost
port: 27017
db: misskey
```
3. migration ブランチに切り替え
4. `npm i`
5. `npm run build`
6. `npm run init`
7. `npm run migrate`
8. master ブランチに戻す
9. enjoy
11.3.0 (2019/04/24)
-------------------
### Improvements
* お知らせにMFMを使えるように
* お知らせに画像を添付できるように
### Fixes
* 投稿のタグ検索APIで大文字小文字が区別されていたのを修正
* 公開範囲がホームの投稿がグローバルTLに流れる問題を修正
* モバイルビューの投稿詳細にて acct が長いとアイコンが圧迫面接される問題を修正
11.2.2 (2019/04/22)
-------------------
### Fixes
* 2段階認証を有効にするとログインできない問題を修正
* リモートユーザーの修復処理が自動的に実行されない問題を修正
* リモートユーザー情報が更新されない問題を修正
11.2.1 (2019/04/21)
-------------------
### Fixes
* MEIDが25桁になっているのを修正
* リモートユーザー情報が更新されない問題を修正
11.2.0 (2019/04/18)
-------------------
### Improvements

View File

@ -88,12 +88,14 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><img src="https://avatars0.githubusercontent.com/u/10798641?s=460&v=4" alt="AyaMorisawa" width="100"></td>
<td><img src="https://avatars1.githubusercontent.com/u/30769358?s=460&v=4" alt="mei23" width="100"></td>
<td><img src="https://avatars2.githubusercontent.com/u/20679825?s=460&v=4" alt="acid-chicken" width="100"></td>
<td><img src="https://avatars2.githubusercontent.com/u/6533808?s=460&v=4" alt="rinsuki" width="100"></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/syuilo">@syuilo</a></td>
<td align="center"><a href="https://github.com/AyaMorisawa">@AyaMorisawa</a></td>
<td align="center"><a href="https://github.com/mei23">@mei23</a></td>
<td align="center"><a href="https://github.com/acid-chicken">@acid-chicken</a></td>
<td align="center"><a href="https://github.com/rinsuki">@rinsuki</a></td>
</tr>
</table>
@ -102,14 +104,14 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<!-- PATREON_START -->
<table><tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5888816/36da0f7c15954df0ab13f9abdf227f66/1.jpeg?token-time=2145916800&token-hash=at8QpJXJ8C0zINY_NmoMKv-MhXVoUK-YzTgaJPJzJYU%3D" alt="Hiroshi Seki" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12190916/fb7fa7983c14425f890369535b1506a4/3.png?token-time=2145916800&token-hash=oH_i7gJjNT7Ot6j9JiVwy7ZJIBqACVnzLqlz4YrDAZA%3D" alt="weep" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12190916/fb7fa7983c14425f890369535b1506a4/3.png?token-time=2145916800&token-hash=oH_i7gJjNT7Ot6j9JiVwy7ZJIBqACVnzLqlz4YrDAZA%3D" alt="weepjp" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13099460/43cecdbaa63a40d79bf50a96b9910b9d/1.jpe?token-time=2145916800&token-hash=bqwLTk0Wo0hUJJ8J5y7ii05bLzz-_CDA7Bo0Mp4RFU0%3D" alt="ne_moni" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/4.jpe?token-time=2145916800&token-hash=zEyJqVM7u9d8Ri-65fJYSJcWF1jBH1nJ5a3taRzrTmw%3D" alt="Melilot" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5670915/ee175f0bfb6347ffa4ea101a8c097bff/1.jpg?token-time=2145916800&token-hash=mPLM9CA-riFHx-myr3bLZJuH2xBRHA9se5VbHhLIOuA%3D" alt="osapon" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/16869916" alt="見当かなみ" width="100"></td>
</tr><tr>
<td><a href="https://www.patreon.com/rane_hs">Hiroshi Seki</a></td>
<td><a href="https://www.patreon.com/weepjp">weep</a></td>
<td><a href="https://www.patreon.com/weepjp">weepjp</a></td>
<td><a href="https://www.patreon.com/user?u=13099460">ne_moni</a></td>
<td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td>
<td><a href="https://www.patreon.com/osapon">osapon</a></td>
@ -124,8 +126,8 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><img src="https://c8.patreon.com/2/200/17866454" alt="sikyosyounin" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/3.png?token-time=2145916800&token-hash=KjfQL8nf3AIf6WqzLshBYAyX44piAqOAZiYXgZS_H6A%3D" alt="YUKIMOCHI" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/17463605" alt="Sampot" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19356899/496b4681d33b4520bd7688e0fd19c04d/2.jpeg?token-time=2145916800&token-hash=_sTj3dUBOhn9qwiJ7F19Qd-yWWfUqJC_0jG1h0agEqQ%3D" alt="sheeta.s" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13737140/1adf7835017d479280d90fe8d30aade2/1.png?token-time=2145916800&token-hash=0pdle8h5pDZrww0BDOjdz6zO-HudeGTh36a3qi1biVU%3D" alt="Satsuki Yanagi" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17880724/311738c8a48f4a6b9443c2445a75adde/1.jpe?token-time=2145916800&token-hash=CPxGQhKIlEaa6WUcgbyHixyKEhakiw9RFdOhsIJBQ_o%3D" alt="takimura" width="100"></td>
</tr><tr>
<td><a href="https://www.patreon.com/gutfuckllc">gutfuckllc</a></td>
<td><a href="https://www.patreon.com/mydarkstar">mydarkstar</a></td>
@ -135,10 +137,11 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><a href="https://www.patreon.com/user?u=17866454">sikyosyounin</a></td>
<td><a href="https://www.patreon.com/yukimochi">YUKIMOCHI</a></td>
<td><a href="https://www.patreon.com/user?u=17463605">Sampot</a></td>
<td><a href="https://www.patreon.com/user?u=19356899">sheeta.s</a></td>
<td><a href="https://www.patreon.com/user?u=13737140">Satsuki Yanagi</a></td>
<td><a href="https://www.patreon.com/takimura">takimura</a></td>
</tr></table>
<table><tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17880724/311738c8a48f4a6b9443c2445a75adde/1.jpe?token-time=2145916800&token-hash=CPxGQhKIlEaa6WUcgbyHixyKEhakiw9RFdOhsIJBQ_o%3D" alt="takimura" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17195955/be45e5e14c3e48b2bee0456c84e19df4/4.jpe?token-time=2145916800&token-hash=UslrPVM-8TXOe8AapuNiaFYjcIJgPNcU-fKpGbfGJNI%3D" alt="Damillora" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/16900731/83884b38afc74d4cbe83c30a13b10edd/1.png?token-time=2145916800&token-hash=R5Tog8RWg0rguRoCIoir3lThokrdPvs8Utfikhc0nhY%3D" alt="Atsuko Tominaga" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/3.png?token-time=2145916800&token-hash=FTm3WVom4dJ9NwWMU4OpCL_8Yc13WiwEbKrDPyTZTPs%3D" alt="natalie" width="100"></td>
@ -147,8 +150,8 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1.jpeg?token-time=2145916800&token-hash=L55UhJ0rcuNAH3w_ryeeGN4hC6taoOixyAhraEi0bzw%3D" alt="dansup" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/619786/32cf01444db24e578cd1982c197f6fc6/1.jpeg?token-time=2145916800&token-hash=d8jBQLMOHD87KtXs5C9fk1o58DMF73pQ-dYH3uZJPBE%3D" alt="Gargron" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5731881/4b6038e6cda34c04b83a5fcce3806a93/1.png?token-time=2145916800&token-hash=hBayGfOmQH3kRMdNnDe4oCZD_9fsJWSt29xXR3KRMVk%3D" alt="Nokotaro Takeda" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1.jpeg?token-time=2145916800&token-hash=vGe7wXGqmA8Q7m-kDNb6fyGdwk-Dxk4F-ut8ZZu51RM%3D" alt="Takashi Shibuya" width="100"></td>
</tr><tr>
<td><a href="https://www.patreon.com/takimura">takimura</a></td>
<td><a href="https://www.patreon.com/damillora">Damillora</a></td>
<td><a href="https://www.patreon.com/user?u=16900731">Atsuko Tominaga</a></td>
<td><a href="https://www.patreon.com/user?u=4389829">natalie</a></td>
@ -157,13 +160,14 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><a href="https://www.patreon.com/dansup">dansup</a></td>
<td><a href="https://www.patreon.com/mastodon">Gargron</a></td>
<td><a href="https://www.patreon.com/takenoko">Nokotaro Takeda</a></td>
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
</tr></table>
<table><tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1.jpeg?token-time=2145916800&token-hash=vGe7wXGqmA8Q7m-kDNb6fyGdwk-Dxk4F-ut8ZZu51RM%3D" alt="Takashi Shibuya" width="100"></td>
</tr><tr>
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
</tr></table>
**Last updated:** Mon, 15 Apr 2019 12:07:08 UTC
**Last updated:** Wed, 24 Apr 2019 05:56:07 UTC
<!-- PATREON_END -->
:four_leaf_clover: Copyright

View File

@ -77,6 +77,9 @@ common:
"read:messaging": "Prohlížet konverzaci"
"write:messaging": "Pracovat s konverzaci"
"read:mutes": "Prohlížet ztlumené"
"read:notifications": "Prohlížet oznámení"
"write:notifications": "Pracovat s oznámeními"
"read:reactions": "Prohlížet reakce"
"write:votes": "Hlasovat"
empty-timeline-info:
follow-users-to-make-your-timeline: "Poznámky sledujících se zobrazí ve vaší časové ose"
@ -285,6 +288,7 @@ common:
nav: "Navigace"
tips: "Tipy"
hashtags: "Hashtagy"
queue: "Ve frontě"
dev: "Nepodařilo se vytvořit aplikace. Prosím zkuste to znovu."
ai-chan-kawaii: "Ai-chan kawaii!"
you: "Vy"
@ -593,6 +597,8 @@ common/views/components/user-list-editor.vue:
remove-user: "Odebrat z tohoto seznamu"
delete-are-you-sure: "Smazat seznam \"$1\"?"
deleted: "Smazáno"
common/views/components/user-lists.vue:
list-name: "Název seznamu"
common/views/widgets/broadcast.vue:
fetching: "Načítám"
next: "Další"
@ -832,8 +838,6 @@ desktop/views/components/received-follow-requests-window.vue:
reject: "Odmítnout"
desktop/views/components/user-lists-window.vue:
title: "Seznamy uživatelů"
create-list: "Vytvořit seznam"
list-name: "Název seznamu"
desktop/views/components/user-preview.vue:
notes: "Příspěvky"
desktop/views/components/users-list.vue:
@ -1078,7 +1082,6 @@ mobile/views/components/drive.vue:
used: "využito"
file-count: "Soubor(ů)"
folder-is-empty: "Tato složka je prázdná"
deletion-alert: "Omlouváme se, ale mazání složek ještě nebylo implementováno."
folder-name: "Název složky"
url-prompt: "URL adresa souboru, který chcete nahrát"
uploading: "Byl zahájen upload. Může chvilku trvat než bude dokončen."
@ -1139,6 +1142,10 @@ mobile/views/components/ui.nav.vue:
game: "Hry"
admin: "Administrace"
about: "O Misskey"
mobile/views/pages/drive.vue:
contextmenu:
upload: "Nahrát soubor"
create-folder: "Vytvořit složku"
mobile/views/pages/user-lists.vue:
title: "Seznamy"
mobile/views/pages/signup.vue:

View File

@ -676,6 +676,10 @@ mobile/views/components/ui.nav.vue:
user-lists: "Listen"
game: "Spielen"
about: "Über"
mobile/views/pages/drive.vue:
contextmenu:
upload: "Eine Datei hochladen"
create-folder: "Ein Verzeichnis erstellen"
mobile/views/pages/user-lists.vue:
title: "Listen"
mobile/views/pages/home.vue:

View File

@ -84,6 +84,11 @@ common:
"write:messaging": "Work with Messaging"
"read:mutes": "View Muted"
"write:mutes": "Work with Muted"
"write:notes": "Create and delete posts"
"read:notifications": "View notifications"
"write:notifications": "Work with notifications"
"read:reactions": "View reactions"
"write:reactions": "Work with reactions"
"write:votes": "Vote"
empty-timeline-info:
follow-users-to-make-your-timeline: "Following users will show their posts in your timeline."
@ -484,8 +489,12 @@ common/views/components/user-menu.vue:
mention: "Mention"
mute: "Mute"
unmute: "Unmute"
mute-confirm: "Are you sure you want to mute this user?"
unmute-confirm: "Are you certain that you want to unmute this user?"
block: "Block"
unblock: "Unblock"
block-confirm: "Are you sure you want to block this user?"
unblock-confirm: "Are you certain that you want to unblock this user?"
push-to-list: "Add to list"
select-list: "Select a list"
report-abuse: "Report abuse"
@ -493,8 +502,12 @@ common/views/components/user-menu.vue:
report-abuse-reported: "The issue has been reported to the administrator. Your cooperation is much appreciated."
silence: "Silence"
unsilence: "Unsilence"
silence-confirm: "Are you sure that you want to silence this user?"
unsilence-confirm: "Are you sure that you want to stop silencing this user?"
suspend: "Suspend"
unsuspend: "Unsuspend"
suspend-confirm: "Are you sure that you want to suspend this user?"
unsuspend-confirm: "Are you sure that you want to unsuspend this user?"
common/views/components/poll.vue:
vote-to: "Vote for '{}'"
vote-count: "{} votes"
@ -677,6 +690,9 @@ common/views/components/user-list-editor.vue:
remove-user: "Remove from this list"
delete-are-you-sure: "Delete list \"$1\"?"
deleted: "Deleted successfully"
common/views/components/user-lists.vue:
create-list: "Create a list"
list-name: "List name"
common/views/widgets/broadcast.vue:
fetching: "Checking"
no-broadcasts: "No announcements"
@ -1025,8 +1041,6 @@ desktop/views/components/received-follow-requests-window.vue:
reject: "Reject"
desktop/views/components/user-lists-window.vue:
title: "User lists"
create-list: "Create a list"
list-name: "List name"
desktop/views/components/user-preview.vue:
notes: "Posts"
following: "Following"
@ -1204,7 +1218,9 @@ admin/views/users.vue:
unsuspend-confirm: "Are you sure you want to unsuspend this account?"
unsuspended: "The user has successfully unsuspended."
make-silence: "Silence"
silence-confirm: "Silence user?"
unmake-silence: "Unsilence"
unsilence-confirm: "Are you certain that you want to stop silencing this user?"
verify: "Verify account"
verify-confirm: "Do you want this to be a verified account?"
verified: "The account is now being verified"
@ -1398,6 +1414,7 @@ desktop/views/widgets/polls.vue:
desktop/views/widgets/post-form.vue:
title: "Post"
note: "Post"
something-happened: "Could not be posted in this circumstance."
desktop/views/widgets/profile.vue:
update-banner: "Click to edit your banner"
update-avatar: "Click to edit your avatar"
@ -1416,12 +1433,11 @@ mobile/views/components/drive.vue:
file-count: "File(s)"
nothing-in-drive: "There's nothing stored."
folder-is-empty: "This folder is empty"
prompt: "What do you want to do? (Please enter a number): <1 → Upload a file | 2 → Upload a file from a URL | 3 → Create a folder | 4 → Change this folder's name | 5 → Move this folder | 6 → Delete this folder>"
deletion-alert: "Sorry! Deleting a folder is not yet implemented."
folder-name: "Folder name"
here-is-root: "Currently, you are on the root, not inside of any folder."
url-prompt: "URL of the file you want to upload"
uploading: "Upload requested. It may take a while for the upload to finish."
folder-name-cannot-empty: "Folder name cannot be blank."
mobile/views/components/drive-file-chooser.vue:
select-file: "Choose files"
mobile/views/components/drive-folder-chooser.vue:
@ -1495,9 +1511,16 @@ mobile/views/components/ui.nav.vue:
game: "Games"
admin: "Admin"
about: "About Misskey"
mobile/views/pages/drive.vue:
contextmenu:
upload: "Upload a file"
url-upload: "Upload file from a URL"
create-folder: "Create a folder"
rename-folder: "Rename folder"
move-folder: "Move this folder"
delete-folder: "Delete this folder"
mobile/views/pages/user-lists.vue:
title: "Lists"
enter-list-name: "Enter a name of the list to make"
mobile/views/pages/signup.vue:
lets-start: "Your account is now ready! 📦"
mobile/views/pages/followers.vue:
@ -1624,10 +1647,12 @@ dev/views/apps.vue:
create-app: "Create app"
app-missing: "No apps"
dev/views/new-app.vue:
new-app: "New Application"
create-app: "Creating application"
app-name: "Application name"
app-name-desc: "The name of your app"
app-overview: "Application summary"
app-overview-desc: "A brief description, or an introduction of your app."
callback-url: "The callback URL (optional)"
callback-url-desc: "The URL to redirect to after the user is authenticated via the authentication form."
authority: "Permissions"

View File

@ -466,6 +466,8 @@ common/views/components/profile-editor.vue:
enter-password: "Escribe una contraseña"
common/views/components/user-list-editor.vue:
users: "Usuarios"
common/views/components/user-lists.vue:
list-name: "Nombre de lista"
common/views/widgets/broadcast.vue:
fetching: "Recuperando"
no-broadcasts: "Sin emisión"
@ -773,8 +775,6 @@ desktop/views/components/received-follow-requests-window.vue:
reject: "Rechazar"
desktop/views/components/user-lists-window.vue:
title: "Listas de usuario"
create-list: "Crear lista"
list-name: "Nombre de lista"
desktop/views/components/user-preview.vue:
notes: "Publicaciones"
following: "Sigue"
@ -931,6 +931,10 @@ mobile/views/components/ui.nav.vue:
game: "Juegos"
admin: "Admin"
about: "Sobre"
mobile/views/pages/drive.vue:
contextmenu:
upload: "Subir fichero"
create-folder: "Crear una carpeta"
mobile/views/pages/user-lists.vue:
title: "Listas"
mobile/views/pages/home.vue:

View File

@ -13,7 +13,7 @@ common:
rich-contents-desc: "Partagez vos idées, les événements et les sujets qui vous tiennent à cœur ainsi que tout autre chose que vous souhaitez partager avec les autres. Si vous le désirez, vous pouvez décorer vos messages en utilisant une syntaxe différente ou en y joignant des sondages et des fichiers, tels que les photos ou les vidéos que vous aimez."
reaction: "Réactions"
reaction-desc: "Une manière simple d'exprimer vos émotions. Misskey peut attacher diverses réactions aux publications des autres utilisateurs. Si vous essayez les réactions sur Misskey, vous ne pourrez plus retourner sur une autre plateforme de réseaux sociaux n'offrant que des « J'aime »."
ui: "Interface utilisateur"
ui: "Interface"
ui-desc: "Aucune interface graphique ne peut plaire à tout le monde. Par conséquent, Misskey possède une interface utilisateur hautement personnalisable selon vos goûts. Vous pouvez rendre votre page d'accueil originale en modifiant la mise en page de votre fil et en déplaçant les widgets que vous pouvez facilement ajuster pour vous approprier cet espace."
drive: "Drive"
drive-desc: "Vous voulez poster une photo que vous avez déjà transférée ? Vous souhaitez organiser, nommer et créer un dossier pour vos fichiers téléversés ? Misskey Drive est la meilleure solution pour vous. Très facile de partager vos fichiers en ligne."
@ -33,6 +33,7 @@ common:
signin: "Se connecter"
signup: "S'enregistrer"
signout: "Se déconnecter"
reload-to-apply-the-setting: "Le rechargement de la page est nécessaire pour appliquer ces paramètres. Désirez-vous la recharger maintenant ?"
got-it: "Jai compris !"
customization-tips:
title: "Conseils de personnalisation"
@ -72,6 +73,10 @@ common:
"write:account": "Mettre à jour les informations de votre compte"
"read:drive": "Parcourir le Drive"
"write:drive": "Écrire sur le Drive"
"read:favorites": "Afficher les favoris"
"write:notes": "Créer ou supprimer des publications"
"read:notifications": "Afficher les notifications"
"read:reactions": "Lire les réactions"
"write:votes": "Vote"
empty-timeline-info:
follow-users-to-make-your-timeline: "Les utilisateurs suivants afficheront leurs publications sur votre fil."
@ -136,6 +141,7 @@ common:
appearance: "Apparence"
behavior: "Comportement"
fetch-on-scroll: "Chargement automatique lors du défilement"
fetch-on-scroll-desc: "Chargement automatique du contenu lors du défilement de la page."
note-visibility: "Visibilité de la publication"
default-note-visibility: "Visibilité par défaut"
remember-note-visibility: "Se souvenir du mode de visibilité de la publication"
@ -206,16 +212,16 @@ common:
debug-mode: "Activer le mode débogage"
debug-mode-desc: "Ce paramètre est stocké dans le navigateur."
navbar-position: "Position de la barre de navigation"
navbar-position-top: "en haut"
navbar-position-top: "En haut"
navbar-position-left: "À gauche"
navbar-position-right: "à droite"
navbar-position-right: "À droite"
i-am-under-limited-internet: "J'ai un accès Internet limité"
post-style: "Style d'affichage des notes"
post-style-standard: "Standard"
post-style-smart: "Intelligent"
notification-position: "Afficher les notifications"
notification-position-bottom: "en bas"
notification-position-top: "en haut"
notification-position-top: "En haut"
disable-via-mobile: "Enlever la mention publié via 'mobile'"
load-raw-images: "Afficher les photos jointes dans leur qualité originale"
load-remote-media: "Afficher les médias depuis le serveur distant"
@ -304,6 +310,7 @@ common/views/pages/explore.vue:
explore: "Explorer {host}"
users-info: "Actuellement, {users} utilisateurs se sont inscrit ici"
common/views/components/url-preview.vue:
enable-player: "Activer la lecture"
disable-player: "Fermer le lecteur"
common/views/components/user-list.vue:
no-users: "Il n'y a aucun utilisateur"
@ -644,6 +651,7 @@ common/views/components/profile-editor.vue:
blocking-list: "Liste des comptes bloqués"
user-lists: "Listes"
export-requested: "Vous avez demandé une exportation. Cela peut prendre un certain temps. Une fois l'exportation terminée, le fichier résultant sera ajouté dans le Drive."
import-requested: "Vous avez initié un import. Ceci peut prendre un peu de temps."
enter-password: "Veuillez saisir votre mot de passe"
danger-zone: "Zone de danger"
delete-account: "Supprimer le compte"
@ -655,6 +663,9 @@ common/views/components/user-list-editor.vue:
remove-user: "Retirer de cette liste"
delete-are-you-sure: "Voulez-vous vraiment supprimer la liste « $1 » ?"
deleted: "Supprimé"
common/views/components/user-lists.vue:
create-list: "Créer une liste"
list-name: "Nom de la liste"
common/views/widgets/broadcast.vue:
fetching: "Récupération"
no-broadcasts: "Aucune annonce"
@ -955,6 +966,7 @@ common/views/components/password-settings.vue:
changed: "Mot de passe modifié avec succès"
failed: "Échec lors de la modification du mot de passe"
common/views/components/post-form-attaches.vue:
attach-cancel: "Enlever le fichier attaché"
mark-as-sensitive: "Marquer comme sensible"
unmark-as-sensitive: "Ne pas marquer comme sensible"
desktop/views/components/sub-note-content.vue:
@ -1001,8 +1013,6 @@ desktop/views/components/received-follow-requests-window.vue:
reject: "Refuser"
desktop/views/components/user-lists-window.vue:
title: "Listes de l'utilisateur"
create-list: "Créer une liste"
list-name: "Nom de la liste"
desktop/views/components/user-preview.vue:
notes: "Publications"
following: "Abonné à"
@ -1379,8 +1389,6 @@ mobile/views/components/drive.vue:
file-count: "Fichier·s"
nothing-in-drive: "Rien"
folder-is-empty: "Ce dossier est vide"
prompt: "Que veux-tu faire ? (Entrez un nombre): <1 → Télécharger le fichier | 2 → Télécharger le fichier avec l'URL | 3 → Créer le dossier | 4 → Modifier le nom du dossier | 5 → Déplacer ce dossier | 6 → Supprimer ce dossier >"
deletion-alert: "Désolé ! La suppression dun dossier nest pas encore implémentée."
folder-name: "Nom du dossier"
url-prompt: "URL du fichier que vous souhaitez téléverser"
uploading: "Envoi demandé. Le téléversement pourrait prendre un certain temps avant de s'achever."
@ -1457,9 +1465,12 @@ mobile/views/components/ui.nav.vue:
game: "Jeux"
admin: "Admin"
about: "À propos de Misskey"
mobile/views/pages/drive.vue:
contextmenu:
upload: "Téléverser un fichier"
create-folder: "Créer un dossier"
mobile/views/pages/user-lists.vue:
title: "Listes"
enter-list-name: "Nom de la liste"
mobile/views/pages/signup.vue:
lets-start: "Votre compte est prêt ! 📦"
mobile/views/pages/followers.vue:

View File

@ -478,6 +478,8 @@ common/views/components/profile-editor.vue:
enter-password: "パスワードを入れてや"
common/views/components/user-list-editor.vue:
users: "ユーザー"
common/views/components/user-lists.vue:
list-name: "リスト名"
common/views/widgets/broadcast.vue:
fetching: "見てみるわ…"
no-broadcasts: "お知らせはあらへんで"
@ -815,8 +817,6 @@ desktop/views/components/received-follow-requests-window.vue:
reject: "許さん"
desktop/views/components/user-lists-window.vue:
title: "リスト"
create-list: "新しいリストを作成"
list-name: "リスト名"
desktop/views/components/user-preview.vue:
notes: "投稿"
following: "フォロー"
@ -1071,8 +1071,6 @@ mobile/views/components/drive.vue:
file-count: "ファイル"
nothing-in-drive: "ドライブには何もあらへんで。"
folder-is-empty: "このフォルダ何もないわ"
prompt: "何すんの?(数字を入れてや): <1 → ファイルをアップロード | 2 → ファイルをURLでアップロード | 3 → フォルダ作成 | 4 → このフォルダ名を変更 | 5 → このフォルダを移動 | 6 → このフォルダを削除>"
deletion-alert: "フォルダの削除は未実装やねん...。堪忍な!"
folder-name: "フォルダー名"
url-prompt: "このURLのファイルをアップロードしたいねん"
uploading: "アップロードをリクエストしたで。アップロードが完了するまで時間がかかるかも分からん、知らんけど。"
@ -1149,9 +1147,12 @@ mobile/views/components/ui.nav.vue:
game: "ゲーム"
admin: "管理"
about: "Misskeyってなんや"
mobile/views/pages/drive.vue:
contextmenu:
upload: "ファイル上げる"
create-folder: "フォルダー作る"
mobile/views/pages/user-lists.vue:
title: "リスト"
enter-list-name: "リスト名を入力してや"
mobile/views/pages/signup.vue:
lets-start: "📦 始めようや"
mobile/views/pages/followers.vue:

View File

@ -667,6 +667,8 @@ common/views/components/user-list-editor.vue:
remove-user: "이 리스트에서 제거"
delete-are-you-sure: "리스트 \"$1\"을 삭제하시겠습니까?"
deleted: "삭제하였습니다"
common/views/components/user-lists.vue:
list-name: "리스트 이름"
common/views/widgets/broadcast.vue:
fetching: "확인중"
no-broadcasts: "공지사항이 없습니다"
@ -1015,8 +1017,6 @@ desktop/views/components/received-follow-requests-window.vue:
reject: "거부"
desktop/views/components/user-lists-window.vue:
title: "리스트"
create-list: "리스트 만들기"
list-name: "리스트 이름"
desktop/views/components/user-preview.vue:
notes: "글"
following: "팔로잉"
@ -1406,8 +1406,6 @@ mobile/views/components/drive.vue:
file-count: "파일"
nothing-in-drive: "드라이브에 아무것도 없습니다"
folder-is-empty: "폴더가 비어있습니다"
prompt: "무엇을 하시겠습니까? (숫자를 입력하여 주십시오): <1 → 파일 업로드 | 2 → 파일을 URL에서 업로드 | 3 → 폴더 만들기 | 4 → 이 폴더의 이름을 변경 | 5 → 현재 폴더 이동| 6 → 현재 폴더 삭제>"
deletion-alert: "죄송합니다! 폴더 삭제는 아직 구현되지 않았습니다..."
folder-name: "폴더 이름"
here-is-root: "현재 경로는 루트 경로로 폴더가 아닙니다."
url-prompt: "업로드 하려는 파일의 URL"
@ -1485,9 +1483,12 @@ mobile/views/components/ui.nav.vue:
game: "게임"
admin: "관리"
about: "Misskey에 대하여"
mobile/views/pages/drive.vue:
contextmenu:
upload: "파일 업로드"
create-folder: "폴더 만들기"
mobile/views/pages/user-lists.vue:
title: "리스트"
enter-list-name: "리스트명을 입력하십시오"
mobile/views/pages/signup.vue:
lets-start: "📦 이제 시작해도 됩니다"
mobile/views/pages/followers.vue:

View File

@ -403,7 +403,6 @@ desktop/views/components/ui.header.search.vue:
placeholder: "Zoeken"
desktop/views/components/user-lists-window.vue:
title: "Lijsten"
create-list: "Lijst creëren"
desktop/views/components/user-preview.vue:
notes: "Berichten"
following: "Volgend"
@ -557,6 +556,10 @@ mobile/views/components/ui.nav.vue:
user-lists: "Lijsten"
game: "Othello spelen"
about: "Over Misskey"
mobile/views/pages/drive.vue:
contextmenu:
upload: "Bestand uploaden"
create-folder: "Map creëren"
mobile/views/pages/user-lists.vue:
title: "Lijsten"
mobile/views/pages/home.vue:

View File

@ -192,6 +192,8 @@ common/views/components/profile-editor.vue:
user-lists: "Lister"
common/views/components/user-list-editor.vue:
users: "Bruker"
common/views/components/user-lists.vue:
list-name: "Liste navn"
common/views/widgets/broadcast.vue:
fetching: "Henter"
next: "Neste"
@ -310,7 +312,6 @@ desktop/views/components/received-follow-requests-window.vue:
reject: "Avslå"
desktop/views/components/user-lists-window.vue:
title: "Brukerlister"
list-name: "Liste navn"
desktop/views/components/user-preview.vue:
notes: "Innlegg"
following: "Følger"

View File

@ -512,6 +512,8 @@ common/views/components/user-list-editor.vue:
remove-user: "Usuń z tej listy"
delete-are-you-sure: "Usunąć listę \"$1\"?"
deleted: "Usunięto"
common/views/components/user-lists.vue:
list-name: "Nazwa listy"
common/views/widgets/broadcast.vue:
fetching: "Sprawdzanie"
no-broadcasts: "Brak transmisji"
@ -834,8 +836,6 @@ desktop/views/components/received-follow-requests-window.vue:
reject: "Odmów"
desktop/views/components/user-lists-window.vue:
title: "Listy"
create-list: "Utwórz listę"
list-name: "Nazwa listy"
desktop/views/components/user-preview.vue:
notes: "Wpisy"
following: "Śledzeni"
@ -1026,8 +1026,6 @@ mobile/views/components/drive.vue:
file-count: "Plik(i)"
nothing-in-drive: "Pusto"
folder-is-empty: "Ten katalog jest pusty"
prompt: "Co chcesz zrobić? (wprowadź odpowiednią cyfrę): <1 → Wysłać plik | 2 → Wysłać plik z adresu URL | 3 → Utworzyć katalog | 4 → Zmienić nazwę tego katalogu | 5 → Przenieść ten katalog | 6 → Usunąć ten katalog>"
deletion-alert: "Przepraszamy. Usuwanie katalogów nie zostało jeszcze zaimplementowane."
folder-name: "Nazwa katalogu"
url-prompt: "Adres URL pliku, który chcesz wysłać"
uploading: "Rozpoczęto wysyłanie. Może to trochę potrwać."
@ -1103,9 +1101,12 @@ mobile/views/components/ui.nav.vue:
game: "Gry"
admin: "Admin"
about: "O Misskey"
mobile/views/pages/drive.vue:
contextmenu:
upload: "Wyślij plik"
create-folder: "Utwórz katalog"
mobile/views/pages/user-lists.vue:
title: "Listy"
enter-list-name: "Wprowadź nazwę listy"
mobile/views/pages/signup.vue:
lets-start: "Rozpocznijmy! 📦"
mobile/views/pages/home.vue:

View File

@ -34,6 +34,7 @@ common:
signup: "注册"
signout: "退出"
reload-to-apply-the-setting: "必须重新加载页面以应用此设置。 确实要立即重新加载吗?"
fetching-as-ap-object: "联合查询"
got-it: "知道了"
customization-tips:
title: "自定义提示"
@ -162,7 +163,7 @@ common:
keep-cw: "保留内容警告"
keep-cw-desc: "在回复帖子时,如果原帖设置了内容警告,默认情况下回帖也会设置相同的内容警告。"
i-like-sushi: "相比于布丁来说, 我更喜欢寿司。"
show-reversi-board-labels: "在 Reversi 中显示行和列表签"
show-reversi-board-labels: "在黑白棋中显示行和列表签"
use-avatar-reversi-stones: "用头像作为黑白棋的棋子"
disable-animated-mfm: "在帖子中禁用动画文本"
disable-showing-animated-images: "不播放动画"
@ -205,9 +206,9 @@ common:
show-clock-on-header: "在右上角显示时钟"
show-reply-target: "显示回复目标"
timeline: "时间线"
show-my-renotes: "在时间线上显示我的Renote"
show-renoted-my-notes: "在时间线上显示我的帖子的Renote"
show-local-renotes: "在时间线上显示本地帖子的Renote"
show-my-renotes: "在时间线上显示我的转推"
show-renoted-my-notes: "在时间线上显示我的帖子的转推"
show-local-renotes: "在时间线上显示本地帖子的转推"
remain-deleted-note: "继续显示已删除的帖子"
sound: "声音"
enable-sounds: "开启声音"
@ -257,7 +258,7 @@ common:
is-remote-user: "此用户信息可能不准确。"
is-remote-post: "该投稿已被复制."
view-on-remote: "查看准确的信息"
renoted-by: "由 {user} Renote"
renoted-by: "由 {user} 转推"
no-notes: "没有帖子"
turn-on-darkmode: "切换暗色主题"
turn-off-darkmode: "切换亮色主题"
@ -338,8 +339,8 @@ common/views/components/games/reversi/reversi.game.vue:
surrender: "认输"
surrendered: "已认输"
is-llotheo: "棋子较少一方获胜(LLoTheO规则)"
looped-map: "环棋盘"
can-put-everywhere: "可以下在任意置"
looped-map: "环棋盘"
can-put-everywhere: "可以下在任意置"
common/views/components/games/reversi/reversi.index.vue:
title: "Misskey 黑白棋"
sub-title: "和其他人一起来玩Misskey黑白棋"
@ -363,8 +364,8 @@ common/views/components/games/reversi/reversi.room.vue:
black-is: "{}是黑"
rules: "规则"
is-llotheo: "棋子较少一方获胜(LLoTheO规则)"
looped-map: "环棋盘"
can-put-everywhere: "可以下在任意置"
looped-map: "环棋盘"
can-put-everywhere: "可以下在任意置"
settings-of-the-bot: "机器人设定"
this-game-is-started-soon: "游戏即将在数秒后开始"
waiting-for-other: "等待对手准备"
@ -480,8 +481,8 @@ common/views/components/note-menu.vue:
unfavorite: "取消收藏"
watch: "关注"
unwatch: "取消关注"
pin: "固定个人资料"
unpin: "解除固定"
pin: "置顶"
unpin: "取消置顶"
delete: "删除"
delete-confirm: "确定删除这个投稿吗?"
remote: "显示原始投稿"
@ -489,8 +490,12 @@ common/views/components/user-menu.vue:
mention: "提到"
mute: "屏蔽"
unmute: "解除屏蔽"
mute-confirm: "屏蔽此用户?"
unmute-confirm: "取消屏蔽用户?"
block: "拉黑"
unblock: "取消拉黑"
block-confirm: "确定拉黑此用户?"
unblock-confirm: "取消拉黑此用户?"
push-to-list: "添加至列表"
select-list: "请选择一个列表"
report-abuse: "举报骚扰"
@ -498,8 +503,12 @@ common/views/components/user-menu.vue:
report-abuse-reported: "已报告给管理员。 非常感谢你的合作。"
silence: "禁言"
unsilence: "解除禁言"
silence-confirm: "确认屏蔽此用户?"
unsilence-confirm: "取消屏蔽此用户?"
suspend: "冻结"
unsuspend: "解除冻结"
suspend-confirm: "确认冻结此用户?"
unsuspend-confirm: "确认解冻此用户?"
common/views/components/poll.vue:
vote-to: "为\"{}\"投票"
vote-count: "{}票"
@ -682,6 +691,9 @@ common/views/components/user-list-editor.vue:
remove-user: "从此列表中删除"
delete-are-you-sure: "删除列表“$1”"
deleted: "已删除"
common/views/components/user-lists.vue:
create-list: "创建列表"
list-name: "列表名称"
common/views/widgets/broadcast.vue:
fetching: "确认中"
no-broadcasts: "没有公告"
@ -724,7 +736,7 @@ common/views/widgets/tips.vue:
tips-line8: "可以从设置中定制主页。"
tips-line9: "Misskey 根据 AGPLv3 获得许可。"
tips-line10: "使用Time Machine(时光机)小部件可以轻松追溯到过去的时间轴。"
tips-line11: "您可以点击“...”将帖子固定到用户页面"
tips-line11: "您可以点击“...”将帖子置顶到用户页面"
tips-line13: "附在帖子上的所有文件都会保存到网盘中。"
tips-line14: "在自定义首页布局时,您可以右键单击窗口小部件以更改其设计。"
tips-line17: "用“**”围绕文本将突出显示它。"
@ -759,7 +771,7 @@ desktop/views/components/activity.chart.vue:
total: "黑 ... 总计"
notes: "蓝 ... 投稿"
replies: "红 ... 回复"
renotes: "绿 ... 转"
renotes: "绿 ... 转"
desktop/views/components/activity.vue:
title: "活动"
toggle: "切换显示"
@ -856,12 +868,12 @@ desktop/views/components/note-detail.vue:
private: "私密投稿"
deleted: "投稿已删除"
location: "位置信息"
renote: "转"
renote: "转"
add-reaction: "回应"
undo-reaction: "取消回应"
desktop/views/components/note.vue:
reply: "回复"
renote: "Renote"
renote: "转推"
add-reaction: "回应"
undo-reaction: "取消回应"
detail: "详细信息"
@ -880,13 +892,13 @@ desktop/views/components/post-form.vue:
quote-placeholder: "引用这个帖子…"
submit: "投稿"
reply: "回复"
renote: "转"
renote: "转"
posted: "已发送投稿!"
replied: "已回复!"
reposted: "已转!"
reposted: "已转!"
note-failed: "发帖失败"
reply-failed: "回复失败"
renote-failed: "转失败"
renote-failed: "转失败"
posting: "发送中"
attach-media-from-local: "从设备中添加媒体文件"
attach-media-from-drive: "从网盘中添加媒体文件"
@ -911,13 +923,13 @@ desktop/views/components/progress-dialog.vue:
desktop/views/components/renote-form.vue:
quote: "引用..."
cancel: "取消"
renote: "重新发送"
renote-home: "重新发送(首页)"
renote: "转推"
renote-home: "转推(首页)"
reposting: "重新发送中..."
success: "已重新发送"
failure: "重新发送失败"
success: "已转推"
failure: "转推失败"
desktop/views/components/renote-form-window.vue:
title: "您是否要重新发送"
title: "您是否要转推"
desktop/views/pages/user-following-or-followers.vue:
following: "{user}的正在关注"
followers: "{user}的关注者"
@ -967,7 +979,7 @@ common/views/components/drive-settings.vue:
common/views/components/mute-and-block.vue:
mute-and-block: "屏蔽/拉黑"
mute: "屏蔽"
block: "拉黑"
block: "拉黑"
no-muted-users: "无屏蔽用户"
no-blocked-users: "无拉黑的用户"
word-mute: "文字屏蔽"
@ -1030,8 +1042,6 @@ desktop/views/components/received-follow-requests-window.vue:
reject: "拒绝"
desktop/views/components/user-lists-window.vue:
title: "用户列表"
create-list: "创建列表"
list-name: "列表名称"
desktop/views/components/user-preview.vue:
notes: "帖子"
following: "关注中"
@ -1209,7 +1219,9 @@ admin/views/users.vue:
unsuspend-confirm: "是否解除冻结?"
unsuspended: "已成功解除用户冻结"
make-silence: "禁言"
silence-confirm: "确认屏蔽?"
unmake-silence: "解除禁言"
unsilence-confirm: "解除屏蔽?"
verify: "认证用户"
verify-confirm: "是否官方账号?"
verified: "此账户已被认证"
@ -1422,12 +1434,11 @@ mobile/views/components/drive.vue:
file-count: "文件"
nothing-in-drive: "网盘为空"
folder-is-empty: "这文件夹是空的"
prompt: "您想要干什么呢?(请输入数字):<1 → 上传文件 | 2 → 从URL上传文件 | 3 → 创建新文件夹 | 4 → 更改这个文件夹的名称 | 5 → 移动这个文件夹 | 6 → 删除这个文件夹>"
deletion-alert: "抱歉! 删除文件夹功能尚未实现。"
folder-name: "文件夹名称"
here-is-root: "当前位置为根目录。"
url-prompt: "要上传的文件的URL"
uploading: "已请求上传。 上传完成可能需要一段时间。"
folder-name-cannot-empty: "文件夹名不能为空。"
mobile/views/components/drive-file-chooser.vue:
select-file: "选择文件"
mobile/views/components/drive-folder-chooser.vue:
@ -1476,7 +1487,7 @@ mobile/views/components/post-form.vue:
add-visible-user: "添加用户"
submit: "帖子"
reply: "回复"
renote: "Renote"
renote: "转推"
quote-placeholder: "引用这个帖子t... (可选)"
reply-placeholder: "回复这个帖子"
cw-placeholder: "评论帖子(可选)"
@ -1501,9 +1512,16 @@ mobile/views/components/ui.nav.vue:
game: "游戏"
admin: "管理"
about: "关于 Misskey"
mobile/views/pages/drive.vue:
contextmenu:
upload: "上传文件"
url-upload: "从URL上传文件"
create-folder: "创建文件夹"
rename-folder: "重命名文件夹"
move-folder: "移动此文件夹"
delete-folder: "删除此文件夹"
mobile/views/pages/user-lists.vue:
title: "列表"
enter-list-name: "输入要列出的列表的名称"
mobile/views/pages/signup.vue:
lets-start: "您的账户现已准备就绪! 📦"
mobile/views/pages/followers.vue:
@ -1600,7 +1618,7 @@ deck/deck.user-column.vue:
images: "图片"
activity: "活动"
timeline: "时间线"
pinned-notes: "标记投稿"
pinned-notes: "置顶帖"
docs:
edit-this-page-on-github: "发现错误或想要为文档做出贡献?"
edit-this-page-on-github-link: "在GitHub上编辑这个页面。"

View File

@ -1,7 +1,7 @@
{
"name": "misskey",
"author": "syuilo <i@syuilo.com>",
"version": "11.2.0",
"version": "11.3.0",
"codename": "daybreak",
"repository": {
"type": "git",
@ -29,7 +29,6 @@
"@fortawesome/free-solid-svg-icons": "5.7.2",
"@fortawesome/vue-fontawesome": "0.1.5",
"@koa/cors": "2.2.3",
"@prezzemolo/rap": "0.1.2",
"@prezzemolo/zip": "0.0.3",
"@types/bcryptjs": "2.4.2",
"@types/bull": "3.5.11",
@ -94,7 +93,7 @@
"@types/websocket": "0.0.40",
"@types/ws": "6.0.1",
"animejs": "3.0.1",
"apexcharts": "3.6.7",
"apexcharts": "3.6.8",
"autobind-decorator": "2.4.0",
"autosize": "4.0.2",
"autwh": "0.1.0",
@ -112,7 +111,7 @@
"cssnano": "4.1.10",
"dateformat": "3.0.3",
"deep-equal": "1.0.1",
"diskusage": "1.0.0",
"diskusage": "1.1.0",
"double-ended-queue": "2.1.0-0",
"elasticsearch": "15.4.1",
"emojilib": "2.4.0",
@ -138,10 +137,10 @@
"html-minifier": "4.0.0",
"http-signature": "1.2.0",
"insert-text-at-cursor": "0.2.0",
"is-root": "2.0.0",
"is-root": "2.1.0",
"is-svg": "4.1.0",
"js-yaml": "3.13.1",
"jsdom": "14.0.0",
"jsdom": "14.1.0",
"json5": "2.1.0",
"json5-loader": "2.0.0",
"katex": "0.10.1",
@ -161,20 +160,20 @@
"loader-utils": "1.2.3",
"lolex": "3.1.0",
"lookup-dns-cache": "2.1.0",
"minio": "7.0.5",
"minio": "7.0.6",
"mocha": "6.1.3",
"moji": "0.5.1",
"moment": "2.24.0",
"ms": "2.1.1",
"nested-property": "0.0.7",
"node-fetch": "2.3.0",
"nodemailer": "6.1.0",
"nodemailer": "6.1.1",
"nprogress": "0.2.0",
"object-assign-deep": "0.4.0",
"os-utils": "0.0.14",
"parse5": "5.1.0",
"parsimmon": "1.12.0",
"pg": "7.9.0",
"pg": "7.10.0",
"portscanner": "2.2.0",
"postcss-loader": "3.0.0",
"prismjs": "1.16.0",
@ -210,7 +209,7 @@
"stylus": "0.54.5",
"stylus-loader": "3.0.2",
"summaly": "2.2.0",
"systeminformation": "4.1.4",
"systeminformation": "4.1.5",
"syuilo-password-strength": "0.0.1",
"terser-webpack-plugin": "1.2.3",
"textarea-caret": "3.1.0",
@ -240,7 +239,7 @@
"vue-loader": "15.7.0",
"vue-marquee-text-component": "1.1.1",
"vue-prism-component": "1.1.1",
"vue-router": "3.0.4",
"vue-router": "3.0.6",
"vue-sequential-entrance": "1.1.3",
"vue-style-loader": "4.1.2",
"vue-svg-inline-loader": "1.2.15",

View File

@ -27,7 +27,7 @@ function greet() {
console.log(' ' + chalk.gray(v) + (' |___|\n'.substr(v.length)));
//#endregion
console.log(' Misskey is maintained by @syuilo, @AyaMorisawa, @mei23, and @acid-chicken.');
console.log(' Misskey is maintained by @syuilo, @AyaMorisawa, @mei23, @acid-chicken, and @rinsuki.');
console.log(chalk.keyword('orange')(' If you like Misskey, please donate to support development. https://www.patreon.com/syuilo'));
console.log('');

View File

@ -9,6 +9,9 @@
<ui-textarea v-model="announcement.text">
<span>{{ $t('text') }}</span>
</ui-textarea>
<ui-input v-model="announcement.image">
<span>{{ $t('image-url') }}</span>
</ui-input>
<ui-horizon-group class="fit-bottom">
<ui-button @click="save()"><fa :icon="['far', 'save']"/> {{ $t('save') }}</ui-button>
<ui-button @click="remove(i)"><fa :icon="['far', 'trash-alt']"/> {{ $t('remove') }}</ui-button>
@ -43,7 +46,8 @@ export default Vue.extend({
add() {
this.announcements.unshift({
title: '',
text: ''
text: '',
image: null
});
},

View File

@ -78,6 +78,7 @@ export default Vue.extend({
.mk-avatar
display inline-block
vertical-align bottom
flex-shrink 0
&:not(.cat)
overflow hidden

View File

@ -77,11 +77,11 @@ export default Vue.extend({
input: {
default: this.list.name
}
}).then(({ canceled, result: title }) => {
}).then(({ canceled, result: name }) => {
if (canceled) return;
this.$root.api('users/lists/update', {
listId: this.list.id,
title: title
name: name
});
});
},

View File

@ -28,10 +28,10 @@ export default Vue.extend({
this.$root.dialog({
title: this.$t('list-name'),
input: true
}).then(async ({ canceled, result: title }) => {
}).then(async ({ canceled, result: name }) => {
if (canceled) return;
const list = await this.$root.api('users/lists/create', {
title
name
});
this.lists.push(list)

View File

@ -18,7 +18,8 @@
<p class="fetching" v-if="fetching">{{ $t('fetching') }}<mk-ellipsis/></p>
<h1 v-if="!fetching">{{ announcements.length == 0 ? $t('no-broadcasts') : announcements[i].title }}</h1>
<p v-if="!fetching">
<span v-if="announcements.length != 0" v-html="announcements[i].text"></span>
<mfm v-if="announcements.length != 0" :text="announcements[i].text"/>
<img v-if="announcements.length != 0 && announcements[i].image" :src="announcements[i].image" alt="" style="display: block; max-height: 130px; max-width: 100%;"/>
<template v-if="announcements.length == 0">{{ $t('have-a-nice-day') }}</template>
</p>
<a v-if="announcements.length > 1" @click="next">{{ $t('next') }} &gt;&gt;</a>

View File

@ -123,10 +123,10 @@ export default Vue.extend({
this.$root.dialog({
title: this.$t('list-name'),
input: true
}).then(async ({ canceled, result: title }) => {
}).then(async ({ canceled, result: name }) => {
if (canceled) return;
const list = await this.$root.api('users/lists/create', {
title
name
});
this.list = list;

View File

@ -44,7 +44,8 @@
<div v-if="announcements && announcements.length > 0">
<div v-for="announcement in announcements">
<h1 v-html="announcement.title"></h1>
<div v-html="announcement.text"></div>
<mfm :text="announcement.text"/>
<img v-if="announcement.image" :src="announcement.image" alt="" style="display: block; max-height: 130px; max-width: 100%;"/>
</div>
</div>
</div>

View File

@ -215,11 +215,6 @@ export default Vue.extend({
@media (min-width 500px)
padding 28px 32px 18px 32px
&:after
content ""
display block
clear both
> header
display flex
line-height 1.1em
@ -236,6 +231,7 @@ export default Vue.extend({
height 60px
> div
min-width 0
> .name
display inline-block

View File

@ -43,7 +43,8 @@
<div class="announcements" v-if="announcements && announcements.length > 0">
<article v-for="announcement in announcements">
<span v-html="announcement.title" class="title"></span>
<div v-html="announcement.text"></div>
<mfm :text="announcement.text"/>
<img v-if="announcement.image" :src="announcement.image" alt="" style="display: block; max-height: 120px; max-width: 100%;"/>
</article>
</div>
<a :href="aboutUrl"><p class="about">{{ $t('about') }}</p></a>

View File

@ -29,7 +29,8 @@
<div class="announcements" v-if="announcements && announcements.length > 0">
<article v-for="announcement in announcements">
<span class="title" v-html="announcement.title"></span>
<div v-html="announcement.text"></div>
<mfm :text="announcement.text"/>
<img v-if="announcement.image" :src="announcement.image" alt="" style="display: block; max-height: 120px; max-width: 100%;"/>
</article>
</div>
<article class="about-misskey">

View File

@ -19,7 +19,7 @@ export const cafeTheme: Theme = require('../themes/cafe.json5');
export const japaneseSushiSetTheme: Theme = require('../themes/japanese-sushi-set.json5');
export const gruvboxDarkTheme: Theme = require('../themes/gruvbox-dark.json5');
export const monokaiTheme: Theme = require('../themes/monokai.json5');
export const colorfulTheme: Theme = require('../themes/colorful.json5');
export const vividTheme: Theme = require('../themes/vivid.json5');
export const rainyTheme: Theme = require('../themes/rainy.json5');
export const mauveTheme: Theme = require('../themes/mauve.json5');
export const grayTheme: Theme = require('../themes/gray.json5');
@ -35,7 +35,7 @@ export const builtinThemes = [
japaneseSushiSetTheme,
gruvboxDarkTheme,
monokaiTheme,
colorfulTheme,
vividTheme,
rainyTheme,
mauveTheme,
grayTheme,

View File

@ -1,7 +1,7 @@
{
id: '2d066d6e-bd39-4f23-bd48-686d5c1c6ae8',
name: 'Colorful',
name: 'Vivid',
author: 'syuilo',
base: 'light',

View File

@ -1,7 +1,11 @@
import { Meta } from '../models/entities/meta';
import { getConnection } from 'typeorm';
export default async function(): Promise<Meta> {
let cache: Meta;
export async function fetchMeta(noCache = false): Promise<Meta> {
if (!noCache && cache) return cache;
return await getConnection().transaction(async transactionalEntityManager => {
// バグでレコードが複数出来てしまっている可能性があるので新しいIDを優先する
const meta = await transactionalEntityManager.findOne(Meta, {
@ -11,11 +15,21 @@ export default async function(): Promise<Meta> {
});
if (meta) {
cache = meta;
return meta;
} else {
return await transactionalEntityManager.save(Meta, {
const saved = await transactionalEntityManager.save(Meta, {
id: 'x'
}) as Meta;
cache = saved;
return saved;
}
});
}
setInterval(() => {
fetchMeta(true).then(meta => {
cache = meta;
});
}, 5000);

View File

@ -1,4 +1,4 @@
import fetchMeta from './fetch-meta';
import { fetchMeta } from './fetch-meta';
import { ILocalUser } from '../models/entities/user';
import { Users } from '../models';
import { ensure } from '../prelude/ensure';

View File

@ -22,5 +22,5 @@ function getRandom() {
}
export function genMeid(date: Date): string {
return 'f' + getTime(date.getTime()) + getRandom();
return getTime(date.getTime()) + getRandom();
}

View File

@ -1,5 +1,5 @@
import { emojiRegex } from './emoji-regex';
import fetchMeta from './fetch-meta';
import { fetchMeta } from './fetch-meta';
import { Emojis } from '../models';
const basic10: Record<string, string> = {

View File

@ -1,14 +1,46 @@
export const types = {
boolean: 'boolean' as 'boolean',
string: 'string' as 'string',
number: 'number' as 'number',
array: 'array' as 'array',
object: 'object' as 'object',
any: 'any' as 'any',
};
export const bool = {
true: true as true,
false: false as false,
};
export type Schema = {
type: 'number' | 'string' | 'array' | 'object' | any;
optional?: boolean;
type: 'boolean' | 'number' | 'string' | 'array' | 'object' | 'any';
nullable: boolean;
optional: boolean;
items?: Schema;
properties?: Obj;
description?: string;
example?: any;
format?: string;
ref?: string;
enum?: string[];
};
type NonUndefinedPropertyNames<T extends Obj> = {
[K in keyof T]: T[K]['optional'] extends true ? never : K
}[keyof T];
type UndefinedPropertyNames<T extends Obj> = {
[K in keyof T]: T[K]['optional'] extends true ? K : never
}[keyof T];
type OnlyRequired<T extends Obj> = Pick<T, NonUndefinedPropertyNames<T>>;
type OnlyOptional<T extends Obj> = Pick<T, UndefinedPropertyNames<T>>;
export type Obj = { [key: string]: Schema };
export type ObjType<s extends Obj> = { [P in keyof s]: SchemaType<s[P]> };
export type ObjType<s extends Obj> =
{ [P in keyof OnlyOptional<s>]?: SchemaType<s[P]> } &
{ [P in keyof OnlyRequired<s>]: SchemaType<s[P]> };
// https://qiita.com/hrsh7th@github/items/84e8968c3601009cdcf2
type MyType<T extends Schema> = {
@ -16,26 +48,20 @@ type MyType<T extends Schema> = {
1: SchemaType<T>;
}[T extends Schema ? 1 : 0];
export type SchemaType<p extends Schema> =
p['type'] extends 'number' ? number :
p['type'] extends 'string' ? string :
p['type'] extends 'array' ? MyType<NonNullable<p['items']>>[] :
p['type'] extends 'object' ? ObjType<NonNullable<p['properties']>> :
any;
type NullOrUndefined<p extends Schema, T> =
p['nullable'] extends true
? p['optional'] extends true
? (T | null | undefined)
: (T | null)
: p['optional'] extends true
? (T | undefined)
: T;
export function convertOpenApiSchema(schema: Schema) {
const x = JSON.parse(JSON.stringify(schema)); // copy
if (!['string', 'number', 'boolean', 'array', 'object'].includes(x.type)) {
x['$ref'] = `#/components/schemas/${x.type}`;
}
if (x.type === 'array' && x.items) {
x.items = convertOpenApiSchema(x.items);
}
if (x.type === 'object' && x.properties) {
x.required = Object.entries(x.properties).filter(([k, v]: any) => !v.isOptional).map(([k, v]: any) => k);
for (const k of Object.keys(x.properties)) {
x.properties[k] = convertOpenApiSchema(x.properties[k]);
}
}
return x;
}
export type SchemaType<p extends Schema> =
p['type'] extends 'number' ? NullOrUndefined<p, number> :
p['type'] extends 'string' ? NullOrUndefined<p, string> :
p['type'] extends 'boolean' ? NullOrUndefined<p, boolean> :
p['type'] extends 'array' ? NullOrUndefined<p, MyType<NonNullable<p['items']>>[]> :
p['type'] extends 'object' ? NullOrUndefined<p, ObjType<NonNullable<p['properties']>>> :
p['type'] extends 'any' ? NullOrUndefined<p, any> :
any;

View File

@ -36,7 +36,7 @@ export class NoteReaction {
public note: Note | null;
@Column('varchar', {
length: 32
length: 128
})
public reaction: string;
}

View File

@ -1,8 +1,8 @@
import { EntityRepository, Repository } from 'typeorm';
import { Users } from '..';
import rap from '@prezzemolo/rap';
import { AbuseUserReport } from '../entities/abuse-user-report';
import { ensure } from '../../prelude/ensure';
import { awaitAll } from '../../prelude/await-all';
@EntityRepository(AbuseUserReport)
export class AbuseUserReportRepository extends Repository<AbuseUserReport> {
@ -17,7 +17,7 @@ export class AbuseUserReportRepository extends Repository<AbuseUserReport> {
) {
const report = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
return await rap({
return await awaitAll({
id: report.id,
createdAt: report.createdAt,
reporterId: report.reporterId,

View File

@ -2,6 +2,9 @@ import { EntityRepository, Repository } from 'typeorm';
import { App } from '../entities/app';
import { AccessTokens } from '..';
import { ensure } from '../../prelude/ensure';
import { types, bool, SchemaType } from '../../misc/schema';
export type PackedApp = SchemaType<typeof packedAppSchema>;
@EntityRepository(App)
export class AppRepository extends Repository<App> {
@ -13,7 +16,7 @@ export class AppRepository extends Repository<App> {
includeSecret?: boolean,
includeProfileImageIds?: boolean
}
) {
): Promise<PackedApp> {
const opts = Object.assign({
detail: false,
includeSecret: false,
@ -37,3 +40,40 @@ export class AppRepository extends Repository<App> {
};
}
}
export const packedAppSchema = {
type: types.object,
optional: bool.false, nullable: bool.false,
properties: {
id: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id',
description: 'The unique identifier for this Note.',
example: 'xxxxxxxxxx',
},
name: {
type: types.string,
optional: bool.false, nullable: bool.false,
description: 'アプリケーションの名前'
},
callbackUrl: {
type: types.string,
optional: bool.false, nullable: bool.true,
description: 'コールバックするURL'
},
permission: {
type: types.array,
optional: bool.true, nullable: bool.false,
items: {
type: types.string,
optional: bool.false, nullable: bool.false,
}
},
secret: {
type: types.string,
optional: bool.true, nullable: bool.false,
description: 'アプリケーションのシークレットキー'
}
},
};

View File

@ -1,8 +1,8 @@
import { EntityRepository, Repository } from 'typeorm';
import { Apps } from '..';
import rap from '@prezzemolo/rap';
import { AuthSession } from '../entities/auth-session';
import { ensure } from '../../prelude/ensure';
import { awaitAll } from '../../prelude/await-all';
@EntityRepository(AuthSession)
export class AuthSessionRepository extends Repository<AuthSession> {
@ -12,7 +12,7 @@ export class AuthSessionRepository extends Repository<AuthSession> {
) {
const session = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
return await rap({
return await awaitAll({
id: session.id,
app: Apps.pack(session.appId, me),
token: session.token

View File

@ -1,8 +1,11 @@
import { EntityRepository, Repository } from 'typeorm';
import { Users } from '..';
import rap from '@prezzemolo/rap';
import { Blocking } from '../entities/blocking';
import { ensure } from '../../prelude/ensure';
import { awaitAll } from '../../prelude/await-all';
import { SchemaType, types, bool } from '../../misc/schema';
export type PackedBlocking = SchemaType<typeof packedBlockingSchema>;
@EntityRepository(Blocking)
export class BlockingRepository extends Repository<Blocking> {
@ -16,14 +19,47 @@ export class BlockingRepository extends Repository<Blocking> {
public async pack(
src: Blocking['id'] | Blocking,
me?: any
) {
): Promise<PackedBlocking> {
const blocking = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
return await rap({
return await awaitAll({
id: blocking.id,
createdAt: blocking.createdAt.toISOString(),
blockeeId: blocking.blockeeId,
blockee: Users.pack(blocking.blockeeId, me, {
detail: true
})
});
}
}
export const packedBlockingSchema = {
type: types.object,
optional: bool.false, nullable: bool.false,
properties: {
id: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id',
description: 'The unique identifier for this blocking.',
example: 'xxxxxxxxxx',
},
createdAt: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'date-time',
description: 'The date that the blocking was created.'
},
blockeeId: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id',
},
blockee: {
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'User',
description: 'The blockee.'
},
}
};

View File

@ -1,10 +1,13 @@
import { EntityRepository, Repository } from 'typeorm';
import { DriveFile } from '../entities/drive-file';
import { Users, DriveFolders } from '..';
import rap from '@prezzemolo/rap';
import { User } from '../entities/user';
import { toPuny } from '../../misc/convert-host';
import { ensure } from '../../prelude/ensure';
import { awaitAll } from '../../prelude/await-all';
import { types, bool, SchemaType } from '../../misc/schema';
export type PackedDriveFile = SchemaType<typeof packedDriveFileSchema>;
@EntityRepository(DriveFile)
export class DriveFileRepository extends Repository<DriveFile> {
@ -82,7 +85,7 @@ export class DriveFileRepository extends Repository<DriveFile> {
self?: boolean,
withUser?: boolean,
}
) {
): Promise<PackedDriveFile> {
const opts = Object.assign({
detail: false,
self: false
@ -90,9 +93,9 @@ export class DriveFileRepository extends Repository<DriveFile> {
const file = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
return await rap({
return await awaitAll({
id: file.id,
createdAt: file.createdAt,
createdAt: file.createdAt.toISOString(),
name: file.name,
type: file.type,
md5: file.md5,
@ -109,3 +112,66 @@ export class DriveFileRepository extends Repository<DriveFile> {
});
}
}
export const packedDriveFileSchema = {
type: types.object,
optional: bool.false, nullable: bool.false,
properties: {
id: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id',
description: 'The unique identifier for this Drive file.',
example: 'xxxxxxxxxx',
},
createdAt: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'date-time',
description: 'The date that the Drive file was created on Misskey.'
},
name: {
type: types.string,
optional: bool.false, nullable: bool.false,
description: 'The file name with extension.',
example: 'lenna.jpg'
},
type: {
type: types.string,
optional: bool.false, nullable: bool.false,
description: 'The MIME type of this Drive file.',
example: 'image/jpeg'
},
md5: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'md5',
description: 'The MD5 hash of this Drive file.',
example: '15eca7fba0480996e2245f5185bf39f2'
},
size: {
type: types.number,
optional: bool.false, nullable: bool.false,
description: 'The size of this Drive file. (bytes)',
example: 51469
},
url: {
type: types.string,
optional: bool.false, nullable: bool.true,
format: 'url',
description: 'The URL of this Drive file.',
},
folderId: {
type: types.string,
optional: bool.false, nullable: bool.true,
format: 'id',
description: 'The parent folder ID of this Drive file.',
example: 'xxxxxxxxxx',
},
isSensitive: {
type: types.boolean,
optional: bool.false, nullable: bool.false,
description: 'Whether this Drive file is sensitive.',
},
},
};

View File

@ -1,8 +1,11 @@
import { EntityRepository, Repository } from 'typeorm';
import { DriveFolders, DriveFiles } from '..';
import rap from '@prezzemolo/rap';
import { DriveFolder } from '../entities/drive-folder';
import { ensure } from '../../prelude/ensure';
import { awaitAll } from '../../prelude/await-all';
import { SchemaType, types, bool } from '../../misc/schema';
export type PackedDriveFolder = SchemaType<typeof packedDriveFolderSchema>;
@EntityRepository(DriveFolder)
export class DriveFolderRepository extends Repository<DriveFolder> {
@ -18,16 +21,16 @@ export class DriveFolderRepository extends Repository<DriveFolder> {
options?: {
detail: boolean
}
): Promise<Record<string, any>> {
): Promise<PackedDriveFolder> {
const opts = Object.assign({
detail: false
}, options);
const folder = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
return await rap({
return await awaitAll({
id: folder.id,
createdAt: folder.createdAt,
createdAt: folder.createdAt.toISOString(),
name: folder.name,
parentId: folder.parentId,
@ -48,3 +51,50 @@ export class DriveFolderRepository extends Repository<DriveFolder> {
});
}
}
export const packedDriveFolderSchema = {
type: types.object,
optional: bool.false, nullable: bool.false,
properties: {
id: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id',
description: 'The unique identifier for this Drive folder.',
example: 'xxxxxxxxxx',
},
createdAt: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'date-time',
description: 'The date that the Drive folder was created.'
},
name: {
type: types.string,
optional: bool.false, nullable: bool.false,
description: 'The folder name.',
},
foldersCount: {
type: types.number,
optional: bool.true, nullable: bool.false,
description: 'The count of child folders.',
},
filesCount: {
type: types.number,
optional: bool.true, nullable: bool.false,
description: 'The count of child files.',
},
parentId: {
type: types.string,
optional: bool.false, nullable: bool.true,
format: 'id',
description: 'The parent folder ID of this folder.',
example: 'xxxxxxxxxx',
},
parent: {
type: types.object,
optional: bool.true, nullable: bool.true,
ref: 'DriveFolder'
},
},
};

View File

@ -1,8 +1,9 @@
import { EntityRepository, Repository } from 'typeorm';
import { Users } from '..';
import rap from '@prezzemolo/rap';
import { Following } from '../entities/following';
import { ensure } from '../../prelude/ensure';
import { awaitAll } from '../../prelude/await-all';
import { SchemaType, types, bool } from '../../misc/schema';
type LocalFollowerFollowing = Following & {
followerHost: null;
@ -28,6 +29,8 @@ type RemoteFolloweeFollowing = Following & {
followeeSharedInbox: string;
};
export type PackedFollowing = SchemaType<typeof packedFollowingSchema>;
@EntityRepository(Following)
export class FollowingRepository extends Repository<Following> {
public isLocalFollower(following: Following): following is LocalFollowerFollowing {
@ -64,22 +67,64 @@ export class FollowingRepository extends Repository<Following> {
populateFollowee?: boolean;
populateFollower?: boolean;
}
) {
): Promise<PackedFollowing> {
const following = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
if (opts == null) opts = {};
return await rap({
return await awaitAll({
id: following.id,
createdAt: following.createdAt,
createdAt: following.createdAt.toISOString(),
followeeId: following.followeeId,
followerId: following.followerId,
followee: opts.populateFollowee ? Users.pack(following.followee || following.followeeId, me, {
detail: true
}) : null,
}) : undefined,
follower: opts.populateFollower ? Users.pack(following.follower || following.followerId, me, {
detail: true
}) : null,
}) : undefined,
});
}
}
export const packedFollowingSchema = {
type: types.object,
optional: bool.false, nullable: bool.false,
properties: {
id: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id',
description: 'The unique identifier for this following.',
example: 'xxxxxxxxxx',
},
createdAt: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'date-time',
description: 'The date that the following was created.'
},
followeeId: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id',
},
followee: {
type: types.object,
optional: bool.true, nullable: bool.false,
ref: 'User',
description: 'The followee.'
},
followerId: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id',
},
follower: {
type: types.object,
optional: bool.true, nullable: bool.false,
ref: 'User',
description: 'The follower.'
},
}
};

View File

@ -1,8 +1,8 @@
import { EntityRepository, Repository } from 'typeorm';
import rap from '@prezzemolo/rap';
import { ReversiMatching } from '../../../entities/games/reversi/matching';
import { Users } from '../../..';
import { ensure } from '../../../../prelude/ensure';
import { awaitAll } from '../../../../prelude/await-all';
@EntityRepository(ReversiMatching)
export class ReversiMatchingRepository extends Repository<ReversiMatching> {
@ -12,7 +12,7 @@ export class ReversiMatchingRepository extends Repository<ReversiMatching> {
) {
const matching = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
return await rap({
return await awaitAll({
id: matching.id,
createdAt: matching.createdAt,
parentId: matching.parentId,

View File

@ -2,6 +2,9 @@ import { EntityRepository, Repository } from 'typeorm';
import { MessagingMessage } from '../entities/messaging-message';
import { Users, DriveFiles } from '..';
import { ensure } from '../../prelude/ensure';
import { types, bool, SchemaType } from '../../misc/schema';
export type PackedMessagingMessage = SchemaType<typeof packedMessagingMessageSchema>;
@EntityRepository(MessagingMessage)
export class MessagingMessageRepository extends Repository<MessagingMessage> {
@ -15,7 +18,7 @@ export class MessagingMessageRepository extends Repository<MessagingMessage> {
options?: {
populateRecipient: boolean
}
) {
): Promise<PackedMessagingMessage> {
const opts = options || {
populateRecipient: true
};
@ -24,15 +27,73 @@ export class MessagingMessageRepository extends Repository<MessagingMessage> {
return {
id: message.id,
createdAt: message.createdAt,
createdAt: message.createdAt.toISOString(),
text: message.text,
userId: message.userId,
user: await Users.pack(message.user || message.userId, me),
recipientId: message.recipientId,
recipient: opts.populateRecipient ? await Users.pack(message.recipient || message.recipientId, me) : null,
recipient: opts.populateRecipient ? await Users.pack(message.recipient || message.recipientId, me) : undefined,
fileId: message.fileId,
file: message.fileId ? await DriveFiles.pack(message.fileId) : null,
isRead: message.isRead
};
}
}
export const packedMessagingMessageSchema = {
type: types.object,
optional: bool.false, nullable: bool.false,
properties: {
id: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id',
description: 'The unique identifier for this MessagingMessage.',
example: 'xxxxxxxxxx',
},
createdAt: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'date-time',
description: 'The date that the MessagingMessage was created.'
},
userId: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id',
},
user: {
type: types.object,
ref: 'User',
optional: bool.true, nullable: bool.false,
},
text: {
type: types.string,
optional: bool.false, nullable: bool.true,
},
fileId: {
type: types.string,
optional: bool.true, nullable: bool.true,
format: 'id',
},
file: {
type: types.object,
optional: bool.true, nullable: bool.true,
ref: 'DriveFile',
},
recipientId: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id',
},
recipient: {
type: types.object,
optional: bool.true, nullable: bool.false,
ref: 'User'
},
isRead: {
type: types.boolean,
optional: bool.true, nullable: bool.false,
},
},
};

View File

@ -1,8 +1,11 @@
import { EntityRepository, Repository } from 'typeorm';
import { Users } from '..';
import rap from '@prezzemolo/rap';
import { Muting } from '../entities/muting';
import { ensure } from '../../prelude/ensure';
import { awaitAll } from '../../prelude/await-all';
import { types, bool, SchemaType } from '../../misc/schema';
export type PackedMuting = SchemaType<typeof packedMutingSchema>;
@EntityRepository(Muting)
export class MutingRepository extends Repository<Muting> {
@ -16,14 +19,47 @@ export class MutingRepository extends Repository<Muting> {
public async pack(
src: Muting['id'] | Muting,
me?: any
) {
): Promise<PackedMuting> {
const muting = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
return await rap({
return await awaitAll({
id: muting.id,
createdAt: muting.createdAt.toISOString(),
muteeId: muting.muteeId,
mutee: Users.pack(muting.muteeId, me, {
detail: true
})
});
}
}
export const packedMutingSchema = {
type: types.object,
optional: bool.false, nullable: bool.false,
properties: {
id: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id',
description: 'The unique identifier for this muting.',
example: 'xxxxxxxxxx',
},
createdAt: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'date-time',
description: 'The date that the muting was created.'
},
muteeId: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id',
},
mutee: {
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'User',
description: 'The mutee.'
},
}
};

View File

@ -2,18 +2,54 @@ import { EntityRepository, Repository } from 'typeorm';
import { NoteReaction } from '../entities/note-reaction';
import { Users } from '..';
import { ensure } from '../../prelude/ensure';
import { types, bool, SchemaType } from '../../misc/schema';
export type PackedNoteReaction = SchemaType<typeof packedNoteReactionSchema>;
@EntityRepository(NoteReaction)
export class NoteReactionRepository extends Repository<NoteReaction> {
public async pack(
src: NoteReaction['id'] | NoteReaction,
me?: any
) {
): Promise<PackedNoteReaction> {
const reaction = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
return {
id: reaction.id,
createdAt: reaction.createdAt.toISOString(),
user: await Users.pack(reaction.userId, me),
type: reaction.reaction,
};
}
}
export const packedNoteReactionSchema = {
type: types.object,
optional: bool.false, nullable: bool.false,
properties: {
id: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id',
description: 'The unique identifier for this reaction.',
example: 'xxxxxxxxxx',
},
createdAt: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'date-time',
description: 'The date that the reaction was created.'
},
user: {
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'User',
description: 'User who performed this reaction.'
},
type: {
type: types.string,
optional: bool.false, nullable: bool.false,
description: 'The reaction type.'
},
},
};

View File

@ -4,8 +4,11 @@ import { User } from '../entities/user';
import { unique, concat } from '../../prelude/array';
import { nyaize } from '../../misc/nyaize';
import { Emojis, Users, Apps, PollVotes, DriveFiles, NoteReactions, Followings, Polls } from '..';
import rap from '@prezzemolo/rap';
import { ensure } from '../../prelude/ensure';
import { SchemaType, types, bool } from '../../misc/schema';
import { awaitAll } from '../../prelude/await-all';
export type PackedNote = SchemaType<typeof packedNoteSchema>;
@EntityRepository(Note)
export class NoteRepository extends Repository<Note> {
@ -13,18 +16,18 @@ export class NoteRepository extends Repository<Note> {
return x.trim().length <= 100;
}
private async hideNote(packedNote: any, meId: User['id'] | null) {
private async hideNote(packedNote: PackedNote, meId: User['id'] | null) {
let hide = false;
// visibility が specified かつ自分が指定されていなかったら非表示
if (packedNote.visibility == 'specified') {
if (packedNote.visibility === 'specified') {
if (meId == null) {
hide = true;
} else if (meId === packedNote.userId) {
hide = false;
} else {
// 指定されているかどうか
const specified = packedNote.visibleUserIds.some((id: any) => meId === id);
const specified = packedNote.visibleUserIds!.some((id: any) => meId === id);
if (specified) {
hide = false;
@ -40,10 +43,10 @@ export class NoteRepository extends Repository<Note> {
hide = true;
} else if (meId === packedNote.userId) {
hide = false;
} else if (packedNote.reply && (meId === packedNote.reply.userId)) {
} else if (packedNote.reply && (meId === (packedNote.reply as PackedNote).userId)) {
// 自分の投稿に対するリプライ
hide = false;
} else if (packedNote.mentions && packedNote.mentions.some((id: any) => meId === id)) {
} else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) {
// 自分へのメンション
hide = false;
} else {
@ -62,14 +65,13 @@ export class NoteRepository extends Repository<Note> {
}
if (hide) {
packedNote.visibleUserIds = null;
packedNote.visibleUserIds = undefined;
packedNote.fileIds = [];
packedNote.files = [];
packedNote.text = null;
packedNote.poll = null;
packedNote.poll = undefined;
packedNote.cw = null;
packedNote.tags = [];
packedNote.geo = null;
packedNote.geo = undefined;
packedNote.isHidden = true;
}
}
@ -92,7 +94,7 @@ export class NoteRepository extends Repository<Note> {
detail?: boolean;
skipHide?: boolean;
}
): Promise<Record<string, any>> {
): Promise<PackedNote> {
const opts = Object.assign({
detail: true,
skipHide: false
@ -159,9 +161,9 @@ export class NoteRepository extends Repository<Note> {
const reactionEmojis = unique(concat([note.emojis, Object.keys(note.reactions)]));
const packed = await rap({
const packed = await awaitAll({
id: note.id,
createdAt: note.createdAt,
createdAt: note.createdAt.toISOString(),
app: note.appId ? Apps.pack(note.appId) : undefined,
userId: note.userId,
user: Users.pack(note.user || note.userId, meId),
@ -213,3 +215,127 @@ export class NoteRepository extends Repository<Note> {
return packed;
}
}
export const packedNoteSchema = {
type: types.object,
optional: bool.false, nullable: bool.false,
properties: {
id: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id',
description: 'The unique identifier for this Note.',
example: 'xxxxxxxxxx',
},
createdAt: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'date-time',
description: 'The date that the Note was created on Misskey.'
},
text: {
type: types.string,
optional: bool.false, nullable: bool.true,
},
cw: {
type: types.string,
optional: bool.true, nullable: bool.true,
},
userId: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id',
},
user: {
type: types.object,
ref: 'User',
optional: bool.false, nullable: bool.false,
},
replyId: {
type: types.string,
optional: bool.true, nullable: bool.true,
format: 'id',
example: 'xxxxxxxxxx',
},
renoteId: {
type: types.string,
optional: bool.true, nullable: bool.true,
format: 'id',
example: 'xxxxxxxxxx',
},
reply: {
type: types.object,
optional: bool.true, nullable: bool.true,
ref: 'Note'
},
renote: {
type: types.object,
optional: bool.true, nullable: bool.true,
ref: 'Note'
},
viaMobile: {
type: types.boolean,
optional: bool.true, nullable: bool.false,
},
isHidden: {
type: types.boolean,
optional: bool.true, nullable: bool.false,
},
visibility: {
type: types.string,
optional: bool.false, nullable: bool.false,
},
mentions: {
type: types.array,
optional: bool.true, nullable: bool.false,
items: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id'
}
},
visibleUserIds: {
type: types.array,
optional: bool.true, nullable: bool.false,
items: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id'
}
},
fileIds: {
type: types.array,
optional: bool.true, nullable: bool.false,
items: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id'
}
},
files: {
type: types.array,
optional: bool.true, nullable: bool.false,
items: {
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'DriveFile'
}
},
tags: {
type: types.array,
optional: bool.true, nullable: bool.false,
items: {
type: types.string,
optional: bool.false, nullable: bool.false,
}
},
poll: {
type: types.object,
optional: bool.true, nullable: bool.true,
},
geo: {
type: types.object,
optional: bool.true, nullable: bool.true,
},
},
};

View File

@ -1,8 +1,11 @@
import { EntityRepository, Repository } from 'typeorm';
import { Users, Notes } from '..';
import rap from '@prezzemolo/rap';
import { Notification } from '../entities/notification';
import { ensure } from '../../prelude/ensure';
import { awaitAll } from '../../prelude/await-all';
import { types, bool, SchemaType } from '../../misc/schema';
export type PackedNotification = SchemaType<typeof packedNotificationSchema>;
@EntityRepository(Notification)
export class NotificationRepository extends Repository<Notification> {
@ -14,12 +17,12 @@ export class NotificationRepository extends Repository<Notification> {
public async pack(
src: Notification['id'] | Notification,
) {
): Promise<PackedNotification> {
const notification = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
return await rap({
return await awaitAll({
id: notification.id,
createdAt: notification.createdAt,
createdAt: notification.createdAt.toISOString(),
type: notification.type,
userId: notification.notifierId,
user: Users.pack(notification.notifier || notification.notifierId),
@ -46,3 +49,39 @@ export class NotificationRepository extends Repository<Notification> {
});
}
}
export const packedNotificationSchema = {
type: types.object,
optional: bool.false, nullable: bool.false,
properties: {
id: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id',
description: 'The unique identifier for this notification.',
example: 'xxxxxxxxxx',
},
createdAt: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'date-time',
description: 'The date that the notification was created.'
},
type: {
type: types.string,
optional: bool.false, nullable: bool.false,
enum: ['follow', 'receiveFollowRequest', 'mention', 'reply', 'renote', 'quote', 'reaction', 'pollVote'],
description: 'The type of the notification.'
},
userId: {
type: types.string,
optional: bool.true, nullable: bool.true,
format: 'id',
},
user: {
type: types.object,
ref: 'User',
optional: bool.true, nullable: bool.true,
},
}
};

View File

@ -2,12 +2,15 @@ import { EntityRepository, Repository } from 'typeorm';
import { UserList } from '../entities/user-list';
import { ensure } from '../../prelude/ensure';
import { UserListJoinings } from '..';
import { bool, types, SchemaType } from '../../misc/schema';
export type PackedUserList = SchemaType<typeof packedUserListSchema>;
@EntityRepository(UserList)
export class UserListRepository extends Repository<UserList> {
public async pack(
src: UserList['id'] | UserList,
) {
): Promise<PackedUserList> {
const userList = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
const users = await UserListJoinings.find({
@ -16,8 +19,43 @@ export class UserListRepository extends Repository<UserList> {
return {
id: userList.id,
createdAt: userList.createdAt.toISOString(),
name: userList.name,
userIds: users.map(x => x.userId)
};
}
}
export const packedUserListSchema = {
type: types.object,
optional: bool.false, nullable: bool.false,
properties: {
id: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'id',
description: 'The unique identifier for this UserList.',
example: 'xxxxxxxxxx',
},
createdAt: {
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'date-time',
description: 'The date that the UserList was created.'
},
name: {
type: types.string,
optional: bool.false, nullable: bool.false,
description: 'The name of the UserList.'
},
userIds: {
type: types.array,
nullable: bool.false, optional: bool.true,
items: {
type: types.string,
nullable: bool.false, optional: bool.false,
format: 'id',
}
},
},
};

View File

@ -1,9 +1,12 @@
import { EntityRepository, Repository, In } from 'typeorm';
import { User, ILocalUser, IRemoteUser } from '../entities/user';
import { Emojis, Notes, NoteUnreads, FollowRequests, Notifications, MessagingMessages, UserNotePinings, Followings, Blockings, Mutings, UserProfiles } from '..';
import rap from '@prezzemolo/rap';
import { ensure } from '../../prelude/ensure';
import config from '../../config';
import { SchemaType, bool, types } from '../../misc/schema';
import { awaitAll } from '../../prelude/await-all';
export type PackedUser = SchemaType<typeof packedUserSchema>;
@EntityRepository(User)
export class UserRepository extends Repository<User> {
@ -71,7 +74,7 @@ export class UserRepository extends Repository<User> {
includeSecrets?: boolean,
includeHasUnreadNotes?: boolean
}
): Promise<Record<string, any>> {
): Promise<PackedUser> {
const opts = Object.assign({
detail: false,
includeSecrets: false
@ -86,7 +89,7 @@ export class UserRepository extends Repository<User> {
const falsy = opts.detail ? false : undefined;
return await rap({
const packed = {
id: user.id,
name: user.name,
username: user.username,
@ -120,8 +123,8 @@ export class UserRepository extends Repository<User> {
...(opts.detail ? {
url: profile!.url,
createdAt: user.createdAt,
updatedAt: user.updatedAt,
createdAt: user.createdAt.toISOString(),
updatedAt: user.updatedAt ? user.updatedAt.toISOString() : null,
bannerUrl: user.bannerUrl,
bannerColor: user.bannerColor,
isLocked: user.isLocked,
@ -144,6 +147,7 @@ export class UserRepository extends Repository<User> {
autoWatch: profile!.autoWatch,
alwaysMarkNsfw: profile!.alwaysMarkNsfw,
carefulBot: profile!.carefulBot,
twoFactorEnabled: profile!.twoFactorEnabled,
hasUnreadMessagingMessage: MessagingMessages.count({
where: {
recipientId: user.id,
@ -178,7 +182,9 @@ export class UserRepository extends Repository<User> {
isBlocked: relation.isBlocked,
isMuted: relation.isMuted,
} : {})
});
};
return await awaitAll(packed);
}
public isLocalUser(user: User): user is ILocalUser {
@ -215,3 +221,156 @@ export class UserRepository extends Repository<User> {
}
//#endregion
}
export const packedUserSchema = {
type: types.object,
nullable: bool.false, optional: bool.false,
properties: {
id: {
type: types.string,
nullable: bool.false, optional: bool.false,
format: 'id',
description: 'The unique identifier for this User.',
example: 'xxxxxxxxxx',
},
username: {
type: types.string,
nullable: bool.false, optional: bool.false,
description: 'The screen name, handle, or alias that this user identifies themselves with.',
example: 'ai'
},
name: {
type: types.string,
nullable: bool.true, optional: bool.false,
description: 'The name of the user, as theyve defined it.',
example: '藍'
},
url: {
type: types.string,
format: 'url',
nullable: bool.true, optional: bool.true,
},
avatarUrl: {
type: types.string,
format: 'url',
nullable: bool.true, optional: bool.false,
},
avatarColor: {
type: types.any,
nullable: bool.true, optional: bool.false,
},
bannerUrl: {
type: types.string,
format: 'url',
nullable: bool.true, optional: bool.true,
},
bannerColor: {
type: types.any,
nullable: bool.true, optional: bool.true,
},
emojis: {
type: types.any,
nullable: bool.true, optional: bool.false,
},
host: {
type: types.string,
nullable: bool.true, optional: bool.false,
example: 'misskey.example.com'
},
description: {
type: types.string,
nullable: bool.true, optional: bool.true,
description: 'The user-defined UTF-8 string describing their account.',
example: 'Hi masters, I am Ai!'
},
birthday: {
type: types.string,
nullable: bool.true, optional: bool.true,
example: '2018-03-12'
},
createdAt: {
type: types.string,
nullable: bool.false, optional: bool.true,
format: 'date-time',
description: 'The date that the user account was created on Misskey.'
},
updatedAt: {
type: types.string,
nullable: bool.true, optional: bool.true,
format: 'date-time',
},
location: {
type: types.string,
nullable: bool.true, optional: bool.true,
},
followersCount: {
type: types.number,
nullable: bool.false, optional: bool.true,
description: 'The number of followers this account currently has.'
},
followingCount: {
type: types.number,
nullable: bool.false, optional: bool.true,
description: 'The number of users this account is following.'
},
notesCount: {
type: types.number,
nullable: bool.false, optional: bool.true,
description: 'The number of Notes (including renotes) issued by the user.'
},
isBot: {
type: types.boolean,
nullable: bool.false, optional: bool.true,
description: 'Whether this account is a bot.'
},
pinnedNoteIds: {
type: types.array,
nullable: bool.false, optional: bool.true,
items: {
type: types.string,
nullable: bool.false, optional: bool.false,
format: 'id',
}
},
pinnedNotes: {
type: types.array,
nullable: bool.false, optional: bool.true,
items: {
type: types.object,
nullable: bool.false, optional: bool.false,
ref: 'Note'
}
},
isCat: {
type: types.boolean,
nullable: bool.false, optional: bool.true,
description: 'Whether this account is a cat.'
},
isAdmin: {
type: types.boolean,
nullable: bool.false, optional: bool.true,
description: 'Whether this account is the admin.'
},
isModerator: {
type: types.boolean,
nullable: bool.false, optional: bool.true,
description: 'Whether this account is a moderator.'
},
isVerified: {
type: types.boolean,
nullable: bool.false, optional: bool.true,
},
isLocked: {
type: types.boolean,
nullable: bool.false, optional: bool.true,
},
hasUnreadSpecifiedNotes: {
type: types.boolean,
nullable: bool.false, optional: bool.true,
},
hasUnreadMentions: {
type: types.boolean,
nullable: bool.false, optional: bool.true,
},
},
};

23
src/prelude/await-all.ts Normal file
View File

@ -0,0 +1,23 @@
type Await<T> = T extends Promise<infer U> ? U : T;
type AwaitAll<T> = {
[P in keyof T]: Await<T[P]>;
};
export async function awaitAll<T>(obj: T): Promise<AwaitAll<T>> {
const target = {} as any;
const keys = Object.keys(obj);
const values = Object.values(obj);
const resolvedValues = await Promise.all(values.map(value =>
(!value || !value.constructor || value.constructor.name !== 'Object')
? value
: awaitAll(value)
));
for (let i = 0; i < keys.length; i++) {
target[keys[i]] = resolvedValues[i];
}
return target;
}

View File

@ -10,7 +10,7 @@ import { registerOrFetchInstanceDoc } from '../../services/register-or-fetch-ins
import { Instances, Users, UserPublickeys } from '../../models';
import { instanceChart } from '../../services/chart';
import { UserPublickey } from '../../models/entities/user-publickey';
import fetchMeta from '../../misc/fetch-meta';
import { fetchMeta } from '../../misc/fetch-meta';
import { toPuny } from '../../misc/convert-host';
import { validActor } from '../../remote/activitypub/type';
import { ensure } from '../../prelude/ensure';
@ -48,7 +48,6 @@ export default async (job: Bull.Job): Promise<void> => {
}
// ブロックしてたら中断
// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
const meta = await fetchMeta();
if (meta.blockedHosts.includes(host)) {
logger.info(`Blocked request: ${host}`);

View File

@ -6,7 +6,7 @@ import { fetchNote, resolveNote } from '../../models/note';
import { resolvePerson } from '../../models/person';
import { apLogger } from '../../logger';
import { extractDbHost } from '../../../../misc/convert-host';
import fetchMeta from '../../../../misc/fetch-meta';
import { fetchMeta } from '../../../../misc/fetch-meta';
const logger = apLogger;
@ -26,7 +26,6 @@ export default async function(resolver: Resolver, actor: IRemoteUser, activity:
}
// アナウンス先をブロックしてたら中断
// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
const meta = await fetchMeta();
if (meta.blockedHosts.includes(extractDbHost(uri))) return;

View File

@ -1,7 +1,7 @@
import uploadFromUrl from '../../../services/drive/upload-from-url';
import { IRemoteUser } from '../../../models/entities/user';
import Resolver from '../resolver';
import fetchMeta from '../../../misc/fetch-meta';
import { fetchMeta } from '../../../misc/fetch-meta';
import { apLogger } from '../logger';
import { DriveFile } from '../../../models/entities/drive-file';
import { DriveFiles } from '../../../models';

View File

@ -20,7 +20,7 @@ import { Note } from '../../../models/entities/note';
import { IObject, INote } from '../type';
import { Emoji } from '../../../models/entities/emoji';
import { genId } from '../../../misc/gen-id';
import fetchMeta from '../../../misc/fetch-meta';
import { fetchMeta } from '../../../misc/fetch-meta';
import { ensure } from '../../../prelude/ensure';
const logger = apLogger;
@ -233,7 +233,6 @@ export async function resolveNote(value: string | IObject, resolver?: Resolver):
if (uri == null) throw new Error('missing uri');
// ブロックしてたら中断
// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
const meta = await fetchMeta();
if (meta.blockedHosts.includes(extractDbHost(uri))) throw { statusCode: 451 };

View File

@ -271,11 +271,6 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
}
//#endregion
// 繋がらないインスタンスに何回も試行するのを防ぐ, 後続の同様処理の連続試行を防ぐ ため 試行前にも更新する
await Users.update(exist.id, {
lastFetchedAt: new Date(),
});
if (resolver == null) resolver = new Resolver();
const object = hint || await resolver.resolve(uri) as any;
@ -318,11 +313,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
sharedInbox: person.sharedInbox || (person.endpoints ? person.endpoints.sharedInbox : undefined),
featured: person.featured,
emojis: emojiNames,
description: person.summary ? fromHtml(person.summary) : null,
name: person.name,
url: person.url,
endpoints: person.endpoints,
fields,
tags,
isBot: object.type == 'Service',
isCat: (person as any).isCat === true,
@ -350,13 +341,16 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
});
await UserProfiles.update({ userId: exist.id }, {
twitterUserId: services.twitter.userId,
twitterScreenName: services.twitter.screenName,
githubId: services.github.id,
githubLogin: services.github.login,
discordId: services.discord.id,
discordUsername: services.discord.username,
discordDiscriminator: services.discord.discriminator,
url: person.url,
fields,
description: person.summary ? fromHtml(person.summary) : null,
twitterUserId: services.twitter ? services.twitter.userId : null,
twitterScreenName: services.twitter ? services.twitter.screenName : null,
githubId: services.github ? services.github.id : null,
githubLogin: services.github ? services.github.login : null,
discordId: services.discord ? services.discord.id : null,
discordUsername: services.discord ? services.discord.username : null,
discordDiscriminator: services.discord ? services.discord.discriminator : null,
});
// ハッシュタグ更新

View File

@ -10,7 +10,7 @@ import { ILocalUser } from '../../models/entities/user';
import { publishApLogStream } from '../../services/stream';
import { apLogger } from './logger';
import { UserKeypairs } from '../../models';
import fetchMeta from '../../misc/fetch-meta';
import { fetchMeta } from '../../misc/fetch-meta';
import { toPuny } from '../../misc/convert-host';
import { ensure } from '../../prelude/ensure';
@ -24,7 +24,6 @@ export default async (user: ILocalUser, url: string, object: any) => {
const { protocol, host, hostname, port, pathname, search } = new URL(url);
// ブロックしてたら中断
// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
const meta = await fetchMeta();
if (meta.blockedHosts.includes(toPuny(host))) return;

View File

@ -37,7 +37,7 @@ export async function resolveUser(username: string, host: string | null, option?
});
}
const user = await Users.findOne({ usernameLower, host }, option);
const user = await Users.findOne({ usernameLower, host }, option) as IRemoteUser;
const acctLower = `${usernameLower}@${host}`;
@ -48,14 +48,20 @@ export async function resolveUser(username: string, host: string | null, option?
return await createPerson(self.href);
}
if (resync) {
// resyncオプション OR ユーザー情報が古い場合は、WebFilgerからやりなおして返す
if (resync || user.lastFetchedAt == null || Date.now() - user.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) {
// 繋がらないインスタンスに何回も試行するのを防ぐ, 後続の同様処理の連続試行を防ぐ ため 試行前にも更新する
await Users.update(user.id, {
lastFetchedAt: new Date(),
});
logger.info(`try resync: ${acctLower}`);
const self = await resolveSelf(acctLower);
if ((user as IRemoteUser).uri !== self.href) {
if (user.uri !== self.href) {
// if uri mismatch, Fix (user@host <=> AP's Person id(IRemoteUser.uri)) mapping.
logger.info(`uri missmatch: ${acctLower}`);
logger.info(`recovery missmatch uri for (username=${username}, host=${host}) from ${(user as IRemoteUser).uri} to ${self.href}`);
logger.info(`recovery missmatch uri for (username=${username}, host=${host}) from ${user.uri} to ${self.href}`);
// validate uri
const uri = new URL(self.href);

View File

@ -3,6 +3,7 @@ import { ILocalUser } from '../../models/entities/user';
import { IEndpointMeta } from './endpoints';
import { ApiError } from './error';
import { App } from '../../models/entities/app';
import { SchemaType } from '../../misc/schema';
type Params<T extends IEndpointMeta> = {
[P in keyof T['params']]: NonNullable<T['params']>[P]['transform'] extends Function
@ -12,7 +13,11 @@ type Params<T extends IEndpointMeta> = {
export type Response = Record<string, any> | void;
export default function <T extends IEndpointMeta>(meta: T, cb: (params: Params<T>, user: ILocalUser, app: App, file?: any, cleanup?: Function) => Promise<Response>): (params: any, user: ILocalUser, app: App, file?: any) => Promise<any> {
type executor<T extends IEndpointMeta> =
(params: Params<T>, user: ILocalUser, app: App, file?: any, cleanup?: Function) => Promise<T['res'] extends undefined ? Response : SchemaType<NonNullable<T['res']>>>;
export default function <T extends IEndpointMeta>(meta: T, cb: executor<T>)
: (params: any, user: ILocalUser, app: App, file?: any) => Promise<any> {
return (params: any, user: ILocalUser, app: App, file?: any) => {
function cleanup() {
fs.unlink(file.path, () => {});

View File

@ -164,7 +164,7 @@ export const meta = {
},
maintainerName: {
validator: $.optional.str,
validator: $.optional.nullable.str,
desc: {
'ja-JP': 'インスタンスの管理者名'
}

View File

@ -9,7 +9,7 @@ import { extractDbHost } from '../../../../misc/convert-host';
import { Users, Notes } from '../../../../models';
import { Note } from '../../../../models/entities/note';
import { User } from '../../../../models/entities/user';
import fetchMeta from '../../../../misc/fetch-meta';
import { fetchMeta } from '../../../../misc/fetch-meta';
import { validActor } from '../../../../remote/activitypub/type';
export const meta = {

View File

@ -4,12 +4,13 @@ import define from '../../define';
import { Apps } from '../../../../models';
import { genId } from '../../../../misc/gen-id';
import { unique } from '../../../../prelude/array';
import { types, bool } from '../../../../misc/schema';
export const meta = {
tags: ['app'],
requireCredential: false,
desc: {
'ja-JP': 'アプリを作成します。',
'en-US': 'Create a application.'
@ -50,29 +51,12 @@ export const meta = {
}
},
},
res: {
type: 'object',
properties: {
id: {
type: 'string',
description: 'アプリケーションのID'
},
name: {
type: 'string',
description: 'アプリケーションの名前'
},
callbackUrl: {
type: 'string',
nullable: true,
description: 'コールバックするURL'
},
secret: {
type: 'string',
description: 'アプリケーションのシークレットキー'
}
}
}
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'App',
},
};
export default define(meta, async (ps, user) => {

View File

@ -3,6 +3,7 @@ import { ID } from '../../../../misc/cafy-id';
import define from '../../define';
import { ApiError } from '../../error';
import { Apps } from '../../../../models';
import { types, bool } from '../../../../misc/schema';
export const meta = {
tags: ['app'],
@ -13,6 +14,12 @@ export const meta = {
},
},
res: {
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'App',
},
errors: {
noSuchApp: {
message: 'No such app.',

View File

@ -5,12 +5,13 @@ import define from '../../../define';
import { ApiError } from '../../../error';
import { Apps, AuthSessions } from '../../../../../models';
import { genId } from '../../../../../misc/gen-id';
import { types, bool } from '../../../../../misc/schema';
export const meta = {
tags: ['auth'],
requireCredential: false,
desc: {
'ja-JP': 'アプリを認証するためのトークンを作成します。',
'en-US': 'Generate a token for authorize application.'
@ -27,14 +28,18 @@ export const meta = {
},
res: {
type: 'object',
type: types.object,
optional: bool.false, nullable: bool.false,
properties: {
token: {
type: 'string',
type: types.string,
optional: bool.false, nullable: bool.false,
description: 'セッションのトークン'
},
url: {
type: 'string',
type: types.string,
optional: bool.false, nullable: bool.false,
format: 'url',
description: 'セッションのURL'
},
}

View File

@ -3,6 +3,7 @@ import define from '../../../define';
import { ApiError } from '../../../error';
import { Apps, AuthSessions, AccessTokens, Users } from '../../../../../models';
import { ensure } from '../../../../../prelude/ensure';
import { types, bool } from '../../../../../misc/schema';
export const meta = {
tags: ['auth'],
@ -28,15 +29,19 @@ export const meta = {
},
res: {
type: 'object',
type: types.object,
optional: bool.false, nullable: bool.false,
properties: {
accessToken: {
type: 'string',
type: types.string,
optional: bool.false, nullable: bool.false,
description: 'ユーザーのアクセストークン',
},
user: {
type: 'User',
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'User',
description: '認証したユーザー'
},
}

View File

@ -3,6 +3,7 @@ import { ID } from '../../../../misc/cafy-id';
import define from '../../define';
import { Blockings } from '../../../../models';
import { makePaginationQuery } from '../../common/make-pagination-query';
import { types, bool } from '../../../../misc/schema';
export const meta = {
desc: {
@ -32,9 +33,12 @@ export const meta = {
},
res: {
type: 'array',
type: types.array,
optional: bool.false, nullable: bool.false,
items: {
type: 'Blocking',
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'Blocking',
}
},
};

View File

@ -1,6 +1,7 @@
import define from '../define';
import fetchMeta from '../../../misc/fetch-meta';
import { fetchMeta } from '../../../misc/fetch-meta';
import { DriveFiles } from '../../../models';
import { types, bool } from '../../../misc/schema';
export const meta = {
desc: {
@ -15,20 +16,23 @@ export const meta = {
kind: 'read:drive',
res: {
type: 'object',
type: types.object,
optional: bool.false, nullable: bool.false,
properties: {
capacity: {
type: 'number'
type: types.number,
optional: bool.false, nullable: bool.false,
},
usage: {
type: 'number'
type: types.number,
optional: bool.false, nullable: bool.false,
}
}
}
};
export default define(meta, async (ps, user) => {
const instance = await fetchMeta();
const instance = await fetchMeta(true);
// Calculate drive usage
const usage = await DriveFiles.clacDriveUsageOf(user);

View File

@ -3,6 +3,7 @@ import { ID } from '../../../../misc/cafy-id';
import define from '../../define';
import { DriveFiles } from '../../../../models';
import { makePaginationQuery } from '../../common/make-pagination-query';
import { types, bool } from '../../../../misc/schema';
export const meta = {
desc: {
@ -41,10 +42,13 @@ export const meta = {
},
res: {
type: 'array',
type: types.array,
optional: bool.false, nullable: bool.false,
items: {
type: 'DriveFile',
},
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'DriveFile',
}
},
};

View File

@ -2,7 +2,8 @@ import $ from 'cafy';
import { ID } from '../../../../../misc/cafy-id';
import define from '../../../define';
import { ApiError } from '../../../error';
import { DriveFiles } from '../../../../../models';
import { DriveFiles, Notes } from '../../../../../models';
import { types, bool } from '../../../../../misc/schema';
export const meta = {
stability: 'stable',
@ -29,10 +30,13 @@ export const meta = {
},
res: {
type: 'array',
type: types.array,
optional: bool.false, nullable: bool.false,
items: {
type: 'Note',
},
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'Note',
}
},
errors: {
@ -55,8 +59,11 @@ export default define(meta, async (ps, user) => {
throw new ApiError(meta.errors.noSuchFile);
}
/* v11 TODO
return await packMany(file.metadata.attachedNoteIds || [], user, {
const notes = await Notes.createQueryBuilder('note')
.where(':file = ANY(note.fileIds)', { file: file.id })
.getMany();
return await Notes.packMany(notes, user, {
detail: true
});*/
});
});

View File

@ -1,6 +1,7 @@
import $ from 'cafy';
import define from '../../../define';
import { DriveFiles } from '../../../../../models';
import { types, bool } from '../../../../../misc/schema';
export const meta = {
desc: {
@ -24,7 +25,8 @@ export const meta = {
},
res: {
type: 'DriveFile',
type: types.boolean,
optional: bool.false, nullable: bool.false,
},
};
@ -34,7 +36,5 @@ export default define(meta, async (ps, user) => {
userId: user.id,
});
return {
file: file ? await DriveFiles.pack(file, { self: true }) : null
};
return file != null;
});

View File

@ -6,6 +6,7 @@ import define from '../../../define';
import { apiLogger } from '../../../logger';
import { ApiError } from '../../../error';
import { DriveFiles } from '../../../../../models';
import { types, bool } from '../../../../../misc/schema';
export const meta = {
desc: {
@ -56,7 +57,9 @@ export const meta = {
},
res: {
type: 'DriveFile',
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'DriveFile',
},
errors: {
@ -87,7 +90,7 @@ export default define(meta, async (ps, user, app, file, cleanup) => {
try {
// Create file
const driveFile = await create(user, file.path, name, null, ps.folderId, ps.force, false, null, null, ps.isSensitive);
return DriveFiles.pack(driveFile, { self: true });
return await DriveFiles.pack(driveFile, { self: true });
} catch (e) {
apiLogger.error(e);
throw new ApiError();

View File

@ -0,0 +1,44 @@
import $ from 'cafy';
import define from '../../../define';
import { DriveFiles } from '../../../../../models';
import { types, bool } from '../../../../../misc/schema';
export const meta = {
desc: {
'ja-JP': '与えられたMD5ハッシュ値を持つファイルを取得します。',
},
tags: ['drive'],
requireCredential: true,
kind: 'read:drive',
params: {
md5: {
validator: $.str,
desc: {
'ja-JP': 'ファイルのMD5ハッシュ'
}
}
},
res: {
type: types.array,
optional: bool.false, nullable: bool.false,
items: {
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'DriveFile',
}
},
};
export default define(meta, async (ps, user) => {
const files = await DriveFiles.find({
md5: ps.md5,
userId: user.id,
});
return await DriveFiles.packMany(files, { self: true });
});

View File

@ -2,6 +2,7 @@ import $ from 'cafy';
import { ID } from '../../../../../misc/cafy-id';
import define from '../../../define';
import { DriveFiles } from '../../../../../models';
import { types, bool } from '../../../../../misc/schema';
export const meta = {
requireCredential: true,
@ -22,7 +23,17 @@ export const meta = {
'ja-JP': 'フォルダID'
}
},
}
},
res: {
type: types.array,
optional: bool.false, nullable: bool.false,
items: {
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'DriveFile',
}
},
};
export default define(meta, async (ps, user) => {

View File

@ -4,6 +4,7 @@ import define from '../../../define';
import { ApiError } from '../../../error';
import { DriveFile } from '../../../../../models/entities/drive-file';
import { DriveFiles } from '../../../../../models';
import { types, bool } from '../../../../../misc/schema';
export const meta = {
stability: 'stable',
@ -38,7 +39,9 @@ export const meta = {
},
res: {
type: 'DriveFile',
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'DriveFile',
},
errors: {

View File

@ -3,6 +3,7 @@ import { ID } from '../../../../misc/cafy-id';
import define from '../../define';
import { DriveFolders } from '../../../../models';
import { makePaginationQuery } from '../../common/make-pagination-query';
import { types, bool } from '../../../../misc/schema';
export const meta = {
desc: {
@ -37,10 +38,13 @@ export const meta = {
},
res: {
type: 'array',
type: types.array,
optional: bool.false, nullable: bool.false,
items: {
type: 'DriveFolder',
},
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'DriveFolder',
}
},
};

View File

@ -2,6 +2,7 @@ import $ from 'cafy';
import { ID } from '../../../../../misc/cafy-id';
import define from '../../../define';
import { DriveFolders } from '../../../../../models';
import { types, bool } from '../../../../../misc/schema';
export const meta = {
tags: ['drive'],
@ -25,10 +26,13 @@ export const meta = {
},
res: {
type: 'array',
type: types.array,
optional: bool.false, nullable: bool.false,
items: {
type: 'DriveFolder',
},
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'DriveFolder',
}
},
};

View File

@ -3,6 +3,7 @@ import { ID } from '../../../../../misc/cafy-id';
import define from '../../../define';
import { ApiError } from '../../../error';
import { DriveFolders } from '../../../../../models';
import { types, bool } from '../../../../../misc/schema';
export const meta = {
stability: 'stable',
@ -29,7 +30,9 @@ export const meta = {
},
res: {
type: 'DriveFolder',
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'DriveFolder',
},
errors: {

View File

@ -3,6 +3,7 @@ import { ID } from '../../../../misc/cafy-id';
import define from '../../define';
import { DriveFiles } from '../../../../models';
import { makePaginationQuery } from '../../common/make-pagination-query';
import { types, bool } from '../../../../misc/schema';
export const meta = {
tags: ['drive'],
@ -31,10 +32,13 @@ export const meta = {
},
res: {
type: 'array',
type: types.array,
optional: bool.false, nullable: bool.false,
items: {
type: 'DriveFile',
},
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'DriveFile',
}
},
};

View File

@ -1,7 +1,7 @@
import $ from 'cafy';
import define from '../../define';
import { Instances } from '../../../../models';
import fetchMeta from '../../../../misc/fetch-meta';
import { fetchMeta } from '../../../../misc/fetch-meta';
export const meta = {
tags: ['federation'],
@ -62,7 +62,7 @@ export default define(meta, async (ps, me) => {
}
if (typeof ps.blocked === 'boolean') {
const meta = await fetchMeta();
const meta = await fetchMeta(true);
if (ps.blocked) {
query.andWhere('instance.host IN (:...blocks)', { blocks: meta.blockedHosts });
} else {

View File

@ -1,6 +1,7 @@
import $ from 'cafy';
import define from '../../define';
import { Hashtags } from '../../../../models';
import { types, bool } from '../../../../misc/schema';
export const meta = {
tags: ['hashtags'],
@ -47,9 +48,12 @@ export const meta = {
},
res: {
type: 'array',
type: types.array,
optional: bool.false, nullable: bool.false,
items: {
type: 'Hashtag'
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'Hashtag',
}
},
};

View File

@ -1,6 +1,7 @@
import $ from 'cafy';
import define from '../../define';
import { Hashtags } from '../../../../models';
import { types, bool } from '../../../../misc/schema';
export const meta = {
desc: {
@ -37,9 +38,11 @@ export const meta = {
},
res: {
type: 'array',
type: types.array,
optional: bool.false, nullable: bool.false,
items: {
type: 'string'
type: types.string,
optional: bool.false, nullable: bool.false,
}
},
};

View File

@ -1,5 +1,5 @@
import define from '../../define';
import fetchMeta from '../../../../misc/fetch-meta';
import { fetchMeta } from '../../../../misc/fetch-meta';
import { Notes } from '../../../../models';
import { Note } from '../../../../models/entities/note';
@ -24,7 +24,7 @@ export const meta = {
};
export default define(meta, async () => {
const instance = await fetchMeta();
const instance = await fetchMeta(true);
const hiddenTags = instance.hiddenTags.map(t => t.toLowerCase());
const tagNotes = await Notes.createQueryBuilder('note')

View File

@ -1,6 +1,7 @@
import $ from 'cafy';
import define from '../../define';
import { Users } from '../../../../models';
import { types, bool } from '../../../../misc/schema';
export const meta = {
requireCredential: false,
@ -47,9 +48,12 @@ export const meta = {
},
res: {
type: 'array',
type: types.array,
optional: bool.false, nullable: bool.false,
items: {
type: 'User'
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'User',
}
},
};

View File

@ -1,5 +1,6 @@
import define from '../define';
import { Users } from '../../../models';
import { types, bool } from '../../../misc/schema';
export const meta = {
stability: 'stable',
@ -15,8 +16,10 @@ export const meta = {
params: {},
res: {
type: 'User',
}
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'User',
},
};
export default define(meta, async (ps, user, app) => {

View File

@ -4,6 +4,7 @@ import { readNotification } from '../../common/read-notification';
import define from '../../define';
import { makePaginationQuery } from '../../common/make-pagination-query';
import { Notifications, Followings, Mutings } from '../../../../models';
import { types, bool } from '../../../../misc/schema';
export const meta = {
desc: {
@ -53,10 +54,13 @@ export const meta = {
},
res: {
type: 'array',
type: types.array,
optional: bool.false, nullable: bool.false,
items: {
type: 'Notification',
},
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'Notification',
}
},
};

View File

@ -2,7 +2,7 @@ import $ from 'cafy';
import { publishMainStream } from '../../../../services/stream';
import define from '../../define';
import * as nodemailer from 'nodemailer';
import fetchMeta from '../../../../misc/fetch-meta';
import { fetchMeta } from '../../../../misc/fetch-meta';
import rndstr from 'rndstr';
import config from '../../../../config';
import * as ms from 'ms';
@ -63,7 +63,7 @@ export default define(meta, async (ps, user) => {
emailVerifyCode: code
});
const meta = await fetchMeta();
const meta = await fetchMeta(true);
const enableAuth = meta.smtpUser != null && meta.smtpUser !== '';

View File

@ -3,6 +3,7 @@ import define from '../../define';
import { MessagingMessage } from '../../../../models/entities/messaging-message';
import { MessagingMessages, Mutings } from '../../../../models';
import { Brackets } from 'typeorm';
import { types, bool } from '../../../../misc/schema';
export const meta = {
desc: {
@ -24,10 +25,13 @@ export const meta = {
},
res: {
type: 'array',
type: types.array,
optional: bool.false, nullable: bool.false,
items: {
type: 'MessagingMessage',
},
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'MessagingMessage',
}
},
};

View File

@ -6,6 +6,7 @@ import { ApiError } from '../../error';
import { getUser } from '../../common/getters';
import { MessagingMessages } from '../../../../models';
import { makePaginationQuery } from '../../common/make-pagination-query';
import { types, bool } from '../../../../misc/schema';
export const meta = {
desc: {
@ -48,10 +49,13 @@ export const meta = {
},
res: {
type: 'array',
type: types.array,
optional: bool.false, nullable: bool.false,
items: {
type: 'MessagingMessage',
},
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'MessagingMessage',
}
},
errors: {

View File

@ -9,6 +9,7 @@ import { getUser } from '../../../common/getters';
import { MessagingMessages, DriveFiles, Mutings } from '../../../../../models';
import { MessagingMessage } from '../../../../../models/entities/messaging-message';
import { genId } from '../../../../../misc/gen-id';
import { types, bool } from '../../../../../misc/schema';
export const meta = {
desc: {
@ -41,7 +42,9 @@ export const meta = {
},
res: {
type: 'MessagingMessage',
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'MessagingMessage',
},
errors: {

View File

@ -2,9 +2,10 @@ import $ from 'cafy';
import * as os from 'os';
import config from '../../../config';
import define from '../define';
import fetchMeta from '../../../misc/fetch-meta';
import { fetchMeta } from '../../../misc/fetch-meta';
import * as pkg from '../../../../package.json';
import { Emojis } from '../../../models';
import { types, bool } from '../../../misc/schema';
export const meta = {
stability: 'stable',
@ -26,32 +27,40 @@ export const meta = {
},
res: {
type: 'object',
type: types.object,
optional: bool.false, nullable: bool.false,
properties: {
version: {
type: 'string',
type: types.string,
optional: bool.false, nullable: bool.false,
description: 'The version of Misskey of this instance.',
example: pkg.version
},
name: {
type: 'string',
type: types.string,
optional: bool.false, nullable: bool.false,
description: 'The name of this instance.',
},
description: {
type: 'string',
type: types.string,
optional: bool.false, nullable: bool.false,
description: 'The description of this instance.',
},
announcements: {
type: 'array',
type: types.array,
optional: bool.false, nullable: bool.false,
items: {
type: 'object',
type: types.object,
optional: bool.false, nullable: bool.false,
properties: {
title: {
type: 'string',
type: types.string,
optional: bool.false, nullable: bool.false,
description: 'The title of the announcement.',
},
text: {
type: 'string',
type: types.string,
optional: bool.false, nullable: bool.false,
description: 'The text of the announcement. (can be HTML)',
},
}
@ -59,19 +68,23 @@ export const meta = {
description: 'The announcements of this instance.',
},
disableRegistration: {
type: 'boolean',
type: types.boolean,
optional: bool.false, nullable: bool.false,
description: 'Whether disabled open registration.',
},
disableLocalTimeline: {
type: 'boolean',
type: types.boolean,
optional: bool.false, nullable: bool.false,
description: 'Whether disabled LTL and STL.',
},
disableGlobalTimeline: {
type: 'boolean',
type: types.boolean,
optional: bool.false, nullable: bool.false,
description: 'Whether disabled GTL.',
},
enableEmojiReaction: {
type: 'boolean',
type: types.boolean,
optional: bool.false, nullable: bool.false,
description: 'Whether enabled emoji reaction.',
},
}
@ -79,7 +92,7 @@ export const meta = {
};
export default define(meta, async (ps, me) => {
const instance = await fetchMeta();
const instance = await fetchMeta(true);
const emojis = await Emojis.find({ host: null });

View File

@ -3,6 +3,7 @@ import { ID } from '../../../../misc/cafy-id';
import define from '../../define';
import { makePaginationQuery } from '../../common/make-pagination-query';
import { Mutings } from '../../../../models';
import { types, bool } from '../../../../misc/schema';
export const meta = {
desc: {
@ -32,9 +33,12 @@ export const meta = {
},
res: {
type: 'array',
type: types.array,
optional: bool.false, nullable: bool.false,
items: {
type: 'Muting',
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'Muting',
}
},
};

View File

@ -3,6 +3,7 @@ import { ID } from '../../../misc/cafy-id';
import define from '../define';
import { makePaginationQuery } from '../common/make-pagination-query';
import { Notes } from '../../../models';
import { types, bool } from '../../../misc/schema';
export const meta = {
desc: {
@ -62,9 +63,12 @@ export const meta = {
},
res: {
type: 'array',
type: types.array,
optional: bool.false, nullable: bool.false,
items: {
type: 'Note',
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'Note',
}
},
};

View File

@ -6,6 +6,7 @@ import { generateVisibilityQuery } from '../../common/generate-visibility-query'
import { generateMuteQuery } from '../../common/generate-mute-query';
import { Brackets } from 'typeorm';
import { Notes } from '../../../../models';
import { types, bool } from '../../../../misc/schema';
export const meta = {
desc: {
@ -41,10 +42,13 @@ export const meta = {
},
res: {
type: 'array',
type: types.array,
optional: bool.false, nullable: bool.false,
items: {
type: 'Note',
},
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'Note',
}
},
};

View File

@ -5,6 +5,7 @@ import { ApiError } from '../../error';
import { getNote } from '../../common/getters';
import { Note } from '../../../../models/entities/note';
import { Notes } from '../../../../models';
import { types, bool } from '../../../../misc/schema';
export const meta = {
desc: {
@ -37,10 +38,13 @@ export const meta = {
},
res: {
type: 'array',
type: types.array,
optional: bool.false, nullable: bool.false,
items: {
type: 'Note',
},
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'Note',
}
},
errors: {

View File

@ -3,13 +3,14 @@ import * as ms from 'ms';
import { length } from 'stringz';
import create from '../../../../services/note/create';
import define from '../../define';
import fetchMeta from '../../../../misc/fetch-meta';
import { fetchMeta } from '../../../../misc/fetch-meta';
import { ApiError } from '../../error';
import { ID } from '../../../../misc/cafy-id';
import { User } from '../../../../models/entities/user';
import { Users, DriveFiles, Notes } from '../../../../models';
import { DriveFile } from '../../../../models/entities/drive-file';
import { Note } from '../../../../models/entities/note';
import { types, bool } from '../../../../misc/schema';
let maxNoteTextLength = 1000;
@ -174,10 +175,13 @@ export const meta = {
},
res: {
type: 'object',
type: types.object,
optional: bool.false, nullable: bool.false,
properties: {
createdNote: {
type: 'Note',
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'Note',
description: '作成した投稿'
}
}

View File

@ -2,6 +2,7 @@ import $ from 'cafy';
import define from '../../define';
import { generateMuteQuery } from '../../common/generate-mute-query';
import { Notes } from '../../../../models';
import { types, bool } from '../../../../misc/schema';
export const meta = {
desc: {
@ -24,10 +25,13 @@ export const meta = {
},
res: {
type: 'array',
type: types.array,
optional: bool.false, nullable: bool.false,
items: {
type: 'Note',
},
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'Note',
}
},
};

View File

@ -1,12 +1,13 @@
import $ from 'cafy';
import { ID } from '../../../../misc/cafy-id';
import define from '../../define';
import fetchMeta from '../../../../misc/fetch-meta';
import { fetchMeta } from '../../../../misc/fetch-meta';
import { ApiError } from '../../error';
import { makePaginationQuery } from '../../common/make-pagination-query';
import { Notes } from '../../../../models';
import { generateMuteQuery } from '../../common/generate-mute-query';
import { activeUsersChart } from '../../../../services/chart';
import { types, bool } from '../../../../misc/schema';
export const meta = {
desc: {
@ -46,10 +47,13 @@ export const meta = {
},
res: {
type: 'array',
type: types.array,
optional: bool.false, nullable: bool.false,
items: {
type: 'Note',
},
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'Note',
}
},
errors: {
@ -62,7 +66,6 @@ export const meta = {
};
export default define(meta, async (ps, user) => {
// TODO どっかにキャッシュ
const m = await fetchMeta();
if (m.disableGlobalTimeline) {
if (user == null || (!user.isAdmin && !user.isModerator)) {

View File

@ -1,7 +1,7 @@
import $ from 'cafy';
import { ID } from '../../../../misc/cafy-id';
import define from '../../define';
import fetchMeta from '../../../../misc/fetch-meta';
import { fetchMeta } from '../../../../misc/fetch-meta';
import { ApiError } from '../../error';
import { makePaginationQuery } from '../../common/make-pagination-query';
import { Followings, Notes } from '../../../../models';
@ -9,6 +9,7 @@ import { Brackets } from 'typeorm';
import { generateVisibilityQuery } from '../../common/generate-visibility-query';
import { generateMuteQuery } from '../../common/generate-mute-query';
import { activeUsersChart } from '../../../../services/chart';
import { types, bool } from '../../../../misc/schema';
export const meta = {
desc: {
@ -89,10 +90,13 @@ export const meta = {
},
res: {
type: 'array',
type: types.array,
optional: bool.false, nullable: bool.false,
items: {
type: 'Note',
},
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'Note',
}
},
errors: {
@ -105,7 +109,6 @@ export const meta = {
};
export default define(meta, async (ps, user) => {
// TODO どっかにキャッシュ
const m = await fetchMeta();
if (m.disableLocalTimeline && !user.isAdmin && !user.isModerator) {
throw new ApiError(meta.errors.stlDisabled);

View File

@ -1,7 +1,7 @@
import $ from 'cafy';
import { ID } from '../../../../misc/cafy-id';
import define from '../../define';
import fetchMeta from '../../../../misc/fetch-meta';
import { fetchMeta } from '../../../../misc/fetch-meta';
import { ApiError } from '../../error';
import { Notes } from '../../../../models';
import { generateMuteQuery } from '../../common/generate-mute-query';
@ -9,6 +9,7 @@ import { makePaginationQuery } from '../../common/make-pagination-query';
import { generateVisibilityQuery } from '../../common/generate-visibility-query';
import { activeUsersChart } from '../../../../services/chart';
import { Brackets } from 'typeorm';
import { types, bool } from '../../../../misc/schema';
export const meta = {
desc: {
@ -63,10 +64,13 @@ export const meta = {
},
res: {
type: 'array',
type: types.array,
optional: bool.false, nullable: bool.false,
items: {
type: 'Note',
},
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'Note',
}
},
errors: {
@ -79,7 +83,6 @@ export const meta = {
};
export default define(meta, async (ps, user) => {
// TODO どっかにキャッシュ
const m = await fetchMeta();
if (m.disableLocalTimeline) {
if (user == null || (!user.isAdmin && !user.isModerator)) {

View File

@ -7,6 +7,7 @@ import { generateVisibilityQuery } from '../../common/generate-visibility-query'
import { generateMuteQuery } from '../../common/generate-mute-query';
import { makePaginationQuery } from '../../common/make-pagination-query';
import { Brackets } from 'typeorm';
import { types, bool } from '../../../../misc/schema';
export const meta = {
desc: {
@ -43,10 +44,13 @@ export const meta = {
},
res: {
type: 'array',
type: types.array,
optional: bool.false, nullable: bool.false,
items: {
type: 'Note',
},
type: types.object,
optional: bool.false, nullable: bool.false,
ref: 'Note',
}
},
};

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