Compare commits

..

34 Commits

Author SHA1 Message Date
syuilo
bb7edfee04 Create aiscript.ja-JP.md 2020-04-05 18:02:39 +09:00
syuilo
caa14c70ef 12.29.0 2020-04-05 18:00:23 +09:00
syuilo
3a0f72867f New Crowdin translations (#6198)
* New translations ja-JP.yml (Korean)

* New translations ja-JP.yml (English)

* New translations ja-JP.yml (Spanish)

* New translations ja-JP.yml (English)

* 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 (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)

* New translations ja-JP.yml (German)
2020-04-05 17:56:43 +09:00
Balazs Nadasdi
10d72742f5 Ability to set header image for a Page (#6210)
* Ability to set header image for a Page

 - Add header image to Page
 - Show it on Page view
 - Show correctly it on Page list view
 - On the Page list view, pages have a light border
   to make it easier to see an image belongs to a page

* Maybe it looks better

* Use <img> instead if <x-image>

* src -> :src; set width

* Update page.vue

Co-authored-by: syuilo <Syuilotan@yahoo.co.jp>
2020-04-05 17:55:51 +09:00
syuilo
1b9f8a87d3 chore: Update dependencies 🚀 2020-04-04 11:08:10 +09:00
syuilo
d4a630902d refactor: Use === 2020-04-04 08:46:54 +09:00
syuilo
fef5ec874b enhance(server): Log error message when internal error occured 2020-04-04 08:27:16 +09:00
syuilo
f2e347fec1 perf(client): Lazy load themes 2020-04-04 08:25:28 +09:00
tamaina
cd3c2484ee Update CHANGELOG.md 2020-04-03 23:57:57 +09:00
MeiMei
6a396ef5e3 APIリファレンスでレスポンスのスキーマを見るのにいちいち2回クリックさせられるのを修正 (#6217) 2020-04-03 23:36:13 +09:00
syuilo
eec1af1f52 Revert 2020-04-03 23:35:14 +09:00
MeiMei
99fc77b678 APメンションはaudienceじゃなくてtagを参照するなど (#6128)
* APメンションはaudienceじゃなくてtagを参照するなど

* AP/tag/Mentionではurlじゃなくてuriを提示する

* createPersonでaliasが入力された場合に対応

* AP HTMLパースでMention/Hashtag判定にtagを使うように

* fix

* indent

* use hashtag name

* fix

* URLエンコード不要だったら<>を使わないの条件が消えたたのを修正
2020-04-03 22:51:38 +09:00
MeiMei
8bb311df51 APIリファレンスのカテゴリ処理の修正 (#6218)
* APIリファレンスのカテゴリ処理の修正

* tune
2020-04-03 22:42:29 +09:00
Satsuki Yanagi
a77df249c2 i18n (#6219) 2020-04-03 22:41:18 +09:00
Acid Chicken (硫酸鶏)
2883bca257 Merge pull request #6211 from syuilo/patch/autogen/v11
[AUTOMATED] Update README.md
2020-04-03 21:43:09 +09:00
Acid Chicken (硫酸鶏)
dd3af6886b Update README.md [AUTOGEN] 2020-04-03 20:52:08 +09:00
tamaina
33bcf2d1ea Fix lint 2020-04-03 17:17:46 +09:00
tamaina
795fb0eb60 Pre-render ReDoc
redoc-cliはexpandResponsesは200のみとすると数値と認識されてしまい設定できないため202,204という指定にしています
2020-04-03 17:13:41 +09:00
syuilo
9e9d378bf1 feat(streaming): Add emoji added event 2020-04-02 22:17:17 +09:00
syuilo
4a6b0edce6 Update api.ja-JP.md 2020-04-02 22:09:25 +09:00
MeiMei
356225af14 Use url if available (#6214)
* Fix #6213

* other link

* fix
2020-04-02 21:59:14 +09:00
mei23
331305e6c7 lint 2020-04-02 05:41:03 +09:00
Acid Chicken (硫酸鶏)
917b9475a5 Merge pull request #6208 from syuilo/patch/autogen/v11
[AUTOMATED] Update README.md
2020-04-01 03:46:30 +09:00
Acid Chicken (硫酸鶏)
e895fc954b Update README.md [AUTOGEN] 2020-04-01 01:58:08 +09:00
Acid Chicken (硫酸鶏)
6d3e18a6a1 Merge pull request #6191 from syuilo/patch/autogen/v11
[AUTOMATED] Update README.md
2020-04-01 01:41:53 +09:00
syuilo
79354f4faf Refactoring 2020-03-31 09:15:04 +09:00
syuilo
28f8933c3c Refactoring 2020-03-31 09:11:43 +09:00
syuilo
10356b4041 Merge branch 'master' into develop 2020-03-31 08:08:22 +09:00
syuilo
6a732ab1cd Fix #6203 2020-03-31 08:07:10 +09:00
MeiMei
47322b35ff APIの権限設定漏れを修正 (#6202)
* Fix: 権限設定漏れ

* requireAdmin
2020-03-30 11:45:57 +09:00
MeiMei
4c6d0386b9 admin/accounts/createで一般ユーザーがアカウントを作成し放題なのを修正 (#6205) 2020-03-30 09:27:39 +09:00
MeiMei
a448172952 Fix #6199 (#6201) 2020-03-29 23:18:03 +09:00
MeiMei
244ef0cb8f トークン系の乱数ソースではcryptoを使うように (#6200) 2020-03-29 23:16:36 +09:00
Acid Chicken (硫酸鶏)
4bf1c23b3c Update README.md [AUTOGEN] 2020-03-28 04:55:08 +09:00
100 changed files with 15711 additions and 11405 deletions

View File

@@ -1,6 +1,15 @@
ChangeLog
=========
12.28.0 (2020/3/29)
-------------------
### ✨Improvements
* インストールされたアプリのページでアプリの権限を確認できるように
* API: api/meta.features.miauthを追加
MiAuthに対応しているかどうかを確認するために利用できます。
値はつねにtrueを取ります。
* インスタンス一覧でソートできるように
12.27.1 (2020/03/28)
-------------------
@@ -13,7 +22,8 @@ ChangeLog
### ✨Improvements
* サードパーティーアプリケーションの認証方法にMiAuthを追加 ([Misskey API ドキュメント](https://github.com/syuilo/misskey/blob/b8088dc01a0c53b264c0697082ff5b16b06c4cda/src/docs/api.ja-JP.md#%E3%82%A2%E3%83%97%E3%83%AA%E3%82%B1%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3%E3%81%A8%E3%81%97%E3%81%A6%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E3%83%88%E3%83%BC%E3%82%AF%E3%83%B3%E3%82%92%E5%8F%96%E5%BE%97%E3%81%99%E3%82%8B))
従来の、API `app/create` => `auth/session/generate` => `auth/session/userkey` を使用する方法は依然として使用可能です。
UIからアプリを作成する画面 (`/dev/apps`) は廃止されました、同等の操作を行いたい場合は API `app/create` で可能です。
UIからアプリを作成する画面 (`/dev/apps`) は廃止されました、同等の操作を行いたい場合は API `app/create` で可能です。
MiAuthに対応しているかどうかは`api/meta.features.miauth`で確認できます12.28.0~)。
* テーマをインポートする前にプレビューできるように
* アプリから通知を作成できるように
* インストールしたアプリを見たり削除したりできるように

108
README.md
View File

@@ -109,100 +109,102 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
----------------------------------------------------------------
<!-- PATREON_START -->
<table><tr>
<td><img src="https://c8.patreon.com/2/200/20832595" alt="Roujo" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/20832595" alt="Roujo " width="100"></td>
<td><img src="https://c8.patreon.com/2/200/27956229" alt="Oliver Maximilian Seidel" 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/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/605366/c9dc408fdcbf412fb183ca5b06235f8d/1.jpeg?token-time=2145916800&token-hash=oaqsjLqOFjWN5I9hm2epOaTXaEtKwQUy5OW-EpAz6-g%3D" alt="Jon Leibowitz" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19045173/cb91c0f345c24d4ebfd05f19906d5e26/1.png?token-time=2145916800&token-hash=o_zKBytJs_AxHwSYw_5R8eD0eSJe3RoTR3kR3Q0syN0%3D" alt="kiritan" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19045173/cb91c0f345c24d4ebfd05f19906d5e26/1.png?token-time=2145916800&token-hash=o_zKBytJs_AxHwSYw_5R8eD0eSJe3RoTR3kR3Q0syN0%3D" alt="kiritan " width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/24430516/b1964ac5b9f746d2a12ff53dbc9aa40a/1.jpg?token-time=2145916800&token-hash=bmEiMGYpp3bS7hCCbymjGGsHBZM3AXuBOFO3Kro37PU%3D" alt="Eduardo Quiros" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/14215107/1cbe1912c26143919fa0faca16f12ce1/3.png?token-time=2145916800&token-hash=Zq1TCK2tdY7xudEm_aV70bc_wxmol6pNj3ZWbpFUNbI%3D" alt="Nesakko" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/14215107/1cbe1912c26143919fa0faca16f12ce1/3.png?token-time=2145916800&token-hash=Zq1TCK2tdY7xudEm_aV70bc_wxmol6pNj3ZWbpFUNbI%3D" alt="Nesakko " width="100"></td>
</tr><tr>
<td><a href="https://www.patreon.com/user?u=20832595">Roujo</a></td>
<td><a href="https://www.patreon.com/user?u=20832595">Roujo </a></td>
<td><a href="https://www.patreon.com/user?u=27956229">Oliver Maximilian Seidel</a></td>
<td><a href="https://www.patreon.com/weepjp">weepjp</a></td>
<td><a href="https://www.patreon.com/weepjp">weepjp </a></td>
<td><a href="https://www.patreon.com/jonleibowitz">Jon Leibowitz</a></td>
<td><a href="https://www.patreon.com/user?u=19045173">kiritan</a></td>
<td><a href="https://www.patreon.com/user?u=19045173">kiritan </a></td>
<td><a href="https://www.patreon.com/user?u=24430516">Eduardo Quiros</a></td>
<td><a href="https://www.patreon.com/Nesakko">Nesakko</a></td>
<td><a href="https://www.patreon.com/Nesakko">Nesakko </a></td>
</tr></table>
<table><tr>
<td><img src="https://c8.patreon.com/2/200/776209" alt="Denshi" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/776209" alt="Denshi " width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/3075183/c2ae575c604e420297f000ccc396e395/1.jpeg?token-time=2145916800&token-hash=O9qmPtpo6wWb0OuvnkEekhk_1WO2MTdytLr7ZgsAr80%3D" alt="Liaizon Wakest" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/557245" alt="mkatze" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/23915207/25428766ecd745478e600b3d7f871eb2/1.png?token-time=2145916800&token-hash=urCLLA4KjJZX92Y1CxcBP4d8bVTHGkiaPnQZp-Tqz68%3D" alt="kabo2468y" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/8249688/4aacf36b6b244ab1bc6653591b6640df/2.png?token-time=2145916800&token-hash=1ZEf2w6L34253cZXS_HlVevLEENWS9QqrnxGUAYblPo%3D" alt="AureoleArk" 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>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18899730/6a22797f68254034a854d69ea2445fc8/1.png?token-time=2145916800&token-hash=b_uj57yxo5VzkSOUS7oXE_762dyOTB_oxzbO6lFNG3k%3D" alt="YuzuRyo61" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/557245" alt="mkatze " width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/23915207/25428766ecd745478e600b3d7f871eb2/1.png?token-time=2145916800&token-hash=urCLLA4KjJZX92Y1CxcBP4d8bVTHGkiaPnQZp-Tqz68%3D" alt="kabo2468y " width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/8249688/4aacf36b6b244ab1bc6653591b6640df/2.png?token-time=2145916800&token-hash=1ZEf2w6L34253cZXS_HlVevLEENWS9QqrnxGUAYblPo%3D" alt="AureoleArk " width="100"></td>
<td><img src="https://c8.patreon.com/2/200/21285325" alt="Nie(sha) " 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>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18899730/6a22797f68254034a854d69ea2445fc8/1.png?token-time=2145916800&token-hash=b_uj57yxo5VzkSOUS7oXE_762dyOTB_oxzbO6lFNG3k%3D" alt="YuzuRyo61 " width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5788159/af42076ab3354bb49803cfba65f94bee/1.jpg?token-time=2145916800&token-hash=iSaxp_Yr2-ZiU2YVi9rcpZZj9mj3UvNSMrZr4CU4qtA%3D" alt="mewl hayabusa" width="100"></td>
</tr><tr>
<td><a href="https://www.patreon.com/user?u=776209">Denshi</a></td>
<td><a href="https://www.patreon.com/user?u=776209">Denshi </a></td>
<td><a href="https://www.patreon.com/wakest">Liaizon Wakest</a></td>
<td><a href="https://www.patreon.com/user?u=557245">mkatze</a></td>
<td><a href="https://www.patreon.com/user?u=23915207">kabo2468y</a></td>
<td><a href="https://www.patreon.com/AureoleArk">AureoleArk</a></td>
<td><a href="https://www.patreon.com/osapon">osapon</a></td>
<td><a href="https://www.patreon.com/user?u=16869916">見当かなみ</a></td>
<td><a href="https://www.patreon.com/Yuzulia">YuzuRyo61</a></td>
<td><a href="https://www.patreon.com/user?u=557245">mkatze </a></td>
<td><a href="https://www.patreon.com/user?u=23915207">kabo2468y </a></td>
<td><a href="https://www.patreon.com/AureoleArk">AureoleArk </a></td>
<td><a href="https://www.patreon.com/user?u=21285325">Nie(sha) </a></td>
<td><a href="https://www.patreon.com/osapon">osapon </a></td>
<td><a href="https://www.patreon.com/user?u=16869916">見当かなみ </a></td>
<td><a href="https://www.patreon.com/Yuzulia">YuzuRyo61 </a></td>
<td><a href="https://www.patreon.com/hs_sh_net">mewl hayabusa</a></td>
</tr></table>
<table><tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/28779508/3cd4cb7f017f4ee0864341e3464d42f9/1.png?token-time=2145916800&token-hash=eGQtR15be44kgvh8fw2Jx8Db4Bv15YBp2ldxh0EKRxA%3D" alt="S Y" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/16542964" alt="Takumi Sugita" width="100"></td>
<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://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/26340354/08834cf767b3449e93098ef73a434e2f/2.png?token-time=2145916800&token-hash=nyM8DnKRL8hR47HQ619mUzsqVRpkWZjgtgBU9RY15Uc%3D" alt="totokoro" 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/5827393/59893c191dda408f9cabd0f20a3a5627/1.jpeg?token-time=2145916800&token-hash=i9N05vOph-eP1LTLb9_npATjYOpntL0ZsHNaZFSsPmE%3D" alt="motcha" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/20494440/540beaf2445f408ea6597bc61e077bb3/1.png?token-time=2145916800&token-hash=UJ0JQge64Bx9XmN_qYA1inMQhrWf4U91fqz7VAKJeSg%3D" alt="axtuki1" width="100"></td>
<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://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/26340354/08834cf767b3449e93098ef73a434e2f/2.png?token-time=2145916800&token-hash=nyM8DnKRL8hR47HQ619mUzsqVRpkWZjgtgBU9RY15Uc%3D" alt="totokoro " 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/5827393/59893c191dda408f9cabd0f20a3a5627/1.jpeg?token-time=2145916800&token-hash=i9N05vOph-eP1LTLb9_npATjYOpntL0ZsHNaZFSsPmE%3D" alt="motcha " width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/20494440/540beaf2445f408ea6597bc61e077bb3/1.png?token-time=2145916800&token-hash=UJ0JQge64Bx9XmN_qYA1inMQhrWf4U91fqz7VAKJeSg%3D" alt="axtuki1 " 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.jpg?token-time=2145916800&token-hash=nVAntpybQrznE0rg05keLrSE6ogPKJXB13rmrJng42c%3D" alt="takimura" width="100"></td>
</tr><tr>
<td><a href="https://www.patreon.com/user?u=28779508">S Y</a></td>
<td><a href="https://www.patreon.com/user?u=16542964">Takumi Sugita</a></td>
<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=26340354">totokoro</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=5827393">motcha</a></td>
<td><a href="https://www.patreon.com/user?u=20494440">axtuki1</a></td>
<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=26340354">totokoro </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=5827393">motcha </a></td>
<td><a href="https://www.patreon.com/user?u=20494440">axtuki1 </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.jpg?token-time=2145916800&token-hash=nVAntpybQrznE0rg05keLrSE6ogPKJXB13rmrJng42c%3D" alt="takimura " width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13100201/fc5be4fa90444f09a9c8a06f72385272/1.png?token-time=2145916800&token-hash=i8PjlgfOB2LPEdbtWyx8ZPsBKhGcNZqcw_FQmH71UGU%3D" alt="aqz tamaina" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/28295158/cd2451bfb94a449dbf705ef4718cd355/2.jpeg?token-time=2145916800&token-hash=MRv3BxufHPuCyiBSxU5UYmLGvD6YZlhtSFRfMWg2k4U%3D" alt="012" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/9109588/e3cffc48d20a4e43afe04123e696781d/3.png?token-time=2145916800&token-hash=T_VIUA0IFIbleZv4pIjiszZGnQonwn34sLCYFIhakBo%3D" alt="nafuchoco" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/28295158/cd2451bfb94a449dbf705ef4718cd355/2.jpeg?token-time=2145916800&token-hash=MRv3BxufHPuCyiBSxU5UYmLGvD6YZlhtSFRfMWg2k4U%3D" alt="012 " width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/9109588/e3cffc48d20a4e43afe04123e696781d/3.png?token-time=2145916800&token-hash=T_VIUA0IFIbleZv4pIjiszZGnQonwn34sLCYFIhakBo%3D" alt="nafuchoco " width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/16900731/619ab87cc08448439222631ebb26802f/1.gif?token-time=2145916800&token-hash=o27K7M02s1z-LkDUEO5Oa7cu-GviRXeOXxryi4o_6VU%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>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5923936/2a743cbfbff946c2af3f09026047c0da/2.png?token-time=2145916800&token-hash=h6yphW1qnM0n_NOWaf8qtszMRLXEwIxfk5beu4RxdT0%3D" alt="noellabo" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/2384390/5681180e1efb46a8b28e0e8d4c8b9037/1.jpg?token-time=2145916800&token-hash=SJcMy-Q1BcS940-LFUVOMfR7-5SgrzsEQGhYb3yowFk%3D" alt="CG" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18072312/98e894d960314fa7bc236a72a39488fe/1.jpg?token-time=2145916800&token-hash=7bkMqTwHPRsJPGAq42PYdDXDZBVGLqdgr1ZmBxX8GFQ%3D" alt="Hekovic" 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>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5923936/2a743cbfbff946c2af3f09026047c0da/2.png?token-time=2145916800&token-hash=h6yphW1qnM0n_NOWaf8qtszMRLXEwIxfk5beu4RxdT0%3D" alt="noellabo " width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/2384390/5681180e1efb46a8b28e0e8d4c8b9037/1.jpg?token-time=2145916800&token-hash=SJcMy-Q1BcS940-LFUVOMfR7-5SgrzsEQGhYb3yowFk%3D" alt="CG " width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18072312/98e894d960314fa7bc236a72a39488fe/1.jpg?token-time=2145916800&token-hash=7bkMqTwHPRsJPGAq42PYdDXDZBVGLqdgr1ZmBxX8GFQ%3D" alt="Hekovic " width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/24641572/b4fd175424814f15b0ca9178d2d2d2e4/1.png?token-time=2145916800&token-hash=e2fyqdbuJbpCckHcwux7rbuW6OPkKdERcus0u2wIEWU%3D" alt="uroco @99" width="100"></td>
</tr><tr>
<td><a href="https://www.patreon.com/takimura">takimura </a></td>
<td><a href="https://www.patreon.com/aqz">aqz tamaina</a></td>
<td><a href="https://www.patreon.com/user?u=28295158">012</a></td>
<td><a href="https://www.patreon.com/nijimiss">nafuchoco</a></td>
<td><a href="https://www.patreon.com/user?u=28295158">012 </a></td>
<td><a href="https://www.patreon.com/nijimiss">nafuchoco </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>
<td><a href="https://www.patreon.com/noellabo">noellabo</a></td>
<td><a href="https://www.patreon.com/Corset">CG</a></td>
<td><a href="https://www.patreon.com/hekovic">Hekovic</a></td>
<td><a href="https://www.patreon.com/user?u=4389829">natalie </a></td>
<td><a href="https://www.patreon.com/noellabo">noellabo </a></td>
<td><a href="https://www.patreon.com/Corset">CG </a></td>
<td><a href="https://www.patreon.com/hekovic">Hekovic </a></td>
<td><a href="https://www.patreon.com/user?u=24641572">uroco @99</a></td>
</tr></table>
<table><tr>
<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://c8.patreon.com/2/200/23932002" alt="nenohi" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/9481273/7fa89168e72943859c3d3c96e424ed31/4.jpeg?token-time=2145916800&token-hash=5w1QV1qXe-NdWbdFmp1H7O_-QBsSiV0haumk3XTHIEg%3D" alt="Efertone" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/23932002" alt="nenohi " width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/9481273/7fa89168e72943859c3d3c96e424ed31/4.jpeg?token-time=2145916800&token-hash=5w1QV1qXe-NdWbdFmp1H7O_-QBsSiV0haumk3XTHIEg%3D" alt="Efertone " 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/takenoko">Nokotaro Takeda</a></td>
<td><a href="https://www.patreon.com/user?u=23932002">nenohi</a></td>
<td><a href="https://www.patreon.com/efertone">Efertone</a></td>
<td><a href="https://www.patreon.com/user?u=23932002">nenohi </a></td>
<td><a href="https://www.patreon.com/efertone">Efertone </a></td>
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
</tr></table>
**Last updated:** Tue, 17 Mar 2020 18:57:08 UTC
**Last updated:** Fri, 03 Apr 2020 11:52:08 UTC
<!-- PATREON_END -->
[backer-url]: #backers

View File

@@ -34,14 +34,16 @@ unpin: "Lösen"
copyContent: "Inhalt kopieren"
copyLink: "Link kopieren"
delete: "Löschen"
deleteAndEdit: "Löschen und Bearbeiten"
deleteAndEditConfirm: "Möchtest du diese Notiz wirklich löschen und bearbeiten? Alle Reaktionen, Renotes und Antworten dieser Notiz werden verloren gehen."
addToList: "Zur Liste hinzufügen"
sendMessage: "Nachricht senden"
copyUsername: "Benutzernamen kopieren"
reply: "Antworten"
loadMore: "Zeige mehr"
youGotNewFollower: "Sie haben einen neuen Follower"
receiveFollowRequest: "Follow Request erhalten."
followRequestAccepted: "FollowRequestAkzeptiert"
receiveFollowRequest: "Follow-Anfrage erhalten."
followRequestAccepted: "Follow-Anfrage akzeptiert"
mentions: "Erwähnungen"
directNotes: "Direktnachrichten"
importAndExport: "Importieren und Exportieren"
@@ -51,6 +53,8 @@ files: "Dateien"
download: "Download"
driveFileDeleteConfirm: "Möchtest du die Datei \"{name}\" löschen? Die zugehörige Notiz wird ebenso verschwinden."
unfollowConfirm: "Möchtest du {name} nicht mehr folgen?"
exportRequested: "Du hast einen Export angefragt. Dies kann etwas Zeit in Anspruch nehmen. Sobald der Export abgeschlossen ist, wird er deiner Drive hinzugefügt."
importRequested: "Du hast einen Import angefragt. Dies kann etwas Zeit in Anspruch nehmen."
lists: "Listen"
noLists: "Keine Liste!"
note: "Notiz"
@@ -109,24 +113,33 @@ addAcount: "Benutzerkonto hinzufügen"
loginFailed: "Login fehlgeschlagen"
general: "Allgemein"
wallpaper: "Hintergrund"
setWallpaper: "Hintergrund festlegen"
removeWallpaper: "Hintergrund entfernen"
searchWith: "Suche: {q}"
youHaveNoLists: "Du hast keine Listen"
followConfirm: "Möchtest du {name} wirklich folgen?"
proxyAccount: "Proxy-Benutzerkonto"
host: "Host"
selectUser: "Benutzer wählen"
recipient: "Empfänger"
annotation: "Anmerkung"
federation: "Föderation"
instances: "Instanz"
registeredAt: "Registriert am"
latestRequestSentAt: "Letzte Anfrage gesendet am"
latestRequestReceivedAt: "Letzte Anfrage erhalten am"
latestStatus: "Neuester Status"
storageUsage: "Speicherplatzverbrauch"
charts: "Charts"
perHour: "Pro Stunde"
perDay: "Pro Tag"
stopActivityDelivery: "Senden von Aktivitäten einstellen"
blockThisInstance: "Diese Instanz blockieren"
software: "Software"
version: "Version"
metadata: "Metadaten"
withNFiles: "{n} Datei(en)"
monitor: "Beobachten"
jobQueue: "Job-Warteschlange"
cpuAndMemory: "CPU und Arbeitsspeicher"
network: "Netzwerk"
@@ -157,6 +170,7 @@ federating: "Föderiert"
blocked: "Blockiert"
suspended: "Gesperrt"
all: "Alles"
notResponding: "Antwortet nicht"
changePassword: "Passwort ändern"
security: "Sicherheit"
retypedNotMatch: "Eingaben stimmen nicht überein."
@@ -178,6 +192,10 @@ messaging: "Nachrichten"
upload: "Hochladen"
fromDrive: "Aus Drive"
fromUrl: "Von einer URL"
uploadFromUrl: "Von einer URL hochladen"
uploadFromUrlDescription: "URL der hochzuladenden Datei"
uploadFromUrlRequested: "Upload angefordert"
uploadFromUrlMayTakeTime: "Es kann eine Weile dauern, bis der Upload abgeschlossen ist."
explore: "Erkunden"
games: "Misskey Spiele"
messageRead: "Gelesen"
@@ -191,21 +209,32 @@ activity: "Aktivität"
images: "Bilder"
birthday: "Geburtstag"
yearsOld: "{age} Jahre alt"
registeredDate: "Registierdatum"
registeredDate: "Registrationsdatum"
location: "Ort"
theme: "Farbthemen"
themeForLightMode: "Farbthema, das im Hellmodus genutzt wird"
themeForDarkMode: "Farbthema, das im Dunkelmodus genutzt wird"
light: "Hell"
dark: "Dunkel"
lightThemes: "Helle Farbthemen"
darkThemes: "Dunkle Farbthemen"
drive: "Drive"
fileName: "Dateiname"
selectFile: "Datei auswählen"
selectFiles: "Dateien auswählen"
renameFile: "Datei umbenennen"
folderName: "Ordnername"
createFolder: "Ordner erstellen"
renameFolder: "Ordner umbenennen"
deleteFolder: "Ordner löschen"
addFile: "Datei hinzufügen"
emptyDrive: "Drive ist leer"
emptyFolder: "Der Ordner ist leer"
unableToDelete: "Nicht löschbar"
inputNewFileName: "Gib einen neuen Dateinamen ein"
inputNewFolderName: "Gib einen neuen Ordnernamen ein"
circularReferenceFolder: "Der Zielordner ist ein Unterorder des Ordners, den du verschieben möchtest."
hasChildFilesOrFolders: "Dieser Ordner kann nicht gelöscht werden, da er nicht leer ist."
copyUrl: "URL kopieren"
rename: "Umbenennen"
avatar: "Profilbild"
@@ -226,6 +255,9 @@ tosUrl: "URL der Nutzungsbedingungen"
thisYear: "Dieses Jahr"
thisMonth: "Dieser Monat"
today: "Heute"
dayX: "{day}"
monthX: "{month}"
yearX: "{year}"
pages: "Seiten"
integration: "Integration"
connectSerice: "Verbinden"
@@ -242,6 +274,7 @@ pinnedUsers: "Angepinnte Benutzer"
pinnedUsersDescription: "Gib einen Benutzernamen pro Zeile ein. Diese werden im \"Erkunden\" Tab angezeigt."
recaptcha: "reCAPTCHA"
enableRecaptcha: "reCAPTCHA aktivieren"
recaptchaSecretKey: "Secret key"
antennas: "Antennen"
manageAntennas: "Antennen verwalten"
name: "Name"
@@ -249,6 +282,8 @@ antennaSource: "Antennenquelle"
antennaKeywords: "Schlüsselwörter, die beobachtet werden sollen"
antennaExcludeKeywords: "Schlüsselwörter, die ignoriert werden sollen"
antennaKeywordsDescription: "Mit Leerzeichen für eine \"UND\"-Verknüpfung trennen, durch Zeilenumbrüche für eine \"ODER\"-Verknüpfung trennen."
notifyAntenna: "Über neue Notizen benachrichtigen"
withFileAntenna: "Nur Notizen mit Dateien"
serviceworker: "ServiceWorker"
enableServiceworker: "ServiceWorker aktivieren"
antennaUsersDescription: "Benutzernamen getrennt durch Zeilenumbrüche angeben"
@@ -263,8 +298,24 @@ recentlyRegisteredUsers: "Vor kurzem registrierte Benutzer"
recentlyDiscoveredUsers: "Vor kurzem gefundene Benutzer"
exploreUsersCount: "Es gibt {count} Benutzer"
exploreFediverse: "Das Fediverse erkunden"
popularTags: "Beliebte Schlagwörter"
userList: "Listen"
about: "Über"
aboutMisskey: "Über Misskey"
misskeySource: "Der Quelltext ist hier verfügbar:"
misskeyTranslation: "Hilf dabei, Misskey zu übersetzen:"
misskeyDonate: "Spende an Misskey, um die Weiterentwicklung zu unterstützen:"
morePatrons: "Wir schätzen ebenso die Unterstützung vieler anderer hier nicht gelisteter Personen sehr. Danke! 🥰"
patrons: "UnterstützerInnen"
administrator: "Administrator"
twoStepAuthentication: "Zwei-Faktor-Authentifizierung"
moderator: "Moderator"
nUsersMentioned: "{n} Benutzer erwähnt"
securityKey: "Sicherheitsschlüssel"
securityKeyName: "Schlüsselname"
registerSecurityKey: "Sicherheitsschlüssel registrieren"
lastUsed: "Zuletzt benutzt"
passwordLessLogin: "Passwortloses Anmelden einrichten"
resetPassword: "Passwort zurücksetzen"
newPasswordIs: "Das neue Passwort ist \"{password}\""
posted: "Gesendet"
@@ -279,7 +330,18 @@ uploadFolder: "Standardordner für Uploads"
cacheClear: "Cache leeren"
markAsReadAllNotifications: "Alle Benachrichtigungen als gelesen markieren"
markAsReadAllUnreadNotes: "Alle Notizen als gelesen markieren"
help: "Hilfe"
inputMessageHere: "Hier Nachricht eingeben"
close: "Schließen"
group: "Gruppe"
groups: "Gruppen"
createGroup: "Gruppe erstellen"
ownedGroups: "Eigene Gruppen"
joinedGroups: "Beigetretene Gruppen"
invites: "Einladen"
groupName: "Gruppenname"
members: "Mitglieder"
transfer: "Übertragen"
title: "Betreff"
text: "Text"
enable: "Aktivieren"
@@ -288,12 +350,40 @@ retype: "Erneut eingeben"
noteOf: "Notiz von {user}"
inviteToGroup: "Zu Gruppe einladen"
maxNoteTextLength: "Maximale Länge von Notizen"
quoteAttached: "Zitiert"
quoteQuestion: "Als Zitat anfügen?"
noMessagesYet: "Noch keine Nachrichten"
newMessageExists: "Du hast eine neue Nachricht"
onlyOneFileCanBeAttached: "Es kann pro Nachricht nur eine Datei angehängt werden"
signinRequired: "Anmeldung erforderlich"
invitationCode: "Einladungscode"
checking: "Wird überprüft..."
available: "Verfügbar"
unavailable: "Unverfügbar"
usernameInvalidFormat: "Buchstaben, Zahlen und Unterstriche sind verwendbar."
tooShort: "Zu kurz"
tooLong: "Zu lang"
weakPassword: "Schwaches Passwort"
normalPassword: "Standardpasswort"
strongPassword: "Starkes Passwort"
passwordMatched: "Stimmt überein"
passwordNotMatched: "Stimmt nicht überein"
signinWith: "Mit {x} anmelden"
signinFailed: "Anmeldung fehlgeschlagen. Überprüfe Benutzername und Passswort."
tapSecurityKey: "Tippe deinen Sicherheitsschlüssel an"
or: "Oder"
uiLanguage: "Sprache der Benutzeroberfläche"
groupInvited: "Du wurdest in eine Gruppe eingeladen"
aboutX: "Über {x}"
useOsNativeEmojis: "Eingebaute Emojis des Betriebssystems benutzen"
youHaveNoGroups: "Keine Gruppen vorhanden"
joinOrCreateGroup: "Lass dich zu einer Gruppe einladen oder erstelle deine eigene."
noHistory: "Kein Verlauf"
disableAnimatedMfm: "MFM, die Animationen enthalten, deaktivieren"
doing: "In Bearbeitung"
category: "Kategorie"
tags: "Schlagwörter"
docSource: "Quelle dieses Dokuments"
createAccount: "Benutzerkonto erstellen"
existingAcount: "Bestehendes Benutzerkonto"
regenerate: "Regenerieren"
@@ -312,14 +402,43 @@ promotion: "Hervorgehoben"
promote: "Hervorheben"
numberOfDays: "Anzahl der Tage"
hideThisNote: "Diese Notiz verstecken"
objectStorage: "Objektspeicher"
useObjectStorage: "Objektspeicher verwenden"
objectStorageBaseUrl: "Basis-URL"
objectStorageBucket: "Bucket"
objectStoragePrefix: "Prefix"
objectStorageEndpoint: "Endpoint"
objectStorageRegion: "Region"
objectStorageUseSSL: "SSL verwenden"
serverLogs: "Serverprotokolle"
deleteAll: "Alle löschen"
newNoteRecived: "Du hast eine neue Notiz empfangen"
sounds: "Töne"
listen: "Anhören"
none: "Keine"
volume: "Lautstärke"
details: "Details"
chooseEmoji: "Wähle ein Emoji"
unableToProcess: "Der Vorgang konnte nicht abgeschlossen werden."
recentUsed: "Vor kurzem verwendet"
install: "Installieren"
uninstall: "Uninstallieren"
installedApps: "Authorisierte Anwendungen"
nothing: "Hier gibt es nichts zu sehen"
installedDate: "Authorisiert"
lastUsedDate: "Zuletzt verwendet"
state: "Status"
sort: "Sortieren"
ascendingOrder: "Aufsteigende Reihenfolge"
descendingOrder: "Absteigende Reihenfolge"
_theme:
explore: "Themen erforschen"
install: "Thema installieren"
manage: "Themaverwaltung"
code: "Themencode"
installed: "{name} wurde installiert"
alreadyInstalled: "Dieses Thema ist bereits installiert"
invalid: "Themenformat ist ungültig"
_sfx:
note: "Notizen"
noteMy: "Meine Notizen"
@@ -342,11 +461,47 @@ _time:
second: "Sekunde"
minute: "Minute"
hour: "Stunde"
day: "t"
_2fa:
alreadyRegistered: "Du hast bereits ein Gerät für Zwei-Faktor-Authentifizierung registriert"
registerDevice: "Neues Gerät registrieren"
registerKey: "Neuen Sicherheitsschlüssel registrieren"
_permissions:
"read:account": "Deine Benutzerkontoinformationen lesen"
"write:account": "Deine Benutzerkontoinformationen bearbeiten"
"read:blocks": "Die Liste deiner blockierten Benutzer lesen"
"write:blocks": "Die Liste deiner blockierten Benutzer bearbeiten"
"read:drive": "Deine Drive-Dateien und Ordner lesen"
"write:drive": "Deine Drive-Dateien und Ordner bearbeiten oder löschen"
"read:favorites": "Deine Favoriten-Liste lesen"
"write:favorites": "Deine Favoriten-Liste bearbeiten"
"read:following": "Deine Follower-Liste lesen"
"write:following": "Anderen Benutzern folgen oder entfolgen"
"read:messaging": "Nachrichten lesen"
"write:messaging": "Nachrichten schicken oder löschen"
"read:mutes": "Stummschaltungen sehen"
"write:mutes": "Stummschaltungen bearbeiten"
"write:notes": "Notizen schreiben oder löschen"
"read:notifications": "Benachrichtigungen lesen"
"write:notifications": "Mit Benachrichtigungen arbeiten"
"read:reactions": "Reaktionen sehen"
"write:reactions": "Reaktionen hinzufügen und bearbeiten"
"write:votes": "In Umfragen abstimmen"
"read:pages": "Deine Seiten lesen"
"write:pages": "Deine Seiten bearbeiten oder löschen"
"read:page-likes": "Seiten-Likes lesen"
"write:page-likes": "Seiten-Likes bearbeiten"
"read:user-groups": "Deine Benutzergruppen lesen"
"write:user-groups": "Benutzergruppen bearbeiten oder löschen"
_auth:
shareAccess: "Möchtest du \"{name}\" authorisieren, auf dieses Benuzerkonto zugreifen zu können?"
shareAccessAsk: "Bist du dir sicher, dass du diese Anwendung authorisieren möchtest, auf dein Benutzerkonto zugreifen zu können?"
permissionAsk: "Diese Anwendung erfordert folgende Berechtigungen:"
pleaseGoBack: "Bitte gehe zurück zur Anwendung"
callback: "Rückkehr zur Anwendung"
denied: "Zugriff verweigert"
_antennaSources:
all: "Alle Notizen"
_weekday:
sunday: "Sonntag"
monday: "Montag"
@@ -449,19 +604,28 @@ _instanceCharts:
_timelines:
home: "Startseite"
local: "Lokal"
social: "Sozial"
global: "Global"
_pages:
viewPage: "Deine Seiten lesen"
content: "Inhalt"
title: "Titel"
url: "Seiten-URL"
summary: "Zusammenfassung"
alignCenter: "Mittig ausrichten"
hideTitleWhenPinned: "Seitentitel ausblenden, wenn an dein Profil angepinnt "
font: "Schriftart"
fontSerif: "Serif"
fontSansSerif: "Sans Serif"
eyeCatchingImageSet: "Vorschaubild festlegen"
eyeCatchingImageRemove: "Vorschaubild entfernen"
chooseBlock: "Block hinzufügen"
selectType: "Wähle einen Typ"
enterVariableName: "Gib einen Namen für deine Variable ein"
variableNameIsAlreadyUsed: "Dieser Name wird bereits von einer anderen Variable verwendet"
contentBlocks: "Inhalt"
inputBlocks: "Eingabe"
specialBlocks: "Spezial"
blocks:
text: "Text"
textarea: "Textfeld"
@@ -476,22 +640,27 @@ _pages:
text: "Inhalt"
textInput: "Texteingabe"
_textInput:
name: "Variablenname"
text: "Titel"
default: "Standardwert"
textareaInput: "Eingabe des mehrzeiligen Textfelds"
_textareaInput:
name: "Variablenname"
text: "Titel"
default: "Standardwert"
numberInput: "Nummereingabe"
_numberInput:
name: "Variablenname"
text: "Titel"
default: "Standardwert"
switch: "Fallunterscheidung"
_switch:
name: "Variablenname"
text: "Titel"
default: "Standardwert"
counter: "Zähler"
_counter:
name: "Variablenname"
text: "Titel"
inc: "Erhöhen um"
_button:
@@ -511,6 +680,7 @@ _pages:
no-variable: "Keine"
radioButton: "Optionsfeld"
_radioButton:
name: "Variablenname"
title: "Titel"
values: "Auswahlmöglichkeiten (getrennt durch Zeilenumbrüche)"
default: "Standardwert"
@@ -666,6 +836,7 @@ _pages:
splitStrByLine: "Text nach Zeilenumbrüchen aufteilen"
_splitStrByLine:
arg1: "Text"
ref: "Variablen"
fn: "Funktionen"
_fn:
arg1: "Ausgabe"

View File

@@ -676,15 +676,15 @@ _charts:
_instanceCharts:
requests: "Requests"
users: "Difference in # of users"
usersTotal: "Total # of users"
usersTotal: "Cumulative total # of users"
notes: "Difference in # of notes"
notesTotal: "Total # of notes"
notesTotal: "Cumulative total # of notes"
ff: "Difference in # of followers"
ffTotal: "Total # of followers"
ffTotal: "Cumulative total # of followers"
cacheSize: "Difference in cache size"
cacheSizeTotal: "Total cache size"
cacheSizeTotal: "Cumulative total cache size"
files: "Difference in # of files"
filesTotal: "Total # of files"
filesTotal: "Cumulative total # of files"
_timelines:
home: "Home"
local: "Local"

View File

@@ -472,6 +472,10 @@ installedApps: "Aplicaciones Autorizadas"
nothing: "No hay nada que ver aqui"
installedDate: "Autorizado"
lastUsedDate: "Utilizado el"
state: "Estado"
sort: "Ordenar"
ascendingOrder: "Ascendente"
descendingOrder: "Descendente"
_theme:
explore: "Explorar temas"
install: "Instalar tema"
@@ -672,10 +676,15 @@ _charts:
_instanceCharts:
requests: "Pedidos"
users: "Variación de usuarios"
usersTotal: "Total acumulado de usuarios"
notes: "Variación de la cantidad de notas"
notesTotal: "Total acumulado de la cantidad de notas"
ff: "Variación de cantidad de seguidos/seguidores"
ffTotal: "Total acumulado de cantidad de seguidos/seguidores"
cacheSize: "Variación del tamaño de la caché"
cacheSizeTotal: "Total acumulado del tamaño de la caché"
files: "Variación de cantidad de archivos"
filesTotal: "Total acumulado de cantidad de archivos"
_timelines:
home: "Inicio"
local: "Local"

View File

@@ -472,6 +472,10 @@ installedApps: "Applications Autorisées"
nothing: "Il n'y a rien à voir ici"
installedDate: "Autorisé"
lastUsedDate: "Dernière utilisation"
state: "État"
sort: "Trier"
ascendingOrder: "Ascendant"
descendingOrder: "Descendant"
_theme:
explore: "Explorer les thèmes"
install: "Installer un thème"
@@ -659,7 +663,7 @@ _charts:
federationInstancesIncDec: "Variation du nombre d'instances"
federationInstancesTotal: "Nombre d'instances au total"
usersIncDec: "Variation du nombre d'utilisateur·rice·s"
usersTotal: "Nombre d'utilsateur·rice·s au total"
usersTotal: "Nombre d'utilisateur·rice·s au total"
activeUsers: "Utilisateur·rice·s actif·ve·s"
notesIncDec: "Variation du nombre d'notes"
localNotesIncDec: "Variation du nombre de notes local"
@@ -672,10 +676,15 @@ _charts:
_instanceCharts:
requests: "Requêtes"
users: "Variation du nombre d'utilisateur·rice·s"
usersTotal: "Nombre d'utilisateur·rice·s au total cumulé"
notes: "Variation du nombre d'notes"
notesTotal: "Nombre d'notes au total cumulé"
ff: "Variation des abonné·e·s"
ffTotal: "Nombre d'abonné·e·s au total cumulé"
cacheSize: "Variation de la taille du cache"
cacheSizeTotal: "La taille du cache au total cumulé"
files: "Variation du nombre de fichiers"
filesTotal: "Nombre de fichiers au total cumulé"
_timelines:
home: "Principal"
local: "Local"

View File

@@ -661,25 +661,30 @@ _exportOrImport:
userLists: "리스트"
_charts:
federationInstancesIncDec: "연합 인스턴스 수 증감"
federationInstancesTotal: "연합 인스턴스 수"
federationInstancesTotal: "연합 인스턴스 수 합계"
usersIncDec: "유저 수 증감"
usersTotal: "유저 수 합계"
activeUsers: "활성 유저 수"
notesIncDec: "노트 수 증감"
localNotesIncDec: "로컬 노트 수 증감"
remoteNotesIncDec: "리모트 노트 수 증감"
notesTotal: "노트 수"
notesTotal: "노트 수 합계"
filesIncDec: "파일 수 증감"
filesTotal: "파일 수"
filesTotal: "파일 수 합계"
storageUsageIncDec: "스토리지 사용량 증감"
storageUsageTotal: "스토리지 사용량"
storageUsageTotal: "스토리지 사용량 합계"
_instanceCharts:
requests: "요청"
users: "유저 수 증감"
usersTotal: "누적 유저 수"
notes: "노트 수 증감"
notesTotal: "누적 노트 수"
ff: "팔로잉/팔로워 증감"
ffTotal: "누적 팔로잉/팔로워 수"
cacheSize: "캐시 용량 증감"
cacheSizeTotal: "누적 캐시 용량"
files: "파일 수 증감"
filesTotal: "누적 파일 수"
_timelines:
home: "홈"
local: "로컬"

View File

@@ -230,8 +230,8 @@ location: "位置"
theme: "主题"
themeForLightMode: "在轻便模式下使用的主题"
themeForDarkMode: "在黑暗模式下使用的主题"
light: "轻便"
dark: "黑暗"
light: "浅色"
dark: "深色"
lightThemes: "亮色主题"
darkThemes: "暗色主题"
syncDeviceDarkMode: "将黑暗模式与设备设置同步"
@@ -486,7 +486,7 @@ _theme:
invalid: "主题格式错误"
_sfx:
note: "帖子"
noteMy: "我的笔记"
noteMy: "我的帖子"
notification: "通知"
chat: "聊天"
chatBg: "聊天背景"

View File

@@ -0,0 +1,15 @@
/* tslint:disable:quotemark class-name indent */
import {MigrationInterface, QueryRunner} from "typeorm";
export class apUrl1585772678853 implements MigrationInterface {
name = 'apUrl1585772678853'
public async up(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(`ALTER TABLE "note" ADD "url" character varying(512)`, undefined);
}
public async down(queryRunner: QueryRunner): Promise<any> {
await queryRunner.query(`ALTER TABLE "note" DROP COLUMN "url"`, undefined);
}
}

View File

@@ -1,7 +1,7 @@
{
"name": "misskey",
"author": "syuilo <syuilotan@yahoo.co.jp>",
"version": "12.28.0",
"version": "12.29.0",
"codename": "indigo",
"repository": {
"type": "git",
@@ -11,11 +11,13 @@
"private": true,
"scripts": {
"start": "node ./index.js",
"start-product": "cross-env NODE_ENV=production node ./index.js",
"init": "npm run migrate",
"ormconfig": "node ./built/ormconfig.js",
"migrate": "ts-node ./node_modules/typeorm/cli.js migration:run",
"migrateandstart": "npm run migrate && npm run start",
"build": "webpack && gulp build",
"build-product": "cross-env NODE_ENV=production webpack && gulp build",
"webpack": "webpack",
"watch": "webpack --watch",
"gulp": "gulp build",
@@ -30,18 +32,18 @@
"lodash": "^4.17.13"
},
"dependencies": {
"@elastic/elasticsearch": "7.6.0",
"@fortawesome/fontawesome-svg-core": "1.2.27",
"@fortawesome/free-brands-svg-icons": "5.12.1",
"@fortawesome/free-regular-svg-icons": "5.12.1",
"@fortawesome/free-solid-svg-icons": "5.12.1",
"@elastic/elasticsearch": "7.6.1",
"@fortawesome/fontawesome-svg-core": "1.2.28",
"@fortawesome/free-brands-svg-icons": "5.13.0",
"@fortawesome/free-regular-svg-icons": "5.13.0",
"@fortawesome/free-solid-svg-icons": "5.13.0",
"@fortawesome/vue-fontawesome": "0.1.9",
"@juggle/resize-observer": "3.0.2",
"@juggle/resize-observer": "3.1.3",
"@koa/cors": "3.0.0",
"@koa/multer": "2.0.2",
"@koa/router": "8.0.8",
"@types/bcryptjs": "2.4.2",
"@types/bull": "3.12.0",
"@types/bull": "3.12.1",
"@types/cbor": "5.0.0",
"@types/dateformat": "3.0.1",
"@types/double-ended-queue": "2.1.1",
@@ -51,10 +53,10 @@
"@types/gulp-rename": "0.0.33",
"@types/gulp-replace": "0.0.31",
"@types/is-url": "1.2.28",
"@types/js-yaml": "3.12.2",
"@types/jsdom": "12.2.4",
"@types/js-yaml": "3.12.3",
"@types/jsdom": "16.2.0",
"@types/katex": "0.11.0",
"@types/koa": "2.11.1",
"@types/koa": "2.11.3",
"@types/koa-bodyparser": "4.3.0",
"@types/koa-compress": "2.0.9",
"@types/koa-cors": "0.0.0",
@@ -68,8 +70,8 @@
"@types/koa__router": "8.0.2",
"@types/lolex": "5.1.0",
"@types/markdown-it": "0.0.9",
"@types/mocha": "7.0.1",
"@types/node": "13.7.1",
"@types/mocha": "7.0.2",
"@types/node": "13.11.0",
"@types/nodemailer": "6.4.0",
"@types/nprogress": "0.2.0",
"@types/oauth": "0.9.1",
@@ -80,7 +82,7 @@
"@types/qrcode": "1.3.4",
"@types/random-seed": "0.3.3",
"@types/ratelimiter": "2.1.28",
"@types/redis": "2.8.15",
"@types/redis": "2.8.17",
"@types/rename": "1.0.1",
"@types/request": "2.48.4",
"@types/request-promise-native": "1.0.17",
@@ -93,26 +95,26 @@
"@types/systeminformation": "3.54.1",
"@types/tinycolor2": "1.4.2",
"@types/tmp": "0.1.0",
"@types/uuid": "3.4.7",
"@types/uuid": "7.0.2",
"@types/web-push": "3.3.0",
"@types/webpack": "4.41.6",
"@types/webpack": "4.41.10",
"@types/webpack-stream": "3.2.10",
"@types/websocket": "1.0.0",
"@types/ws": "7.2.1",
"@typescript-eslint/parser": "2.19.2",
"@types/ws": "7.2.3",
"@typescript-eslint/parser": "2.26.0",
"agentkeepalive": "4.1.0",
"animejs": "3.1.0",
"apexcharts": "3.15.6",
"apexcharts": "3.17.1",
"autobind-decorator": "2.4.0",
"autosize": "4.0.2",
"autwh": "0.1.0",
"aws-sdk": "2.617.0",
"aws-sdk": "2.653.0",
"bcryptjs": "2.4.3",
"bull": "3.12.1",
"bull": "3.13.0",
"cafy": "15.2.1",
"cbor": "5.0.1",
"chai": "4.2.0",
"chalk": "3.0.0",
"chalk": "4.0.0",
"chart.js": "2.9.3",
"cli-highlight": "2.1.4",
"commander": "4.1.1",
@@ -124,16 +126,16 @@
"diskusage": "1.1.3",
"double-ended-queue": "2.1.0-0",
"eslint": "6.8.0",
"eslint-plugin-vue": "6.1.2",
"eslint-plugin-vue": "6.2.2",
"eventemitter3": "4.0.0",
"feed": "4.1.0",
"fibers": "4.0.2",
"file-type": "14.1.2",
"file-type": "14.1.4",
"fluent-ffmpeg": "2.1.2",
"glob": "7.1.6",
"gulp": "4.0.2",
"gulp-clean-css": "4.2.0",
"gulp-dart-sass": "0.9.1",
"gulp-clean-css": "4.3.0",
"gulp-dart-sass": "1.0.0",
"gulp-mocha": "7.0.2",
"gulp-rename": "2.0.0",
"gulp-replace": "1.0.0",
@@ -143,21 +145,21 @@
"gulp-typescript": "5.0.1",
"hard-source-webpack-plugin": "0.13.1",
"html-minifier": "4.0.0",
"http-signature": "1.3.1",
"http-signature": "1.3.4",
"https-proxy-agent": "5.0.0",
"insert-text-at-cursor": "0.3.0",
"is-root": "2.1.0",
"is-svg": "4.2.1",
"js-yaml": "3.13.1",
"jsdom": "16.1.0",
"json5": "2.1.1",
"jsdom": "16.2.2",
"json5": "2.1.2",
"json5-loader": "3.0.0",
"jsrsasign": "8.0.12",
"jsrsasign": "8.0.13",
"katex": "0.11.1",
"koa": "2.11.0",
"koa-bodyparser": "4.2.1",
"koa-bodyparser": "4.3.0",
"koa-compress": "3.0.0",
"koa-favicon": "2.0.1",
"koa-favicon": "2.1.0",
"koa-json-body": "5.3.0",
"koa-logger": "3.2.1",
"koa-mount": "4.0.0",
@@ -165,24 +167,23 @@
"koa-slow": "2.1.0",
"koa-views": "6.2.1",
"langmap": "0.0.16",
"loader-utils": "1.2.3",
"lolex": "5.1.2",
"lookup-dns-cache": "2.1.0",
"markdown-it": "10.0.0",
"markdown-it-anchor": "5.2.5",
"mocha": "7.0.1",
"markdown-it-anchor": "5.2.7",
"mocha": "7.1.1",
"moji": "0.5.1",
"ms": "2.1.2",
"multer": "1.4.2",
"nested-property": "1.0.4",
"node-fetch": "2.6.0",
"nodemailer": "6.4.2",
"nodemailer": "6.4.6",
"nprogress": "0.2.0",
"object-assign-deep": "0.4.0",
"os-utils": "0.0.14",
"parse5": "5.1.1",
"parsimmon": "1.13.0",
"pg": "7.18.1",
"pg": "8.0.0",
"portal-vue": "2.1.7",
"portscanner": "2.2.0",
"postcss-loader": "3.0.0",
@@ -193,11 +194,11 @@
"promise-sequential": "1.1.1",
"pug": "2.0.4",
"punycode": "2.1.1",
"pureimage": "0.1.6",
"pureimage": "0.2.1",
"qrcode": "1.4.4",
"random-seed": "0.3.0",
"randomcolor": "0.5.4",
"ratelimiter": "3.4.0",
"ratelimiter": "3.4.1",
"recaptcha-promise": "0.1.3",
"reconnecting-websocket": "4.4.0",
"redis": "3.0.2",
@@ -211,57 +212,57 @@
"rimraf": "3.0.2",
"rndstr": "1.0.0",
"s-age": "1.1.2",
"sass": "1.25.0",
"sass": "1.26.3",
"sass-loader": "8.0.2",
"seedrandom": "3.0.5",
"sharp": "0.24.0",
"sharp": "0.25.2",
"showdown": "1.9.1",
"showdown-highlightjs-extension": "0.1.2",
"speakeasy": "2.0.0",
"stringz": "2.0.0",
"stringz": "2.1.0",
"style-loader": "1.1.3",
"summaly": "2.3.1",
"syslog-pro": "1.0.0",
"systeminformation": "4.21.2",
"systeminformation": "4.23.1",
"syuilo-password-strength": "0.0.1",
"terser-webpack-plugin": "2.3.4",
"terser-webpack-plugin": "2.3.5",
"textarea-caret": "3.1.0",
"three": "0.113.2",
"three": "0.115.0",
"tinycolor2": "1.4.1",
"tmp": "0.1.0",
"ts-loader": "6.2.1",
"ts-node": "8.6.2",
"tslint": "6.0.0",
"ts-loader": "6.2.2",
"ts-node": "8.8.1",
"tslint": "6.1.1",
"tslint-sonarts": "1.9.0",
"typeorm": "0.2.22",
"typescript": "3.7.5",
"typeorm": "0.2.24",
"typescript": "3.8.3",
"ulid": "2.3.0",
"url-loader": "3.0.0",
"uuid": "3.4.0",
"uuid": "7.0.3",
"v-animate-css": "0.0.3",
"v-debounce": "0.1.2",
"vue": "2.6.11",
"vue-color": "2.7.0",
"vue-color": "2.7.1",
"vue-content-loading": "1.6.0",
"vue-cropperjs": "4.0.1",
"vue-i18n": "8.15.3",
"vue-i18n": "8.16.0",
"vue-json-pretty": "1.6.3",
"vue-loader": "15.9.0",
"vue-loader": "15.9.1",
"vue-marquee-text-component": "1.1.1",
"vue-meta": "2.3.2",
"vue-meta": "2.3.3",
"vue-prism-component": "1.1.1",
"vue-router": "3.1.5",
"vue-router": "3.1.6",
"vue-style-loader": "4.1.2",
"vue-svg-inline-loader": "1.4.5",
"vue-svg-inline-loader": "1.5.0",
"vue-template-compiler": "2.6.11",
"vuedraggable": "2.23.2",
"vuex": "3.1.2",
"vuex-persistedstate": "2.7.1",
"vuex": "3.1.3",
"vuex-persistedstate": "3.0.1",
"web-push": "3.4.3",
"webpack": "4.41.6",
"webpack": "4.42.1",
"webpack-cli": "3.3.11",
"websocket": "1.0.31",
"ws": "7.2.1",
"ws": "7.2.3",
"xev": "2.0.1"
},
"devDependencies": {

View File

@@ -99,7 +99,7 @@ async function isPortAvailable(port: number): Promise<boolean> {
function showEnvironment(): void {
const env = process.env.NODE_ENV;
const logger = bootLogger.createSubLogger('env');
logger.info(typeof env == 'undefined' ? 'NODE_ENV is not set' : `NODE_ENV: ${env}`);
logger.info(typeof env === 'undefined' ? 'NODE_ENV is not set' : `NODE_ENV: ${env}`);
if (env !== 'production') {
logger.warn('The environment is not in production mode.');

View File

@@ -310,7 +310,7 @@ export default Vue.extend({
title: this.$t('search'),
input: true
}).then(async ({ canceled, result: query }) => {
if (canceled || query == null || query == '') return;
if (canceled || query == null || query === '') return;
this.searching = true;
search(this, query).finally(() => {
@@ -320,7 +320,7 @@ export default Vue.extend({
},
searchKeypress(e) {
if (e.keyCode == 13) {
if (e.keyCode === 13) {
this.searchWait = true;
search(this, this.searchQuery).finally(() => {
this.searchWait = false;

View File

@@ -18,7 +18,7 @@
</style>
</head>
<body>
<redoc spec-url='/api.json'></redoc>
<redoc spec-url="/api.json" expand-responses="200" expand-single-schema-field="true"></redoc>
<script src="https://cdn.jsdelivr.net/npm/redoc@next/bundles/redoc.standalone.js"> </script>
</body>
</html>

View File

@@ -517,11 +517,11 @@ export default Vue.extend({
icon: faLink,
text: this.$t('copyLink'),
action: this.copyLink
}, this.appearNote.uri ? {
}, (this.appearNote.url || this.appearNote.uri) ? {
icon: faExternalLinkSquareAlt,
text: this.$t('showOnRemote'),
action: () => {
window.open(this.appearNote.uri, '_blank');
window.open(this.appearNote.url || this.appearNote.uri, '_blank');
}
} : undefined,
null,
@@ -585,11 +585,11 @@ export default Vue.extend({
icon: faLink,
text: this.$t('copyLink'),
action: this.copyLink
}, this.appearNote.uri ? {
}, (this.appearNote.url || this.appearNote.uri) ? {
icon: faExternalLinkSquareAlt,
text: this.$t('showOnRemote'),
action: () => {
window.open(this.appearNote.uri, '_blank');
window.open(this.appearNote.url || this.appearNote.uri, '_blank');
}
} : undefined]
.filter(x => x !== undefined);

View File

@@ -35,6 +35,7 @@ export default Vue.extend({
border: solid var(--lineWidth) var(--urlPreviewBorder);
border-radius: 4px;
overflow: hidden;
border: 1px solid var(--divider);
&:hover {
text-decoration: none;
@@ -42,9 +43,8 @@ export default Vue.extend({
}
> .thumbnail {
position: absolute;
width: 100px;
height: 100%;
width: 100%;
height: 200px;
background-position: center;
background-size: cover;
display: flex;

View File

@@ -6,7 +6,7 @@
<div class="efvhhmdq">
<div class="no-users" v-if="empty">
<p>{{ $t('no-users') }}</p>
<p>{{ $t('noUsers') }}</p>
</div>
<div class="user" v-for="user in users" :key="user.id">
<mk-avatar class="avatar" :user="user"/>

View File

@@ -18,8 +18,9 @@ import PostFormDialog from './components/post-form-dialog.vue';
import Dialog from './components/dialog.vue';
import Menu from './components/menu.vue';
import { router } from './router';
import { applyTheme, lightTheme, builtinThemes } from './theme';
import { applyTheme, lightTheme } from './theme';
import { isDeviceDarkmode } from './scripts/is-device-darkmode';
import createStore from './store';
Vue.use(Vuex);
Vue.use(VueHotkey);
@@ -134,36 +135,39 @@ document.body.setAttribute('ontouchstart', '');
// アプリ基底要素マウント
document.body.innerHTML = '<div id="app"></div>';
const os = new MiOS();
const store = createStore();
const os = new MiOS(store);
os.init(async () => {
window.addEventListener('storage', e => {
if (e.key === 'vuex') {
os.store.replaceState(JSON.parse(localStorage['vuex']));
store.replaceState(JSON.parse(localStorage['vuex']));
} else if (e.key === 'i') {
location.reload();
}
}, false)
os.store.watch(state => state.device.darkMode, darkMode => {
// TODO: このファイルでbuiltinThemesを参照するとcode splittingが効かず、初回読み込み時に全てのテーマコードを読み込むことになってしまい無駄なので何とかする
const themes = builtinThemes.concat(os.store.state.device.themes);
applyTheme(themes.find(x => x.id === (darkMode ? os.store.state.device.darkTheme : os.store.state.device.lightTheme)));
store.watch(state => state.device.darkMode, darkMode => {
import('./theme').then(({ builtinThemes }) => {
const themes = builtinThemes.concat(store.state.device.themes);
applyTheme(themes.find(x => x.id === (darkMode ? store.state.device.darkTheme : store.state.device.lightTheme)));
});
});
//#region Sync dark mode
if (os.store.state.device.syncDeviceDarkMode) {
os.store.commit('device/set', { key: 'darkMode', value: isDeviceDarkmode() });
if (store.state.device.syncDeviceDarkMode) {
store.commit('device/set', { key: 'darkMode', value: isDeviceDarkmode() });
}
window.matchMedia('(prefers-color-scheme: dark)').addListener(mql => {
if (os.store.state.device.syncDeviceDarkMode) {
os.store.commit('device/set', { key: 'darkMode', value: mql.matches });
if (store.state.device.syncDeviceDarkMode) {
store.commit('device/set', { key: 'darkMode', value: mql.matches });
}
});
//#endregion
if ('Notification' in window && os.store.getters.isSignedIn) {
if ('Notification' in window && store.getters.isSignedIn) {
// 許可を得ていなかったらリクエスト
if (Notification.permission === 'default') {
Notification.requestPermission();
@@ -171,7 +175,7 @@ os.init(async () => {
}
const app = new Vue({
store: os.store,
store: store,
metaInfo: {
title: null,
titleTemplate: title => title ? `${title} | ${(instanceName || 'Misskey')}` : (instanceName || 'Misskey')
@@ -183,7 +187,7 @@ os.init(async () => {
};
},
methods: {
api: os.api,
api: (endpoint: string, data: { [x: string]: any } = {}, token?) => store.dispatch('api', { endpoint, data, token }),
signout: os.signout,
new(vm, props) {
const x = new vm({
@@ -234,58 +238,63 @@ os.init(async () => {
// マウント
app.$mount('#app');
if (app.$store.getters.isSignedIn) {
os.stream.on('emojiAdded', data => {
// TODO
//store.commit('instance/set', );
});
if (store.getters.isSignedIn) {
const main = os.stream.useSharedConnection('main');
// 自分の情報が更新されたとき
main.on('meUpdated', i => {
app.$store.dispatch('mergeMe', i);
store.dispatch('mergeMe', i);
});
main.on('readAllNotifications', () => {
app.$store.dispatch('mergeMe', {
store.dispatch('mergeMe', {
hasUnreadNotification: false
});
});
main.on('unreadNotification', () => {
app.$store.dispatch('mergeMe', {
store.dispatch('mergeMe', {
hasUnreadNotification: true
});
});
main.on('unreadMention', () => {
app.$store.dispatch('mergeMe', {
store.dispatch('mergeMe', {
hasUnreadMentions: true
});
});
main.on('readAllUnreadMentions', () => {
app.$store.dispatch('mergeMe', {
store.dispatch('mergeMe', {
hasUnreadMentions: false
});
});
main.on('unreadSpecifiedNote', () => {
app.$store.dispatch('mergeMe', {
store.dispatch('mergeMe', {
hasUnreadSpecifiedNotes: true
});
});
main.on('readAllUnreadSpecifiedNotes', () => {
app.$store.dispatch('mergeMe', {
store.dispatch('mergeMe', {
hasUnreadSpecifiedNotes: false
});
});
main.on('readAllMessagingMessages', () => {
app.$store.dispatch('mergeMe', {
store.dispatch('mergeMe', {
hasUnreadMessagingMessage: false
});
});
main.on('unreadMessagingMessage', () => {
app.$store.dispatch('mergeMe', {
store.dispatch('mergeMe', {
hasUnreadMessagingMessage: true
});
@@ -293,13 +302,13 @@ os.init(async () => {
});
main.on('readAllAntennas', () => {
app.$store.dispatch('mergeMe', {
store.dispatch('mergeMe', {
hasUnreadAntenna: false
});
});
main.on('unreadAntenna', () => {
app.$store.dispatch('mergeMe', {
store.dispatch('mergeMe', {
hasUnreadAntenna: true
});
@@ -307,13 +316,13 @@ os.init(async () => {
});
main.on('readAllAnnouncements', () => {
app.$store.dispatch('mergeMe', {
store.dispatch('mergeMe', {
hasUnreadAnnouncement: false
});
});
main.on('clientSettingUpdated', x => {
app.$store.commit('settings/set', {
store.commit('settings/set', {
key: x.key,
value: x.value
});

View File

@@ -2,16 +2,11 @@ import autobind from 'autobind-decorator';
import Vue from 'vue';
import { EventEmitter } from 'eventemitter3';
import initStore from './store';
import { apiUrl, version } from './config';
import Progress from './scripts/loading';
import Stream from './scripts/stream';
//#region api requests
let spinner = null;
let pending = 0;
//#endregion
import store from './store';
/**
* Misskey Operating System
@@ -19,7 +14,7 @@ let pending = 0;
export default class MiOS extends EventEmitter {
public app: Vue;
public store: ReturnType<typeof initStore>;
public store: ReturnType<typeof store>;
/**
* A connection manager of home stream
@@ -31,6 +26,11 @@ export default class MiOS extends EventEmitter {
*/
private swRegistration: ServiceWorkerRegistration = null;
constructor(vuex: MiOS['store']) {
super();
this.store = vuex;
}
@autobind
public signout() {
this.store.dispatch('logout');
@@ -52,8 +52,6 @@ export default class MiOS extends EventEmitter {
});
};
this.store = initStore(this);
// ユーザーをフェッチしてコールバックする
const fetchme = (token, cb) => {
let me = null;
@@ -187,16 +185,19 @@ export default class MiOS extends EventEmitter {
}
// Register
this.api('sw/register', {
endpoint: subscription.endpoint,
auth: encode(subscription.getKey('auth')),
publickey: encode(subscription.getKey('p256dh'))
this.store.dispatch('api', {
endpoint: 'sw/register',
data: {
endpoint: subscription.endpoint,
auth: encode(subscription.getKey('auth')),
publickey: encode(subscription.getKey('p256dh'))
}
});
})
// When subscribe failed
.catch(async (err: Error) => {
// 通知が許可されていなかったとき
if (err.name == 'NotAllowedError') {
if (err.name === 'NotAllowedError') {
return;
}
@@ -214,52 +215,6 @@ export default class MiOS extends EventEmitter {
// Register service worker
navigator.serviceWorker.register(sw);
}
/**
* Misskey APIにリクエストします
* @param endpoint エンドポイント名
* @param data パラメータ
*/
@autobind
public api(endpoint: string, data: { [x: string]: any } = {}, token?): Promise<{ [x: string]: any }> {
if (++pending === 1) {
spinner = document.createElement('div');
spinner.setAttribute('id', 'wait');
document.body.appendChild(spinner);
}
const onFinally = () => {
if (--pending === 0) spinner.parentNode.removeChild(spinner);
};
const promise = new Promise((resolve, reject) => {
// Append a credential
if (this.store.getters.isSignedIn) (data as any).i = this.store.state.i.token;
if (token) (data as any).i = token;
// Send request
fetch(endpoint.indexOf('://') > -1 ? endpoint : `${apiUrl}/${endpoint}`, {
method: 'POST',
body: JSON.stringify(data),
credentials: 'omit',
cache: 'no-cache'
}).then(async (res) => {
const body = res.status === 204 ? null : await res.json();
if (res.status === 200) {
resolve(body);
} else if (res.status === 204) {
resolve();
} else {
reject(body.error);
}
}).catch(reject);
});
promise.then(onFinally, onFinally);
return promise;
}
}
/**

View File

@@ -13,7 +13,7 @@
<x-notes v-if="showNext" ref="next" :pagination="next"/>
<hr v-if="showNext"/>
<mk-remote-caution v-if="note.user.host != null" :href="note.uri" style="margin-bottom: var(--margin)"/>
<mk-remote-caution v-if="note.user.host != null" :href="note.url || note.uri" style="margin-bottom: var(--margin)"/>
<x-note :note="note" :key="note.id" :detail="true"/>
<div v-if="error">
<mk-error @retry="fetch()"/>

View File

@@ -102,6 +102,7 @@ import { blockDefs } from '../../scripts/aiscript/index';
import { ASTypeChecker } from '../../scripts/aiscript/type-checker';
import { url } from '../../config';
import { collectPageVars } from '../../scripts/collect-page-vars';
import { selectDriveFile } from '../../scripts/select-drive-file';
export default Vue.extend({
i18n,
@@ -405,9 +406,7 @@ export default Vue.extend({
},
setEyeCatchingImage() {
this.$chooseDriveFile({
multiple: false
}).then(file => {
selectDriveFile(this.$root, false).then(file => {
this.eyeCatchingImageId = file.id;
});
},

View File

@@ -5,6 +5,7 @@
<div class="_card" v-if="page" :key="page.id">
<div class="_title">{{ page.title }}</div>
<img class="header" :src="page.eyeCatchingImage.url" v-if="page.eyeCatchingImageId" />
<div class="_content">
<x-page :page="page"/>
</div>
@@ -115,6 +116,8 @@ export default Vue.extend({
<style lang="scss" scoped>
.xcukqgmh {
> ._card > .header {
max-width: 100%;
}
}
</style>

View File

@@ -57,9 +57,9 @@ const ignoreElemens = ['input', 'textarea'];
function match(e: KeyboardEvent, patterns: action['patterns']): boolean {
const key = e.code.toLowerCase();
return patterns.some(pattern => pattern.which.includes(key) &&
pattern.ctrl == e.ctrlKey &&
pattern.shift == e.shiftKey &&
pattern.alt == e.altKey &&
pattern.ctrl === e.ctrlKey &&
pattern.shift === e.shiftKey &&
pattern.alt === e.altKey &&
!e.metaKey
);
}

View File

@@ -1,5 +1,5 @@
export default (input: string): string[] => {
if (Object.keys(aliases).some(a => a.toLowerCase() == input.toLowerCase())) {
if (Object.keys(aliases).some(a => a.toLowerCase() === input.toLowerCase())) {
const codes = aliases[input];
return Array.isArray(codes) ? codes : [codes];
} else {

View File

@@ -20,7 +20,7 @@ export default (opts) => ({
computed: {
empty(): boolean {
return this.items.length == 0 && !this.fetching && this.inited;
return this.items.length === 0 && !this.fetching && this.inited;
},
error(): boolean {

View File

@@ -47,9 +47,9 @@ export async function search(v: any, q: string) {
uri: q
});
dialog.close();
if (res.type == 'User') {
if (res.type === 'User') {
v.$router.push(`/@${res.object.username}@${res.object.host}`);
} else if (res.type == 'Note') {
} else if (res.type === 'Note') {
v.$router.push(`/notes/${res.object.id}`);
}
} catch (e) {

View File

@@ -68,7 +68,7 @@ export default class Stream extends EventEmitter {
*/
@autobind
private onOpen() {
const isReconnect = this.state == 'reconnecting';
const isReconnect = this.state === 'reconnecting';
this.state = 'connected';
this.emit('_connected_');
@@ -87,7 +87,7 @@ export default class Stream extends EventEmitter {
*/
@autobind
private onClose() {
if (this.state == 'connected') {
if (this.state === 'connected') {
this.state = 'reconnecting';
this.emit('_disconnected_');
}
@@ -100,7 +100,7 @@ export default class Stream extends EventEmitter {
private onMessage(message) {
const { type, body } = JSON.parse(message.data);
if (type == 'channel') {
if (type === 'channel') {
const id = body.id;
let connections: Connection[];

View File

@@ -1,8 +1,7 @@
import Vuex from 'vuex';
import createPersistedState from 'vuex-persistedstate';
import * as nestedProperty from 'nested-property';
import MiOS from './mios';
import { apiUrl } from './config';
const defaultSettings = {
tutorial: 0,
@@ -57,13 +56,15 @@ function copy<T>(data: T): T {
return JSON.parse(JSON.stringify(data));
}
export default (os: MiOS) => new Vuex.Store({
export default () => new Vuex.Store({
plugins: [createPersistedState({
paths: ['i', 'device', 'deviceUser', 'settings', 'instance']
})],
state: {
i: null,
pendingApiRequestsCount: 0,
spinner: null
},
getters: {
@@ -121,6 +122,47 @@ export default (os: MiOS) => new Vuex.Store({
ctx.commit('settings/init', me.clientData);
}
},
api(ctx, { endpoint, data, token }) {
if (++ctx.state.pendingApiRequestsCount === 1) {
// TODO: spinnerの表示はstoreでやらない
ctx.state.spinner = document.createElement('div');
ctx.state.spinner.setAttribute('id', 'wait');
document.body.appendChild(ctx.state.spinner);
}
const onFinally = () => {
if (--ctx.state.pendingApiRequestsCount === 0) ctx.state.spinner.parentNode.removeChild(ctx.state.spinner);
};
const promise = new Promise((resolve, reject) => {
// Append a credential
if (ctx.getters.isSignedIn) (data as any).i = ctx.state.i.token;
if (token) (data as any).i = token;
// Send request
fetch(endpoint.indexOf('://') > -1 ? endpoint : `${apiUrl}/${endpoint}`, {
method: 'POST',
body: JSON.stringify(data),
credentials: 'omit',
cache: 'no-cache'
}).then(async (res) => {
const body = res.status === 204 ? null : await res.json();
if (res.status === 200) {
resolve(body);
} else if (res.status === 204) {
resolve();
} else {
reject(body.error);
}
}).catch(reject);
});
promise.then(onFinally, onFinally);
return promise;
}
},
modules: {
@@ -139,9 +181,12 @@ export default (os: MiOS) => new Vuex.Store({
actions: {
async fetch(ctx) {
const meta = await os.api('meta', {
detail: false
});
const meta = await ctx.dispatch('api', {
endpoint: 'meta',
data: {
detail: false
}
}, { root: true });
ctx.commit('set', meta);
}
@@ -212,7 +257,7 @@ export default (os: MiOS) => new Vuex.Store({
},
updateWidget(state, x) {
const w = state.widgets.find(w => w.id == x.id);
const w = state.widgets.find(w => w.id === x.id);
if (w) {
w.data = x.data;
}
@@ -246,10 +291,13 @@ export default (os: MiOS) => new Vuex.Store({
ctx.commit('set', x);
if (ctx.rootGetters.isSignedIn) {
os.api('i/update-client-setting', {
name: x.key,
value: x.value
});
ctx.dispatch('api', {
endpoint: 'i/update-client-setting',
data: {
name: x.key,
value: x.value
}
}, { root: true });
}
},
}

View File

@@ -51,14 +51,14 @@ export default Vue.extend({
weekday: date.getDay()
};
d.v = peak == 0 ? 0 : d.total / (peak / 2);
d.v = peak === 0 ? 0 : d.total / (peak / 2);
if (d.v > 1) d.v = 1;
const ch = d.date.weekday == 0 || d.date.weekday == 6 ? 275 : 170;
const ch = d.date.weekday === 0 || d.date.weekday === 6 ? 275 : 170;
const cs = d.v * 100;
const cl = 15 + ((1 - d.v) * 80);
d.color = `hsl(${ch}, ${cs}%, ${cl}%)`;
if (d.date.weekday == 0) x--;
if (d.date.weekday === 0) x--;
});
}
});

View File

@@ -60,7 +60,7 @@ export default define({
},
methods: {
func() {
if (this.props.design == 2) {
if (this.props.design === 2) {
this.props.design = 0;
} else {
this.props.design++;
@@ -68,7 +68,7 @@ export default define({
this.save();
},
toggleView() {
if (this.props.view == 1) {
if (this.props.view === 1) {
this.props.view = 0;
} else {
this.props.view++;

View File

@@ -65,7 +65,7 @@ export default define({
},
methods: {
func() {
if (this.props.design == 2) {
if (this.props.design === 2) {
this.props.design = 0;
} else {
this.props.design++;
@@ -102,7 +102,7 @@ export default define({
this.monthP = monthNumer / monthDenom * 100;
this.yearP = yearNumer / yearDenom * 100;
this.isHoliday = now.getDay() == 0 || now.getDay() == 6;
this.isHoliday = now.getDay() === 0 || now.getDay() === 6;
}
}
});

View File

@@ -1,6 +1,6 @@
<template>
<div>
<mk-container :show-header="props.design === 0" :naked="props.design === 2" :class="$style.root" :data-melt="props.design == 2">
<mk-container :show-header="props.design === 0" :naked="props.design === 2" :class="$style.root" :data-melt="props.design === 2">
<template #header><fa :icon="faCamera"/>{{ $t('_widgets.photos') }}</template>
<div class="">
@@ -66,7 +66,7 @@ export default define({
},
func() {
if (this.props.design == 2) {
if (this.props.design === 2) {
this.props.design = 0;
} else {
this.props.design++;

View File

@@ -15,7 +15,7 @@ const dir = `${__dirname}/../../.config`;
/**
* Path of configuration file
*/
const path = process.env.NODE_ENV == 'test'
const path = process.env.NODE_ENV === 'test'
? `${dir}/test.yml`
: `${dir}/default.yml`;

View File

@@ -0,0 +1,4 @@
# AiScript
## 関数
デフォルトで値渡しです。

View File

@@ -27,6 +27,8 @@ APIを使い始めるには、まずアクセストークンを取得する必
UUIDを生成する。以後これをセッションIDと呼びます。
> このセッションIDは毎回生成し、使いまわさないようにしてください。
#### Step 2
`{_URL_}/miauth/{session}`をユーザーのブラウザで表示させる。`{session}`の部分は、セッションIDに置き換えてください。

View File

@@ -226,10 +226,10 @@ export default class Reversi {
// 座標が指し示す位置がボード外に出たとき
if (this.opts.loopedBoard && this.transformXyToPos(
(x = ((x % this.mapWidth) + this.mapWidth) % this.mapWidth),
(y = ((y % this.mapHeight) + this.mapHeight) % this.mapHeight)) == initPos)
(y = ((y % this.mapHeight) + this.mapHeight) % this.mapHeight)) === initPos)
// 盤面の境界でループし、自分が石を置く位置に戻ってきたとき、挟めるようにしている (ref: Test4のマップ)
return found;
else if (x == -1 || y == -1 || x == this.mapWidth || y == this.mapHeight)
else if (x === -1 || y === -1 || x === this.mapWidth || y === this.mapHeight)
return []; // 挟めないことが確定 (盤面外に到達)
const pos = this.transformXyToPos(x, y);

View File

@@ -1,7 +1,7 @@
import { parseFragment, DefaultTreeDocumentFragment } from 'parse5';
import { urlRegex } from './prelude';
export function fromHtml(html: string): string {
export function fromHtml(html: string, hashtagNames?: string[]): string {
const dom = parseFragment(html) as DefaultTreeDocumentFragment;
let text = '';
@@ -13,7 +13,7 @@ export function fromHtml(html: string): string {
return text.trim();
function getText(node: any): string {
if (node.nodeName == '#text') return node.value;
if (node.nodeName === '#text') return node.value;
if (node.childNodes) {
return node.childNodes.map((n: any) => getText(n)).join('');
@@ -34,29 +34,27 @@ export function fromHtml(html: string): string {
case 'a':
const txt = getText(node);
const rel = node.attrs.find((x: any) => x.name == 'rel');
const href = node.attrs.find((x: any) => x.name == 'href');
const _class = node.attrs.find((x: any) => x.name == 'class');
const isHashtag = rel?.value?.match('tag') || _class?.value?.match('hashtag');
const rel = node.attrs.find((x: any) => x.name === 'rel');
const href = node.attrs.find((x: any) => x.name === 'href');
// ハッシュタグ / hrefがない / txtがURL
if (isHashtag || !href || href.value == txt) {
text += isHashtag || txt.match(urlRegex) ? txt : `<${txt}>`;
// ハッシュタグ
if (hashtagNames && href && hashtagNames.map(x => x.toLowerCase()).includes(txt.toLowerCase())) {
text += txt;
// メンション
} else if (txt.startsWith('@') && !(rel && rel.value.match(/^me /))) {
const part = txt.split('@');
if (part.length == 2) {
if (part.length === 2) {
//#region ホスト名部分が省略されているので復元する
const acct = `${txt}@${(new URL(href.value)).hostname}`;
text += acct;
//#endregion
} else if (part.length == 3) {
} else if (part.length === 3) {
text += txt;
}
// その他
} else {
text += `[${txt}](${href.value})`;
text += (!href || (txt === href.value && txt.match(urlRegex))) ? txt : `[${txt}](${href.value})`;
}
break;

View File

@@ -31,7 +31,7 @@ export const mfmLanguage = P.createLanguage({
r.center,
),
startOfLine: () => P((input, i) => {
if (i == 0 || input[i] == '\n' || input[i - 1] == '\n') {
if (i === 0 || input[i] === '\n' || input[i - 1] === '\n') {
return P.makeSuccess(i, null);
} else {
return P.makeFailure(i, 'not newline');
@@ -50,7 +50,7 @@ export const mfmLanguage = P.createLanguage({
if (!text.match(/^>[\s\S]+?/)) return P.makeFailure(i, 'not a quote');
const quote = takeWhile(line => line.startsWith('>'), text.split('\n'));
const qInner = quote.join('\n').replace(/^>/gm, '').replace(/^ /gm, '');
if (qInner == '') return P.makeFailure(i, 'not a quote');
if (qInner === '') return P.makeFailure(i, 'not a quote');
const contents = r.root.tryParse(qInner);
return P.makeSuccess(i + quote.join('\n').length + 1, createTree('quote', contents, {}));
})),

View File

@@ -4,7 +4,7 @@ import { MfmForest, MfmTree } from './prelude';
import { createTree, createLeaf } from '../prelude/tree';
function isEmptyTextTree(t: MfmTree): boolean {
return t.node.type == 'text' && t.node.props.text === '';
return t.node.type === 'text' && t.node.props.text === '';
}
function concatTextTrees(ts: MfmForest): MfmTree {

View File

@@ -3,7 +3,7 @@ import { MfmForest } from './prelude';
import { normalize } from './normalize';
export function parse(source: string | null): MfmForest | null {
if (source == null || source == '') {
if (source == null || source === '') {
return null;
}
@@ -11,7 +11,7 @@ export function parse(source: string | null): MfmForest | null {
}
export function parsePlain(source: string | null): MfmForest | null {
if (source == null || source == '') {
if (source == null || source === '') {
return null;
}

21
src/misc/secure-rndstr.ts Normal file
View File

@@ -0,0 +1,21 @@
import * as crypto from 'crypto';
const L_CHARS = '0123456789abcdefghijklmnopqrstuvwxyz';
const LU_CHARS = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
export function secureRndstr(length = 32, useLU = true): string {
const chars = useLU ? LU_CHARS : L_CHARS;
const chars_len = chars.length;
let str = '';
for (let i = 0; i < length; i++) {
let rand = Math.floor((crypto.randomBytes(1).readUInt8(0) / 0xFF) * chars_len);
if (rand === chars_len) {
rand = chars_len - 1;
}
str += chars.charAt(rand);
}
return str;
}

View File

@@ -112,6 +112,12 @@ export class Note {
})
public uri: string | null;
@Column('varchar', {
length: 512, nullable: true,
comment: 'The human readable url of a note. it will be null when the note is local.'
})
public url: string | null;
@Column('integer', {
default: 0, select: false
})

View File

@@ -2,7 +2,6 @@ import { getRepository, getCustomRepository } from 'typeorm';
import { Announcement } from './entities/announcement';
import { AnnouncementRead } from './entities/announcement-read';
import { Instance } from './entities/instance';
import { Emoji } from './entities/emoji';
import { Poll } from './entities/poll';
import { PollVote } from './entities/poll-vote';
import { Meta } from './entities/meta';
@@ -52,6 +51,7 @@ import { AntennaRepository } from './repositories/antenna';
import { AntennaNote } from './entities/antenna-note';
import { PromoNote } from './entities/promo-note';
import { PromoRead } from './entities/promo-read';
import { EmojiRepository } from './repositories/emoji';
export const Announcements = getRepository(Announcement);
export const AnnouncementReads = getRepository(AnnouncementRead);
@@ -79,7 +79,7 @@ export const UsedUsernames = getRepository(UsedUsername);
export const Followings = getCustomRepository(FollowingRepository);
export const FollowRequests = getCustomRepository(FollowRequestRepository);
export const Instances = getRepository(Instance);
export const Emojis = getRepository(Emoji);
export const Emojis = getCustomRepository(EmojiRepository);
export const DriveFiles = getCustomRepository(DriveFileRepository);
export const DriveFolders = getCustomRepository(DriveFolderRepository);
export const Notifications = getCustomRepository(NotificationRepository);

View File

@@ -0,0 +1,27 @@
import { EntityRepository, Repository } from 'typeorm';
import { Emoji } from '../entities/emoji';
import { ensure } from '../../prelude/ensure';
@EntityRepository(Emoji)
export class EmojiRepository extends Repository<Emoji> {
public async pack(
src: Emoji['id'] | Emoji,
) {
const emoji = typeof src === 'object' ? src : await this.findOne(src).then(ensure);
return {
id: emoji.id,
aliases: emoji.aliases,
name: emoji.name,
category: emoji.category,
host: emoji.host,
url: emoji.url,
};
}
public packMany(
emojis: any[],
) {
return Promise.all(emojis.map(x => this.pack(x)));
}
}

View File

@@ -39,7 +39,7 @@ export class NoteRepository extends Repository<Note> {
}
// visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示
if (packedNote.visibility == 'followers') {
if (packedNote.visibility === 'followers') {
if (meId == null) {
hide = true;
} else if (meId === packedNote.userId) {
@@ -171,8 +171,8 @@ export class NoteRepository extends Repository<Note> {
let text = note.text;
if (note.name && note.uri) {
text = `${note.name}\n${(note.text || '').trim()}\n${note.uri}`;
if (note.name && (note.url || note.uri)) {
text = `${note.name}\n${(note.text || '').trim()}\n\n${note.url || note.uri}`;
}
const packed = await awaitAll({
@@ -197,6 +197,7 @@ export class NoteRepository extends Repository<Note> {
renoteId: note.renoteId,
mentions: note.mentions.length > 0 ? note.mentions : undefined,
uri: note.uri || undefined,
url: note.url || undefined,
_featuredId_: (note as any)._featuredId_ || undefined,
_prId_: (note as any)._prId_ || undefined,

View File

@@ -5,7 +5,7 @@ import { IFollow } from '../../type';
import { Users } from '../../../../models';
export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
const id = typeof activity.actor == 'string' ? activity.actor : activity.actor.id;
const id = typeof activity.actor === 'string' ? activity.actor : activity.actor.id;
if (id == null) throw new Error('missing id');
if (!id.startsWith(config.url + '/')) {

View File

@@ -5,7 +5,7 @@ import { IFollow } from '../type';
import { Users } from '../../../models';
export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
const id = typeof activity.object === 'string' ? activity.object : activity.object.id;
if (id == null) throw new Error('missing id');
if (!id.startsWith(config.url + '/')) {

View File

@@ -5,7 +5,7 @@ import { IFollow } from '../../type';
import { Users } from '../../../../models';
export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
const id = typeof activity.actor == 'string' ? activity.actor : activity.actor.id;
const id = typeof activity.actor === 'string' ? activity.actor : activity.actor.id;
if (id == null) throw new Error('missing id');
if (!id.startsWith(config.url + '/')) {

View File

@@ -8,7 +8,7 @@ import { Users } from '../../../../models';
const logger = apLogger;
export default async (actor: IRemoteUser, activity: IBlock): Promise<void> => {
const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
const id = typeof activity.object === 'string' ? activity.object : activity.object.id;
if (id == null) throw new Error('missing id');
const uri = activity.id || activity;

View File

@@ -6,7 +6,7 @@ import { IRemoteUser } from '../../../../models/entities/user';
import { Users, FollowRequests, Followings } from '../../../../models';
export default async (actor: IRemoteUser, activity: IFollow): Promise<void> => {
const id = typeof activity.object == 'string' ? activity.object : activity.object.id;
const id = typeof activity.object === 'string' ? activity.object : activity.object.id;
if (id == null) throw new Error('missing id');
if (!id.startsWith(config.url + '/')) {

View File

@@ -0,0 +1,9 @@
import { IObject } from '../type';
import { extractApHashtagObjects } from '../models/tag';
import { fromHtml } from '../../../mfm/fromHtml';
export function htmlToMfm(html: string, tag?: IObject | IObject[]) {
const hashtagNames = extractApHashtagObjects(tag).map(x => x.name).filter((x): x is string => x != null);
return fromHtml(html, hashtagNames);
}

View File

@@ -0,0 +1,24 @@
import { toArray, unique } from '../../../prelude/array';
import { IObject, isMention, IApMention } from '../type';
import { resolvePerson } from './person';
import * as promiseLimit from 'promise-limit';
import Resolver from '../resolver';
import { User } from '../../../models/entities/user';
export async function extractApMentions(tags: IObject | IObject[] | null | undefined) {
const hrefs = unique(extractApMentionObjects(tags).map(x => x.href as string));
const resolver = new Resolver();
const limit = promiseLimit<User | null>(2);
const mentionedUsers = (await Promise.all(
hrefs.map(x => limit(() => resolvePerson(x, resolver).catch(() => null)))
)).filter((x): x is User => x != null);
return mentionedUsers;
}
export function extractApMentionObjects(tags: IObject | IObject[] | null | undefined): IApMention[] {
if (tags == null) return [];
return toArray(tags).filter(isMention);
}

View File

@@ -6,9 +6,9 @@ import post from '../../../services/note/create';
import { resolvePerson, updatePerson } from './person';
import { resolveImage } from './image';
import { IRemoteUser } from '../../../models/entities/user';
import { fromHtml } from '../../../mfm/fromHtml';
import { ITag, extractHashtags } from './tag';
import { unique } from '../../../prelude/array';
import { htmlToMfm } from '../misc/html-to-mfm';
import { extractApHashtags } from './tag';
import { unique, toArray, toSingle } from '../../../prelude/array';
import { extractPollFromQuestion } from './question';
import vote from '../../../services/note/polls/vote';
import { apLogger } from '../logger';
@@ -17,7 +17,7 @@ import { deliverQuestionUpdate } from '../../../services/note/polls/update';
import { extractDbHost, toPuny } from '../../../misc/convert-host';
import { Notes, Emojis, Polls, MessagingMessages } from '../../../models';
import { Note } from '../../../models/entities/note';
import { IObject, getOneApId, getApId, validPost, IPost } from '../type';
import { IObject, getOneApId, getApId, validPost, IPost, isEmoji } from '../type';
import { Emoji } from '../../../models/entities/emoji';
import { genId } from '../../../misc/gen-id';
import { fetchMeta } from '../../../misc/fetch-meta';
@@ -25,6 +25,7 @@ import { ensure } from '../../../prelude/ensure';
import { getApLock } from '../../../misc/app-lock';
import { createMessage } from '../../../services/messages/create';
import { parseAudience } from '../audience';
import { extractApMentions } from './mention';
const logger = apLogger;
@@ -113,7 +114,6 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
const noteAudience = await parseAudience(actor, note.to, note.cc);
let visibility = noteAudience.visibility;
const visibleUsers = noteAudience.visibleUsers;
const apMentions = noteAudience.mentionedUsers;
// Audience (to, cc) が指定されてなかった場合
if (visibility === 'specified' && visibleUsers.length === 0) {
@@ -125,7 +125,8 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
let isTalk = note._misskey_talk && visibility === 'specified';
const apHashtags = await extractHashtags(note.tag);
const apMentions = await extractApMentions(note.tag);
const apHashtags = await extractApHashtags(note.tag);
// 添付ファイル
// TODO: attachmentは必ずしもImageではない
@@ -210,7 +211,7 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
const cw = note.summary === '' ? null : note.summary;
// テキストのパース
const text = note._misskey_content || (note.content ? fromHtml(note.content) : null);
const text = note._misskey_content || (note.content ? htmlToMfm(note.content, note.tag) : null);
// vote
if (reply && reply.hasPoll) {
@@ -280,7 +281,8 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
apHashtags,
apEmojis,
poll,
uri: note.id
uri: note.id,
url: note.url,
}, silent);
}
@@ -291,7 +293,7 @@ export async function createNote(value: string | IObject, resolver?: Resolver, s
* リモートサーバーからフェッチしてMisskeyに登録しそれを返します。
*/
export async function resolveNote(value: string | IObject, resolver?: Resolver): Promise<Note | null> {
const uri = typeof value == 'string' ? value : value.id;
const uri = typeof value === 'string' ? value : value.id;
if (uri == null) throw new Error('missing uri');
// ブロックしてたら中断
@@ -318,15 +320,16 @@ export async function resolveNote(value: string | IObject, resolver?: Resolver):
}
}
export async function extractEmojis(tags: ITag[], host: string): Promise<Emoji[]> {
export async function extractEmojis(tags: IObject | IObject[], host: string): Promise<Emoji[]> {
host = toPuny(host);
if (!tags) return [];
const eomjiTags = tags.filter(tag => tag.type === 'Emoji' && tag.icon && tag.icon.url && tag.name);
const eomjiTags = toArray(tags).filter(isEmoji);
return await Promise.all(eomjiTags.map(async tag => {
const name = tag.name!.replace(/^:/, '').replace(/:$/, '');
tag.icon = toSingle(tag.icon);
const exists = await Emojis.findOne({
host,

View File

@@ -3,12 +3,12 @@ import * as promiseLimit from 'promise-limit';
import config from '../../../config';
import Resolver from '../resolver';
import { resolveImage } from './image';
import { isCollectionOrOrderedCollection, isCollection, IPerson, getApId } from '../type';
import { isCollectionOrOrderedCollection, isCollection, IPerson, getApId, IObject, isPropertyValue, IApPropertyValue } from '../type';
import { fromHtml } from '../../../mfm/fromHtml';
import { htmlToMfm } from '../misc/html-to-mfm';
import { resolveNote, extractEmojis } from './note';
import { registerOrFetchInstanceDoc } from '../../../services/register-or-fetch-instance-doc';
import { ITag, extractHashtags } from './tag';
import { IIdentifier } from './identifier';
import { extractApHashtags } from './tag';
import { apLogger } from '../logger';
import { Note } from '../../../models/entities/note';
import { updateUsertags } from '../../../services/update-hashtag';
@@ -134,9 +134,9 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
const { fields } = analyzeAttachments(person.attachment || []);
const tags = extractHashtags(person.tag).map(tag => tag.toLowerCase()).splice(0, 32);
const tags = extractApHashtags(person.tag).map(tag => tag.toLowerCase()).splice(0, 32);
const isBot = object.type == 'Service';
const isBot = object.type === 'Service';
// Create user
let user: IRemoteUser;
@@ -165,7 +165,7 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
await transactionalEntityManager.save(new UserProfile({
userId: user.id,
description: person.summary ? fromHtml(person.summary) : null,
description: person.summary ? htmlToMfm(person.summary, person.tag) : null,
url: person.url,
fields,
userHost: host
@@ -180,11 +180,20 @@ export async function createPerson(uri: string, resolver?: Resolver): Promise<Us
} catch (e) {
// duplicate key error
if (isDuplicateKeyValueError(e)) {
throw new Error('already registered');
}
// /users/@a => /users/:id のように入力がaliasなときにエラーになることがあるのを対応
const u = await Users.findOne({
uri: person.id
});
logger.error(e);
throw e;
if (u) {
user = u as IRemoteUser;
} else {
throw new Error('already registered');
}
} else {
logger.error(e);
throw e;
}
}
// Register host
@@ -308,7 +317,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
const { fields } = analyzeAttachments(person.attachment || []);
const tags = extractHashtags(person.tag).map(tag => tag.toLowerCase()).splice(0, 32);
const tags = extractApHashtags(person.tag).map(tag => tag.toLowerCase()).splice(0, 32);
const updates = {
lastFetchedAt: new Date(),
@@ -318,7 +327,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
emojis: emojiNames,
name: person.name,
tags,
isBot: object.type == 'Service',
isBot: object.type === 'Service',
isCat: (person as any).isCat === true,
isLocked: !!person.manuallyApprovesFollowers,
} as Partial<User>;
@@ -346,7 +355,7 @@ export async function updatePerson(uri: string, resolver?: Resolver | null, hint
await UserProfiles.update({ userId: exist.id }, {
url: person.url,
fields,
description: person.summary ? fromHtml(person.summary) : null,
description: person.summary ? htmlToMfm(person.summary, person.tag) : null,
});
// ハッシュタグ更新
@@ -384,16 +393,6 @@ export async function resolvePerson(uri: string, resolver?: Resolver): Promise<U
return await createPerson(uri, resolver);
}
const isPropertyValue = (x: {
type: string,
name?: string,
value?: string
}) =>
x &&
x.type === 'PropertyValue' &&
typeof x.name === 'string' &&
typeof x.value === 'string';
const services: {
[x: string]: (id: string, username: string) => any
} = {
@@ -409,7 +408,7 @@ const $discord = (id: string, name: string) => {
return { id, username, discriminator };
};
function addService(target: { [x: string]: any }, source: IIdentifier) {
function addService(target: { [x: string]: any }, source: IApPropertyValue) {
const service = services[source.name];
if (typeof source.value !== 'string')
@@ -421,7 +420,7 @@ function addService(target: { [x: string]: any }, source: IIdentifier) {
target[source.name.split(':')[2]] = service(id, username);
}
export function analyzeAttachments(attachments: ITag[]) {
export function analyzeAttachments(attachments: IObject | IObject[] | undefined) {
const fields: {
name: string,
value: string
@@ -430,12 +429,12 @@ export function analyzeAttachments(attachments: ITag[]) {
if (Array.isArray(attachments)) {
for (const attachment of attachments.filter(isPropertyValue)) {
if (isPropertyValue(attachment.identifier!)) {
addService(services, attachment.identifier!);
if (isPropertyValue(attachment.identifier)) {
addService(services, attachment.identifier);
} else {
fields.push({
name: attachment.name!,
value: fromHtml(attachment.value!)
name: attachment.name,
value: fromHtml(attachment.value)
});
}
}

View File

@@ -41,7 +41,7 @@ export async function extractPollFromQuestion(source: string | IObject, resolver
* @returns true if updated
*/
export async function updateQuestion(value: any) {
const uri = typeof value == 'string' ? value : value.id;
const uri = typeof value === 'string' ? value : value.id;
// URIがこのサーバーを指しているならスキップ
if (uri.startsWith(config.url + '/')) throw new Error('uri points local');

View File

@@ -1,26 +1,18 @@
import { IIcon } from './icon';
import { IIdentifier } from './identifier';
import { toArray } from '../../../prelude/array';
import { IObject, isHashtag, IApHashtag } from '../type';
/***
* tag (ActivityPub)
*/
export type ITag = {
id: string;
type: string;
name?: string;
value?: string;
updated?: Date;
icon?: IIcon;
identifier?: IIdentifier;
};
export function extractHashtags(tags: ITag[] | null | undefined): string[] {
export function extractApHashtags(tags: IObject | IObject[] | null | undefined) {
if (tags == null) return [];
const hashtags = tags.filter(tag => tag.type === 'Hashtag' && typeof tag.name == 'string');
const hashtags = extractApHashtagObjects(tags);
return hashtags.map(tag => {
const m = tag.name ? tag.name.match(/^#(.+)/) : null;
const m = tag.name.match(/^#(.+)/);
return m ? m[1] : null;
}).filter(x => x != null) as string[];
}).filter((x): x is string => x != null);
}
export function extractApHashtagObjects(tags: IObject | IObject[] | null | undefined): IApHashtag[] {
if (tags == null) return [];
return toArray(tags).filter(isHashtag);
}

View File

@@ -7,10 +7,10 @@ export default (object: any, note: Note) => {
let to: string[] = [];
let cc: string[] = [];
if (note.visibility == 'public') {
if (note.visibility === 'public') {
to = ['https://www.w3.org/ns/activitystreams#Public'];
cc = [`${attributedTo}/followers`];
} else if (note.visibility == 'home') {
} else if (note.visibility === 'home') {
to = [`${attributedTo}/followers`];
cc = ['https://www.w3.org/ns/activitystreams#Public'];
} else {

View File

@@ -4,6 +4,6 @@ import { Users } from '../../../models';
export default (mention: User) => ({
type: 'Mention',
href: Users.isRemoteUser(mention) ? mention.uri : `${config.url}/@${(mention as ILocalUser).username}`,
href: Users.isRemoteUser(mention) ? mention.uri : `${config.url}/users/${(mention as ILocalUser).id}`,
name: Users.isRemoteUser(mention) ? `@${mention.username}@${mention.host}` : `@${(mention as ILocalUser).username}`,
});

View File

@@ -63,13 +63,13 @@ export default async function renderNote(note: Note, dive = true, isTalk = false
let to: string[] = [];
let cc: string[] = [];
if (note.visibility == 'public') {
if (note.visibility === 'public') {
to = ['https://www.w3.org/ns/activitystreams#Public'];
cc = [`${attributedTo}/followers`].concat(mentions);
} else if (note.visibility == 'home') {
} else if (note.visibility === 'home') {
to = [`${attributedTo}/followers`];
cc = ['https://www.w3.org/ns/activitystreams#Public'].concat(mentions);
} else if (note.visibility == 'followers') {
} else if (note.visibility === 'followers') {
to = [`${attributedTo}/followers`];
cc = mentions;
} else {

View File

@@ -20,7 +20,8 @@ export interface IObject {
icon?: any;
image?: any;
url?: string;
tag?: any[];
href?: string;
tag?: IObject | IObject[];
sensitive?: boolean;
}
@@ -130,6 +131,45 @@ export const isOrderedCollection = (object: IObject): object is IOrderedCollecti
export const isCollectionOrOrderedCollection = (object: IObject): object is ICollection | IOrderedCollection =>
isCollection(object) || isOrderedCollection(object);
export interface IApPropertyValue extends IObject {
type: 'PropertyValue';
identifier: IApPropertyValue;
name: string;
value: string;
}
export const isPropertyValue = (object: IObject): object is IApPropertyValue =>
object &&
object.type === 'PropertyValue' &&
typeof object.name === 'string' &&
typeof (object as any).value === 'string';
export interface IApMention extends IObject {
type: 'Mention';
href: string;
}
export const isMention = (object: IObject): object is IApMention=>
object.type === 'Mention' &&
typeof object.href === 'string';
export interface IApHashtag extends IObject {
type: 'Hashtag';
name: string;
}
export const isHashtag = (object: IObject): object is IApHashtag =>
object.type === 'Hashtag' &&
typeof object.name === 'string';
export interface IApEmoji extends IObject {
type: 'Emoji';
updated: Date;
}
export const isEmoji = (object: IObject): object is IApEmoji =>
object.type === 'Emoji' && !Array.isArray(object.icon) && object.icon.url != null;
export interface ICreate extends IActivity {
type: 'Create';
}

View File

@@ -101,7 +101,7 @@ export default async (ctx: Router.RouterContext) => {
* @param note Note
*/
export async function packActivity(note: Note): Promise<any> {
if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length == 0)) {
if (note.renoteId && note.text == null && !note.hasPoll && (note.fileIds == null || note.fileIds.length === 0)) {
const renote = await Notes.findOne(note.renoteId).then(ensure);
return renderAnnounce(renote.uri ? renote.uri : `${config.url}/notes/${renote.id}`, note);
}

View File

@@ -78,7 +78,7 @@ function verifyCertificateChain(certificates: string[]) {
}
function PEMString(pemBuffer: Buffer, type = 'CERTIFICATE') {
if (pemBuffer.length == 65 && pemBuffer[0] == 0x04) {
if (pemBuffer.length === 65 && pemBuffer[0] === 0x04) {
pemBuffer = Buffer.concat([PEM_PRELUDE, pemBuffer], 91);
type = 'PUBLIC KEY';
}

View File

@@ -34,7 +34,7 @@ export default (endpoint: IEndpoint, ctx: Koa.Context) => new Promise((res) => {
call(endpoint.name, user, app, body, (ctx as any).file).then((res: any) => {
reply(res);
}).catch((e: ApiError) => {
reply(e.httpStatusCode ? e.httpStatusCode : e.kind == 'client' ? 400 : 500, e);
reply(e.httpStatusCode ? e.httpStatusCode : e.kind === 'client' ? 400 : 500, e);
});
}).catch(() => {
reply(403, new ApiError({

View File

@@ -77,7 +77,7 @@ export default async (endpoint: string, user: User | null | undefined, token: Ac
if (e instanceof ApiError) {
throw e;
} else {
apiLogger.error(`Internal error occurred in ${ep.name}`, {
apiLogger.error(`Internal error occurred in ${ep.name}: ${e?.message}`, {
ep: ep.name,
ps: data,
e: {

View File

@@ -1,3 +1,3 @@
import rndstr from 'rndstr';
import { secureRndstr } from '../../../misc/secure-rndstr';
export default () => rndstr('a-zA-Z0-9', 16);
export default () => secureRndstr(16, true);

View File

@@ -22,8 +22,6 @@ export async function signup(username: User['username'], password: UserProfile['
throw new Error('INVALID_PASSWORD');
}
const usersCount = await Users.count({});
// Generate hash of password
const salt = await bcrypt.genSalt(8);
const hash = await bcrypt.hash(password, salt);
@@ -76,7 +74,9 @@ export async function signup(username: User['username'], password: UserProfile['
usernameLower: username.toLowerCase(),
host: toPunyNullable(host),
token: secret,
isAdmin: usersCount === 0,
isAdmin: (await Users.count({
host: null,
})) === 0,
}));
await transactionalEntityManager.save(new UserKeypair({

View File

@@ -17,8 +17,10 @@ export const meta = {
};
export default define(meta, async (ps, me) => {
const noUsers = (await Users.count({})) === 0;
if (!noUsers && me == null) throw new Error('access denied');
const noUsers = (await Users.count({
host: null,
})) === 0;
if (!noUsers && !me?.isAdmin) throw new Error('access denied');
const { account, secret } = await signup(ps.username, ps.password);

View File

@@ -51,8 +51,8 @@ const sort: any = { // < https://github.com/Microsoft/TypeScript/issues/1863
export default define(meta, async (ps, me) => {
const q = {} as any;
if (ps.origin == 'local') q['userHost'] = null;
if (ps.origin == 'remote') q['userHost'] = { $ne: null };
if (ps.origin === 'local') q['userHost'] = null;
if (ps.origin === 'remote') q['userHost'] = { $ne: null };
const files = await DriveFiles.find({
where: q,

View File

@@ -7,6 +7,7 @@ import { insertModerationLog } from '../../../../../services/insert-moderation-l
import { ApiError } from '../../../error';
import { ID } from '../../../../../misc/cafy-id';
import rndstr from 'rndstr';
import { publishBroadcastStream } from '../../../../../services/stream';
export const meta = {
desc: {
@@ -53,6 +54,10 @@ export default define(meta, async (ps, me) => {
await getConnection().queryResultCache!.remove(['meta_emojis']);
publishBroadcastStream('emojiAdded', {
emoji: await Emojis.pack(emoji.id)
});
insertModerationLog(me, 'addEmoji', {
emojiId: emoji.id
});

View File

@@ -51,12 +51,5 @@ export default define(meta, async (ps) => {
.take(ps.limit!)
.getMany();
return emojis.map(e => ({
id: e.id,
name: e.name,
category: e.category,
aliases: e.aliases,
host: e.host,
url: e.url
}));
return Emojis.packMany(emojis);
});

View File

@@ -36,12 +36,5 @@ export default define(meta, async (ps) => {
.take(ps.limit!)
.getMany();
return emojis.map(e => ({
id: e.id,
name: e.name,
category: e.category,
aliases: e.aliases,
host: e.host,
url: e.url
}));
return Emojis.packMany(emojis);
});

View File

@@ -2,13 +2,14 @@ import define from '../../define';
import { getConnection } from 'typeorm';
export const meta = {
requireCredential: false as const,
requireCredential: true as const,
requireAdmin: true,
desc: {
'en-US': 'Get table stats'
},
tags: ['meta'],
tags: ['admin'],
params: {
},

View File

@@ -6,6 +6,8 @@ import { getNote } from '../../../common/getters';
import { PromoNotes } from '../../../../../models';
export const meta = {
tags: ['admin'],
requireCredential: true as const,
requireModerator: true,

View File

@@ -5,12 +5,13 @@ import define from '../../define';
import redis from '../../../../db/redis';
export const meta = {
requireCredential: false as const,
requireCredential: true as const,
requireAdmin: true,
desc: {
},
tags: ['meta'],
tags: ['admin', 'meta'],
params: {
},

View File

@@ -5,6 +5,8 @@ import { Announcements, AnnouncementReads } from '../../../models';
import { makePaginationQuery } from '../common/make-pagination-query';
export const meta = {
tags: ['meta'],
requireCredential: false as const,
params: {

View File

@@ -8,7 +8,7 @@ import { generateMuteQuery } from '../../common/generate-mute-query';
import { ApiError } from '../../error';
export const meta = {
tags: ['account', 'notes', 'antennas'],
tags: ['antennas', 'account', 'notes'],
requireCredential: true as const,

View File

@@ -1,9 +1,9 @@
import rndstr from 'rndstr';
import $ from 'cafy';
import define from '../../define';
import { Apps } from '../../../../models';
import { genId } from '../../../../misc/gen-id';
import { unique } from '../../../../prelude/array';
import { secureRndstr } from '../../../../misc/secure-rndstr';
export const meta = {
tags: ['app'],
@@ -60,7 +60,7 @@ export const meta = {
export default define(meta, async (ps, user) => {
// Generate secret
const secret = rndstr('a-zA-Z0-9', 32);
const secret = secureRndstr(32, true);
// for backward compatibility
const permission = unique(ps.permission.map(v => v.replace(/^(.+)(\/|-)(read|write)$/, '$3:$1')));

View File

@@ -1,4 +1,3 @@
import rndstr from 'rndstr';
import * as crypto from 'crypto';
import $ from 'cafy';
import define from '../../define';
@@ -6,6 +5,7 @@ import { ApiError } from '../../error';
import { AuthSessions, AccessTokens, Apps } from '../../../../models';
import { genId } from '../../../../misc/gen-id';
import { ensure } from '../../../../prelude/ensure';
import { secureRndstr } from '../../../../misc/secure-rndstr';
export const meta = {
tags: ['auth'],
@@ -39,7 +39,7 @@ export default define(meta, async (ps, user) => {
}
// Generate access token
const accessToken = rndstr('a-zA-Z0-9', 32);
const accessToken = secureRndstr(32, true);
// Fetch exist access token
const exist = await AccessTokens.findOne({
@@ -60,6 +60,7 @@ export default define(meta, async (ps, user) => {
await AccessTokens.save({
id: genId(),
createdAt: new Date(),
lastUsedAt: new Date(),
appId: session.appId,
userId: user.id,
token: accessToken,

View File

@@ -13,7 +13,7 @@ export const meta = {
'en-US': 'Block a user.'
},
tags: ['blocking', 'users'],
tags: ['account'],
limit: {
duration: ms('1hour'),

View File

@@ -13,7 +13,7 @@ export const meta = {
'en-US': 'Unblock a user.'
},
tags: ['blocking', 'users'],
tags: ['account'],
limit: {
duration: ms('1hour'),

View File

@@ -10,7 +10,7 @@ export const meta = {
'en-US': 'Get blocking users.'
},
tags: ['blocking', 'account'],
tags: ['account'],
requireCredential: true as const,

View File

@@ -5,7 +5,7 @@ import { Followings } from '../../../../models';
import { makePaginationQuery } from '../../common/make-pagination-query';
export const meta = {
tags: ['users'],
tags: ['federation'],
requireCredential: false as const,

View File

@@ -5,7 +5,7 @@ import { Followings } from '../../../../models';
import { makePaginationQuery } from '../../common/make-pagination-query';
export const meta = {
tags: ['users'],
tags: ['federation'],
requireCredential: false as const,

View File

@@ -5,7 +5,7 @@ import { Users } from '../../../../models';
import { makePaginationQuery } from '../../common/make-pagination-query';
export const meta = {
tags: ['users'],
tags: ['federation'],
requireCredential: false as const,

View File

@@ -26,8 +26,8 @@ export default define(meta, async (ps, user) => {
switch (ps.sort) {
case '+createdAt': query.orderBy('token.createdAt', 'DESC'); break;
case '-createdAt': query.orderBy('token.createdAt', 'ASC'); break;
case '+lastUsedAt': query.andWhere('token.lastUsedAt IS NOT NULL').orderBy('token.lastUsedAt', 'DESC'); break;
case '-lastUsedAt': query.andWhere('token.lastUsedAt IS NOT NULL').orderBy('token.lastUsedAt', 'ASC'); break;
case '+lastUsedAt': query.orderBy('token.lastUsedAt', 'DESC'); break;
case '-lastUsedAt': query.orderBy('token.lastUsedAt', 'ASC'); break;
default: query.orderBy('token.id', 'ASC'); break;
}

View File

@@ -193,14 +193,14 @@ export default define(meta, async (ps, user, token) => {
if (ps.birthday !== undefined) profileUpdates.birthday = ps.birthday;
if (ps.avatarId !== undefined) updates.avatarId = ps.avatarId;
if (ps.bannerId !== undefined) updates.bannerId = ps.bannerId;
if (typeof ps.isLocked == 'boolean') updates.isLocked = ps.isLocked;
if (typeof ps.isBot == 'boolean') updates.isBot = ps.isBot;
if (typeof ps.carefulBot == 'boolean') profileUpdates.carefulBot = ps.carefulBot;
if (typeof ps.autoAcceptFollowed == 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed;
if (typeof ps.isCat == 'boolean') updates.isCat = ps.isCat;
if (typeof ps.autoWatch == 'boolean') profileUpdates.autoWatch = ps.autoWatch;
if (typeof ps.injectFeaturedNote == 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote;
if (typeof ps.alwaysMarkNsfw == 'boolean') profileUpdates.alwaysMarkNsfw = ps.alwaysMarkNsfw;
if (typeof ps.isLocked === 'boolean') updates.isLocked = ps.isLocked;
if (typeof ps.isBot === 'boolean') updates.isBot = ps.isBot;
if (typeof ps.carefulBot === 'boolean') profileUpdates.carefulBot = ps.carefulBot;
if (typeof ps.autoAcceptFollowed === 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed;
if (typeof ps.isCat === 'boolean') updates.isCat = ps.isCat;
if (typeof ps.autoWatch === 'boolean') profileUpdates.autoWatch = ps.autoWatch;
if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote;
if (typeof ps.alwaysMarkNsfw === 'boolean') profileUpdates.alwaysMarkNsfw = ps.alwaysMarkNsfw;
if (ps.avatarId) {
const avatar = await DriveFiles.findOne(ps.avatarId);

View File

@@ -130,14 +130,10 @@ export default define(meta, async (ps, me) => {
errorImageUrl: instance.errorImageUrl,
iconUrl: instance.iconUrl,
maxNoteTextLength: Math.min(instance.maxNoteTextLength, DB_MAX_NOTE_TEXT_LENGTH),
emojis: emojis.map(e => ({
id: e.id,
aliases: e.aliases,
name: e.name,
category: e.category,
url: e.url,
})),
requireSetup: (await Users.count({})) === 0,
emojis: await Emojis.packMany(emojis),
requireSetup: (await Users.count({
host: null,
})) === 0,
enableEmail: instance.enableEmail,
enableTwitterIntegration: instance.enableTwitterIntegration,

View File

@@ -1,8 +1,8 @@
import rndstr from 'rndstr';
import $ from 'cafy';
import define from '../../define';
import { AccessTokens } from '../../../../models';
import { genId } from '../../../../misc/gen-id';
import { secureRndstr } from '../../../../misc/secure-rndstr';
export const meta = {
tags: ['auth'],
@@ -36,7 +36,7 @@ export const meta = {
export default define(meta, async (ps, user) => {
// Generate access token
const accessToken = rndstr('a-zA-Z0-9', 32);
const accessToken = secureRndstr(32, true);
// Insert access token doc
await AccessTokens.save({

View File

@@ -13,7 +13,7 @@ export const meta = {
'en-US': 'Mute a user'
},
tags: ['mute', 'users'],
tags: ['account'],
requireCredential: true as const,

View File

@@ -11,7 +11,7 @@ export const meta = {
'en-US': 'Unmute a user'
},
tags: ['mute', 'users'],
tags: ['account'],
requireCredential: true as const,

View File

@@ -10,7 +10,7 @@ export const meta = {
'en-US': 'Get muted users.'
},
tags: ['mute', 'account'],
tags: ['account'],
requireCredential: true as const,

View File

@@ -4,6 +4,8 @@ import define from '../../define';
import { Users, UserProfiles } from '../../../../models';
export const meta = {
tags: ['room'],
requireCredential: true as const,
params: {

View File

@@ -123,7 +123,7 @@ export function genOpenapiSpec(lang = 'ja-JP') {
url: `https://github.com/syuilo/misskey/blob/develop/src/server/api/endpoints/${endpoint.name}.ts`
},
...(endpoint.meta.tags ? {
tags: endpoint.meta.tags
tags: [endpoint.meta.tags[0]]
} : {}),
...(endpoint.meta.requireCredential ? {
security: [{

View File

@@ -39,6 +39,10 @@ export default class Connection {
this.wsConnection.on('message', this.onWsConnectionMessage);
this.subscriber.on('broadcast', async ({ type, body }) => {
this.onBroadcastMessage(type, body);
});
if (this.user) {
this.updateFollowing();
this.followingClock = setInterval(this.updateFollowing, 5000);
@@ -72,6 +76,11 @@ export default class Connection {
}
}
@autobind
private onBroadcastMessage(type: string, body: any) {
this.sendMessageToWs(type, body);
}
/**
* APIリクエスト要求時
*/

View File

@@ -54,7 +54,7 @@ module.exports = (server: http.Server) => {
});
connection.on('message', async (data) => {
if (data.utf8Data == 'ping') {
if (data.utf8Data === 'ping') {
connection.send('pong');
}
});

View File

@@ -150,7 +150,7 @@ export default () => new Promise(resolve => {
// Bulk write
setInterval(() => {
if (queue.length == 0) return;
if (queue.length === 0) return;
const requests = queue.length;
const time = sum(queue.map(x => x.time));

View File

@@ -104,6 +104,7 @@ type Option = {
apHashtags?: string[] | null;
apEmojis?: string[] | null;
uri?: string | null;
url?: string | null;
app?: App | null;
};
@@ -407,6 +408,7 @@ async function insertNote(user: User, data: Option, tags: string[], emojis: stri
});
if (data.uri != null) insert.uri = data.uri;
if (data.url != null) insert.url = data.url;
// Append mentions data
if (mentionedUsers.length > 0) {

View File

@@ -36,7 +36,7 @@ export default async function(userId: string, type: string, body?: any) {
//swLogger.info(err.headers);
//swLogger.info(err.body);
if (err.statusCode == 410) {
if (err.statusCode === 410) {
SwSubscriptions.delete({
userId: userId,
endpoint: subscription.endpoint,

View File

@@ -19,6 +19,10 @@ class Publisher {
}));
}
public publishBroadcastStream = (type: string, value?: any): void => {
this.publish('broadcast', type, typeof value === 'undefined' ? null : value);
}
public publishMainStream = (userId: User['id'], type: string, value?: any): void => {
this.publish(`mainStream:${userId}`, type, typeof value === 'undefined' ? null : value);
}
@@ -75,6 +79,7 @@ const publisher = new Publisher();
export default publisher;
export const publishBroadcastStream = publisher.publishBroadcastStream;
export const publishMainStream = publisher.publishMainStream;
export const publishDriveStream = publisher.publishDriveStream;
export const publishNoteStream = publisher.publishNoteStream;

View File

@@ -18,7 +18,7 @@ class WebpackOnBuildPlugin {
}
}
const isProduction = process.env.NODE_ENV == 'production';
const isProduction = process.env.NODE_ENV === 'production';
const locales = require('./locales');
const meta = require('./package.json');

25870
yarn.lock

File diff suppressed because it is too large Load Diff