Compare commits

...

134 Commits
6.4.0 ... 7.0.0

Author SHA1 Message Date
60269d15e8 Update version 2018-08-19 04:03:55 +09:00
f3936a79aa Fix #2335 2018-08-19 04:01:10 +09:00
78b560d89a #2324 2018-08-19 03:56:44 +09:00
2fc6313380 Fix #2325 2018-08-19 03:36:51 +09:00
c2b882a5d7 Improve cpu and memory chart in control panel 2018-08-19 03:32:01 +09:00
667b18fe24 Refactoring 2018-08-19 03:06:23 +09:00
60c0dac5f2 Merge pull request #2311 from acid-chicken/patch-autogen
[AUTOMATED] Update README.md
2018-08-19 01:14:48 +09:00
79f3e60dac Merge pull request #2331 from syuilo/l10n_master
New Crowdin translations
2018-08-19 01:14:00 +09:00
c4e04d6bf2 New translations ja.yml (English) 2018-08-19 01:01:20 +09:00
49f8219e54 Merge pull request #2286 from syuilo/l10n_master
New Crowdin translations
2018-08-19 00:58:02 +09:00
abd873e0a8 Create README.md 2018-08-19 00:57:40 +09:00
0e45d0d47f Merge branch 'master' into l10n_master 2018-08-19 00:55:07 +09:00
043b66f5da Merge pull request #2330 from syuilo/patch
Clean up
2018-08-19 00:52:10 +09:00
5d6e1fc391 New translations ja.yml (Japanese (Kansai-ben)) 2018-08-19 00:51:23 +09:00
fe292e77de New translations ja.yml (Catalan) 2018-08-19 00:51:20 +09:00
c996b5d0c8 New translations ja.yml (Portuguese) 2018-08-19 00:51:18 +09:00
7beb592f0c New translations ja.yml (Korean) 2018-08-19 00:51:16 +09:00
b3b11acd96 New translations ja.yml (Polish) 2018-08-19 00:51:14 +09:00
3bcdfbbd00 New translations ja.yml (Chinese Simplified) 2018-08-19 00:51:11 +09:00
fdad5d149f New translations ja.yml (Italian) 2018-08-19 00:51:09 +09:00
2e712a9212 New translations ja.yml (Russian) 2018-08-19 00:51:07 +09:00
86c8e634bb New translations ja.yml (English) 2018-08-19 00:51:05 +09:00
197a9306c4 New translations ja.yml (Spanish) 2018-08-19 00:51:03 +09:00
2e502a6b61 New translations ja.yml (German) 2018-08-19 00:51:00 +09:00
aa8a40e950 New translations ja.yml (French) 2018-08-19 00:50:58 +09:00
2f45ac3712 Merge pull request #2329 from syuilo/improve-chart
Improve chart
2018-08-19 00:46:17 +09:00
8ecf3db608 Add drive chart 2018-08-19 00:45:54 +09:00
f59c68022f Fix bug 2018-08-19 00:42:09 +09:00
7aac2c4e29 Clean up 2018-08-19 00:31:25 +09:00
0481de6629 wip 2018-08-19 00:27:23 +09:00
f59d0a75e4 missing semicolon 2018-08-19 00:20:36 +09:00
22f4b3cc7b missing semicolon 2018-08-19 00:20:36 +09:00
a4283c415a improve html to MFM 2018-08-19 00:20:36 +09:00
c0fbcee38a improve MFM to html 2018-08-19 00:20:36 +09:00
335200c31e wip 2018-08-18 23:56:44 +09:00
df71c90f9f wip 2018-08-18 23:48:54 +09:00
4220bdc963 New translations ja.yml (Japanese (Kansai-ben)) 2018-08-18 23:41:44 +09:00
97f3b1e46f New translations ja.yml (Catalan) 2018-08-18 23:41:42 +09:00
3a9dc358f8 New translations ja.yml (Portuguese) 2018-08-18 23:41:40 +09:00
2a3ab46a3d New translations ja.yml (Korean) 2018-08-18 23:41:37 +09:00
200a01f65f New translations ja.yml (Polish) 2018-08-18 23:41:34 +09:00
e68936911d New translations ja.yml (Chinese Simplified) 2018-08-18 23:41:32 +09:00
634ffeec54 New translations ja.yml (Italian) 2018-08-18 23:41:30 +09:00
8486cb785d New translations ja.yml (Russian) 2018-08-18 23:41:28 +09:00
a9eda4e6e2 New translations ja.yml (English) 2018-08-18 23:41:25 +09:00
f918f9b307 New translations ja.yml (Spanish) 2018-08-18 23:41:23 +09:00
d404d02d2d New translations ja.yml (German) 2018-08-18 23:41:21 +09:00
61f7a3974f New translations ja.yml (French) 2018-08-18 23:41:19 +09:00
cad8e4dea2 Update ja.yml 2018-08-18 23:39:25 +09:00
ef79903811 wip 2018-08-18 23:23:55 +09:00
c3cd0451ad wip 2018-08-18 23:15:16 +09:00
773fe28fcb wip 2018-08-18 20:26:01 +09:00
69e2576387 wip 2018-08-18 20:22:56 +09:00
908a5a1bb4 New translations ja.yml (Japanese (Kansai-ben)) 2018-08-18 19:21:12 +09:00
ffbd9ffc75 New translations ja.yml (English) 2018-08-18 18:21:27 +09:00
96223dd5e3 New translations ja.yml (English) 2018-08-18 17:57:09 +09:00
3231f12b93 Merge pull request #2319 from acid-chicken/patch-1
Add 関西弁
2018-08-18 17:13:22 +09:00
1408f39d9a Update ja-ks.yml 2018-08-18 17:07:25 +09:00
4ba06541aa Update ja-ks.yml 2018-08-18 17:07:00 +09:00
5c78ed645a Update index.js 2018-08-18 17:04:00 +09:00
33e19447aa Create ja-ks.yml 2018-08-18 17:02:56 +09:00
b52d995719 Fix typo 2018-08-18 13:12:29 +09:00
db5b90c093 New translations ja.yml (Catalan) 2018-08-18 13:11:48 +09:00
39bc75a7f9 New translations ja.yml (Portuguese) 2018-08-18 13:11:46 +09:00
3492067ecf New translations ja.yml (Korean) 2018-08-18 13:11:43 +09:00
e342a8da05 New translations ja.yml (Polish) 2018-08-18 13:11:40 +09:00
671d71dd47 New translations ja.yml (Chinese Simplified) 2018-08-18 13:11:38 +09:00
2f18d2ed8f New translations ja.yml (Italian) 2018-08-18 13:11:35 +09:00
f6e8165db0 New translations ja.yml (Russian) 2018-08-18 13:11:33 +09:00
eab3b22772 New translations ja.yml (English) 2018-08-18 13:11:31 +09:00
7664aa5a8c New translations ja.yml (Spanish) 2018-08-18 13:11:29 +09:00
a366392cbe New translations ja.yml (German) 2018-08-18 13:11:27 +09:00
c8c1bc09f9 New translations ja.yml (French) 2018-08-18 13:11:24 +09:00
11bb1608cf Resolve #2293 2018-08-18 13:09:15 +09:00
b1d77775aa Update README.md [AUTOGEN] 2018-08-18 11:02:58 +09:00
bc3f5e0d78 Merge pull request #2310 from sei0o/fix-1776
fix #1776
2018-08-18 07:54:49 +09:00
53ea709697 fix #1776 2018-08-18 07:53:06 +09:00
b7adbbb9bf 6.4.1 2018-08-18 06:01:55 +09:00
31aaf559ac Fix bug 2018-08-18 05:46:57 +09:00
160185b108 Fix bug 2018-08-18 05:43:09 +09:00
848ff4440b New translations ja.yml (English) 2018-08-18 05:32:18 +09:00
74291b2cae New translations ja.yml (Catalan) 2018-08-18 05:23:00 +09:00
ae6c4e0c5f New translations ja.yml (Portuguese) 2018-08-18 05:22:57 +09:00
ecb1840e27 New translations ja.yml (Korean) 2018-08-18 05:22:54 +09:00
42eb82a859 New translations ja.yml (Polish) 2018-08-18 05:22:52 +09:00
89a22908ce New translations ja.yml (Chinese Simplified) 2018-08-18 05:22:49 +09:00
bc3a94aa57 New translations ja.yml (Italian) 2018-08-18 05:22:47 +09:00
77fe579d9b New translations ja.yml (Russian) 2018-08-18 05:22:45 +09:00
e8c7803a2f New translations ja.yml (English) 2018-08-18 05:22:43 +09:00
1abaef624c New translations ja.yml (Spanish) 2018-08-18 05:22:40 +09:00
72cf2344a9 New translations ja.yml (German) 2018-08-18 05:22:38 +09:00
0462512e62 New translations ja.yml (French) 2018-08-18 05:22:36 +09:00
e05e97561a New translations ja.yml (French) 2018-08-18 01:42:31 +09:00
f9d21fd34a New translations ja.yml (French) 2018-08-18 01:21:26 +09:00
86aef5f3e0 New translations ja.yml (French) 2018-08-18 01:12:29 +09:00
e6bc7922ac New translations ja.yml (French) 2018-08-18 01:01:48 +09:00
b2f40b7d3e New translations ja.yml (French) 2018-08-18 00:51:35 +09:00
3878793405 New translations ja.yml (English) 2018-08-18 00:42:00 +09:00
33c7aef220 New translations ja.yml (French) 2018-08-18 00:41:58 +09:00
e927f7a4ec New translations ja.yml (English) 2018-08-18 00:32:15 +09:00
f409faea2c New translations ja.yml (Catalan) 2018-08-17 21:52:08 +09:00
77da98cd39 New translations ja.yml (Portuguese) 2018-08-17 21:52:06 +09:00
471afcbd57 New translations ja.yml (Korean) 2018-08-17 21:52:04 +09:00
72809f9d78 New translations ja.yml (Polish) 2018-08-17 21:52:01 +09:00
743fb45c3c New translations ja.yml (Chinese Simplified) 2018-08-17 21:51:59 +09:00
93d6b71a31 New translations ja.yml (Italian) 2018-08-17 21:51:57 +09:00
9b10e93fba New translations ja.yml (Russian) 2018-08-17 21:51:55 +09:00
0740837b6a New translations ja.yml (English) 2018-08-17 21:51:52 +09:00
ff49eacee4 New translations ja.yml (Spanish) 2018-08-17 21:51:50 +09:00
fbc6b267ff New translations ja.yml (German) 2018-08-17 21:51:48 +09:00
10cae8894e New translations ja.yml (French) 2018-08-17 21:51:46 +09:00
9438dc560d New translations ja.yml (Catalan) 2018-08-17 21:32:40 +09:00
003ef65b59 New translations ja.yml (Portuguese) 2018-08-17 21:32:37 +09:00
978fb7e63a New translations ja.yml (Korean) 2018-08-17 21:32:35 +09:00
6203577696 New translations ja.yml (Polish) 2018-08-17 21:32:33 +09:00
f2b61f6a7e New translations ja.yml (Chinese Simplified) 2018-08-17 21:32:29 +09:00
6f4aa69723 New translations ja.yml (Italian) 2018-08-17 21:32:27 +09:00
b308e1bb2b New translations ja.yml (Russian) 2018-08-17 21:32:25 +09:00
ab3dad2d84 New translations ja.yml (English) 2018-08-17 21:32:23 +09:00
0af103f399 New translations ja.yml (Spanish) 2018-08-17 21:32:20 +09:00
873608a7de New translations ja.yml (German) 2018-08-17 21:32:18 +09:00
b04ad167da New translations ja.yml (French) 2018-08-17 21:32:15 +09:00
151476ae8a New translations ja.yml (English) 2018-08-17 19:41:43 +09:00
2239eddf88 New translations ja.yml (Catalan) 2018-08-17 19:32:18 +09:00
94ea21d482 New translations ja.yml (Portuguese) 2018-08-17 19:32:15 +09:00
6a39f92431 New translations ja.yml (Korean) 2018-08-17 19:32:12 +09:00
7b2efa35c7 New translations ja.yml (Polish) 2018-08-17 19:32:09 +09:00
c41ea84568 New translations ja.yml (Chinese Simplified) 2018-08-17 19:32:06 +09:00
0e8262e623 New translations ja.yml (Italian) 2018-08-17 19:32:03 +09:00
4d97946943 New translations ja.yml (Russian) 2018-08-17 19:32:00 +09:00
96d48b5519 New translations ja.yml (English) 2018-08-17 19:31:57 +09:00
4cc54d081e New translations ja.yml (Spanish) 2018-08-17 19:31:55 +09:00
aab5a2ffbd New translations ja.yml (German) 2018-08-17 19:31:52 +09:00
c8f165b4ad New translations ja.yml (French) 2018-08-17 19:31:49 +09:00
62 changed files with 2766 additions and 1271 deletions

View File

@ -5,6 +5,15 @@ ChangeLog
This document describes breaking changes only.
7.0.0
-----
### Migration
起動する前に、`node cli/migration/7.0.0`してください。
Please run `node cli/migration/7.0.0` before launch.
6.0.0
-----

View File

@ -46,34 +46,38 @@ If you want to...
:heart: Backers & Sponsors
----------------------------------------------------------------
<!-- PATREON_START -->
<table>
<tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/619786/32cf01444db24e578cd1982c197f6fc6/1?token-time=2145916800&token-hash=tB1e_r8RlZ5sFL0KV_e8dugapxatNBRK1Z3h67TO1g8%3D"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12378075/0156f769e20f412594fa6b87d85fe228/1?token-time=2145916800&token-hash=IsIJRUXszzoD6-7pDnRY8I05T9nSznc4GTaxj7C9SwU%3D"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1?token-time=2145916800&token-hash=S1zP0QyLU52Dqq6dtc9qNYyWfW86XrYHiR4NMbeOrnA%3D"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1?token-time=2145916800&token-hash=tMosUojzUYJCH_3t--tvYA-SMCyrS__hzSndyaRSnbo%3D"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12959468/c249e15aebec4424b5c0f427173671b6/1?token-time=2145916800&token-hash=lubpCEdxAkxPlpR2O6bvZ7BIh8Q4nGf-U_mE1qpjVAQ%3D"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/1?token-time=2145916800&token-hash=f03BFb4S2FUx9YEt87TnEmifb4h33OywGBW2akQVtQY%3D"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/2?token-time=2145916800&token-hash=zElv7ZcPL3viGsXbNG_KWiKrbV0vvw1gk0panx8DJoo%3D"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12731202/0995c46cdcb54153ab5f073f5869b70a/1?token-time=2145916800&token-hash=Yd60FK_SWfQO56SeiJpy1tDHOnCV4xdEywQe8gn5_Wo%3D"></td>
<td><img src="https://c8.patreon.com/2/100/12718187"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12931605/ead494101f364dffa90efe49e36fb494/1?token-time=2145916800&token-hash=NzSFPjIlodXyv41rwK61aZWVZWfI4surJaNj8vWKvqM%3D"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12021162/963128bb8d14476dbd8407943db8f31a/1?token-time=2145916800&token-hash=GgJ_NmUB6_nnRNLVGUWjV-WX91On7BOu59LKncYV9fE%3D"></td>
</tr>
<tr>
<td><a href="https://www.patreon.com/mastodon">Gargron</a></td>
<td><a href="https://www.patreon.com/user/creators?u=12378075">39ff</a></td>
<td><a href="https://www.patreon.com/dansup">dansup</a></td>
<td><a href="https://www.patreon.com/user/creators?u=12531784">Takashi Shibuya</a></td>
<td><a href="https://www.patreon.com/fujishan">fujishan</a></td>
<td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td>
<td><a href="https://www.patreon.com/user?u=5881381">Naoki Kosaka</a></td>
<td><a href="https://www.patreon.com/user?u=12731202">negao</a></td>
<td><a href="https://www.patreon.com/user?u=12718187">Peter G.</a></td>
<td><a href="https://www.patreon.com/user?u=12931605">Reiju</a></td>
<td><a href="https://www.patreon.com/gutfuckllc">gutfuckllc</a></td>
</tr>
</table>
<table><tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12378075/0156f769e20f412594fa6b87d85fe228/1?token-time=2145916800&token-hash=IsIJRUXszzoD6-7pDnRY8I05T9nSznc4GTaxj7C9SwU%3D" alt="39ff"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12731202/0995c46cdcb54153ab5f073f5869b70a/1?token-time=2145916800&token-hash=Yd60FK_SWfQO56SeiJpy1tDHOnCV4xdEywQe8gn5_Wo%3D" alt="negao"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/1?token-time=2145916800&token-hash=f03BFb4S2FUx9YEt87TnEmifb4h33OywGBW2akQVtQY%3D" alt="Melilot"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/3384329/8b713330cb27404ea6e9fac50ff96efe/1?token-time=2145916800&token-hash=0eu4-m1gTWA9PhptVZt6rdKcusqcD7RB87rJT23VVFI%3D" alt="べすれい"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12021162/963128bb8d14476dbd8407943db8f31a/1?token-time=2145916800&token-hash=GgJ_NmUB6_nnRNLVGUWjV-WX91On7BOu59LKncYV9fE%3D" alt="gutfuckllc"></td>
<td><img src="https://c8.patreon.com/2/100/12718187" alt="Peter G."></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/2?token-time=2145916800&token-hash=zElv7ZcPL3viGsXbNG_KWiKrbV0vvw1gk0panx8DJoo%3D" alt="Naoki Kosaka"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12931605/ead494101f364dffa90efe49e36fb494/1?token-time=2145916800&token-hash=NzSFPjIlodXyv41rwK61aZWVZWfI4surJaNj8vWKvqM%3D" alt="Reiju"></td>
</tr><tr>
<td><a href="https://www.patreon.com/user?u=12378075">39ff</a></td>
<td><a href="https://www.patreon.com/user?u=12731202">negao</a></td>
<td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td>
<td><a href="https://www.patreon.com/user?u=3384329">べすれい</a></td>
<td><a href="https://www.patreon.com/gutfuckllc">gutfuckllc</a></td>
<td><a href="https://www.patreon.com/user?u=12718187">Peter G.</a></td>
<td><a href="https://www.patreon.com/user?u=5881381">Naoki Kosaka</a></td>
<td><a href="https://www.patreon.com/user?u=12931605">Reiju</a></td>
</tr></table>
<table><tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1?token-time=2145916800&token-hash=S1zP0QyLU52Dqq6dtc9qNYyWfW86XrYHiR4NMbeOrnA%3D" alt="dansup"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/4950409/28e7d016209243759d9316be2e21381d/2?token-time=2145916800&token-hash=LuEaDkchH3GQWUcTOhBQ8xfKQYF0s5FjlZRd7Yduia8%3D" alt="mikan54951"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1?token-time=2145916800&token-hash=tMosUojzUYJCH_3t--tvYA-SMCyrS__hzSndyaRSnbo%3D" alt="Takashi Shibuya"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12959468/c249e15aebec4424b5c0f427173671b6/1?token-time=2145916800&token-hash=lubpCEdxAkxPlpR2O6bvZ7BIh8Q4nGf-U_mE1qpjVAQ%3D" alt="fujishan"></td>
</tr><tr>
<td><a href="https://www.patreon.com/dansup">dansup</a></td>
<td><a href="https://www.patreon.com/user?u=4950409">mikan54951</a></td>
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
<td><a href="https://www.patreon.com/fujishan">fujishan</a></td>
</tr></table>
**Last updated:** Sat, 18 Aug 2018 02:02:58 UTC
<!-- PATREON_END -->
:four_leaf_clover: Copyright

View File

@ -1,101 +0,0 @@
const chalk = require('chalk');
const log = require('single-line-log').stdout;
const sequential = require('promise-sequential');
const { default: DriveFile, DriveFileChunk } = require('../built/models/drive-file');
const { default: DriveFileThumbnail, DriveFileThumbnailChunk } = require('../built/models/drive-file-thumbnail');
const { default: User } = require('../built/models/user');
const q = {
'metadata._user.host': {
$ne: null
},
'metadata.withoutChunks': false
};
async function main() {
const promiseGens = [];
const count = await DriveFile.count(q);
let prev;
for (let i = 0; i < count; i++) {
promiseGens.push(() => {
const promise = new Promise(async (res, rej) => {
const file = await DriveFile.findOne(prev ? Object.assign({
_id: { $lt: prev._id }
}, q) : q, {
sort: {
_id: -1
}
});
prev = file;
function skip() {
res([i, file, false]);
}
if (file == null) return skip();
log(chalk`{gray ${i}} scanning {bold ${file._id}} ${file.filename} ...`);
const attachingUsersCount = await User.count({
$or: [{
avatarId: file._id
}, {
bannerId: file._id
}]
}, { limit: 1 });
if (attachingUsersCount !== 0) return skip();
Promise.all([
// チャンクをすべて削除
DriveFileChunk.remove({
files_id: file._id
}),
DriveFile.update({ _id: file._id }, {
$set: {
'metadata.withoutChunks': true
}
})
]).then(async () => {
res([i, file, true]);
//#region サムネイルもあれば削除
const thumbnail = await DriveFileThumbnail.findOne({
'metadata.originalId': file._id
});
if (thumbnail) {
DriveFileThumbnailChunk.remove({
files_id: thumbnail._id
});
DriveFileThumbnail.remove({ _id: thumbnail._id });
}
//#endregion
});
});
promise.then(([i, file, deleted]) => {
if (deleted) {
log(chalk`{gray ${i}} {red deleted: {bold ${file._id}} ${file.filename}}`);
} else {
log(chalk`{gray ${i}} {green skipped: {bold ${file._id}} ${file.filename}}`);
}
log.clear();
console.log();
});
return promise;
});
}
return await sequential(promiseGens);
}
main().then(() => {
console.log('ALL DONE');
}).catch(console.error);

View File

@ -1,80 +0,0 @@
const chalk = require('chalk');
const log = require('single-line-log').stdout;
const sequential = require('promise-sequential');
const { default: DriveFile, deleteDriveFile } = require('../built/models/drive-file');
const { default: Note } = require('../built/models/note');
const { default: MessagingMessage } = require('../built/models/messaging-message');
const { default: User } = require('../built/models/user');
async function main() {
const promiseGens = [];
const count = await DriveFile.count({});
let prev;
for (let i = 0; i < count; i++) {
promiseGens.push(() => {
const promise = new Promise(async (res, rej) => {
const file = await DriveFile.findOne(prev ? {
_id: { $lt: prev._id }
} : {}, {
sort: {
_id: -1
}
});
prev = file;
function skip() {
res([i, file, false]);
}
if (file == null) return skip();
log(chalk`{gray ${i}} scanning {bold ${file._id}} ${file.filename} ...`);
const attachingUsersCount = await User.count({
$or: [{
avatarId: file._id
}, {
bannerId: file._id
}]
}, { limit: 1 });
if (attachingUsersCount !== 0) return skip();
const attachingNotesCount = await Note.count({
mediaIds: file._id
}, { limit: 1 });
if (attachingNotesCount !== 0) return skip();
const attachingMessagesCount = await MessagingMessage.count({
fileId: file._id
}, { limit: 1 });
if (attachingMessagesCount !== 0) return skip();
deleteDriveFile(file).then(() => {
res([i, file, true]);
}).catch(rej);
});
promise.then(([i, file, deleted]) => {
if (deleted) {
log(chalk`{gray ${i}} {red deleted: {bold ${file._id}} ${file.filename}}`);
} else {
log(chalk`{gray ${i}} {green skipped: {bold ${file._id}} ${file.filename}}`);
}
log.clear();
console.log();
});
return promise;
});
}
return await sequential(promiseGens);
}
main().then(() => {
console.log('done');
}).catch(console.error);

134
cli/migration/7.0.0.js Normal file
View File

@ -0,0 +1,134 @@
const { default: Stats } = require('../../built/models/stats');
const { default: User } = require('../../built/models/user');
const { default: Note } = require('../../built/models/note');
const { default: DriveFile } = require('../../built/models/drive-file');
const now = new Date();
const y = now.getFullYear();
const m = now.getMonth();
const d = now.getDate();
const today = new Date(y, m, d);
async function main() {
const localUsersCount = await User.count({
host: null
});
const remoteUsersCount = await User.count({
host: { $ne: null }
});
const localNotesCount = await Note.count({
'_user.host': null
});
const remoteNotesCount = await Note.count({
'_user.host': { $ne: null }
});
const localDriveFilesCount = await DriveFile.count({
'metadata._user.host': null
});
const remoteDriveFilesCount = await DriveFile.count({
'metadata._user.host': { $ne: null }
});
const localDriveFilesSize = await DriveFile
.aggregate([{
$match: {
'metadata._user.host': null,
'metadata.deletedAt': { $exists: false }
}
}, {
$project: {
length: true
}
}, {
$group: {
_id: null,
usage: { $sum: '$length' }
}
}])
.then(aggregates => {
if (aggregates.length > 0) {
return aggregates[0].usage;
}
return 0;
});
const remoteDriveFilesSize = await DriveFile
.aggregate([{
$match: {
'metadata._user.host': { $ne: null },
'metadata.deletedAt': { $exists: false }
}
}, {
$project: {
length: true
}
}, {
$group: {
_id: null,
usage: { $sum: '$length' }
}
}])
.then(aggregates => {
if (aggregates.length > 0) {
return aggregates[0].usage;
}
return 0;
});
await Stats.insert({
date: today,
users: {
local: {
total: localUsersCount,
diff: 0
},
remote: {
total: remoteUsersCount,
diff: 0
}
},
notes: {
local: {
total: localNotesCount,
diff: 0,
diffs: {
normal: 0,
reply: 0,
renote: 0
}
},
remote: {
total: remoteNotesCount,
diff: 0,
diffs: {
normal: 0,
reply: 0,
renote: 0
}
}
},
drive: {
local: {
totalCount: localDriveFilesCount,
totalSize: localDriveFilesSize,
diffCount: 0,
diffSize: 0
},
remote: {
totalCount: remoteDriveFilesCount,
totalSize: remoteDriveFilesSize,
diffCount: 0,
diffSize: 0
}
}
});
console.log('done');
}
main();

View File

@ -33,14 +33,3 @@ node cli/suspend @syuilo@misskey.xyz
``` shell
node cli/reset-password (User-ID or Username)
```
## Clean up cached remote files
``` shell
node cli/clean-cached-remote-files
```
## Clean up unused drive files
``` shell
node cli/clean-unused-drive-files
```
> We recommend that you announce a user that unused drive files will be deleted before performing this operation, as it may delete the user's important files.

View File

@ -33,14 +33,3 @@ node cli/suspend @syuilo@misskey.xyz
``` shell
node cli/reset-password (ユーザーID または ユーザー名)
```
## キャッシュされたリモートファイルをクリーンアップする
``` shell
node cli/clean-cached-remote-files
```
## 使われていないドライブのファイルをクリーンアップする
``` shell
node cli/clean-unused-drive-files
```
> ユーザーの大事なファイルを削除する可能性があるので、この操作を実行する前にユーザーに告知することをお勧めします。

5
locales/README.md Normal file
View File

@ -0,0 +1,5 @@
# **Please DO NOT edit these files** except `ja.yml`.
If you want to...
* i18n ... please see [Translation guide](../docs/translate.en.md).
* l10n ... please visit https://crowdin.com/project/misskey

View File

@ -289,6 +289,8 @@ common/views/components/signin.vue:
signin-with-twitter: "Twitterでログイン"
login-failed: "ログインできませんでした。ユーザー名とパスワードを確認してください。"
common/views/components/signup.vue:
invitation-code: "招待コード"
invitation-info: "招待コードをお持ちでない方は、<a href=\"{}\">管理者</a>までご連絡ください。"
username: "ユーザー名"
checking: "確認しています..."
available: "利用できます"
@ -798,6 +800,7 @@ desktop/views/pages/admin/admin.dashboard.vue:
original-users: "このインスタンスのユーザー"
all-notes: "全てのノート"
original-notes: "このインスタンスのノート"
invite: "招待"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "ユーザーの凍結"
suspend: "凍結"
@ -806,6 +809,26 @@ desktop/views/pages/admin/admin.unsuspend-user.vue:
unsuspend-user: "ユーザーの凍結の解除"
unsuspend: "凍結の解除"
unsuspended: "凍結を解除しました"
desktop/views/pages/admin/admin.verify-user.vue:
verify-user: "ユーザーの公式アカウント設定"
verify: "公式アカウントにする"
verified: "公式アカウントにしました"
desktop/views/pages/admin/admin.unverify-user.vue:
unverify-user: "ユーザーの公式アカウント解除"
unverify: "公式アカウントを解除する"
unverified: "公式アカウントを解除しました"
desktop/views/pages/admin/admin.notes-chart.vue:
title: "投稿"
local: "ローカル"
remote: "リモート"
desktop/views/pages/admin/admin.users-chart.vue:
title: "ユーザー"
local: "ローカル"
remote: "リモート"
desktop/views/pages/admin/admin.drive-chart.vue:
title: "ドライブ"
local: "ローカル"
remote: "リモート"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
@ -838,7 +861,7 @@ desktop/views/pages/selectdrive.vue:
cancel: "キャンセル"
upload: "PCからドライブにファイルをアップロード"
desktop/views/pages/search.vue:
not-available: "検索機能を利用することができません。"
not-available: "検索機能はインスタンスの設定で無効になっています。"
not-found: "「{}」に関する投稿は見つかりませんでした。"
desktop/views/pages/share.vue:
share-with: "{}で共有"

View File

@ -289,6 +289,8 @@ common/views/components/signin.vue:
signin-with-twitter: "Twitterでログイン"
login-failed: "ログインできませんでした。ユーザー名とパスワードを確認してください。"
common/views/components/signup.vue:
invitation-code: "招待コード"
invitation-info: "招待コードをお持ちでない方は、<a href=\"{}\">管理者</a>までご連絡ください。"
username: "Benutzername"
checking: "Überprüfung..."
available: "Verfügbar"
@ -798,6 +800,7 @@ desktop/views/pages/admin/admin.dashboard.vue:
original-users: "このインスタンスのユーザー"
all-notes: "全てのノート"
original-notes: "このインスタンスのノート"
invite: "招待"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "ユーザーの凍結"
suspend: "凍結"
@ -806,6 +809,26 @@ desktop/views/pages/admin/admin.unsuspend-user.vue:
unsuspend-user: "ユーザーの凍結の解除"
unsuspend: "凍結の解除"
unsuspended: "凍結を解除しました"
desktop/views/pages/admin/admin.verify-user.vue:
verify-user: "ユーザーの公式アカウント設定"
verify: "公式アカウントにする"
verified: "公式アカウントにしました"
desktop/views/pages/admin/admin.unverify-user.vue:
unverify-user: "ユーザーの公式アカウント解除"
unverify: "公式アカウントを解除する"
unverified: "公式アカウントを解除しました"
desktop/views/pages/admin/admin.notes-chart.vue:
title: "投稿"
local: "ローカル"
remote: "リモート"
desktop/views/pages/admin/admin.users-chart.vue:
title: "ユーザー"
local: "ローカル"
remote: "リモート"
desktop/views/pages/admin/admin.drive-chart.vue:
title: "ドライブ"
local: "ローカル"
remote: "リモート"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
@ -838,7 +861,7 @@ desktop/views/pages/selectdrive.vue:
cancel: "Abbrechen"
upload: "PCからドライブにファイルをアップロード"
desktop/views/pages/search.vue:
not-available: "検索機能を利用することができません。"
not-available: "検索機能はインスタンスの設定で無効になっています。"
not-found: "「{}」に関する投稿は見つかりませんでした。"
desktop/views/pages/share.vue:
share-with: "{}で共有"

View File

@ -289,6 +289,8 @@ common/views/components/signin.vue:
signin-with-twitter: "Log in with Twitter"
login-failed: "Log in failed. Make sure you have entered your correct username and password."
common/views/components/signup.vue:
invitation-code: "Invitation code"
invitation-info: "If you do not have an invitation code, please contact an <a href=\"{}\">administrator</a>."
username: "Username"
checking: "Confirming..."
available: "Available"
@ -798,6 +800,7 @@ desktop/views/pages/admin/admin.dashboard.vue:
original-users: "Users on this instance"
all-notes: "All Posts"
original-notes: "Posts on this instance"
invite: "Invite"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "Suspend a user"
suspend: "Suspend"
@ -806,6 +809,26 @@ desktop/views/pages/admin/admin.unsuspend-user.vue:
unsuspend-user: "Unsuspend users"
unsuspend: "Unsuspend"
unsuspended: "The user has successfully unsuspended."
desktop/views/pages/admin/admin.verify-user.vue:
verify-user: "User account verification settings"
verify: "Verify account"
verified: "The account is now being verified"
desktop/views/pages/admin/admin.unverify-user.vue:
unverify-user: "User account unverification settings"
unverify: "Unverify account"
unverified: "The account is now being unverified"
desktop/views/pages/admin/admin.notes-chart.vue:
title: "Posts"
local: "Local"
remote: "Remote"
desktop/views/pages/admin/admin.users-chart.vue:
title: "Users"
local: "Local"
remote: "Remote"
desktop/views/pages/admin/admin.drive-chart.vue:
title: "Drive"
local: "Local"
remote: "Remote"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "Only media posts"
is-media-view: "Media view"
@ -838,7 +861,7 @@ desktop/views/pages/selectdrive.vue:
cancel: "Cancel"
upload: "Upload files from your device"
desktop/views/pages/search.vue:
not-available: "The search feature is not available for now."
not-available: "Search feature is turned off in the settings for this instance."
not-found: "No posts were found for '{}'"
desktop/views/pages/share.vue:
share-with: "Share with {}."

View File

@ -289,6 +289,8 @@ common/views/components/signin.vue:
signin-with-twitter: "Ingresar con Twitter"
login-failed: "ログインできませんでした。ユーザー名とパスワードを確認してください。"
common/views/components/signup.vue:
invitation-code: "招待コード"
invitation-info: "招待コードをお持ちでない方は、<a href=\"{}\">管理者</a>までご連絡ください。"
username: "Usuario"
checking: "Comprobando..."
available: "Disponible"
@ -798,6 +800,7 @@ desktop/views/pages/admin/admin.dashboard.vue:
original-users: "このインスタンスのユーザー"
all-notes: "全てのノート"
original-notes: "このインスタンスのノート"
invite: "招待"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "ユーザーの凍結"
suspend: "凍結"
@ -806,6 +809,26 @@ desktop/views/pages/admin/admin.unsuspend-user.vue:
unsuspend-user: "ユーザーの凍結の解除"
unsuspend: "凍結の解除"
unsuspended: "凍結を解除しました"
desktop/views/pages/admin/admin.verify-user.vue:
verify-user: "ユーザーの公式アカウント設定"
verify: "公式アカウントにする"
verified: "公式アカウントにしました"
desktop/views/pages/admin/admin.unverify-user.vue:
unverify-user: "ユーザーの公式アカウント解除"
unverify: "公式アカウントを解除する"
unverified: "公式アカウントを解除しました"
desktop/views/pages/admin/admin.notes-chart.vue:
title: "投稿"
local: "ローカル"
remote: "リモート"
desktop/views/pages/admin/admin.users-chart.vue:
title: "ユーザー"
local: "ローカル"
remote: "リモート"
desktop/views/pages/admin/admin.drive-chart.vue:
title: "ドライブ"
local: "ローカル"
remote: "リモート"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
@ -838,7 +861,7 @@ desktop/views/pages/selectdrive.vue:
cancel: "キャンセル"
upload: "PCからドライブにファイルをアップロード"
desktop/views/pages/search.vue:
not-available: "検索機能を利用することができません。"
not-available: "検索機能はインスタンスの設定で無効になっています。"
not-found: "「{}」に関する投稿は見つかりませんでした。"
desktop/views/pages/share.vue:
share-with: "{}で共有"

View File

@ -4,10 +4,10 @@ meta:
divider: ""
common:
misskey: "Une ⭐ du fédiverse"
about-title: "Une ⭐ du fédiverse."
about: "Merci d'avoir découvert Misskey. Misskey est une <b>plateforme de micro-blogging distribuée</b> née sur Terre. Parce qu'il fait partie du Fédiverse (un univers composé de diverses plateformes de réseaux sociaux organisées), il est mutuellement connecté avec d'autres plateformes de réseaux sociaux. Désirez-vous prendre une pause, pendant un instant, loin de l'agitation de la ville et plonger dans un nouvel Internet ?"
about-title: "Une ⭐ du fédivers."
about: "Merci d'avoir découvert Misskey. Misskey est une <b>plateforme de microblogage distribuée</b> née sur Terre. Parce qu'il fait partie du Fédivers (un univers composé de diverses plateformes de réseaux sociaux organisées), il est mutuellement connecté avec d'autres plateformes de réseaux sociaux. Désirez-vous prendre une pause, pendant un instant, loin de l'agitation de la ville et plonger dans un nouvel Internet ?"
adblock:
detected: "Veuillez désactiver le bloqueur de publicités"
detected: "Veuillez désactiver votre bloqueur de publicités"
warning: "<strong>Misskey n'utilise pas de publicités</strong>, mais quelques options peuvent être non disponibles ou fonctionneraient mal si un bloqueur de publicités est activé."
application-authorization: "Permissions de l'application"
close: "Fermer"
@ -17,7 +17,7 @@ common:
title: "Conseils de personnalisation"
paragraph1: "La personnalisation à la maison vous permet d'ajouter / supprimer, glisser et déposer et réorganiser les widgets."
paragraph2: "Vous pouvez changer l'affichage en <strong>cliquant droit</strong> sur certains widgets."
paragraph3: "Pour supprimer un widget, <strong>glissez et déposez le widget sur la zone étiquetée \"Corbeille\"</strong> dans l'en-tête."
paragraph3: "Pour supprimer un widget, <strong>glissez et déposez le widget sur la zone étiquetée « Corbeille »</strong> dans l'en-tête."
paragraph4: "Pour terminer la personnalisation, cliquez sur \"Terminer\" dans le coin supérieur droit."
gotit: "Compris!"
notification:
@ -69,7 +69,7 @@ common:
rip: "RIP"
pudding: "Pudding"
note-placeholders:
a: "Que faîtes vous maintenant ?"
a: "Que faites vous maintenant ?"
b: "Quoi de neuf ?"
c: "Qu'avez-vous en tête ?"
d: "Voulez-vous exprimer quelque chose ?"
@ -82,9 +82,9 @@ common:
update-available-title: "Mise à jour disponible"
update-available: "Une nouvelle version de Misskey est disponible ({newer}, version actuelle: {current}). Veuillez recharger la page pour appliquer la mise à jour."
my-token-regenerated: "Votre token vient d'être généré, vous allez maintenant être déconnecté."
i-like-sushi: "Je préfère les sushis (au pudding)"
i-like-sushi: "Je préfère les sushis plutôt que le pudding"
show-reversi-board-labels: "Afficher les étiquettes des lignes et colonnes dans Reversi"
verified-user: "公式アカウント"
verified-user: "Compte vérifié"
disable-animated-mfm: "Désactiver les textes animés dans les publications"
reversi:
drawn: "Partie nulle"
@ -101,18 +101,18 @@ common:
analog-clock: "Horloge analogique"
profile: "Profil"
calendar: "Calendrier"
timemachine: "Calendrier (Machine de temps)"
timemachine: "Calendrier (Machine temporelle)"
activity: "Activité"
rss: "Lecteur de flux RSS"
memo: "Pense-bête"
trends: "Tendances"
photo-stream: "Flux de photos"
posts-monitor: "Graph des publications"
posts-monitor: "Graphe des publications"
slideshow: "Diaporama"
version: "Version"
broadcast: "Diffusion"
notifications: "Notifications"
users: "Utilisateurs"
users: "Utilisateur·rice·s"
polls: "Sondages"
post-form: "Formulaire de publication"
messaging: "Messagerie"
@ -174,16 +174,16 @@ common/views/components/games/reversi/reversi.game.vue:
can-put-everywhere: "どこでも置けるモード"
common/views/components/games/reversi/reversi.index.vue:
title: "Misskey Reversi"
sub-title: "Jouer à Reversi avec vos amis·es !"
sub-title: "Jouer à Reversi avec vos ami·e·s !"
invite: "Inviter"
rule: "Comment Jouer ?"
rule: "Comment jouer ?"
rule-desc: "リバーシは、相手と交互に石をボードに置いて、相手の石を挟んで自分の色に変えてゆき、最終的に残った石が多い方が勝ちというボードゲームです。"
mode-invite: "Inviter"
mode-invite-desc: "Inviter un joueur."
mode-invite-desc: "Inviter un·e joueur·se."
invitations: "Vous avez reçu une invitation !"
my-games: "Mes jeux"
all-games: "Tous les jeux"
enter-username: "Saisir un nom d'utilisateur"
enter-username: "Saisir un nom d'utilisateur·rice"
game-state:
ended: "Terminée"
playing: "En cours"
@ -204,7 +204,7 @@ common/views/components/games/reversi/reversi.room.vue:
waiting-for-both: "En attente que vous soyez prêt"
cancel: "Annuler"
ready: "Prêt"
cancel-ready: "Annuler \"Je suis prêt\""
cancel-ready: "Annuler « Prêt »"
common/views/components/connect-failed.vue:
title: "Échec de connexion au serveur"
description: "Il y a soit un problème avec votre connexion internet, soit le serveur est hors-ligne ou en maintenance. Veuillez {ressayer} plus tard."
@ -230,20 +230,20 @@ common/views/components/connect-failed.troubleshooter.vue:
flush: "Vider le cache"
set-version: "Choisissez une version"
common/views/components/messaging.vue:
search-user: "Trouver un utilisateur"
search-user: "Trouver un·e utilisateur·rice"
you: "Vous"
no-history: "Pas d'historique"
common/views/components/messaging-room.vue:
empty: "Pas de conversations"
more: "Voir Plus"
more: "Voir plus"
no-history: "Il n'y a pas plus d'historique"
resize-form: "Faites glisser pour redimensionner"
new-message: "Nouveau message"
only-one-file-attached: "Un seul fichier uniquement peut être joint au message"
only-one-file-attached: "Un seul fichier peut être joint au message"
common/views/components/messaging-room.form.vue:
input-message-here: "Tapez ici votre message"
send: "Envoyer"
attach-from-local: "Joindre un fichier depuis votre PC"
attach-from-local: "Joindre un fichier depuis votre ordinateur"
attach-from-drive: "Joindre un fichier depuis votre Drive"
only-one-file-attached: "Un seul fichier uniquement peut être joint au message"
common/views/components/messaging-room.message.vue:
@ -251,23 +251,23 @@ common/views/components/messaging-room.message.vue:
deleted: "Ce message a été supprimé"
common/views/components/nav.vue:
about: "À propos"
stats: "Stats"
status: "Status"
stats: "Statistiques"
status: "Statut"
wiki: "Wiki"
donors: "Donateurs"
repository: "Repo"
develop: "Développeurs"
donors: "Donateur·rice·s"
repository: "Dépôt"
develop: "Développeur·se·s"
feedback: "Remarques"
common/views/components/note-menu.vue:
favorite: "Favorite this note"
pin: "Épingler sur votre profile"
favorite: "Mettre cette note en favoris"
pin: "Épingler sur votre profil"
delete: "Supprimer"
delete-confirm: "Supprimer cette publication ?"
remote: "Afficher les note originale"
remote: "Afficher la note originale"
common/views/components/poll.vue:
vote-to: "Voter pour '{}'"
vote-count: "{} votes"
total-users: "{} utilisateurs ont voté"
total-users: "{} utilisateur·rice·s ont voté·e·s"
vote: "Vote"
show-result: "Montrer les résultats"
voted: "Voté"
@ -280,32 +280,34 @@ common/views/components/poll-editor.vue:
common/views/components/reaction-picker.vue:
choose-reaction: "Choisissez votre réaction"
common/views/components/signin.vue:
username: "Nom d'utilisateur"
username: "Nom d'utilisateur·rice"
password: "Mot de passe"
token: "Token"
signing-in: "Connexion...."
signing-in: "Connexion"
signin: "Se connecter"
or: "Ou"
signin-with-twitter: "Se connecter via Twitter"
login-failed: "ログインできませんでした。ユーザー名とパスワードを確認してください。"
common/views/components/signup.vue:
username: "Nom d'utilisateur"
checking: "Vérification"
invitation-code: "Code dinvitation"
invitation-info: "Si vous navez pas de code dinvitation, contactez un·e <a href=\"{}\">administrateur·rice</a>."
username: "Nom d'utilisateur·rice"
checking: "Vérification…"
available: "Disponible"
unavailable: "Non disponible"
error: "Erreur de réseau"
invalid-format: "Utilisez seulement des lettres, nombres et/ou -."
error: "Erreur du réseau"
invalid-format: "Vous pouvez utiliser des lettres, des nombres et _."
too-short: "Veuillez saisir au moins un caractère !"
too-long: "Veuillez entrer au maximum 20 caractères."
password: "Mot de Passe"
password-placeholder: "Nous recommendons au moins 8 caractères."
password: "Mot de passe"
password-placeholder: "Nous recommandons au moins 8 caractères."
weak-password: "Faible"
normal-password: "Moyen"
strong-password: "Fort"
retype: "Retapez"
retype-placeholder: "Confirmez votre mot de passe"
password-matched: "OK"
password-not-matched: "Les mots de passes ne correspondent pas."
password-not-matched: "Les mots de passe ne correspondent pas."
recaptcha: "Vérifier"
create: "Créer un compte"
some-error: "La création du compte a échoué. Veuillez réessayer."
@ -317,14 +319,14 @@ common/views/components/stream-indicator.vue:
reconnecting: "Reconnexion en cours"
connected: "Connecté"
common/views/components/twitter-setting.vue:
description: "Si vous liez votre compte Twitter à votre compte Misskey, vous verrez ensuite votre compte Twitter s'afficher sur votre profile, vous aurez aussi la possibilité de vous connecter à Misskey en utilisant votre compte Twitter."
description: "Si vous liez votre compte Twitter à votre compte Misskey, vous verrez ensuite votre compte Twitter s'afficher sur votre profil, vous aurez aussi la possibilité de vous connecter à Misskey en utilisant votre compte Twitter."
connected-to: "Vous êtes connecté à ce compte Twitter"
detail: "Détails …"
reconnect: "Reconnecter"
detail: "Détails…"
reconnect: "Reconnexion"
connect: "Lier votre compte Twitter"
disconnect: "Déconnecter"
disconnect: "Déconnexion"
common/views/components/uploader.vue:
waiting: "En attente"
waiting: "Veuillez patienter"
common/views/components/visibility-chooser.vue:
public: "Public"
home: "Accueil"
@ -332,42 +334,42 @@ common/views/components/visibility-chooser.vue:
followers: "Abonné·e·s"
followers-desc: "Publier à vos abonné·e·s uniquement"
specified: "Direct"
specified-desc: "Publier aux utilisateurs·trices mentionné·es"
specified-desc: "Publier aux utilisateur·rice·s mentionné·e·s"
private: "Privé"
common/views/widgets/broadcast.vue:
fetching: "Récuperation"
fetching: "Récupération"
no-broadcasts: "Aucune annonce"
have-a-nice-day: "Passez une bonne journée !"
next: "Suivant"
common/views/widgets/calendar.vue:
year: "{} année"
month: "{} mois"
day: "{} jour"
year: "Année {}"
month: "Mois {}"
day: "Jour {}"
today: "Aujourd'hui :"
this-month: "Ce mois-ci :"
this-year: "Cette année :"
common/views/widgets/donation.vue:
title: "Dons"
title: "Faire un don"
text: "Les frais pour faire fonctionner Misskey sortent directement de notre poche. Nous ne recevons pas d'argent issu de la publicité, si vous pouvez nous faire des dons, on vous serait éternellement reconnaissants. Si vous êtes intéressé·es veuillez contacter {}. Merci pour votre contribution !"
common/views/widgets/photo-stream.vue:
title: "Flux de photo"
no-photos: "Pas de photos"
title: "Flux de photos"
no-photos: "Pas de photo"
common/views/widgets/posts-monitor.vue:
title: "Graph des publications"
title: "Graphe des publications"
toggle: "Basculer entre les vues"
common/views/widgets/hashtags.vue:
title: "Étiquettes"
count: "{} utilisateurs mentionnés"
count: "{} utilisateur·rice·s mentionné·e·s"
empty: "Aucune tendance"
common/views/widgets/server.vue:
title: "Info sur le serveur"
title: "Informations sur le serveur"
toggle: "Afficher les vues"
common/views/widgets/memo.vue:
title: "Pense-bête"
memo: "Écrivez ici !"
save: "Enregistrer"
common/views/widgets/slideshow.vue:
folder-customize-mode: "Veuillez quitter le mode personnalisé pour pouvour spécifier un dossier"
folder-customize-mode: "Pour pouvoir spécifier un dossier, veuillez quitter le mode de personnalisation"
folder: "Veuillez cliquer pour spécifier le dossier"
no-image: "Il n'y a aucune image dans ce dossier"
common/views/widgets/tips.vue:
@ -375,13 +377,13 @@ common/views/widgets/tips.vue:
tips-line2: "<kbd>p</kbd>または<kbd>n</kbd>で投稿フォームを開きます"
tips-line3: "Vous pouvez glisser et déposer des fichiers sur la fenêtre de la note"
tips-line4: "Vous pouvez coller des images à partir du presse-papier sur la fenêtre de la note"
tips-line5: "Vous pouvez téléverser des fichiers sur le Drive en faisant un glisser/déplacer"
tips-line5: "Vous pouvez téléverser des fichiers sur le Drive en faisant un glisser-déposer"
tips-line6: "ドライブでファイルをドラッグしてフォルダ移動できます"
tips-line7: "ドライブでフォルダをドラッグしてフォルダ移動できます"
tips-line8: "Vous pouvez personnaliser l'Accueil via les paramètres"
tips-line9: "Misskey est sous licence AGPLv3"
tips-line10: "タイムマシンウィジェットを利用すると、簡単に過去のタイムラインに遡れます"
tips-line11: "Vous pouvez épingler des notes sur votre page en appuyant sur \"…\""
tips-line11: "Vous pouvez épingler des notes sur votre page en cliquant sur « … »"
tips-line13: "Tous les fichiers attachés à cette publication sont sauvegardés dans le Drive"
tips-line14: "ホームのカスタマイズ中、ウィジェットを右クリックしてデザインを変更できます"
tips-line17: "Vous pouvez mettre un texte en surbrillance en le mettant entre ** **"
@ -392,18 +394,18 @@ common/views/widgets/tips.vue:
tips-line24: "Misskey a vu le jour en 2014"
tips-line25: "対応ブラウザではMisskeyを開いていなくても通知を受け取れます"
common/views/pages/follow.vue:
signed-in-as: "Connecté·é en tant que {}"
signed-in-as: "Connecté·e en tant que {}"
following: "Suit"
follow: "Suivre"
request-pending: "Demande d'abonnement en attente"
follow-request: "Demande d'abonnement"
desktop:
banner-crop-title: "Découpez la partie qui apparaîtra comme une bannière"
banner-crop-title: "Découpez la partie qui apparaitra comme bannière"
banner: "Bannière"
uploading-banner: "Téléversement d'une nouvelle bannière"
banner-updated: "La bannière est mise à jour"
choose-banner: "Choisir une bannière"
avatar-crop-title: "Découpez la partie qui apparaîtra dans l'avatar"
avatar-crop-title: "Découpez la partie qui apparaitra comme avatar"
avatar: "Avatar"
uploading-avatar: "Téléversement du nouvel avatar"
avatar-updated: "L'avatar est mis à jour"
@ -414,16 +416,16 @@ desktop/views/components/activity.chart.vue:
replies: "Rouge ... Réponses"
renotes: "Vert ... Partages"
desktop/views/components/activity.vue:
title: "Activitié"
title: "Activité"
toggle: "Afficher les vues"
desktop/views/components/calendar.vue:
title: "{1} / {2}"
prev: "Mois dernier"
next: "Mois prochain"
go: "Cliquer pour naviguer"
go: "Cliquez pour naviguer"
desktop/views/components/choose-file-from-drive-window.vue:
choose-file: "Sélection de fichiers"
upload: "Téléverser des fichiers à partir de votre PC"
upload: "Téléverser des fichiers à partir de votre ordinateur"
cancel: "Annuler"
ok: "OK"
choose-prompt: "Choisir un fichier"
@ -454,14 +456,14 @@ desktop/views/components/drive.file.vue:
add-app: "Ajouter une application"
rename-file: "Renommer le ficher"
input-new-file-name: "Entrer un nouveau nom"
copied: "Copied"
copied-url-to-clipboard: "L'URL a été copié dans le presse-papier"
copied: "Copié"
copied-url-to-clipboard: "L'URL a été copiée dans le presse-papier"
desktop/views/components/drive.folder.vue:
unable-to-process: "L'opération n'a pas pu être complétée"
circular-reference-detected: "Le dossier de destination est un sous-dossier du dossier que vous souhaitez déplacer."
unhandled-error: "Erreur inconnue"
contextmenu:
move-to-this-folder: "Bouger dans ce dossier"
move-to-this-folder: "Déplacer dans ce dossier"
show-in-new-window: "Ouvrir dans une nouvelle fenêtre"
rename: "Renommer"
rename-folder: "Renommer le dossier"
@ -789,23 +791,44 @@ desktop/views/components/window.vue:
close: "Fermer"
desktop/views/pages/admin/admin.vue:
dashboard: "ダッシュボード"
drive: "ドライブ"
users: "ユーザー"
update: "更新"
drive: "Drive"
users: "Utilisateur·rice·s"
update: "Mises à jour"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
all-users: "全てのユーザー"
original-users: "このインスタンスのユーザー"
all-notes: "全てのノート"
original-notes: "このインスタンスのノート"
all-users: "Tou·te·s les utilisateur·rice·s"
original-users: "Utilisateur·rice·s sur cette instance"
all-notes: "Toutes les publications"
original-notes: "Publication sur cette instance"
invite: "Invitation"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "ユーザーの凍結"
suspend: "凍結"
suspended: "凍結しました"
suspend-user: "Suspendre un·e utilisateur·rice"
suspend: "Suspendre"
suspended: "Suspendu avec succès"
desktop/views/pages/admin/admin.unsuspend-user.vue:
unsuspend-user: "ユーザーの凍結の解除"
unsuspend: "凍結の解除"
unsuspended: "凍結を解除しました"
unsuspend-user: "Lever la suspension dutilisateur·rice·s"
unsuspend: "Suspension levée"
unsuspended: "La suspension de lutilisateur·rice a été levée avec succès"
desktop/views/pages/admin/admin.verify-user.vue:
verify-user: "ユーザーの公式アカウント設定"
verify: "Vérification du compte"
verified: "Le compte a été vérifié"
desktop/views/pages/admin/admin.unverify-user.vue:
unverify-user: "ユーザーの公式アカウント解除"
unverify: "公式アカウントを解除する"
unverified: "公式アカウントを解除しました"
desktop/views/pages/admin/admin.notes-chart.vue:
title: "投稿"
local: "ローカル"
remote: "リモート"
desktop/views/pages/admin/admin.users-chart.vue:
title: "ユーザー"
local: "ローカル"
remote: "リモート"
desktop/views/pages/admin/admin.drive-chart.vue:
title: "ドライブ"
local: "ローカル"
remote: "リモート"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "Les publications médias uniquement"
is-media-view: "Vue média"
@ -838,7 +861,7 @@ desktop/views/pages/selectdrive.vue:
cancel: "Annuler"
upload: "Uploader un ou plusieurs fichier(s) depuis votre PC"
desktop/views/pages/search.vue:
not-available: "Vous ne pouvez pas utiliser la fonctionnalité de recherche."
not-available: "検索機能はインスタンスの設定で無効になっています。"
not-found: "Aucun message trouvé pour '{}'"
desktop/views/pages/share.vue:
share-with: "Partager avec {}"
@ -1176,14 +1199,14 @@ docs:
params: "Paramètres"
no-params: "Aucun paramètre"
res: "Réponse"
require-credential: "このエンドポイントは認証情報が必須です。"
require-permission: "このエンドポイントは{permission}の権限を必要とします。"
require-credential: "Ce point de communication nécessite une authentification."
require-permission: "Ce point de communication nécessite la permission {permission}."
has-limit: "レートリミットがあります。"
duration-limit: "直近{duration}ミリ秒の間のこのエンドポイントへのリクエスト数の合計が{max}を超える場合はリクエストできません。"
min-interval-limit: "前回のリクエストから{interval}ミリ秒経っていない場合はリクエストできません。"
show-src: "このエンドポイントのソースコードも閲覧できます。"
show-src: "Vous pouvez voir le code source ce point de communication."
show-src-link: "Consulter le code sur GitHub"
generated: "このドキュメントはAPI定義に基づき自動生成されています。"
generated: "Ce document est généré à partir de la définition de lAPI."
props:
name: "Nom"
type: "Type"

View File

@ -15,6 +15,7 @@ const langs = {
'en': loadLang('en'),
'fr': loadLang('fr'),
'ja': native,
'ja-ks': loadLang('ja-ks'),
'pl': loadLang('pl'),
'es': loadLang('es')
};

View File

@ -289,6 +289,8 @@ common/views/components/signin.vue:
signin-with-twitter: "Twitterでログイン"
login-failed: "ログインできませんでした。ユーザー名とパスワードを確認してください。"
common/views/components/signup.vue:
invitation-code: "招待コード"
invitation-info: "招待コードをお持ちでない方は、<a href=\"{}\">管理者</a>までご連絡ください。"
username: "ユーザー名"
checking: "確認しています..."
available: "利用できます"
@ -798,6 +800,7 @@ desktop/views/pages/admin/admin.dashboard.vue:
original-users: "このインスタンスのユーザー"
all-notes: "全てのノート"
original-notes: "このインスタンスのノート"
invite: "招待"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "ユーザーの凍結"
suspend: "凍結"
@ -806,6 +809,26 @@ desktop/views/pages/admin/admin.unsuspend-user.vue:
unsuspend-user: "ユーザーの凍結の解除"
unsuspend: "凍結の解除"
unsuspended: "凍結を解除しました"
desktop/views/pages/admin/admin.verify-user.vue:
verify-user: "ユーザーの公式アカウント設定"
verify: "公式アカウントにする"
verified: "公式アカウントにしました"
desktop/views/pages/admin/admin.unverify-user.vue:
unverify-user: "ユーザーの公式アカウント解除"
unverify: "公式アカウントを解除する"
unverified: "公式アカウントを解除しました"
desktop/views/pages/admin/admin.notes-chart.vue:
title: "投稿"
local: "ローカル"
remote: "リモート"
desktop/views/pages/admin/admin.users-chart.vue:
title: "ユーザー"
local: "ローカル"
remote: "リモート"
desktop/views/pages/admin/admin.drive-chart.vue:
title: "ドライブ"
local: "ローカル"
remote: "リモート"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
@ -838,7 +861,7 @@ desktop/views/pages/selectdrive.vue:
cancel: "キャンセル"
upload: "PCからドライブにファイルをアップロード"
desktop/views/pages/search.vue:
not-available: "検索機能を利用することができません。"
not-available: "検索機能はインスタンスの設定で無効になっています。"
not-found: "「{}」に関する投稿は見つかりませんでした。"
desktop/views/pages/share.vue:
share-with: "{}で共有"

1412
locales/ja-ks.yml Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -289,6 +289,8 @@ common/views/components/signin.vue:
signin-with-twitter: "Twitterでログイン"
login-failed: "ログインできませんでした。ユーザー名とパスワードを確認してください。"
common/views/components/signup.vue:
invitation-code: "招待コード"
invitation-info: "招待コードをお持ちでない方は、<a href=\"{}\">管理者</a>までご連絡ください。"
username: "ユーザー名"
checking: "確認しています..."
available: "利用できます"
@ -798,6 +800,7 @@ desktop/views/pages/admin/admin.dashboard.vue:
original-users: "このインスタンスのユーザー"
all-notes: "全てのノート"
original-notes: "このインスタンスのノート"
invite: "招待"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "ユーザーの凍結"
suspend: "凍結"
@ -806,6 +809,26 @@ desktop/views/pages/admin/admin.unsuspend-user.vue:
unsuspend-user: "ユーザーの凍結の解除"
unsuspend: "凍結の解除"
unsuspended: "凍結を解除しました"
desktop/views/pages/admin/admin.verify-user.vue:
verify-user: "ユーザーの公式アカウント設定"
verify: "公式アカウントにする"
verified: "公式アカウントにしました"
desktop/views/pages/admin/admin.unverify-user.vue:
unverify-user: "ユーザーの公式アカウント解除"
unverify: "公式アカウントを解除する"
unverified: "公式アカウントを解除しました"
desktop/views/pages/admin/admin.notes-chart.vue:
title: "投稿"
local: "ローカル"
remote: "リモート"
desktop/views/pages/admin/admin.users-chart.vue:
title: "ユーザー"
local: "ローカル"
remote: "リモート"
desktop/views/pages/admin/admin.drive-chart.vue:
title: "ドライブ"
local: "ローカル"
remote: "リモート"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
@ -838,7 +861,7 @@ desktop/views/pages/selectdrive.vue:
cancel: "キャンセル"
upload: "PCからドライブにファイルをアップロード"
desktop/views/pages/search.vue:
not-available: "検索機能を利用することができません。"
not-available: "検索機能はインスタンスの設定で無効になっています。"
not-found: "「{}」に関する投稿は見つかりませんでした。"
desktop/views/pages/share.vue:
share-with: "{}で共有"

View File

@ -289,6 +289,8 @@ common/views/components/signin.vue:
signin-with-twitter: "Zaloguj się za pomocą Twittera"
login-failed: "ログインできませんでした。ユーザー名とパスワードを確認してください。"
common/views/components/signup.vue:
invitation-code: "招待コード"
invitation-info: "招待コードをお持ちでない方は、<a href=\"{}\">管理者</a>までご連絡ください。"
username: "Nazwa użytkownika"
checking: "Sprawdzanie…"
available: "Dostępna"
@ -798,6 +800,7 @@ desktop/views/pages/admin/admin.dashboard.vue:
original-users: "このインスタンスのユーザー"
all-notes: "全てのノート"
original-notes: "このインスタンスのノート"
invite: "招待"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "ユーザーの凍結"
suspend: "凍結"
@ -806,6 +809,26 @@ desktop/views/pages/admin/admin.unsuspend-user.vue:
unsuspend-user: "ユーザーの凍結の解除"
unsuspend: "凍結の解除"
unsuspended: "凍結を解除しました"
desktop/views/pages/admin/admin.verify-user.vue:
verify-user: "ユーザーの公式アカウント設定"
verify: "公式アカウントにする"
verified: "公式アカウントにしました"
desktop/views/pages/admin/admin.unverify-user.vue:
unverify-user: "ユーザーの公式アカウント解除"
unverify: "公式アカウントを解除する"
unverified: "公式アカウントを解除しました"
desktop/views/pages/admin/admin.notes-chart.vue:
title: "投稿"
local: "ローカル"
remote: "リモート"
desktop/views/pages/admin/admin.users-chart.vue:
title: "ユーザー"
local: "ローカル"
remote: "リモート"
desktop/views/pages/admin/admin.drive-chart.vue:
title: "ドライブ"
local: "ローカル"
remote: "リモート"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "Tylko wpisy z zawartością multimedialną"
is-media-view: "Widok multimediów"
@ -838,7 +861,7 @@ desktop/views/pages/selectdrive.vue:
cancel: "Anuluj"
upload: "Wyślij pliki z Twojego komputera"
desktop/views/pages/search.vue:
not-available: "Funkcja wyszukiwania nie może zostać wykorzystywana."
not-available: "検索機能はインスタンスの設定で無効になっています。"
not-found: "Nie znaleziono wpisów zawierających „{}”"
desktop/views/pages/share.vue:
share-with: "Udostępnij z {}."

View File

@ -289,6 +289,8 @@ common/views/components/signin.vue:
signin-with-twitter: "Twitterでログイン"
login-failed: "ログインできませんでした。ユーザー名とパスワードを確認してください。"
common/views/components/signup.vue:
invitation-code: "招待コード"
invitation-info: "招待コードをお持ちでない方は、<a href=\"{}\">管理者</a>までご連絡ください。"
username: "ユーザー名"
checking: "確認しています..."
available: "利用できます"
@ -798,6 +800,7 @@ desktop/views/pages/admin/admin.dashboard.vue:
original-users: "このインスタンスのユーザー"
all-notes: "全てのノート"
original-notes: "このインスタンスのノート"
invite: "招待"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "ユーザーの凍結"
suspend: "凍結"
@ -806,6 +809,26 @@ desktop/views/pages/admin/admin.unsuspend-user.vue:
unsuspend-user: "ユーザーの凍結の解除"
unsuspend: "凍結の解除"
unsuspended: "凍結を解除しました"
desktop/views/pages/admin/admin.verify-user.vue:
verify-user: "ユーザーの公式アカウント設定"
verify: "公式アカウントにする"
verified: "公式アカウントにしました"
desktop/views/pages/admin/admin.unverify-user.vue:
unverify-user: "ユーザーの公式アカウント解除"
unverify: "公式アカウントを解除する"
unverified: "公式アカウントを解除しました"
desktop/views/pages/admin/admin.notes-chart.vue:
title: "投稿"
local: "ローカル"
remote: "リモート"
desktop/views/pages/admin/admin.users-chart.vue:
title: "ユーザー"
local: "ローカル"
remote: "リモート"
desktop/views/pages/admin/admin.drive-chart.vue:
title: "ドライブ"
local: "ローカル"
remote: "リモート"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
@ -838,7 +861,7 @@ desktop/views/pages/selectdrive.vue:
cancel: "キャンセル"
upload: "PCからドライブにファイルをアップロード"
desktop/views/pages/search.vue:
not-available: "検索機能を利用することができません。"
not-available: "検索機能はインスタンスの設定で無効になっています。"
not-found: "「{}」に関する投稿は見つかりませんでした。"
desktop/views/pages/share.vue:
share-with: "{}で共有"

View File

@ -289,6 +289,8 @@ common/views/components/signin.vue:
signin-with-twitter: "Twitterでログイン"
login-failed: "ログインできませんでした。ユーザー名とパスワードを確認してください。"
common/views/components/signup.vue:
invitation-code: "招待コード"
invitation-info: "招待コードをお持ちでない方は、<a href=\"{}\">管理者</a>までご連絡ください。"
username: "ユーザー名"
checking: "確認しています..."
available: "利用できます"
@ -798,6 +800,7 @@ desktop/views/pages/admin/admin.dashboard.vue:
original-users: "このインスタンスのユーザー"
all-notes: "全てのノート"
original-notes: "このインスタンスのノート"
invite: "招待"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "ユーザーの凍結"
suspend: "凍結"
@ -806,6 +809,26 @@ desktop/views/pages/admin/admin.unsuspend-user.vue:
unsuspend-user: "ユーザーの凍結の解除"
unsuspend: "凍結の解除"
unsuspended: "凍結を解除しました"
desktop/views/pages/admin/admin.verify-user.vue:
verify-user: "ユーザーの公式アカウント設定"
verify: "公式アカウントにする"
verified: "公式アカウントにしました"
desktop/views/pages/admin/admin.unverify-user.vue:
unverify-user: "ユーザーの公式アカウント解除"
unverify: "公式アカウントを解除する"
unverified: "公式アカウントを解除しました"
desktop/views/pages/admin/admin.notes-chart.vue:
title: "投稿"
local: "ローカル"
remote: "リモート"
desktop/views/pages/admin/admin.users-chart.vue:
title: "ユーザー"
local: "ローカル"
remote: "リモート"
desktop/views/pages/admin/admin.drive-chart.vue:
title: "ドライブ"
local: "ローカル"
remote: "リモート"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
@ -838,7 +861,7 @@ desktop/views/pages/selectdrive.vue:
cancel: "キャンセル"
upload: "PCからドライブにファイルをアップロード"
desktop/views/pages/search.vue:
not-available: "検索機能を利用することができません。"
not-available: "検索機能はインスタンスの設定で無効になっています。"
not-found: "「{}」に関する投稿は見つかりませんでした。"
desktop/views/pages/share.vue:
share-with: "{}で共有"

View File

@ -289,6 +289,8 @@ common/views/components/signin.vue:
signin-with-twitter: "Twitterでログイン"
login-failed: "ログインできませんでした。ユーザー名とパスワードを確認してください。"
common/views/components/signup.vue:
invitation-code: "招待コード"
invitation-info: "招待コードをお持ちでない方は、<a href=\"{}\">管理者</a>までご連絡ください。"
username: "ユーザー名"
checking: "確認しています..."
available: "利用できます"
@ -798,6 +800,7 @@ desktop/views/pages/admin/admin.dashboard.vue:
original-users: "このインスタンスのユーザー"
all-notes: "全てのノート"
original-notes: "このインスタンスのノート"
invite: "招待"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "ユーザーの凍結"
suspend: "凍結"
@ -806,6 +809,26 @@ desktop/views/pages/admin/admin.unsuspend-user.vue:
unsuspend-user: "ユーザーの凍結の解除"
unsuspend: "凍結の解除"
unsuspended: "凍結を解除しました"
desktop/views/pages/admin/admin.verify-user.vue:
verify-user: "ユーザーの公式アカウント設定"
verify: "公式アカウントにする"
verified: "公式アカウントにしました"
desktop/views/pages/admin/admin.unverify-user.vue:
unverify-user: "ユーザーの公式アカウント解除"
unverify: "公式アカウントを解除する"
unverified: "公式アカウントを解除しました"
desktop/views/pages/admin/admin.notes-chart.vue:
title: "投稿"
local: "ローカル"
remote: "リモート"
desktop/views/pages/admin/admin.users-chart.vue:
title: "ユーザー"
local: "ローカル"
remote: "リモート"
desktop/views/pages/admin/admin.drive-chart.vue:
title: "ドライブ"
local: "ローカル"
remote: "リモート"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"
@ -838,7 +861,7 @@ desktop/views/pages/selectdrive.vue:
cancel: "キャンセル"
upload: "PCからドライブにファイルをアップロード"
desktop/views/pages/search.vue:
not-available: "検索機能を利用することができません。"
not-available: "検索機能はインスタンスの設定で無効になっています。"
not-found: "「{}」に関する投稿は見つかりませんでした。"
desktop/views/pages/share.vue:
share-with: "{}で共有"

View File

@ -1,8 +1,8 @@
{
"name": "misskey",
"author": "syuilo <i@syuilo.com>",
"version": "6.4.0",
"clientVersion": "1.0.8520",
"version": "7.0.0",
"clientVersion": "1.0.8654",
"codename": "nighthike",
"main": "./built/index.js",
"private": true,

View File

@ -32,6 +32,7 @@
<mk-avatar class="avatar" :user="g.user2"/>
<span><b>{{ g.user1 | userName }}</b> vs <b>{{ g.user2 | userName }}</b></span>
<span class="state">{{ g.isEnded ? '%i18n:@game-state.ended%' : '%i18n:@game-state.playing%' }}</span>
<mk-time :time="g.createdAt" />
</a>
</section>
<section v-if="games.length > 0">
@ -41,6 +42,7 @@
<mk-avatar class="avatar" :user="g.user2"/>
<span><b>{{ g.user1 | userName }}</b> vs <b>{{ g.user2 | userName }}</b></span>
<span class="state">{{ g.isEnded ? '%i18n:@game-state.ended%' : '%i18n:@game-state.playing%' }}</span>
<mk-time :time="g.createdAt" />
</a>
</section>
</div>

View File

@ -122,7 +122,7 @@ export default Vue.extend({
this.memP = (stats.mem.used / stats.mem.total * 100).toFixed(0);
},
onStatsLog(statsLog) {
statsLog.forEach(stats => this.onStats(stats));
statsLog.reverse().forEach(stats => this.onStats(stats));
}
}
});

View File

@ -9,12 +9,18 @@
@contextmenu.prevent.stop="onContextmenu"
:title="title"
>
<div class="label" v-if="$store.state.i.avatarId == file.id"><img src="/assets/label.svg"/>
<div class="label" v-if="$store.state.i.avatarId == file.id">
<img src="/assets/label.svg"/>
<p>%i18n:@avatar%</p>
</div>
<div class="label" v-if="$store.state.i.bannerId == file.id"><img src="/assets/label.svg"/>
<div class="label" v-if="$store.state.i.bannerId == file.id">
<img src="/assets/label.svg"/>
<p>%i18n:@banner%</p>
</div>
<div class="label red" v-if="file.isSensitive">
<img src="/assets/label-red.svg"/>
<p>%i18n:@nsfw%</p>
</div>
<div class="thumbnail" ref="thumbnail" :style="`background-color: ${ background }`">
<img :src="file.thumbnailUrl" alt="" @load="onThumbnailLoaded"/>
</div>
@ -212,6 +218,11 @@ root(isDark)
&:after
background #0b65a5
&.red
&:before
&:after
background #c12113
&:active
background rgba(#000, 0.1)
@ -220,6 +231,11 @@ root(isDark)
&:after
background #0b588c
&.red
&:before
&:after
background #ce2212
&[data-is-selected]
background $theme-color
@ -256,26 +272,29 @@ root(isDark)
pointer-events none
&:before
content ""
display block
position absolute
z-index 1
top 0
left 57px
width 28px
height 8px
background #0c7ac9
&:after
content ""
display block
position absolute
z-index 1
background #0c7ac9
&:before
top 0
left 57px
width 28px
height 8px
&:after
top 57px
left 0
width 8px
height 28px
background #0c7ac9
&.red
&:before
&:after
background #c12113
> img
position absolute

View File

@ -15,14 +15,14 @@
:points="cpuPolylinePoints"
fill="none"
stroke="#fff"
stroke-width="0.3"/>
stroke-width="1"/>
</mask>
</defs>
<rect
x="0" y="0"
:width="viewBoxX" :height="viewBoxY"
:style="`stroke: none; fill: url(#${ cpuGradientId }); mask: url(#${ cpuMaskId })`"/>
<text x="1" y="5">CPU <tspan>{{ cpuP }}%</tspan></text>
<text x="1" y="12">CPU <tspan>{{ cpuP }}%</tspan></text>
</svg>
<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`">
<defs>
@ -39,14 +39,14 @@
:points="memPolylinePoints"
fill="none"
stroke="#fff"
stroke-width="0.3"/>
stroke-width="1"/>
</mask>
</defs>
<rect
x="0" y="0"
:width="viewBoxX" :height="viewBoxY"
:style="`stroke: none; fill: url(#${ memGradientId }); mask: url(#${ memMaskId })`"/>
<text x="1" y="5">MEM <tspan>{{ memP }}%</tspan></text>
<text x="1" y="12">MEM <tspan>{{ memP }}%</tspan></text>
</svg>
</div>
</template>
@ -59,8 +59,8 @@ export default Vue.extend({
props: ['connection'],
data() {
return {
viewBoxX: 50,
viewBoxY: 20,
viewBoxX: 200,
viewBoxY: 70,
stats: [],
cpuGradientId: uuid(),
cpuMaskId: uuid(),
@ -79,7 +79,8 @@ export default Vue.extend({
this.connection.on('statsLog', this.onStatsLog);
this.connection.send({
type: 'requestLog',
id: Math.random().toString()
id: Math.random().toString(),
length: 200
});
},
beforeDestroy() {
@ -89,7 +90,7 @@ export default Vue.extend({
methods: {
onStats(stats) {
this.stats.push(stats);
if (this.stats.length > 50) this.stats.shift();
if (this.stats.length > 200) this.stats.shift();
const cpuPolylinePoints = this.stats.map((s, i) => [this.viewBoxX - ((this.stats.length - 1) - i), (1 - s.cpu_usage) * this.viewBoxY]);
const memPolylinePoints = this.stats.map((s, i) => [this.viewBoxX - ((this.stats.length - 1) - i), (1 - (s.mem.used / s.mem.total)) * this.viewBoxY]);
@ -103,7 +104,7 @@ export default Vue.extend({
this.memP = (stats.mem.used / stats.mem.total * 100).toFixed(0);
},
onStatsLog(statsLog) {
statsLog.forEach(stats => this.onStats(stats));
statsLog.reverse().forEach(stats => this.onStats(stats));
}
}
});
@ -111,8 +112,6 @@ export default Vue.extend({
<style lang="stylus" scoped>
root(isDark)
margin-bottom 16px
> svg
display block
width 50%
@ -125,7 +124,7 @@ root(isDark)
padding-left 5px
> text
font-size 2px
font-size 10px
fill isDark ? rgba(#fff, 0.55) : rgba(#000, 0.55)
> tspan

View File

@ -77,4 +77,10 @@ export default Vue.extend({
> *:last-child
font-size 70%
> .cpu-memory
margin-bottom 16px
padding 16px
border solid 1px #eee
border-radius: 8px
</style>

View File

@ -0,0 +1,51 @@
<template>
<svg :viewBox="`0 0 ${ viewBoxX } ${ viewBoxY }`">
<polyline
:points="points"
fill="none"
stroke-width="1"
stroke="#555"/>
</svg>
</template>
<script lang="ts">
import Vue from 'vue';
export default Vue.extend({
props: {
chart: {
required: true
},
type: {
type: String,
required: true
}
},
data() {
return {
viewBoxX: 365,
viewBoxY: 70,
points: null
};
},
created() {
const peak = Math.max.apply(null, this.chart.map(d => this.type == 'local' ? d.drive.local.totalSize : d.drive.remote.totalSize));
if (peak != 0) {
const data = this.chart.slice().reverse().map(x => ({
size: this.type == 'local' ? x.drive.local.totalSize : x.drive.remote.totalSize
}));
this.points = data.map((d, i) => `${i},${(1 - (d.size / peak)) * this.viewBoxY}`).join(' ');
}
}
});
</script>
<style lang="stylus" scoped>
svg
display block
padding 10px
width 100%
</style>

View File

@ -0,0 +1,34 @@
<template>
<div class="card">
<header>%i18n:@title%</header>
<div class="card">
<header>%i18n:@local%</header>
<x-chart v-if="chart" :chart="chart" type="local"/>
</div>
<div class="card">
<header>%i18n:@remote%</header>
<x-chart v-if="chart" :chart="chart" type="remote"/>
</div>
</div>
</template>
<script lang="ts">
import Vue from "vue";
import XChart from "./admin.drive-chart.chart.vue";
export default Vue.extend({
components: {
XChart
},
props: {
chart: {
required: true
}
}
});
</script>
<style lang="stylus" scoped>
@import '~const.styl'
</style>

View File

@ -29,7 +29,7 @@ import Vue from 'vue';
export default Vue.extend({
props: {
data: {
chart: {
required: true
},
type: {
@ -39,7 +39,6 @@ export default Vue.extend({
},
data() {
return {
chart: this.data,
viewBoxX: 365,
viewBoxY: 70,
pointsNote: null,
@ -49,23 +48,19 @@ export default Vue.extend({
};
},
created() {
this.chart.forEach(d => {
d.notes = this.type == 'local' ? d.localNotes : d.remoteNotes;
d.replies = this.type == 'local' ? d.localReplies : d.remoteReplies;
d.renotes = this.type == 'local' ? d.localRenotes : d.remoteRenotes;
});
this.chart.forEach(d => {
d.total = d.notes + d.replies + d.renotes;
});
const peak = Math.max.apply(null, this.chart.map(d => d.total));
const peak = Math.max.apply(null, this.chart.map(d => this.type == 'local' ? d.notes.local.diff : d.notes.remote.diff));
if (peak != 0) {
const data = this.chart.slice().reverse();
this.pointsNote = data.map((d, i) => `${i},${(1 - (d.notes / peak)) * this.viewBoxY}`).join(' ');
this.pointsReply = data.map((d, i) => `${i},${(1 - (d.replies / peak)) * this.viewBoxY}`).join(' ');
this.pointsRenote = data.map((d, i) => `${i},${(1 - (d.renotes / peak)) * this.viewBoxY}`).join(' ');
const data = this.chart.slice().reverse().map(x => ({
normal: this.type == 'local' ? x.notes.local.diffs.normal : x.notes.remote.diffs.normal,
reply: this.type == 'local' ? x.notes.local.diffs.reply : x.notes.remote.diffs.reply,
renote: this.type == 'local' ? x.notes.local.diffs.renote : x.notes.remote.diffs.renote,
total: this.type == 'local' ? x.notes.local.diff : x.notes.remote.diff
}));
this.pointsNote = data.map((d, i) => `${i},${(1 - (d.normal / peak)) * this.viewBoxY}`).join(' ');
this.pointsReply = data.map((d, i) => `${i},${(1 - (d.reply / peak)) * this.viewBoxY}`).join(' ');
this.pointsRenote = data.map((d, i) => `${i},${(1 - (d.renote / peak)) * this.viewBoxY}`).join(' ');
this.pointsTotal = data.map((d, i) => `${i},${(1 - (d.total / peak)) * this.viewBoxY}`).join(' ');
}
}

View File

@ -3,11 +3,11 @@
<header>%i18n:@title%</header>
<div class="card">
<header>%i18n:@local%</header>
<x-chart v-if="data" :data="data" type="local"/>
<x-chart v-if="chart" :chart="chart" type="local"/>
</div>
<div class="card">
<header>%i18n:@remote%</header>
<x-chart v-if="data" :data="data" type="remote"/>
<x-chart v-if="chart" :chart="chart" type="remote"/>
</div>
</div>
</template>
@ -20,15 +20,10 @@ export default Vue.extend({
components: {
XChart
},
data() {
return {
data: null
};
},
created() {
(this as any).api('aggregation/notes').then(res => {
this.data = res;
});
props: {
chart: {
required: true
}
}
});
</script>

View File

@ -0,0 +1,51 @@
<template>
<div class="card">
<header>%i18n:@unverify-user%</header>
<input v-model="username" type="text" class="ui"/>
<button class="ui" @click="unverifyUser" :disabled="unverifying">%i18n:@unverify%</button>
</div>
</template>
<script lang="ts">
import Vue from "vue";
import parseAcct from "../../../../../../misc/acct/parse";
export default Vue.extend({
data() {
return {
username: null,
unverifying: false
};
},
methods: {
async unverifyUser() {
this.unverifying = true;
const user = await (this as any).os.api(
"users/show",
parseAcct(this.username)
);
await (this as any).os.api("admin/unverify-user", {
userId: user.id
});
this.unverifying = false;
(this as any).os.apis.dialog({ text: "%i18n:@unverified%" });
}
}
});
</script>
<style lang="stylus" scoped>
@import '~const.styl'
header
margin 10px 0
button
margin 16px 0
</style>

View File

@ -13,7 +13,7 @@ import Vue from 'vue';
export default Vue.extend({
props: {
data: {
chart: {
required: true
},
type: {
@ -23,21 +23,19 @@ export default Vue.extend({
},
data() {
return {
chart: this.data,
viewBoxX: 365,
viewBoxY: 70,
points: null
};
},
created() {
this.chart.forEach(d => {
d.count = this.type == 'local' ? d.local : d.remote;
});
const peak = Math.max.apply(null, this.chart.map(d => d.count));
const peak = Math.max.apply(null, this.chart.map(d => this.type == 'local' ? d.users.local.diff : d.users.remote.diff));
if (peak != 0) {
const data = this.chart.slice().reverse();
const data = this.chart.slice().reverse().map(x => ({
count: this.type == 'local' ? x.users.local.diff : x.users.remote.diff
}));
this.points = data.map((d, i) => `${i},${(1 - (d.count / peak)) * this.viewBoxY}`).join(' ');
}
}

View File

@ -3,11 +3,11 @@
<header>%i18n:@title%</header>
<div class="card">
<header>%i18n:@local%</header>
<x-chart v-if="data" :data="data" type="local"/>
<x-chart v-if="chart" :chart="chart" type="local"/>
</div>
<div class="card">
<header>%i18n:@remote%</header>
<x-chart v-if="data" :data="data" type="remote"/>
<x-chart v-if="chart" :chart="chart" type="remote"/>
</div>
</div>
</template>
@ -20,15 +20,10 @@ export default Vue.extend({
components: {
XChart
},
data() {
return {
data: null
};
},
created() {
(this as any).api('aggregation/users').then(res => {
this.data = res;
});
props: {
chart: {
required: true
}
}
});
</script>

View File

@ -11,13 +11,15 @@
<main>
<div v-show="page == 'dashboard'">
<x-dashboard/>
<x-users-chart/>
<x-notes-chart/>
<x-users-chart :chart="chart"/>
<x-notes-chart :chart="chart"/>
<x-drive-chart :chart="chart"/>
</div>
<div v-if="page == 'users'">
<x-suspend-user/>
<x-unsuspend-user/>
<x-verify-user/>
<x-unverify-user/>
</div>
<div v-if="page == 'drive'"></div>
<div v-if="page == 'update'"></div>
@ -31,8 +33,10 @@ import XDashboard from "./admin.dashboard.vue";
import XSuspendUser from "./admin.suspend-user.vue";
import XUnsuspendUser from "./admin.unsuspend-user.vue";
import XVerifyUser from "./admin.verify-user.vue";
import XUnverifyUser from "./admin.unverify-user.vue";
import XUsersChart from "./admin.users-chart.vue";
import XNotesChart from "./admin.notes-chart.vue";
import XDriveChart from "./admin.drive-chart.vue";
export default Vue.extend({
components: {
@ -40,14 +44,22 @@ export default Vue.extend({
XSuspendUser,
XUnsuspendUser,
XVerifyUser,
XUnverifyUser,
XUsersChart,
XNotesChart
XNotesChart,
XDriveChart
},
data() {
return {
page: 'dashboard'
page: 'dashboard',
chart: null
};
},
created() {
(this as any).api('admin/chart').then(chart => {
this.chart = chart;
});
},
methods: {
nav(page: string) {
this.page = page;

View File

@ -30,6 +30,10 @@
<span class="data-size">{{ file.datasize | bytes }}</span>
<span class="separator"></span>
<span class="created-at" @click="showCreatedAt">%fa:R clock%<mk-time :time="file.createdAt"/></span>
<template v-if="file.isSensitive">
<span class="separator"></span>
<span class="nsfw">%fa:eye-slash% %i18n:@nsfw%</span>
</template>
</div>
</div>
<div class="menu">
@ -198,6 +202,9 @@ export default Vue.extend({
> [data-fa]
margin-right 2px
> .nsfw
color #bf4633
> .menu
padding 14px
border-top solid 1px #dfdfdf

View File

@ -14,13 +14,17 @@
li.tag(style={background: tag.color, color: contrast(tag.color)})= tag.name
-->
<footer>
<p class="type"><mk-file-type-icon :type="file.type"/>{{ file.type }}</p>
<p class="separator"></p>
<p class="data-size">{{ file.datasize | bytes }}</p>
<p class="separator"></p>
<p class="created-at">
<span class="type"><mk-file-type-icon :type="file.type"/>{{ file.type }}</span>
<span class="separator"></span>
<span class="data-size">{{ file.datasize | bytes }}</span>
<span class="separator"></span>
<span class="created-at">
%fa:R clock%<mk-time :time="file.createdAt"/>
</p>
</span>
<template v-if="file.isSensitive">
<span class="separator"></span>
<span class="nsfw">%fa:eye-slash% %i18n:@nsfw%</span>
</template>
</footer>
</div>
</div>
@ -133,35 +137,27 @@ export default Vue.extend({
font-size 0.7em
> .separator
display inline
margin 0
padding 0 4px
color #CDCDCD
> .type
display inline
margin 0
padding 0
color #9D9D9D
> .mk-file-type-icon
margin-right 4px
> .data-size
display inline
margin 0
padding 0
color #9D9D9D
> .created-at
display inline
margin 0
padding 0
color #BDBDBD
> [data-fa]
margin-right 2px
> .nsfw
color #bf4633
&[data-is-selected]
background $theme-color

View File

@ -1,10 +0,0 @@
@import "../app"
@import "../reset"
html
color #456267
background #fff
body
margin 0
padding 0

View File

@ -1,209 +0,0 @@
<mk-index>
<h1>Misskey<i>Statistics</i></h1>
<main v-if="!initializing">
<mk-users stats={ stats }/>
<mk-notes stats={ stats }/>
</main>
<footer><a href={ _URL_ }>{ _HOST_ }</a></footer>
<style lang="stylus" scoped>
:scope
display block
margin 0 auto
padding 0 16px
max-width 700px
> h1
margin 0
padding 24px 0 0 0
font-size 24px
font-weight normal
> i
font-style normal
color #f43b16
> main
> *
margin 24px 0
padding-top 24px
border-top solid 1px #eee
> h2
margin 0 0 12px 0
font-size 18px
font-weight normal
> footer
margin 24px 0
text-align center
> a
color #546567
</style>
<script lang="typescript">
this.mixin('api');
this.initializing = true;
this.on('mount', () => {
this.$root.$data.os.api('stats').then(stats => {
this.update({
initializing: false,
stats
});
});
});
</script>
</mk-index>
<mk-notes>
<h2>%i18n:stats.notes-count% <b>{ stats.notesCount }</b></h2>
<mk-notes-chart v-if="!initializing" data={ data }/>
<style lang="stylus" scoped>
:scope
display block
</style>
<script lang="typescript">
this.mixin('api');
this.initializing = true;
this.stats = this.opts.stats;
this.on('mount', () => {
this.$root.$data.os.api('aggregation/notes', {
limit: 365
}).then(data => {
this.update({
initializing: false,
data
});
});
});
</script>
</mk-notes>
<mk-users>
<h2>%i18n:stats.users-count% <b>{ stats.usersCount }</b></h2>
<mk-users-chart v-if="!initializing" data={ data }/>
<style lang="stylus" scoped>
:scope
display block
</style>
<script lang="typescript">
this.mixin('api');
this.initializing = true;
this.stats = this.opts.stats;
this.on('mount', () => {
this.$root.$data.os.api('aggregation/users', {
limit: 365
}).then(data => {
this.update({
initializing: false,
data
});
});
});
</script>
</mk-users>
<mk-notes-chart>
<svg riot-viewBox="0 0 { viewBoxX } { viewBoxY }" preserveAspectRatio="none">
<title>Black ... Total<br/>Blue ... Notes<br/>Red ... Replies<br/>Green ... Renotes</title>
<polyline
riot-points={ pointsNote }
fill="none"
stroke-width="1"
stroke="#41ddde"/>
<polyline
riot-points={ pointsReply }
fill="none"
stroke-width="1"
stroke="#f7796c"/>
<polyline
riot-points={ pointsRenote }
fill="none"
stroke-width="1"
stroke="#a1de41"/>
<polyline
riot-points={ pointsTotal }
fill="none"
stroke-width="1"
stroke="#555"
stroke-dasharray="2 2"/>
</svg>
<style lang="stylus" scoped>
:scope
display block
> svg
display block
padding 1px
width 100%
</style>
<script lang="typescript">
this.viewBoxX = 365;
this.viewBoxY = 80;
this.data = this.opts.data.reverse();
this.data.forEach(d => d.total = d.notes + d.replies + d.renotes);
const peak = Math.max.apply(null, this.data.map(d => d.total));
this.on('mount', () => {
this.render();
});
this.render = () => {
this.update({
pointsNote: this.data.map((d, i) => `${i},${(1 - (d.notes / peak)) * this.viewBoxY}`).join(' '),
pointsReply: this.data.map((d, i) => `${i},${(1 - (d.replies / peak)) * this.viewBoxY}`).join(' '),
pointsRenote: this.data.map((d, i) => `${i},${(1 - (d.renotes / peak)) * this.viewBoxY}`).join(' '),
pointsTotal: this.data.map((d, i) => `${i},${(1 - (d.total / peak)) * this.viewBoxY}`).join(' ')
});
};
</script>
</mk-notes-chart>
<mk-users-chart>
<svg riot-viewBox="0 0 { viewBoxX } { viewBoxY }" preserveAspectRatio="none">
<polyline
riot-points={ createdPoints }
fill="none"
stroke-width="1"
stroke="#1cde84"/>
<polyline
riot-points={ totalPoints }
fill="none"
stroke-width="1"
stroke="#555"/>
</svg>
<style lang="stylus" scoped>
:scope
display block
> svg
display block
padding 1px
width 100%
</style>
<script lang="typescript">
this.viewBoxX = 365;
this.viewBoxY = 80;
this.data = this.opts.data.reverse();
const totalPeak = Math.max.apply(null, this.data.map(d => d.total));
const createdPeak = Math.max.apply(null, this.data.map(d => d.created));
this.on('mount', () => {
this.render();
});
this.render = () => {
this.update({
totalPoints: this.data.map((d, i) => `${i},${(1 - (d.total / totalPeak)) * this.viewBoxY}`).join(' '),
createdPoints: this.data.map((d, i) => `${i},${(1 - (d.created / createdPeak)) * this.viewBoxY}`).join(' ')
});
};
</script>
</mk-users-chart>

View File

@ -1 +0,0 @@
require('./index.tag');

View File

@ -1,10 +0,0 @@
@import "../app"
@import "../reset"
html
color #456267
background #fff
body
margin 0
padding 0

View File

@ -1,201 +0,0 @@
<mk-index>
<h1>Misskey<i>Status</i></h1>
<p>%fa:info-circle%%i18n:status.all-systems-maybe-operational%</p>
<main>
<mk-cpu-usage connection={ connection }/>
<mk-mem-usage connection={ connection }/>
</main>
<footer><a href={ _URL_ }>{ _HOST_ }</a></footer>
<style lang="stylus" scoped>
:scope
display block
margin 0 auto
padding 0 16px
max-width 700px
> h1
margin 0
padding 24px 0 16px 0
font-size 24px
font-weight normal
> [data-fa]
font-style normal
color #f43b16
> p
display block
margin 0
padding 12px 16px
background #eaf4ef
//border solid 1px #99ccb2
border-radius 4px
> [data-fa]
margin-right 5px
> main
> *
margin 24px 0
> h2
margin 0 0 12px 0
font-size 18px
font-weight normal
> footer
margin 24px 0
text-align center
> a
color #546567
</style>
<script lang="typescript">
import Connection from '../../common/scripts/streaming/server-stream';
this.mixin('api');
this.initializing = true;
this.connection = new Connection();
this.on('mount', () => {
this.$root.$data.os.api('meta').then(meta => {
this.update({
initializing: false,
meta
});
});
});
this.on('unmount', () => {
this.connection.close();
});
</script>
</mk-index>
<mk-cpu-usage>
<h2>CPU <b>{ percentage }%</b></h2>
<mk-line-chart ref="chart"/>
<style lang="stylus" scoped>
:scope
display block
</style>
<script lang="typescript">
this.connection = this.opts.connection;
this.on('mount', () => {
this.connection.on('stats', this.onStats);
});
this.on('unmount', () => {
this.connection.off('stats', this.onStats);
});
this.onStats = stats => {
this.$refs.chart.addData(1 - stats.cpu_usage);
const percentage = (stats.cpu_usage * 100).toFixed(0);
this.update({
percentage
});
};
</script>
</mk-cpu-usage>
<mk-mem-usage>
<h2>MEM <b>{ percentage }%</b></h2>
<mk-line-chart ref="chart"/>
<style lang="stylus" scoped>
:scope
display block
</style>
<script lang="typescript">
this.connection = this.opts.connection;
this.on('mount', () => {
this.connection.on('stats', this.onStats);
});
this.on('unmount', () => {
this.connection.off('stats', this.onStats);
});
this.onStats = stats => {
stats.mem.used = stats.mem.total - stats.mem.free;
this.$refs.chart.addData(1 - (stats.mem.used / stats.mem.total));
const percentage = (stats.mem.used / stats.mem.total * 100).toFixed(0);
this.update({
percentage
});
};
</script>
</mk-mem-usage>
<mk-line-chart>
<svg riot-viewBox="0 0 { viewBoxX } { viewBoxY }" preserveAspectRatio="none">
<defs>
<linearGradient id={ gradientId } x1="0" x2="0" y1="1" y2="0">
<stop offset="0%" stop-color="rgba(244, 59, 22, 0)"></stop>
<stop offset="100%" stop-color="#f43b16"></stop>
</linearGradient>
<mask id={ maskId } x="0" y="0" riot-width={ viewBoxX } riot-height={ viewBoxY }>
<polygon
riot-points={ polygonPoints }
fill="#fff"
fill-opacity="0.5"/>
</mask>
</defs>
<line x1="0" y1="0" riot-x2={ viewBoxX } y2="0" stroke="rgba(255, 255, 255, 0.1)" stroke-width="0.25" stroke-dasharray="1"/>
<line x1="0" y1="25%" riot-x2={ viewBoxX } y2="25%" stroke="rgba(255, 255, 255, 0.1)" stroke-width="0.25" stroke-dasharray="1"/>
<line x1="0" y1="50%" riot-x2={ viewBoxX } y2="50%" stroke="rgba(255, 255, 255, 0.1)" stroke-width="0.25" stroke-dasharray="1"/>
<line x1="0" y1="75%" riot-x2={ viewBoxX } y2="75%" stroke="rgba(255, 255, 255, 0.1)" stroke-width="0.25" stroke-dasharray="1"/>
<line x1="0" y1="100%" riot-x2={ viewBoxX } y2="100%" stroke="rgba(255, 255, 255, 0.1)" stroke-width="0.25" stroke-dasharray="1"/>
<rect
x="-1" y="-1"
riot-width={ viewBoxX + 2 } riot-height={ viewBoxY + 2 }
style="stroke: none; fill: url(#{ gradientId }); mask: url(#{ maskId })"/>
<polyline
riot-points={ polylinePoints }
fill="none"
stroke="#f43b16"
stroke-width="0.5"/>
</svg>
<style lang="stylus" scoped>
:scope
display block
padding 16px
border-radius 8px
background #1c2531
> svg
display block
padding 1px
width 100%
</style>
<script lang="typescript">
import uuid from 'uuid';
this.viewBoxX = 100;
this.viewBoxY = 30;
this.data = [];
this.gradientId = uuid();
this.maskId = uuid();
this.addData = data => {
this.data.push(data);
if (this.data.length > 100) this.data.shift();
const polylinePoints = this.data.map((d, i) => `${this.viewBoxX - ((this.data.length - 1) - i)},${d * this.viewBoxY}`).join(' ');
const polygonPoints = `${this.viewBoxX - (this.data.length - 1)},${ this.viewBoxY } ${ polylinePoints } ${ this.viewBoxX },${ this.viewBoxY }`;
this.update({
polylinePoints,
polygonPoints
});
};
</script>
</mk-line-chart>

View File

@ -1 +0,0 @@
require('./index.tag');

View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
y="0px" width="96px" height="96px" viewBox="0 0 96 96" enable-background="new 0 0 96 96" xml:space="preserve">
<polygon fill="#ea2412" points="0,45.255 45.254,0 84.854,0 0,84.854 "/>
</svg>

After

Width:  |  Height:  |  Size: 441 B

View File

@ -15,8 +15,8 @@ const interval = 1000;
export default function() {
const log = new Deque<any>();
ev.on('requestServerStatsLog', id => {
ev.emit('serverStatsLog:' + id, log.toArray());
ev.on('requestServerStatsLog', x => {
ev.emit('serverStatsLog:' + x.id, log.toArray().slice(0, x.length || 50));
});
async function tick() {
@ -36,8 +36,8 @@ export default function() {
process_uptime: process.uptime()
};
ev.emit('serverStats', stats);
log.push(stats);
if (log.length > 50) log.shift();
log.unshift(stats);
if (log.length > 200) log.pop();
}
tick();

View File

@ -49,6 +49,9 @@ export default function(html: string): string {
text += txt;
break;
}
// メンション以外
} else {
text += `[${txt}](${node.attrs.find((x: any) => x.name == 'href').value})`;
}
if (node.childNodes) {

View File

@ -5,6 +5,10 @@ import config from '../config';
import { INote } from '../models/note';
import { TextElement } from './parse';
function intersperse<T>(sep: T, xs: T[]): T[] {
return [].concat(...xs.map(x => [sep, x])).slice(1);
}
const handlers: { [key: string]: (window: any, token: any, mentionedRemoteUsers: INote['mentionedRemoteUsers']) => void } = {
bold({ document }, { bold }) {
const b = document.createElement('b');
@ -80,12 +84,9 @@ const handlers: { [key: string]: (window: any, token: any, mentionedRemoteUsers:
},
text({ document }, { content }) {
for (const text of content.split('\n')) {
const node = document.createTextNode(text);
document.body.appendChild(node);
const br = document.createElement('br');
document.body.appendChild(br);
const nodes = (content as string).split('\n').map(x => document.createTextNode(x));
for (const x of intersperse(document.createElement('br'), nodes)) {
document.body.appendChild(x);
}
},

View File

@ -52,6 +52,11 @@ export type IDriveFile = {
filename: string;
contentType: string;
metadata: IMetadata;
/**
* ファイルサイズ
*/
length: number;
};
export function validateFileName(name: string): boolean {

153
src/models/stats.ts Normal file
View File

@ -0,0 +1,153 @@
import * as mongo from 'mongodb';
import db from '../db/mongodb';
const Stats = db.get<IStats>('stats');
Stats.createIndex({ date: -1 }, { unique: true });
export default Stats;
export interface IStats {
_id: mongo.ObjectID;
date: Date;
/**
* ユーザーに関する統計
*/
users: {
local: {
/**
* この日時点での、ローカルのユーザーの総計
*/
total: number;
/**
* ローカルのユーザー数の前日比
*/
diff: number;
};
remote: {
/**
* この日時点での、リモートのユーザーの総計
*/
total: number;
/**
* リモートのユーザー数の前日比
*/
diff: number;
};
};
/**
* 投稿に関する統計
*/
notes: {
local: {
/**
* この日時点での、ローカルの投稿の総計
*/
total: number;
/**
* ローカルの投稿数の前日比
*/
diff: number;
diffs: {
/**
* ローカルの通常の投稿数の前日比
*/
normal: number;
/**
* ローカルのリプライの投稿数の前日比
*/
reply: number;
/**
* ローカルのRenoteの投稿数の前日比
*/
renote: number;
};
};
remote: {
/**
* この日時点での、リモートの投稿の総計
*/
total: number;
/**
* リモートの投稿数の前日比
*/
diff: number;
diffs: {
/**
* リモートの通常の投稿数の前日比
*/
normal: number;
/**
* リモートのリプライの投稿数の前日比
*/
reply: number;
/**
* リモートのRenoteの投稿数の前日比
*/
renote: number;
};
};
};
/**
* ドライブ(のファイル)に関する統計
*/
drive: {
local: {
/**
* この日時点での、ローカルのドライブファイル数の総計
*/
totalCount: number;
/**
* この日時点での、ローカルのドライブファイルサイズの総計
*/
totalSize: number;
/**
* ローカルのドライブファイル数の前日比
*/
diffCount: number;
/**
* ローカルのドライブファイルサイズの前日比
*/
diffSize: number;
};
remote: {
/**
* この日時点での、リモートのドライブファイル数の総計
*/
totalCount: number;
/**
* この日時点での、リモートのドライブファイルサイズの総計
*/
totalSize: number;
/**
* リモートのドライブファイル数の前日比
*/
diffCount: number;
/**
* リモートのドライブファイルサイズの前日比
*/
diffSize: number;
};
};
}

View File

@ -0,0 +1,101 @@
import Stats, { IStats } from '../../../../models/stats';
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
export const meta = {
requireCredential: true,
requireAdmin: true
};
export default (params: any) => new Promise(async (res, rej) => {
const now = new Date();
const y = now.getFullYear();
const m = now.getMonth();
const d = now.getDate();
const stats = await Stats.find({
date: {
$gt: new Date(y - 1, m, d)
}
}, {
sort: {
date: -1
},
fields: {
_id: 0
}
});
const chart: Array<Omit<IStats, '_id'>> = [];
for (let i = 364; i >= 0; i--) {
const day = new Date(y, m, d - i);
const stat = stats.find(s => s.date.getTime() == day.getTime());
if (stat) {
chart.unshift(stat);
} else { // 隙間埋め
const mostRecent = stats.find(s => s.date.getTime() < day.getTime());
if (mostRecent) {
chart.unshift(Object.assign({}, mostRecent, {
date: day
}));
} else {
chart.unshift({
date: day,
users: {
local: {
total: 0,
diff: 0
},
remote: {
total: 0,
diff: 0
}
},
notes: {
local: {
total: 0,
diff: 0,
diffs: {
normal: 0,
reply: 0,
renote: 0
}
},
remote: {
total: 0,
diff: 0,
diffs: {
normal: 0,
reply: 0,
renote: 0
}
}
},
drive: {
local: {
totalCount: 0,
totalSize: 0,
diffCount: 0,
diffSize: 0
},
remote: {
totalCount: 0,
totalSize: 0,
diffCount: 0,
diffSize: 0
}
}
});
}
}
}
chart.forEach(x => {
delete x.date;
});
res(chart);
});

View File

@ -0,0 +1,46 @@
import $ from 'cafy';
import ID from '../../../../misc/cafy-id';
import getParams from '../../get-params';
import User from '../../../../models/user';
export const meta = {
desc: {
ja: '指定したユーザーの公式アカウントを解除します。',
en: 'Mark a user as unverified.'
},
requireCredential: true,
requireAdmin: true,
params: {
userId: $.type(ID).note({
desc: {
ja: '対象のユーザーID',
en: 'The user ID which you want to unverify'
}
}),
}
};
export default (params: any) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) return rej(psErr);
const user = await User.findOne({
_id: ps.userId
});
if (user == null) {
return rej('user not found');
}
await User.findOneAndUpdate({
_id: user._id
}, {
$set: {
isVerified: false
}
});
res();
});

View File

@ -1,110 +0,0 @@
import $ from 'cafy';
import Note from '../../../../models/note';
/**
* Aggregate notes
*/
export default (params: any) => new Promise(async (res, rej) => {
// Get 'limit' parameter
const [limit = 365, limitErr] = $.num.optional.range(1, 365).get(params.limit);
if (limitErr) return rej('invalid limit param');
const query = [{
$project: {
renoteId: '$renoteId',
replyId: '$replyId',
user: '$_user',
createdAt: { $add: ['$createdAt', 9 * 60 * 60 * 1000] } // Convert into JST
}
}, {
$project: {
date: {
year: { $year: '$createdAt' },
month: { $month: '$createdAt' },
day: { $dayOfMonth: '$createdAt' }
},
type: {
$cond: {
if: { $ne: ['$renoteId', null] },
then: 'renote',
else: {
$cond: {
if: { $ne: ['$replyId', null] },
then: 'reply',
else: 'note'
}
}
}
},
origin: {
$cond: {
if: { $eq: ['$user.host', null] },
then: 'local',
else: 'remote'
}
}
}
}, {
$group: {
_id: {
date: '$date',
type: '$type',
origin: '$origin'
},
count: { $sum: 1 }
}
}, {
$group: {
_id: '$_id.date',
data: {
$addToSet: {
type: '$_id.type',
origin: '$_id.origin',
count: '$count'
}
}
}
}] as any;
const datas = await Note.aggregate(query);
datas.forEach((data: any) => {
data.date = data._id;
delete data._id;
data.localNotes = (data.data.filter((x: any) => x.type == 'note' && x.origin == 'local')[0] || { count: 0 }).count;
data.localRenotes = (data.data.filter((x: any) => x.type == 'renote' && x.origin == 'local')[0] || { count: 0 }).count;
data.localReplies = (data.data.filter((x: any) => x.type == 'reply' && x.origin == 'local')[0] || { count: 0 }).count;
data.remoteNotes = (data.data.filter((x: any) => x.type == 'note' && x.origin == 'remote')[0] || { count: 0 }).count;
data.remoteRenotes = (data.data.filter((x: any) => x.type == 'renote' && x.origin == 'remote')[0] || { count: 0 }).count;
data.remoteReplies = (data.data.filter((x: any) => x.type == 'reply' && x.origin == 'remote')[0] || { count: 0 }).count;
delete data.data;
});
const graph = [];
for (let i = 0; i < limit; i++) {
const day = new Date(new Date().setDate(new Date().getDate() - i));
const data = datas.filter((d: any) =>
d.date.year == day.getFullYear() && d.date.month == day.getMonth() + 1 && d.date.day == day.getDate()
)[0];
if (data) {
graph.push(data);
} else {
graph.push({
date: { year: day.getFullYear(), month: day.getMonth() + 1, day: day.getDate() },
localNotes: 0,
localRenotes: 0,
localReplies: 0,
remoteNotes: 0,
remoteRenotes: 0,
remoteReplies: 0
});
}
}
res(graph);
});

View File

@ -1,86 +0,0 @@
import $ from 'cafy';
import User from '../../../../models/user';
/**
* Aggregate users
*/
export default (params: any) => new Promise(async (res, rej) => {
// Get 'limit' parameter
const [limit = 365, limitErr] = $.num.optional.range(1, 365).get(params.limit);
if (limitErr) return rej('invalid limit param');
const query = [{
$project: {
host: '$host',
createdAt: { $add: ['$createdAt', 9 * 60 * 60 * 1000] } // Convert into JST
}
}, {
$project: {
date: {
year: { $year: '$createdAt' },
month: { $month: '$createdAt' },
day: { $dayOfMonth: '$createdAt' }
},
origin: {
$cond: {
if: { $eq: ['$host', null] },
then: 'local',
else: 'remote'
}
}
}
}, {
$group: {
_id: {
date: '$date',
origin: '$origin'
},
count: { $sum: 1 }
}
}, {
$group: {
_id: '$_id.date',
data: {
$addToSet: {
type: '$_id.type',
origin: '$_id.origin',
count: '$count'
}
}
}
}] as any;
const datas = await User.aggregate(query);
datas.forEach((data: any) => {
data.date = data._id;
delete data._id;
data.local = (data.data.filter((x: any) => x.origin == 'local')[0] || { count: 0 }).count;
data.remote = (data.data.filter((x: any) => x.origin == 'remote')[0] || { count: 0 }).count;
delete data.data;
});
const graph = [];
for (let i = 0; i < limit; i++) {
const day = new Date(new Date().setDate(new Date().getDate() - i));
const data = datas.filter((d: any) =>
d.date.year == day.getFullYear() && d.date.month == day.getMonth() + 1 && d.date.day == day.getDate()
)[0];
if (data) {
graph.push(data);
} else {
graph.push({
date: { year: day.getFullYear(), month: day.getMonth() + 1, day: day.getDate() },
local: 0,
remote: 0
});
}
}
res(graph);
});

View File

@ -64,7 +64,7 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) =
if (ps.name) file.filename = ps.name;
if (ps.isSensitive) file.metadata.isSensitive = ps.isSensitive;
if (ps.isSensitive !== undefined) file.metadata.isSensitive = ps.isSensitive;
if (ps.folderId !== undefined) {
if (ps.folderId === null) {

View File

@ -105,17 +105,22 @@ export default async (params: any, user: ILocalUser, app: IApp) => new Promise(a
}
}
if (wallpaperId) {
const wallpaper = await DriveFile.findOne({
_id: wallpaperId
});
if (wallpaperId !== undefined) {
if (wallpaperId === null) {
updates.wallpaperUrl = null;
updates.wallpaperColor = null;
} else {
const wallpaper = await DriveFile.findOne({
_id: wallpaperId
});
if (wallpaper == null) return rej('wallpaper not found');
if (wallpaper == null) return rej('wallpaper not found');
updates.wallpaperUrl = wallpaper.metadata.url || `${config.drive_url}/${wallpaper._id}`;
updates.wallpaperUrl = wallpaper.metadata.url || `${config.drive_url}/${wallpaper._id}`;
if (wallpaper.metadata.properties.avgColor) {
updates.wallpaperColor = wallpaper.metadata.properties.avgColor;
if (wallpaper.metadata.properties.avgColor) {
updates.wallpaperColor = wallpaper.metadata.properties.avgColor;
}
}
}

View File

@ -22,7 +22,10 @@ export default function(request: websocket.request, connection: websocket.connec
body: statsLog
}));
});
ev.emit('requestServerStatsLog', msg.id);
ev.emit('requestServerStatsLog', {
id: msg.id,
length: msg.length
});
break;
}
});

View File

@ -17,6 +17,7 @@ import { isLocalUser, IUser, IRemoteUser } from '../../models/user';
import delFile from './delete-file';
import config from '../../config';
import { getDriveFileThumbnailBucket } from '../../models/drive-file-thumbnail';
import { updateDriveStats } from '../update-chart';
const log = debug('misskey:drive:add-file');
@ -377,7 +378,8 @@ export default async function(
publishDriveStream(user._id, 'file_created', packedFile);
});
// TODO: サムネイル生成
// 統計を更新
updateDriveStats(driveFile, true);
return driveFile;
}

View File

@ -2,6 +2,7 @@ import * as Minio from 'minio';
import DriveFile, { DriveFileChunk, IDriveFile } from '../../models/drive-file';
import DriveFileThumbnail, { DriveFileThumbnailChunk } from '../../models/drive-file-thumbnail';
import config from '../../config';
import { updateDriveStats } from '../update-chart';
export default async function(file: IDriveFile, isExpired = false) {
if (file.metadata.storage == 'minio') {
@ -45,4 +46,7 @@ export default async function(file: IDriveFile, isExpired = false) {
await DriveFileThumbnail.remove({ _id: thumbnail._id });
}
//#endregion
// 統計を更新
updateDriveStats(file, false);
}

View File

@ -23,6 +23,7 @@ import registerHashtag from '../register-hashtag';
import isQuote from '../../misc/is-quote';
import { TextElementMention } from '../../mfm/parse/elements/mention';
import { TextElementHashtag } from '../../mfm/parse/elements/hashtag';
import { updateNoteStats } from '../update-chart';
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
@ -142,6 +143,9 @@ export default async (user: IUser, data: Option, silent = false) => new Promise<
return;
}
// 統計を更新
updateNoteStats(note, true);
// ハッシュタグ登録
tags.map(tag => registerHashtag(user, tag));

View File

@ -6,6 +6,7 @@ import pack from '../../remote/activitypub/renderer';
import { deliver } from '../../queue';
import Following from '../../models/following';
import renderNote from '../../remote/activitypub/renderer/note';
import { updateNoteStats } from '../update-chart';
/**
* 投稿を削除します。
@ -43,4 +44,7 @@ export default async function(user: IUser, note: INote) {
});
}
//#endregion
// 統計を更新
updateNoteStats(note, false);
}

View File

@ -0,0 +1,199 @@
import { INote } from '../models/note';
import Stats, { IStats } from '../models/stats';
import { isLocalUser, IUser } from '../models/user';
import { IDriveFile } from '../models/drive-file';
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
async function getTodayStats(): Promise<IStats> {
const now = new Date();
const y = now.getFullYear();
const m = now.getMonth();
const d = now.getDate();
const today = new Date(y, m, d);
// 今日の統計
const todayStats = await Stats.findOne({
date: today
});
// 日付が変わってから、初めてのチャート更新なら
if (todayStats == null) {
// 最も最近の統計を持ってくる
// * 昨日何もチャートを更新するような出来事がなかった場合は、
// 統計がそもそも作られずドキュメントが存在しないということがあり得るため、
// 「昨日の」と決め打ちせずに「もっとも最近の」とします
const mostRecentStats = await Stats.findOne({}, {
sort: {
date: -1
}
});
// 統計が存在しなかったら
// * Misskeyインスタンスを建てて初めてのチャート更新時など
if (mostRecentStats == null) {
// 空の統計を作成
const data: Omit<IStats, '_id'> = {
date: today,
users: {
local: {
total: 0,
diff: 0
},
remote: {
total: 0,
diff: 0
}
},
notes: {
local: {
total: 0,
diff: 0,
diffs: {
normal: 0,
reply: 0,
renote: 0
}
},
remote: {
total: 0,
diff: 0,
diffs: {
normal: 0,
reply: 0,
renote: 0
}
}
},
drive: {
local: {
totalCount: 0,
totalSize: 0,
diffCount: 0,
diffSize: 0
},
remote: {
totalCount: 0,
totalSize: 0,
diffCount: 0,
diffSize: 0
}
}
};
const stats = await Stats.insert(data);
return stats;
} else {
// 今日の統計を初期挿入
const data: Omit<IStats, '_id'> = {
date: today,
users: {
local: {
total: mostRecentStats.users.local.total,
diff: 0
},
remote: {
total: mostRecentStats.users.remote.total,
diff: 0
}
},
notes: {
local: {
total: mostRecentStats.notes.local.total,
diff: 0,
diffs: {
normal: 0,
reply: 0,
renote: 0
}
},
remote: {
total: mostRecentStats.notes.remote.total,
diff: 0,
diffs: {
normal: 0,
reply: 0,
renote: 0
}
}
},
drive: {
local: {
totalCount: mostRecentStats.drive.local.totalCount,
totalSize: mostRecentStats.drive.local.totalSize,
diffCount: 0,
diffSize: 0
},
remote: {
totalCount: mostRecentStats.drive.remote.totalCount,
totalSize: mostRecentStats.drive.remote.totalSize,
diffCount: 0,
diffSize: 0
}
}
};
const stats = await Stats.insert(data);
return stats;
}
} else {
return todayStats;
}
}
async function update(inc: any) {
const stats = await getTodayStats();
await Stats.findOneAndUpdate({
_id: stats._id
}, {
$inc: inc
});
}
export async function updateUserStats(user: IUser, isAdditional: boolean) {
const amount = isAdditional ? 1 : -1;
const origin = isLocalUser(user) ? 'local' : 'remote';
const inc = {} as any;
inc[`users.${origin}.total`] = amount;
inc[`users.${origin}.diff`] = amount;
await update(inc);
}
export async function updateNoteStats(note: INote, isAdditional: boolean) {
const amount = isAdditional ? 1 : -1;
const origin = isLocalUser(note._user) ? 'local' : 'remote';
const inc = {} as any;
inc[`notes.${origin}.total`] = amount;
inc[`notes.${origin}.diff`] = amount;
if (note.replyId != null) {
inc[`notes.${origin}.diffs.reply`] = amount;
} else if (note.renoteId != null) {
inc[`notes.${origin}.diffs.renote`] = amount;
} else {
inc[`notes.${origin}.diffs.normal`] = amount;
}
await update(inc);
}
export async function updateDriveStats(file: IDriveFile, isAdditional: boolean) {
const amount = isAdditional ? 1 : -1;
const size = isAdditional ? file.length : -file.length;
const origin = isLocalUser(file.metadata._user) ? 'local' : 'remote';
const inc = {} as any;
inc[`drive.${origin}.totalCount`] = amount;
inc[`drive.${origin}.diffCount`] = amount;
inc[`drive.${origin}.totalSize`] = size;
inc[`drive.${origin}.diffSize`] = size;
await update(inc);
}