Compare commits
34 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
bb7edfee04 | ||
![]() |
caa14c70ef | ||
![]() |
3a0f72867f | ||
![]() |
10d72742f5 | ||
![]() |
1b9f8a87d3 | ||
![]() |
d4a630902d | ||
![]() |
fef5ec874b | ||
![]() |
f2e347fec1 | ||
![]() |
cd3c2484ee | ||
![]() |
6a396ef5e3 | ||
![]() |
eec1af1f52 | ||
![]() |
99fc77b678 | ||
![]() |
8bb311df51 | ||
![]() |
a77df249c2 | ||
![]() |
2883bca257 | ||
![]() |
dd3af6886b | ||
![]() |
33bcf2d1ea | ||
![]() |
795fb0eb60 | ||
![]() |
9e9d378bf1 | ||
![]() |
4a6b0edce6 | ||
![]() |
356225af14 | ||
![]() |
331305e6c7 | ||
![]() |
917b9475a5 | ||
![]() |
e895fc954b | ||
![]() |
6d3e18a6a1 | ||
![]() |
79354f4faf | ||
![]() |
28f8933c3c | ||
![]() |
10356b4041 | ||
![]() |
6a732ab1cd | ||
![]() |
47322b35ff | ||
![]() |
4c6d0386b9 | ||
![]() |
a448172952 | ||
![]() |
244ef0cb8f | ||
![]() |
4bf1c23b3c |
12
CHANGELOG.md
12
CHANGELOG.md
@@ -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
108
README.md
@@ -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
|
||||
|
@@ -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"
|
||||
|
@@ -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"
|
||||
|
@@ -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"
|
||||
|
@@ -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"
|
||||
|
@@ -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: "로컬"
|
||||
|
@@ -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: "聊天背景"
|
||||
|
15
migration/1585772678853-ap-url.ts
Normal file
15
migration/1585772678853-ap-url.ts
Normal 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);
|
||||
}
|
||||
|
||||
}
|
123
package.json
123
package.json
@@ -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": {
|
||||
|
@@ -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.');
|
||||
|
@@ -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;
|
||||
|
@@ -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>
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
|
@@ -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"/>
|
||||
|
@@ -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
|
||||
});
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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()"/>
|
||||
|
@@ -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;
|
||||
});
|
||||
},
|
||||
|
@@ -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>
|
||||
|
@@ -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
|
||||
);
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
@@ -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 {
|
||||
|
@@ -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) {
|
||||
|
@@ -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[];
|
||||
|
@@ -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 });
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@@ -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--;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
@@ -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++;
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@@ -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++;
|
||||
|
@@ -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`;
|
||||
|
||||
|
4
src/docs/aiscript.ja-JP.md
Normal file
4
src/docs/aiscript.ja-JP.md
Normal file
@@ -0,0 +1,4 @@
|
||||
# AiScript
|
||||
|
||||
## 関数
|
||||
デフォルトで値渡しです。
|
@@ -27,6 +27,8 @@ APIを使い始めるには、まずアクセストークンを取得する必
|
||||
|
||||
UUIDを生成する。以後これをセッションIDと呼びます。
|
||||
|
||||
> このセッションIDは毎回生成し、使いまわさないようにしてください。
|
||||
|
||||
#### Step 2
|
||||
|
||||
`{_URL_}/miauth/{session}`をユーザーのブラウザで表示させる。`{session}`の部分は、セッションIDに置き換えてください。
|
||||
|
@@ -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);
|
||||
|
@@ -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;
|
||||
|
||||
|
@@ -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, {}));
|
||||
})),
|
||||
|
@@ -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 {
|
||||
|
@@ -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
21
src/misc/secure-rndstr.ts
Normal 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;
|
||||
}
|
@@ -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
|
||||
})
|
||||
|
@@ -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);
|
||||
|
27
src/models/repositories/emoji.ts
Normal file
27
src/models/repositories/emoji.ts
Normal 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)));
|
||||
}
|
||||
}
|
@@ -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,
|
||||
|
||||
|
@@ -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 + '/')) {
|
||||
|
@@ -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 + '/')) {
|
||||
|
@@ -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 + '/')) {
|
||||
|
@@ -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;
|
||||
|
@@ -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 + '/')) {
|
||||
|
9
src/remote/activitypub/misc/html-to-mfm.ts
Normal file
9
src/remote/activitypub/misc/html-to-mfm.ts
Normal 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);
|
||||
}
|
24
src/remote/activitypub/models/mention.ts
Normal file
24
src/remote/activitypub/models/mention.ts
Normal 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);
|
||||
}
|
@@ -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,
|
||||
|
@@ -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)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@@ -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');
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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 {
|
||||
|
@@ -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}`,
|
||||
});
|
||||
|
@@ -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 {
|
||||
|
@@ -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';
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
@@ -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';
|
||||
}
|
||||
|
@@ -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({
|
||||
|
@@ -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: {
|
||||
|
@@ -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);
|
||||
|
@@ -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({
|
||||
|
@@ -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);
|
||||
|
||||
|
@@ -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,
|
||||
|
@@ -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
|
||||
});
|
||||
|
@@ -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);
|
||||
});
|
||||
|
@@ -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);
|
||||
});
|
||||
|
@@ -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: {
|
||||
},
|
||||
|
@@ -6,6 +6,8 @@ import { getNote } from '../../../common/getters';
|
||||
import { PromoNotes } from '../../../../../models';
|
||||
|
||||
export const meta = {
|
||||
tags: ['admin'],
|
||||
|
||||
requireCredential: true as const,
|
||||
requireModerator: true,
|
||||
|
||||
|
@@ -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: {
|
||||
},
|
||||
|
@@ -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: {
|
||||
|
@@ -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,
|
||||
|
||||
|
@@ -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')));
|
||||
|
@@ -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,
|
||||
|
@@ -13,7 +13,7 @@ export const meta = {
|
||||
'en-US': 'Block a user.'
|
||||
},
|
||||
|
||||
tags: ['blocking', 'users'],
|
||||
tags: ['account'],
|
||||
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
|
@@ -13,7 +13,7 @@ export const meta = {
|
||||
'en-US': 'Unblock a user.'
|
||||
},
|
||||
|
||||
tags: ['blocking', 'users'],
|
||||
tags: ['account'],
|
||||
|
||||
limit: {
|
||||
duration: ms('1hour'),
|
||||
|
@@ -10,7 +10,7 @@ export const meta = {
|
||||
'en-US': 'Get blocking users.'
|
||||
},
|
||||
|
||||
tags: ['blocking', 'account'],
|
||||
tags: ['account'],
|
||||
|
||||
requireCredential: true as const,
|
||||
|
||||
|
@@ -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,
|
||||
|
||||
|
@@ -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,
|
||||
|
||||
|
@@ -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,
|
||||
|
||||
|
@@ -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;
|
||||
}
|
||||
|
||||
|
@@ -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);
|
||||
|
@@ -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,
|
||||
|
@@ -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({
|
||||
|
@@ -13,7 +13,7 @@ export const meta = {
|
||||
'en-US': 'Mute a user'
|
||||
},
|
||||
|
||||
tags: ['mute', 'users'],
|
||||
tags: ['account'],
|
||||
|
||||
requireCredential: true as const,
|
||||
|
||||
|
@@ -11,7 +11,7 @@ export const meta = {
|
||||
'en-US': 'Unmute a user'
|
||||
},
|
||||
|
||||
tags: ['mute', 'users'],
|
||||
tags: ['account'],
|
||||
|
||||
requireCredential: true as const,
|
||||
|
||||
|
@@ -10,7 +10,7 @@ export const meta = {
|
||||
'en-US': 'Get muted users.'
|
||||
},
|
||||
|
||||
tags: ['mute', 'account'],
|
||||
tags: ['account'],
|
||||
|
||||
requireCredential: true as const,
|
||||
|
||||
|
@@ -4,6 +4,8 @@ import define from '../../define';
|
||||
import { Users, UserProfiles } from '../../../../models';
|
||||
|
||||
export const meta = {
|
||||
tags: ['room'],
|
||||
|
||||
requireCredential: true as const,
|
||||
|
||||
params: {
|
||||
|
@@ -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: [{
|
||||
|
@@ -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リクエスト要求時
|
||||
*/
|
||||
|
@@ -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');
|
||||
}
|
||||
});
|
||||
|
@@ -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));
|
||||
|
@@ -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) {
|
||||
|
@@ -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,
|
||||
|
@@ -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;
|
||||
|
@@ -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');
|
||||
|
Reference in New Issue
Block a user