Compare commits

...

54 Commits

Author SHA1 Message Date
ea60565b0d Update package.json 2019-06-10 21:09:06 +09:00
a26585dcc7 Add @rinsuki into collaborators (#4762) 2019-06-10 14:23:39 +09:00
008e5bd24c Merge branch 'riamu' into v10 2019-06-10 14:01:51 +09:00
ebc6564fbb Update packages 2019-06-10 14:01:36 +09:00
06b5078245 Add resolitions 2019-06-10 13:19:04 +09:00
68337f95ff Update README.md [AUTOGEN] (#5026) 2019-06-04 18:19:19 +09:00
72e148002a Update README.md [AUTOGEN] (#5018) 2019-06-02 10:13:07 +09:00
f0a03f71eb Update README.md [AUTOGEN] (#5012) 2019-06-01 20:38:43 +09:00
6049473e46 Update README.md [AUTOGEN] (#5004) 2019-05-27 23:44:49 +09:00
f9f2787dfc Update README.md [AUTOGEN] (#5002) 2019-05-27 23:15:51 +09:00
b0a068e269 Fix: Firefoxでトークの下が隠れてしまう (#4973) 2019-05-25 09:11:09 +09:00
dd5546690a Update README.md [AUTOGEN] (#4939) 2019-05-19 15:08:06 +09:00
d87b6d38ab Update ObjectStorage example (#4919) 2019-05-14 02:53:27 +09:00
4069bb170a Fix meta tags (#4917) 2019-05-14 02:50:53 +09:00
600c009549 Update README.md [AUTOGEN] (#4915) 2019-05-13 18:03:24 +09:00
c953a28201 10.102.3 2019-05-12 11:40:51 +09:00
f6541df42a Update README.md [AUTOGEN] (#4887) 2019-05-09 22:04:15 +09:00
a57e9460c8 Fix: IPv4 onlyホストからDualstackホストにAP deliverできない (#4879) 2019-05-09 15:43:49 +09:00
c7bcf31105 Supports Node.js v12 for Misskey v10 (#4851)
* Update dependencies

* Update diskusage to 1.1.1

* Fix: Node v12 で TS error がでる
2019-05-04 13:53:16 +09:00
a397c040fe エクスポートファイルでは同一ハッシュチェックをしないように (#4841) 2019-05-03 18:34:45 +09:00
e311d73ffc Fix: user menu (#4845)
* Fix: Firefoxで自分のメニューが開けないなど

* 自分のユーザーメニューにはミュートなどを表示しないようになど
2019-05-03 18:34:22 +09:00
2a3599a14d Fix: mention (あなた宛て) streaming にミュートが効かない (#4822) 2019-04-30 15:37:10 +09:00
b85dc8a658 Fix: エクスポートリクエストに失敗してもエラーが出ない (#4821) 2019-04-30 15:01:29 +09:00
7513123052 Update package.json 2019-04-30 07:13:37 +09:00
2526b86ec5 Fix: Mastodon v2.8.0 のフォローリストがインポートできない (#4817) 2019-04-30 05:54:36 +09:00
e45aa0532c Noteが間接参照されたときはstreaming等にpublishしない (#4796) 2019-04-25 04:08:03 +09:00
71fc84e224 Use meid7 for Note 2019-04-24 02:52:48 +09:00
6b726eea39 リファクタリング syuilo#4587 (#4766) 2019-04-22 03:22:19 +09:00
acd4b101e1 10.102.1 2019-04-22 00:51:57 +09:00
98b8a94f2b Fix: リモートユーザーの修復処理が自動的に実行されない for v10 (#4763)
* Fix: ユーザーresyncが自動実行されない

* ここにあっても意味がない

* lastFetchedAt事前更新はWebFingerする前のみ

* lint fix
2019-04-21 23:56:56 +09:00
7d31bd97ff Validate Note on createNote for v10 (#4757)
* Validate Note on createNote

* Add extractApHost
2019-04-21 02:41:12 +09:00
828a2a73c9 Fix 投稿増殖 for v10 (#4758)
* Fix #4632

* remove unused import
2019-04-21 02:38:02 +09:00
bdc7167cf4 10.102.0 2019-04-19 01:39:39 +09:00
45b94086ed サイレンス, ブロック, ミュートの確認表示 for v10 (#4744)
* confirm on user menu

* confirm silence

* Resolve #4554
2019-04-18 04:34:47 +09:00
e62bb7cdaf Update dependencies (#4739) 2019-04-18 03:30:01 +09:00
c5f65d9eeb アンケートウィジットでもMFMを使用するように v10 (#4740)
* MFM in poll

* use mfm
2019-04-18 03:15:00 +09:00
4557856104 Doc: Update setup documents for 10.x (#4676)
* Doc: Update setup documents

Use GitHub api to checkout latest release instead of "git tag" command
which cannot accurately determine prerelease tag.
Also, Changed numbered list format because
the shell command is too long to fit on one line.

 Conflicts:
	docs/docker.en.md
	docs/docker.fr.md
	docs/docker.ja.md
	docs/setup.en.md
	docs/setup.fr.md
	docs/setup.ja.md

* Doc: Checkout 10.x tag only

Update "checkout latest release" command.
Checkout latest tag but 10.x tag only.

* Doc: Restore MongoDB

* Use tab instead of spaces
2019-04-16 13:09:31 +09:00
4a23c36740 Backport #4674 (#4708) 2019-04-16 02:05:16 +09:00
2553b20130 10.101.0 2019-04-15 12:44:11 +09:00
6982faf668 Fix: ObjectStrage利用時にドライブファイルアイコンが表示されない (#4677) 2019-04-14 17:19:25 +09:00
4db972318f インスタンスブロックチェック時のhostのnormalizeを統一 (#4669) 2019-04-12 20:54:50 +09:00
81006566a5 Fix: Punycodeなインスタンスが重複登録される (#4667) 2019-04-12 20:03:00 +09:00
6abc053a48 Fix: AP actor Service のサポートが不完全 (#4661) 2019-04-11 02:46:29 +09:00
03a3c56a54 WebFingerリクエストで Proxy, Keep-Alive などをサポート (#4658)
* no webfinger.js

* Fix: エラーメッセージがくどい
2019-04-10 12:28:59 +09:00
83b7010d6a 10.100.0 2019-04-09 21:13:52 +09:00
71654cbe47 Fix #4636 2019-04-09 21:11:05 +09:00
e8f96e848a Merge branch 'v10' of https://github.com/syuilo/misskey into v10 2019-04-09 21:10:36 +09:00
251abf21d4 Update .gitignore 2019-04-09 21:10:18 +09:00
d103427932 Fix non media thumbnails (#4380) 2019-04-09 21:07:46 +09:00
592cdfa910 ユーザーリストでフォローボタンを表示するように (#4654) 2019-04-08 20:18:42 +09:00
f2ad1a0406 Fix: 投稿ウィジットでローカルのみの公開範囲で投稿できない (#4653) 2019-04-08 20:16:00 +09:00
82af9320c0 Fix: TLを遡った時に抜けがある時がある (v10) (#4629)
* Update the cursor when the timeline is updated

* fix releaseQueue
2019-04-08 15:18:44 +09:00
fceebf7388 Fix #4562 (#4563) 2019-04-08 15:17:39 +09:00
13caf37991 Update README.md [AUTOGEN] (#4639) 2019-04-06 12:47:02 +09:00
88 changed files with 992 additions and 571 deletions

View File

@ -53,40 +53,54 @@ mongodb:
drive: drive:
storage: 'db' storage: 'db'
# OR # OR
# storage: 'minio' #drive:
# bucket: # storage: 'minio'
# prefix: # bucket:
# config: # prefix:
# endPoint: # config:
# port: # endPoint:
# useSSL: # port:
# accessKey: # useSSL:
# secretKey: # accessKey:
# secretKey:
# S3 example # S3/GCS example
# storage: 'minio' #
# bucket: bucket-name # * Replace <endpoint> to
# prefix: files # S3: see https://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region
# config: # GCS: use 'storage.googleapis.com'
# endPoint: s3-us-west-2.amazonaws.com #
# region: us-west-2 # * Replace <region> to
# useSSL: true # S3: see https://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region
# accessKey: XXX # GCS: not needed (just delete the region line)
# secretKey: YYY #
#drive:
# storage: 'minio'
# bucket: bucket-name
# prefix: files
# baseUrl: https://bucket-name.<endpoint>
# config:
# endPoint: <endpoint>
# region: <region>
# useSSL: true
# accessKey: XXX
# secretKey: YYY
# S3 example (with CDN, custom domain) # S3/GCS example (with CDN, custom domain)
# storage: 'minio' #
# bucket: drive.example.com #drive:
# prefix: files # storage: 'minio'
# baseUrl: https://drive.example.com # bucket: drive.example.com
# config: # prefix: files
# endPoint: s3-us-west-2.amazonaws.com # baseUrl: https://drive.example.com
# region: us-west-2 # config:
# useSSL: true # endPoint: <endpoint>
# accessKey: XXX # region: <region>
# secretKey: YYY # useSSL: true
# accessKey: XXX
# secretKey: YYY
# If enabled: # If enabled:
# The first account created is automatically marked as Admin. # The first account created is automatically marked as Admin.
@ -113,3 +127,6 @@ autoAdmin: true
# Clustering # Clustering
#clusterLimit: 1 #clusterLimit: 1
# IP address family used for outgoing request (ipv4, ipv6 or dual)
#outgoingAddressFamily: ipv4

4
.github/CODEOWNERS vendored
View File

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

1
.gitignore vendored
View File

@ -19,3 +19,4 @@ api-docs.json
*.code-workspace *.code-workspace
yarn.lock yarn.lock
.DS_Store .DS_Store
/files

View File

@ -5,6 +5,27 @@ If you encounter any problems with updating, please try the following:
1. `npm run clean` or `npm run cleanall` 1. `npm run clean` or `npm run cleanall`
2. Retry update (Don't forget `npm i`) 2. Retry update (Don't forget `npm i`)
10.102.1
----------
* 投稿が増殖する問題を修正
* リモートユーザーの修復処理が自動的に実行されない問題を修正
10.101.0
----------
* WebFingerリクエストで Proxy, Keep-Alive などをサポート
* AP actor Service のサポートが不完全な問題を修正
* Punycodeなインスタンスが重複登録される問題を修正
* ObjectStrage利用時にドライブファイルアイコンが表示されない問題を修正
10.100.0
----------
* ユーザーリストでフォローボタンを表示するように
* ドライブのファイルのサムネイルを修正
* 投稿ウィジットでローカルのみの公開範囲で投稿できない問題を修正
* TLを遡った時に抜けがある時がある問題を修正
* ユーザータイムラインが投稿日時順ではなくなっているのを修正
* 10.99.0 でチャートのレンダリングがおかしい問題を修正
10.99.0 10.99.0
---------- ----------
* manifest.json にインスタンス名を反映させるように * manifest.json にインスタンス名を反映させるように

View File

@ -88,12 +88,14 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><img src="https://avatars0.githubusercontent.com/u/10798641?s=460&v=4" alt="AyaMorisawa" width="100"></td> <td><img src="https://avatars0.githubusercontent.com/u/10798641?s=460&v=4" alt="AyaMorisawa" width="100"></td>
<td><img src="https://avatars1.githubusercontent.com/u/30769358?s=460&v=4" alt="mei23" width="100"></td> <td><img src="https://avatars1.githubusercontent.com/u/30769358?s=460&v=4" alt="mei23" width="100"></td>
<td><img src="https://avatars2.githubusercontent.com/u/20679825?s=460&v=4" alt="acid-chicken" width="100"></td> <td><img src="https://avatars2.githubusercontent.com/u/20679825?s=460&v=4" alt="acid-chicken" width="100"></td>
<td><img src="https://avatars2.githubusercontent.com/u/6533808?s=460&v=4" alt="rinsuki" width="100"></td>
</tr> </tr>
<tr> <tr>
<td align="center"><a href="https://github.com/syuilo">@syuilo</a></td> <td align="center"><a href="https://github.com/syuilo">@syuilo</a></td>
<td align="center"><a href="https://github.com/AyaMorisawa">@AyaMorisawa</a></td> <td align="center"><a href="https://github.com/AyaMorisawa">@AyaMorisawa</a></td>
<td align="center"><a href="https://github.com/mei23">@mei23</a></td> <td align="center"><a href="https://github.com/mei23">@mei23</a></td>
<td align="center"><a href="https://github.com/acid-chicken">@acid-chicken</a></td> <td align="center"><a href="https://github.com/acid-chicken">@acid-chicken</a></td>
<td align="center"><a href="https://github.com/rinsuki">@rinsuki</a></td>
</tr> </tr>
</table> </table>
@ -101,62 +103,83 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
---------------------------------------------------------------- ----------------------------------------------------------------
<!-- PATREON_START --> <!-- PATREON_START -->
<table><tr> <table><tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5888816/36da0f7c15954df0ab13f9abdf227f66/1?token-time=2145916800&token-hash=HGkZJ7s4bSaQVoOJ5q30mTWHTxDLiw1LuyaogKPLy24%3D" alt="Hiroshi Seki" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5888816/36da0f7c15954df0ab13f9abdf227f66/1.jpeg?token-time=2145916800&token-hash=at8QpJXJ8C0zINY_NmoMKv-MhXVoUK-YzTgaJPJzJYU%3D" alt="Hiroshi Seki" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12190916/fb7fa7983c14425f890369535b1506a4/1?token-time=2145916800&token-hash=WeuDzzz24cRXJogyIkU-mxARqkdyms-rcZKbO-GpGjw%3D" alt="weep" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12190916/fb7fa7983c14425f890369535b1506a4/3.png?token-time=2145916800&token-hash=oH_i7gJjNT7Ot6j9JiVwy7ZJIBqACVnzLqlz4YrDAZA%3D" alt="weepjp" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/12059069" alt="naga_rus" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19045173/cb91c0f345c24d4ebfd05f19906d5e26/1.png?token-time=2145916800&token-hash=o_zKBytJs_AxHwSYw_5R8eD0eSJe3RoTR3kR3Q0syN0%3D" alt="kiritan" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/4?token-time=2145916800&token-hash=vZdDTTF-ahiKBjjgppS2ev4rkD8H7TTKkXXoxsucs6Y%3D" alt="Melilot" width="100"></td> <td><img src="https://c8.patreon.com/2/200/776209" alt="Denshi" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/16869916" alt="見当かなみ" width="100"></td> <td><img src="https://c8.patreon.com/2/200/557245" alt="mkatze" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13099460/43cecdbaa63a40d79bf50a96b9910b9d/1.jpe?token-time=2145916800&token-hash=bqwLTk0Wo0hUJJ8J5y7ii05bLzz-_CDA7Bo0Mp4RFU0%3D" alt="ne_moni" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12913507/f7181eacafe8469a93033d85f5969c29/4.jpe?token-time=2145916800&token-hash=zEyJqVM7u9d8Ri-65fJYSJcWF1jBH1nJ5a3taRzrTmw%3D" alt="Melilot" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5670915/ee175f0bfb6347ffa4ea101a8c097bff/1.jpg?token-time=2145916800&token-hash=mPLM9CA-riFHx-myr3bLZJuH2xBRHA9se5VbHhLIOuA%3D" alt="osapon" width="100"></td>
</tr><tr> </tr><tr>
<td><a href="https://www.patreon.com/rane_hs">Hiroshi Seki</a></td> <td><a href="https://www.patreon.com/rane_hs">Hiroshi Seki</a></td>
<td><a href="https://www.patreon.com/weepjp">weep</a></td> <td><a href="https://www.patreon.com/weepjp">weepjp</a></td>
<td><a href="https://www.patreon.com/user?u=12059069">naga_rus</a></td> <td><a href="https://www.patreon.com/user?u=19045173">kiritan</a></td>
<td><a href="https://www.patreon.com/user?u=776209">Denshi</a></td>
<td><a href="https://www.patreon.com/user?u=557245">mkatze</a></td>
<td><a href="https://www.patreon.com/user?u=13099460">ne_moni</a></td>
<td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td> <td><a href="https://www.patreon.com/user?u=12913507">Melilot</a></td>
<td><a href="https://www.patreon.com/user?u=16869916">見当かなみ</a></td> <td><a href="https://www.patreon.com/osapon">osapon</a></td>
</tr></table> </tr></table>
<table><tr> <table><tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12021162/963128bb8d14476dbd8407943db8f31a/1?token-time=2145916800&token-hash=1FlxS9MEgmNGH_RHUVHbO5hIXB5I1z0lvA33CTvYvjA%3D" alt="gutfuckllc" width="100"></td> <td><img src="https://c8.patreon.com/2/200/16869916" alt="見当かなみ" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/11357794/923ce94cd8c44ba788ee931907881839/1?token-time=2145916800&token-hash=0xgcpqvFDqRcV_YIEhcPNVH7gs9sLg_BBnTJXCkN4ao%3D" alt="mydarkstar" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18899730/6a22797f68254034a854d69ea2445fc8/1.png?token-time=2145916800&token-hash=b_uj57yxo5VzkSOUS7oXE_762dyOTB_oxzbO6lFNG3k%3D" alt="YuzuRyo61" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12021162/963128bb8d14476dbd8407943db8f31a/1.png?token-time=2145916800&token-hash=FMV7cPKBD1TU2WTbl1jg6AcdKSvTb2BSFcDhgc-EO8w%3D" alt="gutfuckllc" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/11357794/923ce94cd8c44ba788ee931907881839/1.png?token-time=2145916800&token-hash=9nEQje_eMvUjq9a7L3uBqW-MQbS-rRMaMgd7UYVoFNM%3D" alt="mydarkstar" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/12718187" alt="Peter G." width="100"></td> <td><img src="https://c8.patreon.com/2/200/12718187" alt="Peter G." width="100"></td>
<td><img src="https://c8.patreon.com/2/200/18833336" alt="itiradi" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13039004/509d0c412eb14ae08d6a812a3054f7d6/1.jpe?token-time=2145916800&token-hash=UQRWf01TwHDV4Cls1K0YAOAjM29ssif7hLVq0ESQ0hs%3D" alt="nemu" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13039004/509d0c412eb14ae08d6a812a3054f7d6/1?token-time=2145916800&token-hash=2PsbFNw0tnubZzgSXD01R6hIgncfiElG7H7HX2Y3dyo%3D" alt="nemu" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/17866454" alt="sikyosyounin" width="100"></td> <td><img src="https://c8.patreon.com/2/200/17866454" alt="sikyosyounin" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/3?token-time=2145916800&token-hash=9JtETp0X8gI280Ne1E8bxn6j4Lw5o2k4mJkICx97V_k%3D" alt="YUKIMOCHI" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/3.png?token-time=2145916800&token-hash=KjfQL8nf3AIf6WqzLshBYAyX44piAqOAZiYXgZS_H6A%3D" alt="YUKIMOCHI" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/17463605" alt="Sampot" width="100"></td> <td><img src="https://c8.patreon.com/2/200/17463605" alt="Sampot" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17880724/311738c8a48f4a6b9443c2445a75adde/1?token-time=2145916800&token-hash=95p8VdGX45E8BitZR_eOcDlqCjumjzNLBPQJrJdeCpI%3D" alt="takimura" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19356899/496b4681d33b4520bd7688e0fd19c04d/2.jpeg?token-time=2145916800&token-hash=_sTj3dUBOhn9qwiJ7F19Qd-yWWfUqJC_0jG1h0agEqQ%3D" alt="sheeta.s" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17195955/be45e5e14c3e48b2bee0456c84e19df4/4?token-time=2145916800&token-hash=SbdZeN5SmsuT9stD6v0jN1z0hftg0FmRiCTxysU0Ihw%3D" alt="Damillora" width="100"></td>
</tr><tr> </tr><tr>
<td><a href="https://www.patreon.com/user?u=16869916">見当かなみ</a></td>
<td><a href="https://www.patreon.com/Yuzulia">YuzuRyo61</a></td>
<td><a href="https://www.patreon.com/gutfuckllc">gutfuckllc</a></td> <td><a href="https://www.patreon.com/gutfuckllc">gutfuckllc</a></td>
<td><a href="https://www.patreon.com/mydarkstar">mydarkstar</a></td> <td><a href="https://www.patreon.com/mydarkstar">mydarkstar</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=12718187">Peter G.</a></td>
<td><a href="https://www.patreon.com/user?u=18833336">itiradi</a></td>
<td><a href="https://www.patreon.com/user?u=13039004">nemu</a></td> <td><a href="https://www.patreon.com/user?u=13039004">nemu</a></td>
<td><a href="https://www.patreon.com/user?u=17866454">sikyosyounin</a></td> <td><a href="https://www.patreon.com/user?u=17866454">sikyosyounin</a></td>
<td><a href="https://www.patreon.com/yukimochi">YUKIMOCHI</a></td> <td><a href="https://www.patreon.com/yukimochi">YUKIMOCHI</a></td>
<td><a href="https://www.patreon.com/user?u=17463605">Sampot</a></td> <td><a href="https://www.patreon.com/user?u=17463605">Sampot</a></td>
<td><a href="https://www.patreon.com/takimura">takimura</a></td> <td><a href="https://www.patreon.com/user?u=19356899">sheeta.s</a></td>
<td><a href="https://www.patreon.com/damillora">Damillora</a></td>
</tr></table> </tr></table>
<table><tr> <table><tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/16900731/935a10339daa4ede8e555903a0707060/1?token-time=2145916800&token-hash=3CrpqH-XtKs_NoIlSsTyVs8wCzP1WFCsG2xwps1IJq0%3D" alt="Atsuko Tominaga" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13737140/1adf7835017d479280d90fe8d30aade2/1.png?token-time=2145916800&token-hash=0pdle8h5pDZrww0BDOjdz6zO-HudeGTh36a3qi1biVU%3D" alt="Satsuki Yanagi" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/3?token-time=2145916800&token-hash=-iJszBqgYBhsM5qMdA1knf9wvprhEfESzKfR2oh7mIA%3D" alt="natalie" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17880724/311738c8a48f4a6b9443c2445a75adde/1.jpe?token-time=2145916800&token-hash=CPxGQhKIlEaa6WUcgbyHixyKEhakiw9RFdOhsIJBQ_o%3D" alt="takimura" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13034746/c711c7f58e204ecfbc2fd646bc8a4eee/1?token-time=2145916800&token-hash=5T8XcaAf9Zyzfg3QubR06s_kJZkArVEM2dwObrBVAU4%3D" alt="Hiratake" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17195955/be45e5e14c3e48b2bee0456c84e19df4/4.jpe?token-time=2145916800&token-hash=UslrPVM-8TXOe8AapuNiaFYjcIJgPNcU-fKpGbfGJNI%3D" alt="Damillora" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18072312/98e894d960314fa7bc236a72a39488fe/1?token-time=2145916800&token-hash=D6QK3fPyqiYKJfOzc-QqaSSairUrWdjld-ewp2waj6s%3D" alt="Hekovic" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/16900731/83884b38afc74d4cbe83c30a13b10edd/1.png?token-time=2145916800&token-hash=R5Tog8RWg0rguRoCIoir3lThokrdPvs8Utfikhc0nhY%3D" alt="Atsuko Tominaga" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1?token-time=2145916800&token-hash=Ksk_2l3gjPDbnzMUOCSW1E-hdPJsNs2tSR4_RAakRK8%3D" alt="dansup" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/3.png?token-time=2145916800&token-hash=FTm3WVom4dJ9NwWMU4OpCL_8Yc13WiwEbKrDPyTZTPs%3D" alt="natalie" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/619786/32cf01444db24e578cd1982c197f6fc6/1?token-time=2145916800&token-hash=CXe9AqlZy9AsYfiWd3OBYVOzvODoN47Litz0Tu4BFpU%3D" alt="Gargron" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13034746/c711c7f58e204ecfbc2fd646bc8a4eee/1.jpe?token-time=2145916800&token-hash=EWxXhVbZYH7KB4IDT3joc8TbIg8zPO40x1r5IDn3R7c%3D" alt="Hiratake" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5731881/4b6038e6cda34c04b83a5fcce3806a93/1?token-time=2145916800&token-hash=xhR1n6NAAyEb-IUXLD6_dshkFa3mefU5ZZuk1L8qKTs%3D" alt="Nokotaro Takeda" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5923936/2a743cbfbff946c2af3f09026047c0da/2.png?token-time=2145916800&token-hash=h6yphW1qnM0n_NOWaf8qtszMRLXEwIxfk5beu4RxdT0%3D" alt="noellabo" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1?token-time=2145916800&token-hash=uR-48MQ0A4j0irQSrCAQZJ-sJUSs_Fkihlg3-l59b7c%3D" alt="Takashi Shibuya" width="100"></td> <td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/2384390/5681180e1efb46a8b28e0e8d4c8b9037/1.jpg?token-time=2145916800&token-hash=SJcMy-Q1BcS940-LFUVOMfR7-5SgrzsEQGhYb3yowFk%3D" alt="CG" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18072312/98e894d960314fa7bc236a72a39488fe/1.jpe?token-time=2145916800&token-hash=qA8j97lIZNc-74AuZ0p4F3ms6sKPeKjtNt2vEuwpsyo%3D" alt="Hekovic" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/10789744/97175095d8f04c0f86225ff47cb98d40/1.jpeg?token-time=2145916800&token-hash=l4AoMR7Nj7K4yAHrkrk2hAoggPkbSPm12m1nmbe9Pb8%3D" alt="Naoki Hirayama" width="100"></td>
</tr><tr> </tr><tr>
<td><a href="https://www.patreon.com/user?u=13737140">Satsuki Yanagi</a></td>
<td><a href="https://www.patreon.com/takimura">takimura</a></td>
<td><a href="https://www.patreon.com/damillora">Damillora</a></td>
<td><a href="https://www.patreon.com/user?u=16900731">Atsuko Tominaga</a></td> <td><a href="https://www.patreon.com/user?u=16900731">Atsuko Tominaga</a></td>
<td><a href="https://www.patreon.com/user?u=4389829">natalie</a></td> <td><a href="https://www.patreon.com/user?u=4389829">natalie</a></td>
<td><a href="https://www.patreon.com/hiratake">Hiratake</a></td> <td><a href="https://www.patreon.com/hiratake">Hiratake</a></td>
<td><a href="https://www.patreon.com/noellabo">noellabo</a></td>
<td><a href="https://www.patreon.com/Corset">CG</a></td>
<td><a href="https://www.patreon.com/hekovic">Hekovic</a></td> <td><a href="https://www.patreon.com/hekovic">Hekovic</a></td>
<td><a href="https://www.patreon.com/spinlock">Naoki Hirayama</a></td>
</tr></table>
<table><tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1.jpeg?token-time=2145916800&token-hash=L55UhJ0rcuNAH3w_ryeeGN4hC6taoOixyAhraEi0bzw%3D" alt="dansup" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/619786/32cf01444db24e578cd1982c197f6fc6/1.jpeg?token-time=2145916800&token-hash=d8jBQLMOHD87KtXs5C9fk1o58DMF73pQ-dYH3uZJPBE%3D" alt="Gargron" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5731881/4b6038e6cda34c04b83a5fcce3806a93/1.png?token-time=2145916800&token-hash=hBayGfOmQH3kRMdNnDe4oCZD_9fsJWSt29xXR3KRMVk%3D" alt="Nokotaro Takeda" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1.jpeg?token-time=2145916800&token-hash=vGe7wXGqmA8Q7m-kDNb6fyGdwk-Dxk4F-ut8ZZu51RM%3D" alt="Takashi Shibuya" width="100"></td>
</tr><tr>
<td><a href="https://www.patreon.com/dansup">dansup</a></td> <td><a href="https://www.patreon.com/dansup">dansup</a></td>
<td><a href="https://www.patreon.com/mastodon">Gargron</a></td> <td><a href="https://www.patreon.com/mastodon">Gargron</a></td>
<td><a href="https://www.patreon.com/takenoko">Nokotaro Takeda</a></td> <td><a href="https://www.patreon.com/takenoko">Nokotaro Takeda</a></td>
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td> <td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
</tr></table> </tr></table>
**Last updated:** Fri, 05 Apr 2019 09:39:06 UTC **Last updated:** Mon, 03 Jun 2019 17:28:09 UTC
<!-- PATREON_END --> <!-- PATREON_END -->
:four_leaf_clover: Copyright :four_leaf_clover: Copyright

View File

@ -9,9 +9,23 @@ This guide describes how to install and setup Misskey with Docker.
*1.* Download Misskey *1.* Download Misskey
---------------------------------------------------------------- ----------------------------------------------------------------
1. `git clone -b master git://github.com/syuilo/misskey.git` Clone Misskey repository's master branch. 1. Clone Misskey repository's master branch.
2. `cd misskey` Move to misskey directory.
3. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` Checkout to the [latest release](https://github.com/syuilo/misskey/releases/latest) tag. `git clone -b master git://github.com/syuilo/misskey.git`
2. Move to misskey directory.
`cd misskey`
3. Checkout to the [latest release](https://github.com/syuilo/misskey/releases/latest) tag.
```bash
git tag | grep '^10\.' | sort -V --reverse | \
while read tag_name; do \
if ! curl -s "https://api.github.com/repos/syuilo/misskey/releases/tags/$tag_name" \
| grep -qE '"(draft|prerelease)": true'; \
then git checkout $tag_name; break; fi ; done
```
*2.* Configure Misskey *2.* Configure Misskey
---------------------------------------------------------------- ----------------------------------------------------------------
@ -39,7 +53,15 @@ Just `docker-compose up -d`. GLHF!
### How to update your Misskey server to the latest version ### How to update your Misskey server to the latest version
1. `git fetch` 1. `git fetch`
2. `git stash` 2. `git stash`
3. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` 3.
```bash
git tag | grep '^10\.' | sort -V --reverse | \
while read tag_name; do \
if ! curl -s "https://api.github.com/repos/syuilo/misskey/releases/tags/$tag_name" \
| grep -qE '"(draft|prerelease)": true'; \
then git checkout $tag_name; break; fi ; done
```
4. `git stash pop` 4. `git stash pop`
5. `docker-compose build` 5. `docker-compose build`
6. Check [ChangeLog](../CHANGELOG.md) for migration information 6. Check [ChangeLog](../CHANGELOG.md) for migration information

View File

@ -10,9 +10,23 @@ Ce guide explique comment installer et configurer Misskey avec Docker.
*1.* Télécharger Misskey *1.* Télécharger Misskey
---------------------------------------------------------------- ----------------------------------------------------------------
1. `git clone -b master git://github.com/syuilo/misskey.git` Clone le dépôt de Misskey sur la branche master. 1. Clone le dépôt de Misskey sur la branche master.
2. `cd misskey` Naviguez dans le dossier du dépôt.
3. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` Checkout sur le tag de la [dernière version](https://github.com/syuilo/misskey/releases/latest). `git clone -b master git://github.com/syuilo/misskey.git`
2. Naviguez dans le dossier du dépôt.
`cd misskey`
3. Checkout sur le tag de la [dernière version](https://github.com/syuilo/misskey/releases/latest).
```bash
git tag | grep '^10\.' | sort -V --reverse | \
while read tag_name; do \
if ! curl -s "https://api.github.com/repos/syuilo/misskey/releases/tags/$tag_name" \
| grep -qE '"(draft|prerelease)": true'; \
then git checkout $tag_name; break; fi ; done
```
*2.* Configuration de Misskey *2.* Configuration de Misskey
---------------------------------------------------------------- ----------------------------------------------------------------
@ -40,7 +54,15 @@ Utilisez la commande `docker-compose up -d`. GLHF!
### How to update your Misskey server to the latest version ### How to update your Misskey server to the latest version
1. `git fetch` 1. `git fetch`
2. `git stash` 2. `git stash`
3. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` 3.
```bash
git tag | grep '^10\.' | sort -V --reverse | \
while read tag_name; do \
if ! curl -s "https://api.github.com/repos/syuilo/misskey/releases/tags/$tag_name" \
| grep -qE '"(draft|prerelease)": true'; \
then git checkout $tag_name; break; fi ; done
```
4. `git stash pop` 4. `git stash pop`
5. `docker-compose build` 5. `docker-compose build`
6. Consultez le [ChangeLog](../CHANGELOG.md) pour avoir les éventuelles informations de migration 6. Consultez le [ChangeLog](../CHANGELOG.md) pour avoir les éventuelles informations de migration
@ -52,14 +74,28 @@ Utilisez la commande `docker-compose up -d`. GLHF!
### Configuration d'ElasticSearch (pour la fonction de recherche) ### Configuration d'ElasticSearch (pour la fonction de recherche)
*1.* Préparation de l'environnement *1.* Préparation de l'environnement
---------------------------------------------------------------- ----------------------------------------------------------------
1. `mkdir elasticsearch && chown 1000:1000 elasticsearch` Permet de créer le dossier d'accueil de la base ElasticSearch aves les bons droits 1. Permet de créer le dossier d'accueil de la base ElasticSearch aves les bons droits
2. `sysctl -w vm.max_map_count=262144` Augmente la valeur max du paramètre map_count du système (valeur minimum pour pouvoir lancer ES)
`mkdir elasticsearch && chown 1000:1000 elasticsearch`
2. Augmente la valeur max du paramètre map_count du système (valeur minimum pour pouvoir lancer ES)
`sysctl -w vm.max_map_count=262144`
*2.* Après lancement du docker-compose, initialisation de la base ElasticSearch *2.* Après lancement du docker-compose, initialisation de la base ElasticSearch
---------------------------------------------------------------- ----------------------------------------------------------------
1. `docker-compose -it web /bin/sh` Connexion dans le conteneur web 1. Connexion dans le conteneur web
2. `apk add curl` Ajout du paquet curl
3. `curl -X PUT "es:9200/misskey" -H 'Content-Type: application/json' -d'{ "settings" : { "index" : { } }}'` Création de la base ES `docker-compose -it web /bin/sh`
2. Ajout du paquet curl
`apk add curl`
3. Création de la base ES
`curl -X PUT "es:9200/misskey" -H 'Content-Type: application/json' -d'{ "settings" : { "index" : { } }}'`
4. `exit` 4. `exit`
---------------------------------------------------------------- ----------------------------------------------------------------

View File

@ -9,9 +9,23 @@ Dockerを使ったMisskey構築方法
*1.* Misskeyのダウンロード *1.* Misskeyのダウンロード
---------------------------------------------------------------- ----------------------------------------------------------------
1. `git clone -b master git://github.com/syuilo/misskey.git` masterブランチからMisskeyレポジトリをクローン 1. masterブランチからMisskeyレポジトリをクローン
2. `cd misskey` misskeyディレクトリに移動
3. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` [最新のリリース](https://github.com/syuilo/misskey/releases/latest)を確認 `git clone -b master git://github.com/syuilo/misskey.git`
2. misskeyディレクトリに移動
`cd misskey`
3. [最新のリリース](https://github.com/syuilo/misskey/releases/latest)を確認
```bash
git tag | grep '^10\.' | sort -V --reverse | \
while read tag_name; do \
if ! curl -s "https://api.github.com/repos/syuilo/misskey/releases/tags/$tag_name" \
| grep -qE '"(draft|prerelease)": true'; \
then git checkout $tag_name; break; fi ; done
```
*2.* 設定ファイルを作成する *2.* 設定ファイルを作成する
---------------------------------------------------------------- ----------------------------------------------------------------
@ -39,7 +53,15 @@ Dockerを使ったMisskey構築方法
### Misskeyを最新バージョンにアップデートする方法: ### Misskeyを最新バージョンにアップデートする方法:
1. `git fetch` 1. `git fetch`
2. `git stash` 2. `git stash`
3. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` 3.
```bash
git tag | grep '^10\.' | sort -V --reverse | \
while read tag_name; do \
if ! curl -s "https://api.github.com/repos/syuilo/misskey/releases/tags/$tag_name" \
| grep -qE '"(draft|prerelease)": true'; \
then git checkout $tag_name; break; fi ; done
```
4. `git stash pop` 4. `git stash pop`
5. `docker-compose build` 5. `docker-compose build`
6. [ChangeLog](../CHANGELOG.md)でマイグレーション情報を確認する 6. [ChangeLog](../CHANGELOG.md)でマイグレーション情報を確認する

View File

@ -41,15 +41,38 @@ As root:
*4.* Install Misskey *4.* Install Misskey
---------------------------------------------------------------- ----------------------------------------------------------------
1. `su - misskey` Connect to misskey user. 1. Connect to misskey user.
2. `git clone -b master git://github.com/syuilo/misskey.git` Clone the misskey repo from master branch.
3. `cd misskey` Navigate to misskey directory `su - misskey`
4. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` Checkout to the [latest release](https://github.com/syuilo/misskey/releases/latest)
5. `npm install` Install misskey dependencies. 2. Clone the misskey repo from master branch.
`git clone -b master git://github.com/syuilo/misskey.git`
3. Navigate to misskey directory
`cd misskey`
4. Checkout to the [latest release](https://github.com/syuilo/misskey/releases/latest)
```bash
git tag | grep '^10\.' | sort -V --reverse | \
while read tag_name; do \
if ! curl -s "https://api.github.com/repos/syuilo/misskey/releases/tags/$tag_name" \
| grep -qE '"(draft|prerelease)": true'; \
then git checkout $tag_name; break; fi ; done
```
5. Install misskey dependencies.
`npm install`
*5.* Configure Misskey *5.* Configure Misskey
---------------------------------------------------------------- ----------------------------------------------------------------
1. `cp .config/example.yml .config/default.yml` Copy the `.config/example.yml` and rename it to `default.yml`. 1. Copy the `.config/example.yml` and rename it to `default.yml`.
`cp .config/example.yml .config/default.yml`
2. Edit `default.yml` 2. Edit `default.yml`
*6.* Build Misskey *6.* Build Misskey
@ -77,37 +100,53 @@ Just `NODE_ENV=production npm start`. GLHF!
### Launch with systemd ### Launch with systemd
1. Create a systemd service here: `/etc/systemd/system/misskey.service` 1. Create a systemd service here
`/etc/systemd/system/misskey.service`
2. Edit it, and paste this and save: 2. Edit it, and paste this and save:
``` ```
[Unit] [Unit]
Description=Misskey daemon Description=Misskey daemon
[Service] [Service]
Type=simple Type=simple
User=misskey User=misskey
ExecStart=/usr/bin/npm start ExecStart=/usr/bin/npm start
WorkingDirectory=/home/misskey/misskey WorkingDirectory=/home/misskey/misskey
Environment="NODE_ENV=production" Environment="NODE_ENV=production"
TimeoutSec=60 TimeoutSec=60
StandardOutput=syslog StandardOutput=syslog
StandardError=syslog StandardError=syslog
SyslogIdentifier=misskey SyslogIdentifier=misskey
Restart=always Restart=always
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
``` ```
3. `systemctl daemon-reload ; systemctl enable misskey` Reload systemd and enable the misskey service. 3. Reload systemd and enable the misskey service.
4. `systemctl start misskey` Start the misskey service.
`systemctl daemon-reload ; systemctl enable misskey`
4. Start the misskey service.
`systemctl start misskey`
You can check if the service is running with `systemctl status misskey`. You can check if the service is running with `systemctl status misskey`.
### How to update your Misskey server to the latest version ### How to update your Misskey server to the latest version
1. `git fetch` 1. `git fetch`
2. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` 2.  
```bash
git tag | grep '^10\.' | sort -V --reverse | \
while read tag_name; do \
if ! curl -s "https://api.github.com/repos/syuilo/misskey/releases/tags/$tag_name" \
| grep -qE '"(draft|prerelease)": true'; \
then git checkout $tag_name; break; fi ; done
```
3. `npm install` 3. `npm install`
4. `NODE_ENV=production npm run build` 4. `NODE_ENV=production npm run build`
5. Check [ChangeLog](../CHANGELOG.md) for migration information 5. Check [ChangeLog](../CHANGELOG.md) for migration information

View File

@ -41,15 +41,38 @@ En root :
*4.* Installation de Misskey *4.* Installation de Misskey
---------------------------------------------------------------- ----------------------------------------------------------------
1. `su - misskey` Basculez vers l'utilisateur misskey. 1. Basculez vers l'utilisateur misskey.
2. `git clone -b master git://github.com/syuilo/misskey.git` Clonez la branche master du dépôt misskey.
3. `cd misskey` Accédez au dossier misskey. `su - misskey`
4. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` Checkout sur le tag de la [version la plus récente](https://github.com/syuilo/misskey/releases/latest)
5. `npm install` Installez les dépendances de misskey. 2. Clonez la branche master du dépôt misskey.
`git clone -b master git://github.com/syuilo/misskey.git`
3. Accédez au dossier misskey.
`cd misskey`
4. Checkout sur le tag de la [version la plus récente](https://github.com/syuilo/misskey/releases/latest)
```bash
git tag | grep '^10\.' | sort -V --reverse | \
while read tag_name; do \
if ! curl -s "https://api.github.com/repos/syuilo/misskey/releases/tags/$tag_name" \
| grep -qE '"(draft|prerelease)": true'; \
then git checkout $tag_name; break; fi ; done
```
5. Installez les dépendances de misskey.
`npm install`
*5.* Création du fichier de configuration *5.* Création du fichier de configuration
---------------------------------------------------------------- ----------------------------------------------------------------
1. `cp .config/example.yml .config/default.yml` Copiez le fichier `.config/example.yml` et renommez-le`default.yml`. 1. Copiez le fichier `.config/example.yml` et renommez-le`default.yml`.
`cp .config/example.yml .config/default.yml`
2. Editez le fichier `default.yml` 2. Editez le fichier `default.yml`
*6.* Construction de Misskey *6.* Construction de Misskey
@ -77,37 +100,53 @@ Lancez tout simplement `NODE_ENV=production npm start`. Bonne chance et amusez-v
### Démarrage avec systemd ### Démarrage avec systemd
1. Créez un service systemd sur : `/etc/systemd/system/misskey.service` 1. Créez un service systemd sur
`/etc/systemd/system/misskey.service`
2. Editez-le puis copiez et coller ceci dans le fichier : 2. Editez-le puis copiez et coller ceci dans le fichier :
``` ```
[Unit] [Unit]
Description=Misskey daemon Description=Misskey daemon
[Service] [Service]
Type=simple Type=simple
User=misskey User=misskey
ExecStart=/usr/bin/npm start ExecStart=/usr/bin/npm start
WorkingDirectory=/home/misskey/misskey WorkingDirectory=/home/misskey/misskey
Environment="NODE_ENV=production" Environment="NODE_ENV=production"
TimeoutSec=60 TimeoutSec=60
StandardOutput=syslog StandardOutput=syslog
StandardError=syslog StandardError=syslog
SyslogIdentifier=misskey SyslogIdentifier=misskey
Restart=always Restart=always
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
``` ```
3. `systemctl daemon-reload ; systemctl enable misskey` Redémarre systemd et active le service misskey. 3. Redémarre systemd et active le service misskey.
4. `systemctl start misskey` Démarre le service misskey.
`systemctl daemon-reload ; systemctl enable misskey`
4. Démarre le service misskey.
`systemctl start misskey`
Vous pouvez vérifier si le service a démarré en utilisant la commande `systemctl status misskey`. Vous pouvez vérifier si le service a démarré en utilisant la commande `systemctl status misskey`.
### Méthode de mise à jour vers la plus récente version de Misskey ### Méthode de mise à jour vers la plus récente version de Misskey
1. `git fetch` 1. `git fetch`
2. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` 2.  
```bash
git tag | grep '^10\.' | sort -V --reverse | \
while read tag_name; do \
if ! curl -s "https://api.github.com/repos/syuilo/misskey/releases/tags/$tag_name" \
| grep -qE '"(draft|prerelease)": true'; \
then git checkout $tag_name; break; fi ; done
```
3. `npm install` 3. `npm install`
4. `NODE_ENV=production npm run build` 4. `NODE_ENV=production npm run build`
5. Consultez [ChangeLog](../CHANGELOG.md) pour les information de migration. 5. Consultez [ChangeLog](../CHANGELOG.md) pour les information de migration.

View File

@ -48,15 +48,38 @@ adduser --disabled-password --disabled-login misskey
*4.* Misskeyのインストール *4.* Misskeyのインストール
---------------------------------------------------------------- ----------------------------------------------------------------
1. `su - misskey` misskeyユーザーを使用 1. misskeyユーザーを使用
2. `git clone -b master git://github.com/syuilo/misskey.git` masterブランチからMisskeyレポジトリをクローン
3. `cd misskey` misskeyディレクトリに移動 `su - misskey`
4. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` [最新のリリース](https://github.com/syuilo/misskey/releases/latest)を確認
5. `npm install` Misskeyの依存パッケージをインストール 2. masterブランチからMisskeyレポジトリをクローン
`git clone -b master git://github.com/syuilo/misskey.git`
3. misskeyディレクトリに移動
`cd misskey`
4. [最新のリリース](https://github.com/syuilo/misskey/releases/latest)を確認
```bash
git tag | grep '^10\.' | sort -V --reverse | \
while read tag_name; do \
if ! curl -s "https://api.github.com/repos/syuilo/misskey/releases/tags/$tag_name" \
| grep -qE '"(draft|prerelease)": true'; \
then git checkout $tag_name; break; fi ; done
```
5. Misskeyの依存パッケージをインストール
`npm install`
*5.* 設定ファイルを作成する *5.* 設定ファイルを作成する
---------------------------------------------------------------- ----------------------------------------------------------------
1. `cp .config/example.yml .config/default.yml` `.config/example.yml`をコピーし名前を`default.yml`にする。 1. `.config/example.yml`をコピーし名前を`default.yml`にする。
`cp .config/example.yml .config/default.yml`
2. `default.yml` を編集する。 2. `default.yml` を編集する。
*6.* Misskeyのビルド *6.* Misskeyのビルド
@ -82,38 +105,56 @@ Debianをお使いであれば、`build-essential`パッケージをインスト
`NODE_ENV=production npm start`するだけです。GLHF! `NODE_ENV=production npm start`するだけです。GLHF!
### systemdを用いた起動 ### systemdを用いた起動
1. systemdサービスのファイルを作成: `/etc/systemd/system/misskey.service` 1. systemdサービスのファイルを作成
`/etc/systemd/system/misskey.service`
2. エディタで開き、以下のコードを貼り付けて保存: 2. エディタで開き、以下のコードを貼り付けて保存:
``` ```
[Unit] [Unit]
Description=Misskey daemon Description=Misskey daemon
[Service] [Service]
Type=simple Type=simple
User=misskey User=misskey
ExecStart=/usr/bin/npm start ExecStart=/usr/bin/npm start
WorkingDirectory=/home/misskey/misskey WorkingDirectory=/home/misskey/misskey
Environment="NODE_ENV=production" Environment="NODE_ENV=production"
TimeoutSec=60 TimeoutSec=60
StandardOutput=syslog StandardOutput=syslog
StandardError=syslog StandardError=syslog
SyslogIdentifier=misskey SyslogIdentifier=misskey
Restart=always Restart=always
[Install] [Install]
WantedBy=multi-user.target WantedBy=multi-user.target
``` ```
CentOSで1024以下のポートを使用してMisskeyを使用する場合は`ExecStart=/usr/bin/sudo /usr/bin/npm start`に変更する必要があります。
3. `systemctl daemon-reload ; systemctl enable misskey` systemdを再読み込みしmisskeyサービスを有効化 CentOSで1024以下のポートを使用してMisskeyを使用する場合は`ExecStart=/usr/bin/sudo /usr/bin/npm start`に変更する必要があります。
4. `systemctl start misskey` misskeyサービスの起動
3. systemdを再読み込みしmisskeyサービスを有効化
`systemctl daemon-reload ; systemctl enable misskey`
4. misskeyサービスの起動
`systemctl start misskey`
`systemctl status misskey`と入力すると、サービスの状態を調べることができます。 `systemctl status misskey`と入力すると、サービスの状態を調べることができます。
### Misskeyを最新バージョンにアップデートする方法: ### Misskeyを最新バージョンにアップデートする方法:
1. `git fetch` 1. `git fetch`
2. `git checkout $(git tag -l | grep -v 'rc[0-9]*$' | sort -V | tail -n 1)` 2.  
```bash
git tag | grep '^10\.' | sort -V --reverse | \
while read tag_name; do \
if ! curl -s "https://api.github.com/repos/syuilo/misskey/releases/tags/$tag_name" \
| grep -qE '"(draft|prerelease)": true'; \
then git checkout $tag_name; break; fi ; done
```
3. `npm install` 3. `npm install`
4. `NODE_ENV=production npm run build` 4. `NODE_ENV=production npm run build`
5. [ChangeLog](../CHANGELOG.md)でマイグレーション情報を確認する 5. [ChangeLog](../CHANGELOG.md)でマイグレーション情報を確認する

View File

@ -877,7 +877,6 @@ desktop/views/components/post-form.vue:
posting: "Posting" posting: "Posting"
attach-media-from-local: "Attach media from your device" attach-media-from-local: "Attach media from your device"
attach-media-from-drive: "Attach media from your Drive" attach-media-from-drive: "Attach media from your Drive"
attach-cancel: "Cancel attachment"
insert-a-kao: "v('ω')v" insert-a-kao: "v('ω')v"
create-poll: "Create a poll" create-poll: "Create a poll"
text-remain: "{} characters remaining" text-remain: "{} characters remaining"
@ -970,6 +969,10 @@ common/views/components/password-settings.vue:
not-match: "The new passwords do not match" not-match: "The new passwords do not match"
changed: "Password changed" changed: "Password changed"
failed: "Failed to change password" failed: "Failed to change password"
common/views/components/post-form-attaches.vue:
attach-cancel: "Remove Attachment"
mark-as-sensitive: "Mark as 'sensitive'"
unmark-as-sensitive: "Unmark as 'sensitive'"
desktop/views/components/sub-note-content.vue: desktop/views/components/sub-note-content.vue:
private: "This post is private" private: "This post is private"
deleted: "This post has been deleted" deleted: "This post has been deleted"

View File

@ -513,8 +513,12 @@ common/views/components/user-menu.vue:
mention: "メンション" mention: "メンション"
mute: "ミュート" mute: "ミュート"
unmute: "ミュート解除" unmute: "ミュート解除"
mute-confirm: "このユーザーをミュートしますか?"
unmute-confirm: "このユーザーをミュート解除しますか?"
block: "ブロック" block: "ブロック"
unblock: "ブロック解除" unblock: "ブロック解除"
block-confirm: "このユーザーをブロックしますか?"
unblock-confirm: "このユーザーをブロック解除しますか?"
push-to-list: "リストに追加" push-to-list: "リストに追加"
select-list: "リストを選択してください" select-list: "リストを選択してください"
report-abuse: "スパムを報告" report-abuse: "スパムを報告"
@ -522,8 +526,12 @@ common/views/components/user-menu.vue:
report-abuse-reported: "管理者に報告されました。ご協力ありがとうございました。" report-abuse-reported: "管理者に報告されました。ご協力ありがとうございました。"
silence: "サイレンス" silence: "サイレンス"
unsilence: "サイレンス解除" unsilence: "サイレンス解除"
silence-confirm: "このユーザーをサイレンスしますか?"
unsilence-confirm: "このユーザーをサイレンス解除しますか?"
suspend: "凍結" suspend: "凍結"
unsuspend: "凍結解除" unsuspend: "凍結解除"
suspend-confirm: "このユーザーを凍結しますか?"
unsuspend-confirm: "このユーザーを凍結解除しますか?"
common/views/components/poll.vue: common/views/components/poll.vue:
vote-to: "「{}」に投票する" vote-to: "「{}」に投票する"
@ -967,7 +975,6 @@ desktop/views/components/post-form.vue:
posting: "投稿中" posting: "投稿中"
attach-media-from-local: "PCからメディアを添付" attach-media-from-local: "PCからメディアを添付"
attach-media-from-drive: "ドライブからメディアを添付" attach-media-from-drive: "ドライブからメディアを添付"
attach-cancel: "添付取り消し"
insert-a-kao: "v('ω')v" insert-a-kao: "v('ω')v"
create-poll: "アンケートを作成" create-poll: "アンケートを作成"
text-remain: "残り{}文字" text-remain: "残り{}文字"
@ -1073,6 +1080,11 @@ common/views/components/password-settings.vue:
changed: "パスワードを変更しました" changed: "パスワードを変更しました"
failed: "パスワード変更に失敗しました" failed: "パスワード変更に失敗しました"
common/views/components/post-form-attaches.vue:
attach-cancel: "添付取り消し"
mark-as-sensitive: "閲覧注意に設定"
unmark-as-sensitive: "閲覧注意を解除"
desktop/views/components/sub-note-content.vue: desktop/views/components/sub-note-content.vue:
private: "この投稿は非公開です" private: "この投稿は非公開です"
deleted: "この投稿は削除されました" deleted: "この投稿は削除されました"
@ -1326,7 +1338,9 @@ admin/views/users.vue:
unsuspend-confirm: "凍結を解除しますか?" unsuspend-confirm: "凍結を解除しますか?"
unsuspended: "凍結を解除しました" unsuspended: "凍結を解除しました"
make-silence: "サイレンス" make-silence: "サイレンス"
silence-confirm: "サイレンスしますか?"
unmake-silence: "サイレンスの解除" unmake-silence: "サイレンスの解除"
unsilence-confirm: "サイレンスを解除しますか?"
verify: "公式アカウントにする" verify: "公式アカウントにする"
verify-confirm: "公式アカウントにしますか?" verify-confirm: "公式アカウントにしますか?"
verified: "公式アカウントにしました" verified: "公式アカウントにしました"

View File

@ -1,7 +1,7 @@
{ {
"name": "misskey", "name": "misskey",
"author": "syuilo <i@syuilo.com>", "author": "syuilo <i@syuilo.com>",
"version": "10.99.0", "version": "10.102.4",
"codename": "nighthike", "codename": "nighthike",
"repository": { "repository": {
"type": "git", "type": "git",
@ -22,24 +22,27 @@
"test": "gulp test", "test": "gulp test",
"format": "gulp format" "format": "gulp format"
}, },
"resolutions": {
"gulp-cssnano/cssnano/postcss-svgo/svgo/js-yaml": "^3.13.1",
"video-thumbnail-generator/lodash": "^4.17.11"
},
"dependencies": { "dependencies": {
"@fortawesome/fontawesome-svg-core": "1.2.15", "@fortawesome/fontawesome-svg-core": "1.2.19",
"@fortawesome/free-brands-svg-icons": "5.7.2", "@fortawesome/free-brands-svg-icons": "5.9.0",
"@fortawesome/free-regular-svg-icons": "5.7.2", "@fortawesome/free-regular-svg-icons": "5.9.0",
"@fortawesome/free-solid-svg-icons": "5.7.2", "@fortawesome/free-solid-svg-icons": "5.9.0",
"@fortawesome/vue-fontawesome": "0.1.5", "@fortawesome/vue-fontawesome": "0.1.6",
"@koa/cors": "2.2.3", "@koa/cors": "2.2.3",
"@prezzemolo/rap": "0.1.2", "@prezzemolo/rap": "0.1.2",
"@prezzemolo/zip": "0.0.3", "@prezzemolo/zip": "0.0.3",
"@types/bcryptjs": "2.4.2", "@types/bcryptjs": "2.4.2",
"@types/bull": "3.5.8", "@types/bull": "3.5.14",
"@types/chai-http": "3.0.5", "@types/chai-http": "3.0.5",
"@types/dateformat": "3.0.0", "@types/dateformat": "3.0.0",
"@types/deep-equal": "1.0.1", "@types/deep-equal": "1.0.1",
"@types/double-ended-queue": "2.1.0", "@types/double-ended-queue": "2.1.0",
"@types/elasticsearch": "5.0.30", "@types/elasticsearch": "5.0.34",
"@types/file-type": "10.6.0", "@types/gulp": "4.0.6",
"@types/gulp": "4.0.5",
"@types/gulp-mocha": "0.0.32", "@types/gulp-mocha": "0.0.32",
"@types/gulp-rename": "0.0.33", "@types/gulp-rename": "0.0.33",
"@types/gulp-replace": "0.0.31", "@types/gulp-replace": "0.0.31",
@ -47,65 +50,65 @@
"@types/gulp-util": "3.0.34", "@types/gulp-util": "3.0.34",
"@types/is-root": "1.0.0", "@types/is-root": "1.0.0",
"@types/is-url": "1.2.28", "@types/is-url": "1.2.28",
"@types/js-yaml": "3.12.0", "@types/js-yaml": "3.12.1",
"@types/jsdom": "12.2.3", "@types/jsdom": "12.2.3",
"@types/katex": "0.10.1", "@types/katex": "0.10.1",
"@types/koa": "2.0.48", "@types/koa": "2.0.48",
"@types/koa-bodyparser": "5.0.2", "@types/koa-bodyparser": "5.0.2",
"@types/koa-compress": "2.0.8", "@types/koa-compress": "2.0.9",
"@types/koa-cors": "0.0.0", "@types/koa-cors": "0.0.0",
"@types/koa-favicon": "2.0.19", "@types/koa-favicon": "2.0.19",
"@types/koa-logger": "3.1.1", "@types/koa-logger": "3.1.1",
"@types/koa-mount": "3.0.1", "@types/koa-mount": "3.0.1",
"@types/koa-multer": "1.0.0", "@types/koa-multer": "1.0.0",
"@types/koa-router": "7.0.40", "@types/koa-router": "7.0.40",
"@types/koa-send": "4.1.1", "@types/koa-send": "4.1.2",
"@types/koa-views": "2.0.3", "@types/koa-views": "2.0.3",
"@types/koa__cors": "2.2.3", "@types/koa__cors": "2.2.3",
"@types/minio": "7.0.1", "@types/minio": "7.0.2",
"@types/mkdirp": "0.5.2", "@types/mkdirp": "0.5.2",
"@types/mocha": "5.2.5", "@types/mocha": "5.2.7",
"@types/mongodb": "3.1.20", "@types/mongodb": "3.1.28",
"@types/node": "11.10.4", "@types/node": "11.10.4",
"@types/nodemailer": "4.6.6", "@types/nodemailer": "6.2.0",
"@types/nprogress": "0.0.29", "@types/nprogress": "0.2.0",
"@types/oauth": "0.9.1", "@types/oauth": "0.9.1",
"@types/parse5": "5.0.0", "@types/parse5": "5.0.0",
"@types/parsimmon": "1.10.0", "@types/parsimmon": "1.10.0",
"@types/portscanner": "2.1.0", "@types/portscanner": "2.1.0",
"@types/pug": "2.0.4", "@types/pug": "2.0.4",
"@types/qrcode": "1.3.0", "@types/qrcode": "1.3.3",
"@types/ratelimiter": "2.1.28", "@types/ratelimiter": "2.1.28",
"@types/redis": "2.8.10", "@types/redis": "2.8.13",
"@types/rename": "1.0.1", "@types/rename": "1.0.1",
"@types/request": "2.48.1", "@types/request": "2.48.1",
"@types/request-promise-native": "1.0.15", "@types/request-promise-native": "1.0.16",
"@types/request-stats": "3.0.0", "@types/request-stats": "3.0.0",
"@types/rimraf": "2.0.2", "@types/rimraf": "2.0.2",
"@types/seedrandom": "2.4.27", "@types/seedrandom": "2.4.28",
"@types/sharp": "0.21.2", "@types/sharp": "0.22.2",
"@types/showdown": "1.9.2", "@types/showdown": "1.9.2",
"@types/speakeasy": "2.0.4", "@types/speakeasy": "2.0.4",
"@types/systeminformation": "3.23.1", "@types/systeminformation": "3.23.1",
"@types/tinycolor2": "1.4.1", "@types/tinycolor2": "1.4.2",
"@types/tmp": "0.0.33", "@types/tmp": "0.1.0",
"@types/uuid": "3.4.4", "@types/uuid": "3.4.4",
"@types/web-push": "3.3.0", "@types/web-push": "3.3.0",
"@types/webpack": "4.4.24", "@types/webpack": "4.4.32",
"@types/webpack-stream": "3.2.10", "@types/webpack-stream": "3.2.10",
"@types/websocket": "0.0.40", "@types/websocket": "0.0.40",
"@types/ws": "6.0.1", "@types/ws": "6.0.1",
"animejs": "3.0.1", "animejs": "3.0.1",
"apexcharts": "3.6.5", "apexcharts": "3.8.0",
"autobind-decorator": "2.4.0", "autobind-decorator": "2.4.0",
"autosize": "4.0.2", "autosize": "4.0.2",
"autwh": "0.1.0", "autwh": "0.1.0",
"bcryptjs": "2.4.3", "bcryptjs": "2.4.3",
"bootstrap-vue": "2.0.0-rc.13", "bootstrap-vue": "2.0.0-rc.22",
"bull": "3.7.0", "bull": "3.10.0",
"cafy": "15.1.0", "cafy": "15.1.1",
"chai": "4.2.0", "chai": "4.2.0",
"chai-http": "4.2.1", "chai-http": "4.3.0",
"chalk": "2.4.2", "chalk": "2.4.2",
"commander": "2.20.0", "commander": "2.20.0",
"content-disposition": "0.5.3", "content-disposition": "0.5.3",
@ -115,18 +118,18 @@
"dateformat": "3.0.3", "dateformat": "3.0.3",
"deep-equal": "1.0.1", "deep-equal": "1.0.1",
"deepcopy": "0.6.3", "deepcopy": "0.6.3",
"diskusage": "1.0.0", "diskusage": "1.1.1",
"double-ended-queue": "2.1.0-0", "double-ended-queue": "2.1.0-0",
"elasticsearch": "15.4.1", "elasticsearch": "15.4.1",
"emojilib": "2.4.0", "emojilib": "2.4.0",
"escape-regexp": "0.0.1", "escape-regexp": "0.0.1",
"eslint": "5.15.1", "eslint": "5.16.0",
"eslint-plugin-vue": "5.2.2", "eslint-plugin-vue": "5.2.2",
"eventemitter3": "3.1.0", "eventemitter3": "3.1.2",
"feed": "2.0.4", "feed": "2.0.4",
"file-type": "10.10.0", "file-type": "10.10.0",
"fuckadblock": "3.2.1", "fuckadblock": "3.2.1",
"gulp": "4.0.0", "gulp": "4.0.2",
"gulp-cssnano": "2.1.3", "gulp-cssnano": "2.1.3",
"gulp-imagemin": "5.0.3", "gulp-imagemin": "5.0.3",
"gulp-mocha": "6.0.0", "gulp-mocha": "6.0.0",
@ -135,20 +138,20 @@
"gulp-sourcemaps": "2.6.5", "gulp-sourcemaps": "2.6.5",
"gulp-stylus": "2.7.0", "gulp-stylus": "2.7.0",
"gulp-tslint": "8.1.4", "gulp-tslint": "8.1.4",
"gulp-typescript": "5.0.0", "gulp-typescript": "5.0.1",
"gulp-uglify": "3.0.2", "gulp-uglify": "3.0.2",
"gulp-util": "3.0.8", "gulp-util": "3.0.8",
"hard-source-webpack-plugin": "0.13.1", "hard-source-webpack-plugin": "0.13.1",
"html-minifier": "3.5.21", "html-minifier": "4.0.0",
"http-signature": "1.2.0", "http-signature": "1.2.0",
"insert-text-at-cursor": "0.1.2", "insert-text-at-cursor": "0.2.0",
"is-root": "2.0.0", "is-root": "2.1.0",
"is-svg": "4.0.0", "is-svg": "4.2.0",
"js-yaml": "3.13.0", "js-yaml": "3.13.1",
"jsdom": "14.0.0", "jsdom": "15.1.1",
"json5": "2.1.0", "json5": "2.1.0",
"json5-loader": "1.0.1", "json5-loader": "2.0.0",
"katex": "0.10.1", "katex": "0.10.2",
"koa": "2.7.0", "koa": "2.7.0",
"koa-bodyparser": "4.2.1", "koa-bodyparser": "4.2.1",
"koa-compress": "3.0.0", "koa-compress": "3.0.0",
@ -164,15 +167,15 @@
"langmap": "0.0.16", "langmap": "0.0.16",
"loader-utils": "1.2.3", "loader-utils": "1.2.3",
"lookup-dns-cache": "2.1.0", "lookup-dns-cache": "2.1.0",
"minio": "7.0.5", "minio": "7.0.8",
"mkdirp": "0.5.1", "mkdirp": "0.5.1",
"mocha": "5.2.0", "mocha": "5.2.0",
"moji": "0.5.1", "moji": "0.5.1",
"moment": "2.24.0", "moment": "2.24.0",
"mongodb": "3.2.2", "mongodb": "3.2.7",
"monk": "6.0.6", "monk": "6.0.6",
"ms": "2.1.1", "ms": "2.1.2",
"nan": "2.12.1", "nan": "2.14.0",
"nested-property": "0.0.7", "nested-property": "0.0.7",
"nodemailer": "5.1.1", "nodemailer": "5.1.1",
"nprogress": "0.2.0", "nprogress": "0.2.0",
@ -203,7 +206,7 @@
"rndstr": "1.0.0", "rndstr": "1.0.0",
"s-age": "1.1.2", "s-age": "1.1.2",
"seedrandom": "2.4.4", "seedrandom": "2.4.4",
"sharp": "0.22.0", "sharp": "0.22.1",
"showdown": "1.9.0", "showdown": "1.9.0",
"showdown-highlightjs-extension": "0.1.2", "showdown-highlightjs-extension": "0.1.2",
"speakeasy": "2.0.0", "speakeasy": "2.0.0",
@ -212,17 +215,17 @@
"stylus": "0.54.5", "stylus": "0.54.5",
"stylus-loader": "3.0.2", "stylus-loader": "3.0.2",
"summaly": "2.2.0", "summaly": "2.2.0",
"systeminformation": "4.0.16", "systeminformation": "4.9.0",
"syuilo-password-strength": "0.0.1", "syuilo-password-strength": "0.0.1",
"terser-webpack-plugin": "1.2.3", "terser-webpack-plugin": "1.3.0",
"textarea-caret": "3.1.0", "textarea-caret": "3.1.0",
"tinycolor2": "1.4.1", "tinycolor2": "1.4.1",
"tmp": "0.0.33", "tmp": "0.1.0",
"ts-loader": "5.3.3", "ts-loader": "5.3.3",
"ts-node": "8.0.3", "ts-node": "8.0.3",
"tslint": "5.13.1", "tslint": "5.17.0",
"tslint-sonarts": "1.9.0", "tslint-sonarts": "1.9.0",
"typescript": "3.3.3333", "typescript": "3.5.1",
"typescript-eslint-parser": "22.0.0", "typescript-eslint-parser": "22.0.0",
"uglify-es": "3.3.9", "uglify-es": "3.3.9",
"url-loader": "1.1.2", "url-loader": "1.1.2",
@ -232,27 +235,26 @@
"video-thumbnail-generator": "1.1.3", "video-thumbnail-generator": "1.1.3",
"vue": "2.6.10", "vue": "2.6.10",
"vue-color": "2.7.0", "vue-color": "2.7.0",
"vue-content-loading": "1.5.3", "vue-content-loading": "1.6.0",
"vue-cropperjs": "3.0.0", "vue-cropperjs": "3.0.0",
"vue-i18n": "8.10.0", "vue-i18n": "8.11.2",
"vue-js-modal": "1.3.28", "vue-js-modal": "1.3.31",
"vue-json-pretty": "1.6.0", "vue-json-pretty": "1.6.0",
"vue-loader": "15.7.0", "vue-loader": "15.7.0",
"vue-marquee-text-component": "1.1.1", "vue-marquee-text-component": "1.1.1",
"vue-prism-component": "1.1.1", "vue-prism-component": "1.1.1",
"vue-router": "3.0.2", "vue-router": "3.0.6",
"vue-sequential-entrance": "1.1.3", "vue-sequential-entrance": "1.1.3",
"vue-style-loader": "4.1.2", "vue-style-loader": "4.1.2",
"vue-svg-inline-loader": "1.2.15", "vue-svg-inline-loader": "1.2.15",
"vue-template-compiler": "2.6.10", "vue-template-compiler": "2.6.10",
"vuedraggable": "2.20.0", "vuedraggable": "2.21.0",
"vuewordcloud": "18.7.11", "vuewordcloud": "18.7.11",
"vuex": "3.1.0", "vuex": "3.1.1",
"vuex-persistedstate": "2.5.4", "vuex-persistedstate": "2.5.4",
"web-push": "3.3.3", "web-push": "3.3.5",
"webfinger.js": "2.7.0", "webpack": "4.33.0",
"webpack": "4.28.4", "webpack-cli": "3.3.3",
"webpack-cli": "3.2.3",
"websocket": "1.0.28", "websocket": "1.0.28",
"ws": "6.2.1", "ws": "6.2.1",
"xev": "2.0.1" "xev": "2.0.1"

View File

@ -1,65 +0,0 @@
declare module 'webfinger.js' {
interface IWebFingerConstructorConfig {
tls_only?: boolean;
webfist_fallback?: boolean;
uri_fallback?: boolean;
request_timeout?: number;
}
type JRDProperties = { [type: string]: string };
interface IJRDLink {
rel: string;
type?: string;
href?: string;
template?: string;
titles?: { [lang: string]: string };
properties?: JRDProperties;
}
interface IJRD {
subject?: string;
expires?: Date;
aliases?: string[];
properties?: JRDProperties;
links?: IJRDLink[];
}
interface IIDXLinks {
'avatar': IJRDLink[];
'remotestorage': IJRDLink[];
'blog': IJRDLink[];
'vcard': IJRDLink[];
'updates': IJRDLink[];
'share': IJRDLink[];
'profile': IJRDLink[];
'webfist': IJRDLink[];
'camlistore': IJRDLink[];
[type: string]: IJRDLink[];
}
interface IIDXProperties {
'name': string;
[type: string]: string;
}
interface IIDX {
links: IIDXLinks;
properties: IIDXProperties;
}
interface ILookupCallbackResult {
object: IJRD;
json: string;
idx: IIDX;
}
type LookupCallback = (err: Error | string, result?: ILookupCallbackResult) => void;
export class WebFinger {
constructor(config?: IWebFingerConstructorConfig);
public lookup(address: string, cb: LookupCallback): NodeJS.Timeout;
public lookupLink(address: string, rel: string, cb: IJRDLink): void;
}
}

View File

@ -38,7 +38,7 @@
<div class="kidvdlkg" v-for="file in files"> <div class="kidvdlkg" v-for="file in files">
<div @click="file._open = !file._open"> <div @click="file._open = !file._open">
<div> <div>
<div class="thumbnail" :style="thumbnail(file)"></div> <x-file-thumbnail class="thumbnail" :file="file" fit="contain" @click="showFileMenu(file)"/>
</div> </div>
<div> <div>
<header> <header>
@ -75,10 +75,15 @@ import Vue from 'vue';
import i18n from '../../i18n'; import i18n from '../../i18n';
import { faCloud, faTerminal, faSearch } from '@fortawesome/free-solid-svg-icons'; import { faCloud, faTerminal, faSearch } from '@fortawesome/free-solid-svg-icons';
import { faTrashAlt, faEye, faEyeSlash } from '@fortawesome/free-regular-svg-icons'; import { faTrashAlt, faEye, faEyeSlash } from '@fortawesome/free-regular-svg-icons';
import XFileThumbnail from '../../common/views/components/drive-file-thumbnail.vue';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('admin/views/drive.vue'), i18n: i18n('admin/views/drive.vue'),
components: {
XFileThumbnail
},
data() { data() {
return { return {
file: null, file: null,
@ -151,13 +156,6 @@ export default Vue.extend({
}); });
}, },
thumbnail(file: any): any {
return {
'background-color': file.properties.avgColor && file.properties.avgColor.length == 3 ? `rgb(${file.properties.avgColor.join(',')})` : 'transparent',
'background-image': `url(${file.thumbnailUrl})`
};
},
async del(file: any) { async del(file: any) {
const process = async () => { const process = async () => {
await this.$root.api('drive/files/delete', { fileId: file.id }); await this.$root.api('drive/files/delete', { fileId: file.id });
@ -179,9 +177,9 @@ export default Vue.extend({
this.$root.api('drive/files/update', { this.$root.api('drive/files/update', {
fileId: file.id, fileId: file.id,
isSensitive: !file.isSensitive isSensitive: !file.isSensitive
}).then(() => {
file.isSensitive = !file.isSensitive;
}); });
file.isSensitive = !file.isSensitive;
}, },
async show() { async show() {
@ -244,7 +242,7 @@ export default Vue.extend({
> div:nth-child(1) > div:nth-child(1)
> .thumbnail > .thumbnail
display block display flex
width 64px width 64px
height 64px height 64px
background-size cover background-size cover

View File

@ -130,7 +130,7 @@
<span>{{ $t('status') }}</span> <span>{{ $t('status') }}</span>
</header> </header>
<div v-for="instance in instances" :style="{ opacity: instance.isNotResponding ? 0.5 : 1 }"> <div v-for="instance in instances" :style="{ opacity: instance.isNotResponding ? 0.5 : 1 }">
<a @click.prevent="showInstance(instance.host)" target="_blank" :href="`https://${instance.host}`" :style="{ textDecoration: instance.isMarkedAsClosed ? 'line-through' : 'none' }">{{ instance.host }}</a> <a @click.prevent="showInstance(instance.host)" rel="nofollow noopener" target="_blank" :href="`https://${instance.host}`" :style="{ textDecoration: instance.isMarkedAsClosed ? 'line-through' : 'none' }">{{ instance.host }}</a>
<span>{{ instance.notesCount | number }}</span> <span>{{ instance.notesCount | number }}</span>
<span>{{ instance.usersCount | number }}</span> <span>{{ instance.usersCount | number }}</span>
<span>{{ instance.followingCount | number }}</span> <span>{{ instance.followingCount | number }}</span>

View File

@ -232,6 +232,8 @@ export default Vue.extend({
}, },
async silenceUser() { async silenceUser() {
if (!await this.getConfirmed(this.$t('silence-confirm'))) return;
const process = async () => { const process = async () => {
await this.$root.api('admin/silence-user', { userId: this.user._id }); await this.$root.api('admin/silence-user', { userId: this.user._id });
this.$root.dialog({ this.$root.dialog({
@ -251,6 +253,8 @@ export default Vue.extend({
}, },
async unsilenceUser() { async unsilenceUser() {
if (!await this.getConfirmed(this.$t('unsilence-confirm'))) return;
const process = async () => { const process = async () => {
await this.$root.api('admin/unsilence-user', { userId: this.user._id }); await this.$root.api('admin/unsilence-user', { userId: this.user._id });
this.$root.dialog({ this.$root.dialog({

View File

@ -183,9 +183,6 @@ export default Vue.extend({
height 100% height 100%
&.splash &.splash
&, *
pointer-events none !important
> .main > .main
min-width 0 min-width 0
width initial width initial

View File

@ -1,5 +1,5 @@
<template> <template>
<a class="a" :href="repositoryUrl" target="_blank" title="View source on GitHub"> <a class="a" :href="repositoryUrl" rel="noopener" target="_blank" title="View source on GitHub">
<svg width="80" height="80" viewBox="0 0 250 250" aria-hidden="aria-hidden"> <svg width="80" height="80" viewBox="0 0 250 250" aria-hidden="aria-hidden">
<path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path> <path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"></path>
<path class="octo-arm" d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor"></path> <path class="octo-arm" d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor"></path>

View File

@ -1,5 +1,5 @@
<template> <template>
<a class="zxrjzpcj" :href="url" :class="service" target="_blank"> <a class="zxrjzpcj" :href="url" :class="service" rel="noopener" target="_blank">
<fa :icon="icon" size="lg" fixed-width /><span>{{ text }}</span> <fa :icon="icon" size="lg" fixed-width /><span>{{ text }}</span>
</a> </a>
</template> </template>

View File

@ -9,7 +9,7 @@
<div class="content" v-if="!message.isDeleted"> <div class="content" v-if="!message.isDeleted">
<mfm class="text" v-if="message.text" ref="text" :text="message.text" :i="$store.state.i"/> <mfm class="text" v-if="message.text" ref="text" :text="message.text" :i="$store.state.i"/>
<div class="file" v-if="message.file"> <div class="file" v-if="message.file">
<a :href="message.file.url" target="_blank" :title="message.file.name"> <a :href="message.file.url" rel="noopener" target="_blank" :title="message.file.name">
<img v-if="message.file.type.split('/')[0] == 'image'" :src="message.file.url" :alt="message.file.name" <img v-if="message.file.type.split('/')[0] == 'image'" :src="message.file.url" :alt="message.file.name"
:style="{ backgroundColor: message.file.properties.avgColor && message.file.properties.avgColor.length == 3 ? `rgb(${message.file.properties.avgColor.join(',')})` : 'transparent' }"/> :style="{ backgroundColor: message.file.properties.avgColor && message.file.properties.avgColor.length == 3 ? `rgb(${message.file.properties.avgColor.join(',')})` : 'transparent' }"/>
<p v-else>{{ message.file.name }}</p> <p v-else>{{ message.file.name }}</p>

View File

@ -270,17 +270,13 @@ export default Vue.extend({
<style lang="stylus" scoped> <style lang="stylus" scoped>
.mk-messaging-room .mk-messaging-room
display flex
flex 1
flex-direction column
height 100%
background var(--messagingRoomBg) background var(--messagingRoomBg)
> .body > .body
width 100% width 100%
max-width 600px max-width 600px
margin 0 auto margin 0 auto
flex 1 min-height calc(100% - 103px)
> .init, > .init,
> .empty > .empty

View File

@ -174,6 +174,7 @@ export default Vue.component('misskey-flavored-markdown', {
key: Math.random(), key: Math.random(),
props: { props: {
url: token.node.props.url, url: token.node.props.url,
rel: 'nofollow noopener',
target: '_blank' target: '_blank'
}, },
attrs: { attrs: {
@ -187,6 +188,7 @@ export default Vue.component('misskey-flavored-markdown', {
attrs: { attrs: {
class: 'link', class: 'link',
href: token.node.props.url, href: token.node.props.url,
rel: 'nofollow noopener',
target: '_blank', target: '_blank',
title: token.node.props.url, title: token.node.props.url,
style: 'color:var(--mfmLink);' style: 'color:var(--mfmLink);'

View File

@ -2,9 +2,9 @@
<span class="mk-nav"> <span class="mk-nav">
<a :href="aboutUrl">{{ $t('about') }}</a> <a :href="aboutUrl">{{ $t('about') }}</a>
<i></i> <i></i>
<a :href="repositoryUrl">{{ $t('repository') }}</a> <a :href="repositoryUrl" rel="noopener" target="_blank">{{ $t('repository') }}</a>
<i></i> <i></i>
<a :href="feedbackUrl" target="_blank">{{ $t('feedback') }}</a> <a :href="feedbackUrl" rel="noopener" target="_blank">{{ $t('feedback') }}</a>
<i></i> <i></i>
<a href="/dev">{{ $t('develop') }}</a> <a href="/dev">{{ $t('develop') }}</a>
</span> </span>

View File

@ -0,0 +1,140 @@
<template>
<div class="skeikyzd" v-show="files.length != 0">
<x-draggable class="files" :list="files" :options="{ animation: 150 }">
<div v-for="file in files" :key="file.id" @click="showFileMenu(file, $event)" @contextmenu.prevent="showFileMenu(file, $event)">
<x-file-thumbnail :data-id="file.id" class="thumbnail" :file="file" fit="cover"/>
<img class="remove" @click.stop="detachMedia(file.id)" src="/assets/desktop/remove.png" :title="$t('attach-cancel')" alt=""/>
<div class="sensitive" v-if="file.isSensitive">
<fa class="icon" :icon="faExclamationTriangle"/>
</div>
</div>
</x-draggable>
<p class="remain">{{ 4 - files.length }}/4</p>
</div>
</template>
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../i18n';
import * as XDraggable from 'vuedraggable';
import XMenu from '../../../common/views/components/menu.vue';
import { faTimesCircle, faEye, faEyeSlash } from '@fortawesome/free-regular-svg-icons';
import { faExclamationTriangle } from '@fortawesome/free-solid-svg-icons';
import XFileThumbnail from './drive-file-thumbnail.vue'
export default Vue.extend({
i18n: i18n('common/views/components/post-form-attaches.vue'),
components: {
XDraggable,
XFileThumbnail
},
props: {
files: {
type: Object,
required: true
},
detachMediaFn: {
type: Object,
required: false
}
},
data() {
return {
faExclamationTriangle
};
},
methods: {
detachMedia(id) {
if (this.detachMediaFn) this.detachMediaFn(id)
else if (this.$parent.detachMedia) this.$parent.detachMedia(id)
},
toggleSensitive(file) {
this.$root.api('drive/files/update', {
fileId: file.id,
isSensitive: !file.isSensitive
}).then(() => {
file.isSensitive = !file.isSensitive;
});
},
showFileMenu(file, ev: MouseEvent) {
this.$root.new(XMenu, {
items: [{
type: 'item',
text: file.isSensitive ? this.$t('unmark-as-sensitive') : this.$t('mark-as-sensitive'),
icon: file.isSensitive ? faEyeSlash : faEye,
action: () => { this.toggleSensitive(file) }
}, {
type: 'item',
text: this.$t('attach-cancel'),
icon: faTimesCircle,
action: () => { this.detachMedia(file.id) }
}],
source: ev.currentTarget || ev.target
});
}
}
});
</script>
<style lang="stylus" scoped>
.skeikyzd
padding 4px
> .files
display flex
flex-wrap wrap
> div
width 64px
height 64px
margin 4px
cursor move
&:hover > .remove
display block
> .thumbnail
width 100%
height 100%
z-index 1
color var(--text)
background-color: rgba(128, 128, 128, 0.3)
> .remove
display none
position absolute
top -6px
right -6px
width 16px
height 16px
cursor pointer
z-index 1000
> .sensitive
display flex
position absolute
width 64px
height 64px
top 0
left 0
z-index 2
background rgba(17, 17, 17, .7)
color #fff
> .icon
margin auto
> .remain
display block
position absolute
top 8px
right 8px
margin 0
padding 0
color var(--primaryAlpha04)
</style>

View File

@ -9,7 +9,7 @@
</template> </template>
<div v-if="data && !$store.state.i.twoFactorEnabled"> <div v-if="data && !$store.state.i.twoFactorEnabled">
<ol> <ol>
<li>{{ $t('authenticator') }}<a href="https://support.google.com/accounts/answer/1066447" target="_blank">{{ $t('howtoinstall') }}</a></li> <li>{{ $t('authenticator') }}<a href="https://support.google.com/accounts/answer/1066447" rel="noopener" target="_blank">{{ $t('howtoinstall') }}</a></li>
<li>{{ $t('scan') }}<br><img :src="data.qr"></li> <li>{{ $t('scan') }}<br><img :src="data.qr"></li>
<li>{{ $t('done') }}<br> <li>{{ $t('done') }}<br>
<ui-input v-model="token">{{ $t('token') }}</ui-input> <ui-input v-model="token">{{ $t('token') }}</ui-input>

View File

@ -4,21 +4,21 @@
<section v-if="enableTwitterIntegration"> <section v-if="enableTwitterIntegration">
<header><fa :icon="['fab', 'twitter']"/> Twitter</header> <header><fa :icon="['fab', 'twitter']"/> Twitter</header>
<p v-if="$store.state.i.twitter">{{ $t('connected-to') }}: <a :href="`https://twitter.com/${$store.state.i.twitter.screenName}`" target="_blank">@{{ $store.state.i.twitter.screenName }}</a></p> <p v-if="$store.state.i.twitter">{{ $t('connected-to') }}: <a :href="`https://twitter.com/${$store.state.i.twitter.screenName}`" rel="nofollow noopener" target="_blank">@{{ $store.state.i.twitter.screenName }}</a></p>
<ui-button v-if="$store.state.i.twitter" @click="disconnectTwitter">{{ $t('disconnect') }}</ui-button> <ui-button v-if="$store.state.i.twitter" @click="disconnectTwitter">{{ $t('disconnect') }}</ui-button>
<ui-button v-else @click="connectTwitter">{{ $t('connect') }}</ui-button> <ui-button v-else @click="connectTwitter">{{ $t('connect') }}</ui-button>
</section> </section>
<section v-if="enableDiscordIntegration"> <section v-if="enableDiscordIntegration">
<header><fa :icon="['fab', 'discord']"/> Discord</header> <header><fa :icon="['fab', 'discord']"/> Discord</header>
<p v-if="$store.state.i.discord">{{ $t('connected-to') }}: <a :href="`https://discordapp.com/users/${$store.state.i.discord.id}`" target="_blank">@{{ $store.state.i.discord.username }}#{{ $store.state.i.discord.discriminator }}</a></p> <p v-if="$store.state.i.discord">{{ $t('connected-to') }}: <a :href="`https://discordapp.com/users/${$store.state.i.discord.id}`" rel="nofollow noopener" target="_blank">@{{ $store.state.i.discord.username }}#{{ $store.state.i.discord.discriminator }}</a></p>
<ui-button v-if="$store.state.i.discord" @click="disconnectDiscord">{{ $t('disconnect') }}</ui-button> <ui-button v-if="$store.state.i.discord" @click="disconnectDiscord">{{ $t('disconnect') }}</ui-button>
<ui-button v-else @click="connectDiscord">{{ $t('connect') }}</ui-button> <ui-button v-else @click="connectDiscord">{{ $t('connect') }}</ui-button>
</section> </section>
<section v-if="enableGithubIntegration"> <section v-if="enableGithubIntegration">
<header><fa :icon="['fab', 'github']"/> GitHub</header> <header><fa :icon="['fab', 'github']"/> GitHub</header>
<p v-if="$store.state.i.github">{{ $t('connected-to') }}: <a :href="`https://github.com/${$store.state.i.github.login}`" target="_blank">@{{ $store.state.i.github.login }}</a></p> <p v-if="$store.state.i.github">{{ $t('connected-to') }}: <a :href="`https://github.com/${$store.state.i.github.login}`" rel="nofollow noopener" target="_blank">@{{ $store.state.i.github.login }}</a></p>
<ui-button v-if="$store.state.i.github" @click="disconnectGithub">{{ $t('disconnect') }}</ui-button> <ui-button v-if="$store.state.i.github" @click="disconnectGithub">{{ $t('disconnect') }}</ui-button>
<ui-button v-else @click="connectGithub">{{ $t('connect') }}</ui-button> <ui-button v-else @click="connectGithub">{{ $t('connect') }}</ui-button>
</section> </section>

View File

@ -290,12 +290,17 @@ export default Vue.extend({
this.exportTarget == 'mute' ? 'i/export-mute' : this.exportTarget == 'mute' ? 'i/export-mute' :
this.exportTarget == 'blocking' ? 'i/export-blocking' : this.exportTarget == 'blocking' ? 'i/export-blocking' :
this.exportTarget == 'user-lists' ? 'i/export-user-lists' : this.exportTarget == 'user-lists' ? 'i/export-user-lists' :
null, {}); null, {}).then(() => {
this.$root.dialog({
this.$root.dialog({ type: 'info',
type: 'info', text: this.$t('export-requested')
text: this.$t('export-requested') });
}); }).catch((e: any) => {
this.$root.dialog({
type: 'error',
text: e.message
});
});
}, },
doImport() { doImport() {

View File

@ -45,7 +45,7 @@
</ui-select> </ui-select>
</label> </label>
<a href="https://assets.msky.cafe/theme/list" target="_blank">{{ $t('find-more-theme') }}</a> <a href="https://assets.msky.cafe/theme/list" rel="noopener" target="_blank">{{ $t('find-more-theme') }}</a>
<details class="creator"> <details class="creator">
<summary><fa icon="palette"/> {{ $t('create-a-theme') }}</summary> <summary><fa icon="palette"/> {{ $t('create-a-theme') }}</summary>

View File

@ -9,7 +9,7 @@
</blockquote> </blockquote>
</div> </div>
<div v-else class="mk-url-preview"> <div v-else class="mk-url-preview">
<a :class="{ mini: narrow, compact }" :href="url" target="_blank" :title="url" v-if="!fetching"> <a :class="{ mini: narrow, compact }" :href="url" rel="nofollow noopener" target="_blank" :title="url" v-if="!fetching">
<div class="thumbnail" v-if="thumbnail" :style="`background-image: url('${thumbnail}')`"> <div class="thumbnail" v-if="thumbnail" :style="`background-image: url('${thumbnail}')`">
<button v-if="!playerEnabled && player.url" @click.prevent="playerEnabled = true" :title="$t('enable-player')"><fa :icon="['far', 'play-circle']"/></button> <button v-if="!playerEnabled && player.url" @click.prevent="playerEnabled = true" :title="$t('enable-player')"><fa :icon="['far', 'play-circle']"/></button>
</div> </div>

View File

@ -1,5 +1,5 @@
<template> <template>
<a class="mk-url" :href="url" :target="target"> <a class="mk-url" :href="url" :rel="rel" :target="target">
<span class="schema">{{ schema }}//</span> <span class="schema">{{ schema }}//</span>
<span class="hostname">{{ hostname }}</span> <span class="hostname">{{ hostname }}</span>
<span class="port" v-if="port != ''">:{{ port }}</span> <span class="port" v-if="port != ''">:{{ port }}</span>
@ -15,7 +15,7 @@ import Vue from 'vue';
import { toUnicode as decodePunycode } from 'punycode'; import { toUnicode as decodePunycode } from 'punycode';
export default Vue.extend({ export default Vue.extend({
props: ['url', 'target'], props: ['url', 'rel', 'target'],
data() { data() {
return { return {
schema: null, schema: null,

View File

@ -8,7 +8,7 @@
<div class="no-users" v-if="inited && us.length == 0"> <div class="no-users" v-if="inited && us.length == 0">
<p>{{ $t('no-users') }}</p> <p>{{ $t('no-users') }}</p>
</div> </div>
<div class="user" v-for="user in us"> <div class="user" v-for="user in us" :key="user.id">
<mk-avatar class="avatar" :user="user"/> <mk-avatar class="avatar" :user="user"/>
<div class="body" v-if="!iconOnly"> <div class="body" v-if="!iconOnly">
<div class="name"> <div class="name">
@ -18,6 +18,7 @@
<div class="description" v-if="user.description" :title="user.description"> <div class="description" v-if="user.description" :title="user.description">
<mfm :text="user.description" :is-note="false" :author="user" :i="$store.state.i" :custom-emojis="user.emojis" :should-break="false" :plain-text="true"/> <mfm :text="user.description" :is-note="false" :author="user" :i="$store.state.i" :custom-emojis="user.emojis" :should-break="false" :plain-text="true"/>
</div> </div>
<mk-follow-button class="follow-button" v-if="$store.getters.isSignedIn && user.id != $store.state.i.id" :user="user" mini/>
</div> </div>
</div> </div>
<button class="more" :class="{ fetching: fetchingMoreUsers }" v-if="cursor != null" @click="fetchMoreUsers()" :disabled="fetchingMoreUsers"> <button class="more" :class="{ fetching: fetchingMoreUsers }" v-if="cursor != null" @click="fetchMoreUsers()" :disabled="fetchingMoreUsers">
@ -160,6 +161,12 @@ export default Vue.extend({
text-overflow ellipsis text-overflow ellipsis
opacity 0.7 opacity 0.7
font-size 14px font-size 14px
padding-right 40px
> .follow-button
position absolute
top 8px
right 0px
> .more > .more
display block display block

View File

@ -7,7 +7,6 @@
<script lang="ts"> <script lang="ts">
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
import copyToClipboard from '../../../common/scripts/copy-to-clipboard';
import { faExclamationCircle, faMicrophoneSlash } from '@fortawesome/free-solid-svg-icons'; import { faExclamationCircle, faMicrophoneSlash } from '@fortawesome/free-solid-svg-icons';
import { faSnowflake } from '@fortawesome/free-regular-svg-icons'; import { faSnowflake } from '@fortawesome/free-regular-svg-icons';
@ -27,19 +26,23 @@ export default Vue.extend({
icon: ['fas', 'list'], icon: ['fas', 'list'],
text: this.$t('push-to-list'), text: this.$t('push-to-list'),
action: this.pushList action: this.pushList
}, null, { }] as any;
icon: this.user.isMuted ? ['fas', 'eye'] : ['far', 'eye-slash'],
text: this.user.isMuted ? this.$t('unmute') : this.$t('mute'), if (this.$store.getters.isSignedIn && this.$store.state.i.id != this.user.id) {
action: this.toggleMute menu = menu.concat([null, {
}, { icon: this.user.isMuted ? ['fas', 'eye'] : ['far', 'eye-slash'],
icon: 'ban', text: this.user.isMuted ? this.$t('unmute') : this.$t('mute'),
text: this.user.isBlocking ? this.$t('unblock') : this.$t('block'), action: this.toggleMute
action: this.toggleBlock }, {
}, null, { icon: 'ban',
icon: faExclamationCircle, text: this.user.isBlocking ? this.$t('unblock') : this.$t('block'),
text: this.$t('report-abuse'), action: this.toggleBlock
action: this.reportAbuse }, null, {
}]; icon: faExclamationCircle,
text: this.$t('report-abuse'),
action: this.reportAbuse
}]);
}
if (this.$store.getters.isSignedIn && (this.$store.state.i.isAdmin || this.$store.state.i.isModerator)) { if (this.$store.getters.isSignedIn && (this.$store.state.i.isAdmin || this.$store.state.i.isModerator)) {
menu = menu.concat([null, { menu = menu.concat([null, {
@ -89,8 +92,10 @@ export default Vue.extend({
}); });
}, },
toggleMute() { async toggleMute() {
if (this.user.isMuted) { if (this.user.isMuted) {
if (!await this.getConfirmed(this.$t('unmute-confirm'))) return;
this.$root.api('mute/delete', { this.$root.api('mute/delete', {
userId: this.user.id userId: this.user.id
}).then(() => { }).then(() => {
@ -102,6 +107,8 @@ export default Vue.extend({
}); });
}); });
} else { } else {
if (!await this.getConfirmed(this.$t('mute-confirm'))) return;
this.$root.api('mute/create', { this.$root.api('mute/create', {
userId: this.user.id userId: this.user.id
}).then(() => { }).then(() => {
@ -115,8 +122,10 @@ export default Vue.extend({
} }
}, },
toggleBlock() { async toggleBlock() {
if (this.user.isBlocking) { if (this.user.isBlocking) {
if (!await this.getConfirmed(this.$t('unblock-confirm'))) return;
this.$root.api('blocking/delete', { this.$root.api('blocking/delete', {
userId: this.user.id userId: this.user.id
}).then(() => { }).then(() => {
@ -128,6 +137,8 @@ export default Vue.extend({
}); });
}); });
} else { } else {
if (!await this.getConfirmed(this.$t('block-confirm'))) return;
this.$root.api('blocking/create', { this.$root.api('blocking/create', {
userId: this.user.id userId: this.user.id
}).then(() => { }).then(() => {
@ -164,7 +175,9 @@ export default Vue.extend({
}); });
}, },
toggleSilence() { async toggleSilence() {
if (!await this.getConfirmed(this.$t(this.user.isSilenced ? 'unsilence-confirm' : 'silence-confirm'))) return;
this.$root.api(this.user.isSilenced ? 'admin/unsilence-user' : 'admin/silence-user', { this.$root.api(this.user.isSilenced ? 'admin/unsilence-user' : 'admin/silence-user', {
userId: this.user.id userId: this.user.id
}).then(() => { }).then(() => {
@ -181,7 +194,9 @@ export default Vue.extend({
}); });
}, },
toggleSuspend() { async toggleSuspend() {
if (!await this.getConfirmed(this.$t(this.user.isSuspended ? 'unsuspend-confirm' : 'suspend-confirm'))) return;
this.$root.api(this.user.isSuspended ? 'admin/unsuspend-user' : 'admin/suspend-user', { this.$root.api(this.user.isSuspended ? 'admin/unsuspend-user' : 'admin/suspend-user', {
userId: this.user.id userId: this.user.id
}).then(() => { }).then(() => {
@ -196,7 +211,18 @@ export default Vue.extend({
text: e text: e
}); });
}); });
} },
async getConfirmed(text: string): Promise<Boolean> {
const confirm = await this.$root.dialog({
type: 'warning',
showCancelButton: true,
title: 'confirm',
text,
});
return !confirm.canceled;
},
} }
}); });
</script> </script>

View File

@ -8,7 +8,7 @@
<div class="is-remote" v-if="note.user.host != null"> <div class="is-remote" v-if="note.user.host != null">
<details> <details>
<summary><fa icon="exclamation-triangle"/> {{ $t('@.is-remote-post') }}</summary> <summary><fa icon="exclamation-triangle"/> {{ $t('@.is-remote-post') }}</summary>
<a :href="note.url || note.uri" target="_blank">{{ $t('@.view-on-remote') }}</a> <a :href="note.url || note.uri" rel="nofollow noopener" target="_blank">{{ $t('@.view-on-remote') }}</a>
</details> </details>
</div> </div>
<mk-note :note="note" :detail="true" :key="note.id"/> <mk-note :note="note" :detail="true" :key="note.id"/>

View File

@ -157,6 +157,7 @@ export default Vue.extend({
// オーバーフローしたら古い投稿は捨てる // オーバーフローしたら古い投稿は捨てる
if (this.notes.length >= displayLimit) { if (this.notes.length >= displayLimit) {
this.notes = this.notes.slice(0, displayLimit); this.notes = this.notes.slice(0, displayLimit);
this.cursor = this.notes[this.notes.length - 1].id
} }
} else { } else {
this.queue.push(note); this.queue.push(note);
@ -165,6 +166,7 @@ export default Vue.extend({
append(note) { append(note) {
this.notes.push(note); this.notes.push(note);
this.cursor = this.notes[this.notes.length - 1].id
}, },
releaseQueue() { releaseQueue() {

View File

@ -85,7 +85,7 @@ export default Vue.extend({
this.makePromise = cursor => this.$root.api('users/notes', { this.makePromise = cursor => this.$root.api('users/notes', {
userId: this.user.id, userId: this.user.id,
limit: fetchLimit + 1, limit: fetchLimit + 1,
untilId: cursor ? cursor : undefined, untilDate: cursor ? cursor : new Date().getTime() + 1000 * 86400 * 365,
withFiles: this.withFiles, withFiles: this.withFiles,
includeMyRenotes: this.$store.state.settings.showMyRenotes, includeMyRenotes: this.$store.state.settings.showMyRenotes,
includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes, includeRenotedMyNotes: this.$store.state.settings.showRenotedMyNotes,
@ -95,7 +95,7 @@ export default Vue.extend({
notes.pop(); notes.pop();
return { return {
notes: notes, notes: notes,
cursor: notes[notes.length - 1].id cursor: new Date(notes[notes.length - 1].createdAt).getTime()
}; };
} else { } else {
return { return {

View File

@ -8,7 +8,7 @@
<div class="is-remote" v-if="user.host != null"> <div class="is-remote" v-if="user.host != null">
<details> <details>
<summary><fa icon="exclamation-triangle"/> {{ $t('@.is-remote-user') }}</summary> <summary><fa icon="exclamation-triangle"/> {{ $t('@.is-remote-user') }}</summary>
<a :href="user.url || user.uri" target="_blank">{{ $t('@.view-on-remote') }}</a> <a :href="user.url || user.uri" rel="nofollow noopener" target="_blank">{{ $t('@.view-on-remote') }}</a>
</details> </details>
</div> </div>
<header :style="bannerStyle"> <header :style="bannerStyle">

View File

@ -21,14 +21,7 @@
<fa :icon="['far', 'laugh']"/> <fa :icon="['far', 'laugh']"/>
</button> </button>
</div> </div>
<div class="files" v-show="files.length != 0"> <x-post-form-attaches class="files" :files="files" :detachMediaFn="detachMedia"/>
<x-draggable :list="files" :options="{ animation: 150 }">
<div v-for="file in files" :key="file.id">
<div class="img" :style="{ backgroundImage: `url(${file.thumbnailUrl})` }" :title="file.name"></div>
<img class="remove" @click="detachMedia(file.id)" src="/assets/desktop/remove.png" :title="$t('attach-cancel')" alt=""/>
</div>
</x-draggable>
</div>
<input ref="file" type="file" multiple="multiple" tabindex="-1" @change="onChangeFile"/> <input ref="file" type="file" multiple="multiple" tabindex="-1" @change="onChangeFile"/>
<mk-uploader ref="uploader" @uploaded="attachMedia"/> <mk-uploader ref="uploader" @uploaded="attachMedia"/>
<footer> <footer>
@ -45,7 +38,7 @@
import define from '../../../common/define-widget'; import define from '../../../common/define-widget';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
import insertTextAtCursor from 'insert-text-at-cursor'; import insertTextAtCursor from 'insert-text-at-cursor';
import * as XDraggable from 'vuedraggable'; import XPostFormAttaches from '../components/post-form-attaches.vue';
export default define({ export default define({
name: 'post-form', name: 'post-form',
@ -56,7 +49,7 @@ export default define({
i18n: i18n('desktop/views/widgets/post-form.vue'), i18n: i18n('desktop/views/widgets/post-form.vue'),
components: { components: {
XDraggable XPostFormAttaches
}, },
data() { data() {
@ -176,10 +169,22 @@ export default define({
post() { post() {
this.posting = true; this.posting = true;
let visibility = 'public';
let localOnly = false;
const m = this.$store.state.settings.defaultNoteVisibility.match(/^local-(.+)/);
if (m) {
visibility = m[1];
localOnly = true;
} else {
visibility = this.$store.state.settings.defaultNoteVisibility;
}
this.$root.api('notes/create', { this.$root.api('notes/create', {
text: this.text == '' ? undefined : this.text, text: this.text == '' ? undefined : this.text,
fileIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined, fileIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined,
visibility: this.$store.state.settings.defaultNoteVisibility visibility,
localOnly,
}).then(data => { }).then(data => {
this.clear(); this.clear();
}).catch(err => { }).catch(err => {
@ -237,38 +242,6 @@ export default define({
& + .emoji & + .emoji
opacity 0.7 opacity 0.7
> .files
> div
padding 4px
&:after
content ""
display block
clear both
> div
float left
border solid 4px transparent
cursor move
&:hover > .remove
display block
> .img
width 64px
height 64px
background-size cover
background-position center center
> .remove
display none
position absolute
top -6px
right -6px
width 16px
height 16px
cursor pointer
> input[type=file] > input[type=file]
display none display none

View File

@ -7,7 +7,7 @@
<div class="mkw-rss--body" :data-mobile="platform == 'mobile'"> <div class="mkw-rss--body" :data-mobile="platform == 'mobile'">
<p class="fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p> <p class="fetching" v-if="fetching"><fa icon="spinner" pulse fixed-width/>{{ $t('@.loading') }}<mk-ellipsis/></p>
<div class="feed" v-else> <div class="feed" v-else>
<a v-for="item in items" :href="item.link" target="_blank" :title="item.title">{{ item.title }}</a> <a v-for="item in items" :href="item.link" rel="nofollow noopener" target="_blank" :title="item.title">{{ item.title }}</a>
</div> </div>
</div> </div>
</ui-container> </ui-container>

View File

@ -769,7 +769,6 @@ export default Vue.extend({
> .mk-uploader > .mk-uploader
height 100px height 100px
padding 16px padding 16px
background #fff
> input > input
display none display none

View File

@ -54,7 +54,7 @@
</div> </div>
<mk-poll v-if="appearNote.poll" :note="appearNote"/> <mk-poll v-if="appearNote.poll" :note="appearNote"/>
<mk-url-preview v-for="url in urls" :url="url" :key="url" :detail="true"/> <mk-url-preview v-for="url in urls" :url="url" :key="url" :detail="true"/>
<a class="location" v-if="appearNote.geo" :href="`https://maps.google.com/maps?q=${appearNote.geo.coordinates[1]},${appearNote.geo.coordinates[0]}`" target="_blank"><fa icon="map-marker-alt"/> {{ $t('location') }}</a> <a class="location" v-if="appearNote.geo" :href="`https://maps.google.com/maps?q=${appearNote.geo.coordinates[1]},${appearNote.geo.coordinates[0]}`" rel="noopener" target="_blank"><fa icon="map-marker-alt"/> {{ $t('location') }}</a>
<div class="map" v-if="appearNote.geo" ref="map"></div> <div class="map" v-if="appearNote.geo" ref="map"></div>
<div class="renote" v-if="appearNote.renote"> <div class="renote" v-if="appearNote.renote">
<mk-note-preview :note="appearNote.renote"/> <mk-note-preview :note="appearNote.renote"/>

View File

@ -32,7 +32,7 @@
<mk-media-list :media-list="appearNote.files"/> <mk-media-list :media-list="appearNote.files"/>
</div> </div>
<mk-poll v-if="appearNote.poll" :note="appearNote" ref="pollViewer"/> <mk-poll v-if="appearNote.poll" :note="appearNote" ref="pollViewer"/>
<a class="location" v-if="appearNote.geo" :href="`https://maps.google.com/maps?q=${appearNote.geo.coordinates[1]},${appearNote.geo.coordinates[0]}`" target="_blank"><fa icon="map-marker-alt"/> 位置情報</a> <a class="location" v-if="appearNote.geo" :href="`https://maps.google.com/maps?q=${appearNote.geo.coordinates[1]},${appearNote.geo.coordinates[0]}`" rel="noopener" target="_blank"><fa icon="map-marker-alt"/> 位置情報</a>
<div class="renote" v-if="appearNote.renote"><mk-note-preview :note="appearNote.renote"/></div> <div class="renote" v-if="appearNote.renote"><mk-note-preview :note="appearNote.renote"/></div>
<mk-url-preview v-for="url in urls" :url="url" :key="url" :compact="compact"/> <mk-url-preview v-for="url in urls" :url="url" :key="url" :compact="compact"/>
</div> </div>

View File

@ -157,6 +157,7 @@ export default Vue.extend({
// オーバーフローしたら古い投稿は捨てる // オーバーフローしたら古い投稿は捨てる
if (this.notes.length >= displayLimit) { if (this.notes.length >= displayLimit) {
this.notes = this.notes.slice(0, displayLimit); this.notes = this.notes.slice(0, displayLimit);
this.cursor = this.notes[this.notes.length - 1].id
} }
} else { } else {
this.queue.push(note); this.queue.push(note);
@ -165,6 +166,7 @@ export default Vue.extend({
append(note) { append(note) {
this.notes.push(note); this.notes.push(note);
this.cursor = this.notes[this.notes.length - 1].id
}, },
releaseQueue() { releaseQueue() {

View File

@ -27,15 +27,7 @@
<button class="emoji" @click="emoji" ref="emoji"> <button class="emoji" @click="emoji" ref="emoji">
<fa :icon="['far', 'laugh']"/> <fa :icon="['far', 'laugh']"/>
</button> </button>
<div class="files" :class="{ with: poll }" v-show="files.length != 0"> <x-post-form-attaches class="files" :files="files" :detachMediaFn="detachMedia"/>
<x-draggable :list="files" :options="{ animation: 150 }">
<div v-for="file in files" :key="file.id">
<div class="img" :style="{ backgroundImage: `url(${file.thumbnailUrl})` }" :title="file.name"></div>
<img class="remove" @click="detachMedia(file.id)" src="/assets/desktop/remove.png" :title="$t('attach-cancel')" alt=""/>
</div>
</x-draggable>
<p class="remain">{{ 4 - files.length }}/4</p>
</div>
<mk-poll-editor v-if="poll" ref="poll" @destroyed="poll = false" @updated="onPollUpdate()"/> <mk-poll-editor v-if="poll" ref="poll" @destroyed="poll = false" @updated="onPollUpdate()"/>
</div> </div>
</div> </div>
@ -65,7 +57,6 @@
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
import insertTextAtCursor from 'insert-text-at-cursor'; import insertTextAtCursor from 'insert-text-at-cursor';
import * as XDraggable from 'vuedraggable';
import getFace from '../../../common/scripts/get-face'; import getFace from '../../../common/scripts/get-face';
import MkVisibilityChooser from '../../../common/views/components/visibility-chooser.vue'; import MkVisibilityChooser from '../../../common/views/components/visibility-chooser.vue';
import { parse } from '../../../../../mfm/parse'; import { parse } from '../../../../../mfm/parse';
@ -74,13 +65,14 @@ import { erase, unique } from '../../../../../prelude/array';
import { length } from 'stringz'; import { length } from 'stringz';
import { toASCII } from 'punycode'; import { toASCII } from 'punycode';
import extractMentions from '../../../../../misc/extract-mentions'; import extractMentions from '../../../../../misc/extract-mentions';
import XPostFormAttaches from '../../../common/views/components/post-form-attaches.vue';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('desktop/views/components/post-form.vue'), i18n: i18n('desktop/views/components/post-form.vue'),
components: { components: {
XDraggable, MkVisibilityChooser,
MkVisibilityChooser XPostFormAttaches
}, },
props: { props: {
@ -640,17 +632,14 @@ export default Vue.extend({
border solid 4px transparent border solid 4px transparent
cursor move cursor move
&:hover > .remove
display block
> .img > .img
width 64px width 64px
height 64px height 64px
background-size cover background-size cover
background-position center center background-position center center
background-color: rgba(128, 128, 128, 0.3)
> .remove > .remove
display none
position absolute position absolute
top -6px top -6px
right -6px right -6px

View File

@ -4,7 +4,7 @@
<fa icon="exclamation-triangle"/> {{ $t('@.user-suspended') }} <fa icon="exclamation-triangle"/> {{ $t('@.user-suspended') }}
</div> </div>
<div class="is-remote" v-if="user.host != null" :class="{ shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }"> <div class="is-remote" v-if="user.host != null" :class="{ shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }">
<fa icon="exclamation-triangle"/> {{ $t('@.is-remote-user') }}<a :href="user.url || user.uri" target="_blank">{{ $t('@.view-on-remote') }}</a> <fa icon="exclamation-triangle"/> {{ $t('@.is-remote-user') }}<a :href="user.url || user.uri" rel="nofollow noopener" target="_blank">{{ $t('@.view-on-remote') }}</a>
</div> </div>
<div class="main"> <div class="main">
<x-header class="header" :user="user"/> <x-header class="header" :user="user"/>

View File

@ -1,7 +1,7 @@
<template> <template>
<div class="header" :class="{ shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }"> <div class="header" :class="{ shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }">
<div class="banner-container" :style="style"> <div class="banner-container" :style="style">
<div class="banner" ref="banner" :style="style" @click="onBannerClick"></div> <div class="banner" ref="banner" :style="style"></div>
<div class="fade"></div> <div class="fade"></div>
<div class="title"> <div class="title">
<p class="name"> <p class="name">
@ -105,14 +105,6 @@ export default Vue.extend({
if (blur <= 10) banner.style.filter = `blur(${blur}px)`; if (blur <= 10) banner.style.filter = `blur(${blur}px)`;
}, },
onBannerClick() {
if (!this.$store.getters.isSignedIn || this.$store.state.i.id != this.user.id) return;
this.$updateBanner().then(i => {
this.user.bannerUrl = i.bannerUrl;
});
},
menu() { menu() {
this.$root.new(XUserMenu, { this.$root.new(XUserMenu, {
source: this.$refs.menu, source: this.$refs.menu,
@ -171,9 +163,6 @@ export default Vue.extend({
> .menu > .menu
height 100% height 100%
display block
position absolute
left -42px
padding 0 14px padding 0 14px
color #fff color #fff
text-shadow 0 0 8px #000 text-shadow 0 0 8px #000

View File

@ -36,13 +36,13 @@ export default Vue.extend({
includeReplies: this.mode == 'with-replies', includeReplies: this.mode == 'with-replies',
includeMyRenotes: this.mode != 'my-posts', includeMyRenotes: this.mode != 'my-posts',
withFiles: this.mode == 'with-media', withFiles: this.mode == 'with-media',
untilId: cursor ? cursor : undefined untilDate: cursor ? cursor : new Date().getTime() + 1000 * 86400 * 365
}).then(notes => { }).then(notes => {
if (notes.length == fetchLimit + 1) { if (notes.length == fetchLimit + 1) {
notes.pop(); notes.pop();
return { return {
notes: notes, notes: notes,
cursor: notes[notes.length - 1].id cursor: new Date(notes[notes.length - 1].createdAt).getTime()
}; };
} else { } else {
return { return {

View File

@ -11,7 +11,9 @@
<div class="mkw-polls--body"> <div class="mkw-polls--body">
<div class="poll" v-if="!fetching && poll != null"> <div class="poll" v-if="!fetching && poll != null">
<p v-if="poll.text"><router-link :to="poll | notePage">{{ poll.text }}</router-link></p> <p v-if="poll.text"><router-link :to="poll | notePage">
<mfm :text="poll.text" :author="poll.user" :custom-emojis="poll.emojis"/>
</router-link></p>
<p v-if="!poll.text"><router-link :to="poll | notePage"><fa icon="link"/></router-link></p> <p v-if="!poll.text"><router-link :to="poll | notePage"><fa icon="link"/></router-link></p>
<mk-poll :note="poll"/> <mk-poll :note="poll"/>
</div> </div>

View File

@ -7,6 +7,7 @@
</div> </div>
<a class="kkjnbbplepmiyuadieoenjgutgcmtsvu" v-else <a class="kkjnbbplepmiyuadieoenjgutgcmtsvu" v-else
:href="video.url" :href="video.url"
rel="nofollow noopener"
target="_blank" target="_blank"
:style="imageStyle" :style="imageStyle"
:title="video.name" :title="video.name"

View File

@ -40,7 +40,7 @@
</div> </div>
<mk-poll v-if="appearNote.poll" :note="appearNote"/> <mk-poll v-if="appearNote.poll" :note="appearNote"/>
<mk-url-preview v-for="url in urls" :url="url" :key="url" :detail="true"/> <mk-url-preview v-for="url in urls" :url="url" :key="url" :detail="true"/>
<a class="location" v-if="appearNote.geo" :href="`https://maps.google.com/maps?q=${appearNote.geo.coordinates[1]},${appearNote.geo.coordinates[0]}`" target="_blank"><fa icon="map-marker-alt"/> {{ $t('location') }}</a> <a class="location" v-if="appearNote.geo" :href="`https://maps.google.com/maps?q=${appearNote.geo.coordinates[1]},${appearNote.geo.coordinates[0]}`" rel="noopener" target="_blank"><fa icon="map-marker-alt"/> {{ $t('location') }}</a>
<div class="map" v-if="appearNote.geo" ref="map"></div> <div class="map" v-if="appearNote.geo" ref="map"></div>
<div class="renote" v-if="appearNote.renote"> <div class="renote" v-if="appearNote.renote">
<mk-note-preview :note="appearNote.renote"/> <mk-note-preview :note="appearNote.renote"/>

View File

@ -32,7 +32,7 @@
</div> </div>
<mk-poll v-if="appearNote.poll" :note="appearNote" ref="pollViewer"/> <mk-poll v-if="appearNote.poll" :note="appearNote" ref="pollViewer"/>
<mk-url-preview v-for="url in urls" :url="url" :key="url" :compact="true"/> <mk-url-preview v-for="url in urls" :url="url" :key="url" :compact="true"/>
<a class="location" v-if="appearNote.geo" :href="`https://maps.google.com/maps?q=${appearNote.geo.coordinates[1]},${appearNote.geo.coordinates[0]}`" target="_blank"><fa icon="map-marker-alt"/> {{ $t('location') }}</a> <a class="location" v-if="appearNote.geo" :href="`https://maps.google.com/maps?q=${appearNote.geo.coordinates[1]},${appearNote.geo.coordinates[0]}`" rel="noopener" target="_blank"><fa icon="map-marker-alt"/> {{ $t('location') }}</a>
<div class="renote" v-if="appearNote.renote"><mk-note-preview :note="appearNote.renote"/></div> <div class="renote" v-if="appearNote.renote"><mk-note-preview :note="appearNote.renote"/></div>
</div> </div>
<span class="app" v-if="appearNote.app && $store.state.settings.showVia">via <b>{{ appearNote.app.name }}</b></span> <span class="app" v-if="appearNote.app && $store.state.settings.showVia">via <b>{{ appearNote.app.name }}</b></span>

View File

@ -151,6 +151,7 @@ export default Vue.extend({
// オーバーフローしたら古い投稿は捨てる // オーバーフローしたら古い投稿は捨てる
if (this.notes.length >= displayLimit) { if (this.notes.length >= displayLimit) {
this.notes = this.notes.slice(0, displayLimit); this.notes = this.notes.slice(0, displayLimit);
this.cursor = this.notes[this.notes.length - 1].id
} }
} else { } else {
this.queue.push(note); this.queue.push(note);
@ -159,6 +160,7 @@ export default Vue.extend({
append(note) { append(note) {
this.notes.push(note); this.notes.push(note);
this.cursor = this.notes[this.notes.length - 1].id
}, },
releaseQueue() { releaseQueue() {

View File

@ -21,13 +21,7 @@
</div> </div>
<input v-show="useCw" ref="cw" v-model="cw" :placeholder="$t('annotations')" v-autocomplete="{ model: 'cw' }"> <input v-show="useCw" ref="cw" v-model="cw" :placeholder="$t('annotations')" v-autocomplete="{ model: 'cw' }">
<textarea v-model="text" ref="text" :disabled="posting" :placeholder="placeholder" v-autocomplete="{ model: 'text' }"></textarea> <textarea v-model="text" ref="text" :disabled="posting" :placeholder="placeholder" v-autocomplete="{ model: 'text' }"></textarea>
<div class="attaches" v-show="files.length != 0"> <x-post-form-attaches class="attaches" :files="files"/>
<x-draggable class="files" :list="files" :options="{ animation: 150 }">
<div class="file" v-for="file in files" :key="file.id">
<div class="img" :style="`background-image: url(${file.thumbnailUrl})`" @click="detachMedia(file)"></div>
</div>
</x-draggable>
</div>
<mk-poll-editor v-if="poll" ref="poll" @destroyed="poll = false" @updated="onPollUpdate()"/> <mk-poll-editor v-if="poll" ref="poll" @destroyed="poll = false" @updated="onPollUpdate()"/>
<mk-uploader ref="uploader" @uploaded="attachMedia" @change="onChangeUploadings"/> <mk-uploader ref="uploader" @uploaded="attachMedia" @change="onChangeUploadings"/>
<footer> <footer>
@ -57,7 +51,6 @@
import Vue from 'vue'; import Vue from 'vue';
import i18n from '../../../i18n'; import i18n from '../../../i18n';
import insertTextAtCursor from 'insert-text-at-cursor'; import insertTextAtCursor from 'insert-text-at-cursor';
import * as XDraggable from 'vuedraggable';
import MkVisibilityChooser from '../../../common/views/components/visibility-chooser.vue'; import MkVisibilityChooser from '../../../common/views/components/visibility-chooser.vue';
import getFace from '../../../common/scripts/get-face'; import getFace from '../../../common/scripts/get-face';
import { parse } from '../../../../../mfm/parse'; import { parse } from '../../../../../mfm/parse';
@ -66,11 +59,12 @@ import { erase, unique } from '../../../../../prelude/array';
import { length } from 'stringz'; import { length } from 'stringz';
import { toASCII } from 'punycode'; import { toASCII } from 'punycode';
import extractMentions from '../../../../../misc/extract-mentions'; import extractMentions from '../../../../../misc/extract-mentions';
import XPostFormAttaches from '../../../common/views/components/post-form-attaches.vue';
export default Vue.extend({ export default Vue.extend({
i18n: i18n('mobile/views/components/post-form.vue'), i18n: i18n('mobile/views/components/post-form.vue'),
components: { components: {
XDraggable XPostFormAttaches
}, },
props: { props: {
@ -264,8 +258,8 @@ export default Vue.extend({
this.$emit('change-attached-files', this.files); this.$emit('change-attached-files', this.files);
}, },
detachMedia(file) { detachMedia(id) {
this.files = this.files.filter(x => x.id != file.id); this.files = this.files.filter(x => x.id != id);
this.$emit('change-attached-files', this.files); this.$emit('change-attached-files', this.files);
}, },
@ -481,32 +475,6 @@ export default Vue.extend({
min-width 100% min-width 100%
min-height 80px min-height 80px
> .attaches
> .files
display block
margin 0
padding 4px
list-style none
&:after
content ""
display block
clear both
> .file
display block
float left
margin 0
padding 0
border solid 4px transparent
> .img
width 64px
height 64px
background-size cover
background-position center center
> .mk-uploader > .mk-uploader
margin 8px 0 0 0 margin 8px 0 0 0
padding 8px padding 8px

View File

@ -21,13 +21,13 @@ export default Vue.extend({
userId: this.user.id, userId: this.user.id,
limit: fetchLimit + 1, limit: fetchLimit + 1,
withFiles: this.withMedia, withFiles: this.withMedia,
untilId: cursor ? cursor : undefined untilDate: cursor ? cursor : new Date().getTime() + 1000 * 86400 * 365
}).then(notes => { }).then(notes => {
if (notes.length == fetchLimit + 1) { if (notes.length == fetchLimit + 1) {
notes.pop(); notes.pop();
return { return {
notes: notes, notes: notes,
cursor: notes[notes.length - 1].id cursor: new Date(notes[notes.length - 1].createdAt).getTime()
}; };
} else { } else {
return { return {

View File

@ -5,7 +5,7 @@
</template> </template>
<div class="wwtwuxyh" v-if="!fetching"> <div class="wwtwuxyh" v-if="!fetching">
<div class="is-suspended" v-if="user.isSuspended"><p><fa icon="exclamation-triangle"/> {{ $t('@.user-suspended') }}</p></div> <div class="is-suspended" v-if="user.isSuspended"><p><fa icon="exclamation-triangle"/> {{ $t('@.user-suspended') }}</p></div>
<div class="is-remote" v-if="user.host != null"><p><fa icon="exclamation-triangle"/> {{ $t('@.is-remote-user') }}<a :href="user.url || user.uri" target="_blank">{{ $t('@.view-on-remote') }}</a></p></div> <div class="is-remote" v-if="user.host != null"><p><fa icon="exclamation-triangle"/> {{ $t('@.is-remote-user') }}<a :href="user.url || user.uri" rel="nofollow noopener" target="_blank">{{ $t('@.view-on-remote') }}</a></p></div>
<header> <header>
<div class="banner" :style="style"></div> <div class="banner" :style="style"></div>
<div class="body"> <div class="body">

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@ -42,6 +42,8 @@ export type Source = {
accesslog?: string; accesslog?: string;
clusterLimit?: number; clusterLimit?: number;
outgoingAddressFamily?: 'ipv4' | 'ipv6' | 'dual';
}; };
/** /**

View File

@ -56,20 +56,12 @@ function cpuUsage() {
// MEMORY(excl buffer + cache) STAT // MEMORY(excl buffer + cache) STAT
async function usedMem() { async function usedMem() {
try { const data = await sysUtils.mem();
const data = await sysUtils.mem(); return data.active;
return data.active;
} catch (error) {
throw error;
}
} }
// TOTAL MEMORY STAT // TOTAL MEMORY STAT
async function totalMem() { async function totalMem() {
try { const data = await sysUtils.mem();
const data = await sysUtils.mem(); return data.total;
return data.total;
} catch (error) {
throw error;
}
} }

View File

@ -6,4 +6,4 @@ block main
block footer block footer
p p
= i18n('docs.edit-this-page-on-github') = i18n('docs.edit-this-page-on-github')
a(href=src target="_blank")= i18n('docs.edit-this-page-on-github-link') a(href=src rel="noopener" target="_blank")= i18n('docs.edit-this-page-on-github-link')

View File

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

View File

@ -16,6 +16,11 @@ export function extractDbHost(uri: string) {
return toDbHost(url.hostname); return toDbHost(url.hostname);
} }
export function extractApHost(uri: string) {
const url = new URL(uri);
return toApHost(url.hostname);
}
export function toDbHost(host: string) { export function toDbHost(host: string) {
if (host == null) return null; if (host == null) return null;
return toUnicode(host.toLowerCase()); return toUnicode(host.toLowerCase());

12
src/misc/gen-id.ts Normal file
View File

@ -0,0 +1,12 @@
import { genMeid7 } from './id/meid7';
const method = 'meid7';
export function genId(date?: Date): string {
if (!date || (date > new Date())) date = new Date();
switch (method) {
case 'meid7': return genMeid7(date);
default: throw new Error('unknown id generation method');
}
}

28
src/misc/id/meid7.ts Normal file
View File

@ -0,0 +1,28 @@
const CHARS = '0123456789abcdef';
// 4bit Fixed hex value '7'
// 44bit UNIX Time ms in Hex
// 48bit Random value in Hex
function getTime(time: number) {
if (time < 0) time = 0;
if (time === 0) {
return CHARS[0];
}
return time.toString(16).padStart(11, CHARS[0]);
}
function getRandom() {
let str = '';
for (let i = 0; i < 12; i++) {
str += CHARS[Math.floor(Math.random() * CHARS.length)];
}
return str;
}
export function genMeid7(date: Date): string {
return '7' + getTime(date.getTime()) + getRandom();
}

View File

@ -1,19 +1,19 @@
export interface Maybe<T> { export interface IMaybe<T> {
isJust(): this is Just<T>; isJust(): this is IJust<T>;
} }
export type Just<T> = Maybe<T> & { export interface IJust<T> extends IMaybe<T> {
get(): T get(): T;
}; }
export function just<T>(value: T): Just<T> { export function just<T>(value: T): IJust<T> {
return { return {
isJust: () => true, isJust: () => true,
get: () => value get: () => value
}; };
} }
export function nothing<T>(): Maybe<T> { export function nothing<T>(): IMaybe<T> {
return { return {
isJust: () => false, isJust: () => false,
}; };

View File

@ -16,10 +16,9 @@ export async function deleteDriveFiles(job: Bull.Job, done: any): Promise<void>
}); });
let deletedCount = 0; let deletedCount = 0;
let ended = false;
let cursor: any = null; let cursor: any = null;
while (!ended) { while (true) {
const files = await DriveFile.find({ const files = await DriveFile.find({
userId: user._id, userId: user._id,
...(cursor ? { _id: { $gt: cursor } } : {}) ...(cursor ? { _id: { $gt: cursor } } : {})
@ -31,7 +30,6 @@ export async function deleteDriveFiles(job: Bull.Job, done: any): Promise<void>
}); });
if (files.length === 0) { if (files.length === 0) {
ended = true;
job.progress(100); job.progress(100);
break; break;
} }

View File

@ -32,10 +32,9 @@ export async function exportBlocking(job: Bull.Job, done: any): Promise<void> {
const stream = fs.createWriteStream(path, { flags: 'a' }); const stream = fs.createWriteStream(path, { flags: 'a' });
let exportedCount = 0; let exportedCount = 0;
let ended = false;
let cursor: any = null; let cursor: any = null;
while (!ended) { while (true) {
const blockings = await Blocking.find({ const blockings = await Blocking.find({
blockerId: user._id, blockerId: user._id,
...(cursor ? { _id: { $gt: cursor } } : {}) ...(cursor ? { _id: { $gt: cursor } } : {})
@ -47,7 +46,6 @@ export async function exportBlocking(job: Bull.Job, done: any): Promise<void> {
}); });
if (blockings.length === 0) { if (blockings.length === 0) {
ended = true;
job.progress(100); job.progress(100);
break; break;
} }
@ -81,7 +79,7 @@ export async function exportBlocking(job: Bull.Job, done: any): Promise<void> {
logger.succ(`Exported to: ${path}`); logger.succ(`Exported to: ${path}`);
const fileName = 'blocking-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv'; const fileName = 'blocking-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv';
const driveFile = await addFile(user, path, fileName); const driveFile = await addFile(user, path, fileName, null, null, true);
logger.succ(`Exported to: ${driveFile._id}`); logger.succ(`Exported to: ${driveFile._id}`);
cleanup(); cleanup();

View File

@ -32,10 +32,9 @@ export async function exportFollowing(job: Bull.Job, done: any): Promise<void> {
const stream = fs.createWriteStream(path, { flags: 'a' }); const stream = fs.createWriteStream(path, { flags: 'a' });
let exportedCount = 0; let exportedCount = 0;
let ended = false;
let cursor: any = null; let cursor: any = null;
while (!ended) { while (true) {
const followings = await Following.find({ const followings = await Following.find({
followerId: user._id, followerId: user._id,
...(cursor ? { _id: { $gt: cursor } } : {}) ...(cursor ? { _id: { $gt: cursor } } : {})
@ -47,7 +46,6 @@ export async function exportFollowing(job: Bull.Job, done: any): Promise<void> {
}); });
if (followings.length === 0) { if (followings.length === 0) {
ended = true;
job.progress(100); job.progress(100);
break; break;
} }
@ -81,7 +79,7 @@ export async function exportFollowing(job: Bull.Job, done: any): Promise<void> {
logger.succ(`Exported to: ${path}`); logger.succ(`Exported to: ${path}`);
const fileName = 'following-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv'; const fileName = 'following-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv';
const driveFile = await addFile(user, path, fileName); const driveFile = await addFile(user, path, fileName, null, null, true);
logger.succ(`Exported to: ${driveFile._id}`); logger.succ(`Exported to: ${driveFile._id}`);
cleanup(); cleanup();

View File

@ -32,10 +32,9 @@ export async function exportMute(job: Bull.Job, done: any): Promise<void> {
const stream = fs.createWriteStream(path, { flags: 'a' }); const stream = fs.createWriteStream(path, { flags: 'a' });
let exportedCount = 0; let exportedCount = 0;
let ended = false;
let cursor: any = null; let cursor: any = null;
while (!ended) { while (true) {
const mutes = await Mute.find({ const mutes = await Mute.find({
muterId: user._id, muterId: user._id,
...(cursor ? { _id: { $gt: cursor } } : {}) ...(cursor ? { _id: { $gt: cursor } } : {})
@ -47,7 +46,6 @@ export async function exportMute(job: Bull.Job, done: any): Promise<void> {
}); });
if (mutes.length === 0) { if (mutes.length === 0) {
ended = true;
job.progress(100); job.progress(100);
break; break;
} }
@ -81,7 +79,7 @@ export async function exportMute(job: Bull.Job, done: any): Promise<void> {
logger.succ(`Exported to: ${path}`); logger.succ(`Exported to: ${path}`);
const fileName = 'mute-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv'; const fileName = 'mute-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv';
const driveFile = await addFile(user, path, fileName); const driveFile = await addFile(user, path, fileName, null, null, true);
logger.succ(`Exported to: ${driveFile._id}`); logger.succ(`Exported to: ${driveFile._id}`);
cleanup(); cleanup();

View File

@ -42,10 +42,9 @@ export async function exportNotes(job: Bull.Job, done: any): Promise<void> {
}); });
let exportedNotesCount = 0; let exportedNotesCount = 0;
let ended = false;
let cursor: any = null; let cursor: any = null;
while (!ended) { while (true) {
const notes = await Note.find({ const notes = await Note.find({
userId: user._id, userId: user._id,
...(cursor ? { _id: { $gt: cursor } } : {}) ...(cursor ? { _id: { $gt: cursor } } : {})
@ -57,7 +56,6 @@ export async function exportNotes(job: Bull.Job, done: any): Promise<void> {
}); });
if (notes.length === 0) { if (notes.length === 0) {
ended = true;
job.progress(100); job.progress(100);
break; break;
} }
@ -101,7 +99,7 @@ export async function exportNotes(job: Bull.Job, done: any): Promise<void> {
logger.succ(`Exported to: ${path}`); logger.succ(`Exported to: ${path}`);
const fileName = 'notes-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.json'; const fileName = 'notes-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.json';
const driveFile = await addFile(user, path, fileName); const driveFile = await addFile(user, path, fileName, null, null, true);
logger.succ(`Exported to: ${driveFile._id}`); logger.succ(`Exported to: ${driveFile._id}`);
cleanup(); cleanup();

View File

@ -65,7 +65,7 @@ export async function exportUserLists(job: Bull.Job, done: any): Promise<void> {
logger.succ(`Exported to: ${path}`); logger.succ(`Exported to: ${path}`);
const fileName = 'user-lists-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv'; const fileName = 'user-lists-' + dateFormat(new Date(), 'yyyy-mm-dd-HH-MM-ss') + '.csv';
const driveFile = await addFile(user, path, fileName); const driveFile = await addFile(user, path, fileName, null, null, true);
logger.succ(`Exported to: ${driveFile._id}`); logger.succ(`Exported to: ${driveFile._id}`);
cleanup(); cleanup();

View File

@ -34,7 +34,8 @@ export async function importFollowing(job: Bull.Job, done: any): Promise<void> {
linenum++; linenum++;
try { try {
const { username, host } = parseAcct(line.trim()); const acct = line.split(',')[0].trim();
const { username, host } = parseAcct(acct);
let target = isSelfHost(host) ? await User.findOne({ let target = isSelfHost(host) ? await User.findOne({
host: null, host: null,

View File

@ -11,6 +11,8 @@ import Logger from '../../services/logger';
import { registerOrFetchInstanceDoc } from '../../services/register-or-fetch-instance-doc'; import { registerOrFetchInstanceDoc } from '../../services/register-or-fetch-instance-doc';
import Instance from '../../models/instance'; import Instance from '../../models/instance';
import instanceChart from '../../services/chart/instance'; import instanceChart from '../../services/chart/instance';
import { validActor } from '../../remote/activitypub/type';
import { toDbHost } from '../../misc/convert-host';
const logger = new Logger('inbox'); const logger = new Logger('inbox');
@ -46,7 +48,7 @@ export default async (job: Bull.Job): Promise<void> => {
// ブロックしてたら中断 // ブロックしてたら中断
// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく // TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
const instance = await Instance.findOne({ host: host.toLowerCase() }); const instance = await Instance.findOne({ host: toDbHost(host) });
if (instance && instance.isBlocked) { if (instance && instance.isBlocked) {
logger.info(`Blocked request: ${host}`); logger.info(`Blocked request: ${host}`);
return; return;
@ -65,7 +67,7 @@ export default async (job: Bull.Job): Promise<void> => {
// ブロックしてたら中断 // ブロックしてたら中断
// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく // TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
const instance = await Instance.findOne({ host: host.toLowerCase() }); const instance = await Instance.findOne({ host: toDbHost(host) });
if (instance && instance.isBlocked) { if (instance && instance.isBlocked) {
logger.warn(`Blocked request: ${host}`); logger.warn(`Blocked request: ${host}`);
return; return;
@ -79,7 +81,7 @@ export default async (job: Bull.Job): Promise<void> => {
// Update Person activityの場合は、ここで署名検証/更新処理まで実施して終了 // Update Person activityの場合は、ここで署名検証/更新処理まで実施して終了
if (activity.type === 'Update') { if (activity.type === 'Update') {
if (activity.object && activity.object.type === 'Person') { if (activity.object && validActor.includes(activity.object.type)) {
if (user == null) { if (user == null) {
logger.warn('Update activity received, but user not registed.'); logger.warn('Update activity received, but user not registed.');
} else if (!httpSignature.verifySignature(signature, user.publicKey.publicKeyPem)) { } else if (!httpSignature.verifySignature(signature, user.publicKey.publicKeyPem)) {

View File

@ -20,10 +20,32 @@ import { apLogger } from '../logger';
import { IDriveFile } from '../../../models/drive-file'; import { IDriveFile } from '../../../models/drive-file';
import { deliverQuestionUpdate } from '../../../services/note/polls/update'; import { deliverQuestionUpdate } from '../../../services/note/polls/update';
import Instance from '../../../models/instance'; import Instance from '../../../models/instance';
import { extractDbHost } from '../../../misc/convert-host'; import { extractDbHost, extractApHost } from '../../../misc/convert-host';
const logger = apLogger; const logger = apLogger;
export function validateNote(object: any, uri: string) {
const expectHost = extractApHost(uri);
if (object == null) {
return new Error('invalid Note: object is null');
}
if (!['Note', 'Question', 'Article'].includes(object.type)) {
return new Error(`invalid Note: invalied object type ${object.type}`);
}
if (object.id && extractApHost(object.id) !== expectHost) {
return new Error(`invalid Note: id has different host. expected: ${expectHost}, actual: ${extractApHost(object.id)}`);
}
if (object.attributedTo && extractApHost(object.attributedTo) !== expectHost) {
return new Error(`invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${extractApHost(object.attributedTo)}`);
}
return null;
}
/** /**
* Noteをフェッチします。 * Noteをフェッチします。
* *
@ -57,8 +79,10 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
const object: any = await resolver.resolve(value); const object: any = await resolver.resolve(value);
if (!object || !['Note', 'Question', 'Article'].includes(object.type)) { const entryUri = value.id || value;
logger.error(`invalid note: ${value}`, { const err = validateNote(object, entryUri);
if (err) {
logger.error(`${err.message}`, {
resolver: { resolver: {
history: resolver.getHistory() history: resolver.getHistory()
}, },
@ -241,7 +265,7 @@ export async function resolveNote(value: string | IObject, resolver?: Resolver):
// リモートサーバーからフェッチしてきて登録 // リモートサーバーからフェッチしてきて登録
// ここでuriの代わりに添付されてきたNote Objectが指定されていると、サーバーフェッチを経ずにートが生成されるが // ここでuriの代わりに添付されてきたNote Objectが指定されていると、サーバーフェッチを経ずにートが生成されるが
// 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。 // 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。
return await createNote(uri, resolver); return await createNote(uri, resolver, true);
} }
export async function extractEmojis(tags: ITag[], host_: string) { export async function extractEmojis(tags: ITag[], host_: string) {

View File

@ -6,7 +6,7 @@ import config from '../../../config';
import User, { validateUsername, isValidName, IUser, IRemoteUser, isRemoteUser } from '../../../models/user'; import User, { validateUsername, isValidName, IUser, IRemoteUser, isRemoteUser } from '../../../models/user';
import Resolver from '../resolver'; import Resolver from '../resolver';
import { resolveImage } from './image'; import { resolveImage } from './image';
import { isCollectionOrOrderedCollection, isCollection, IPerson } from '../type'; import { isCollectionOrOrderedCollection, isCollection, IPerson, validActor } from '../type';
import { IDriveFile } from '../../../models/drive-file'; import { IDriveFile } from '../../../models/drive-file';
import Meta from '../../../models/meta'; import Meta from '../../../models/meta';
import { fromHtml } from '../../../mfm/fromHtml'; import { fromHtml } from '../../../mfm/fromHtml';
@ -38,7 +38,7 @@ function validatePerson(x: any, uri: string) {
return new Error('invalid person: object is null'); return new Error('invalid person: object is null');
} }
if (x.type != 'Person' && x.type != 'Service') { if (!validActor.includes(x.type)) {
return new Error(`invalid person: object is not a person or service '${x.type}'`); return new Error(`invalid person: object is not a person or service '${x.type}'`);
} }
@ -294,13 +294,6 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje
} }
//#endregion //#endregion
// 繋がらないインスタンスに何回も試行するのを防ぐ, 後続の同様処理の連続試行を防ぐ ため 試行前にも更新する
await User.update({ _id: exist._id }, {
$set: {
lastFetchedAt: new Date(),
},
});
if (resolver == null) resolver = new Resolver(); if (resolver == null) resolver = new Resolver();
const object = hint || await resolver.resolve(uri) as any; const object = hint || await resolver.resolve(uri) as any;

View File

@ -4,13 +4,13 @@ import { URL } from 'url';
import * as crypto from 'crypto'; import * as crypto from 'crypto';
import { lookup, IRunOptions } from 'lookup-dns-cache'; import { lookup, IRunOptions } from 'lookup-dns-cache';
import * as promiseAny from 'promise-any'; import * as promiseAny from 'promise-any';
import { toUnicode } from 'punycode';
import config from '../../config'; import config from '../../config';
import { ILocalUser } from '../../models/user'; import { ILocalUser } from '../../models/user';
import { publishApLogStream } from '../../services/stream'; import { publishApLogStream } from '../../services/stream';
import { apLogger } from './logger'; import { apLogger } from './logger';
import Instance from '../../models/instance'; import Instance from '../../models/instance';
import { toDbHost } from '../../misc/convert-host';
export const logger = apLogger.createSubLogger('deliver'); export const logger = apLogger.createSubLogger('deliver');
@ -23,7 +23,7 @@ export default async (user: ILocalUser, url: string, object: any) => {
// ブロックしてたら中断 // ブロックしてたら中断
// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく // TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
const instance = await Instance.findOne({ host: toUnicode(host) }); const instance = await Instance.findOne({ host: toDbHost(host) });
if (instance && instance.isBlocked) return; if (instance && instance.isBlocked) return;
const data = JSON.stringify(object); const data = JSON.stringify(object);
@ -35,7 +35,7 @@ export default async (user: ILocalUser, url: string, object: any) => {
const addr = await resolveAddr(hostname); const addr = await resolveAddr(hostname);
if (!addr) return; if (!addr) return;
const _ = new Promise((resolve, reject) => { await new Promise((resolve, reject) => {
const req = request({ const req = request({
protocol, protocol,
hostname: addr, hostname: addr,
@ -82,8 +82,6 @@ export default async (user: ILocalUser, url: string, object: any) => {
req.end(data); req.end(data);
}); });
await _;
//#region Log //#region Log
publishApLogStream({ publishApLogStream({
direction: 'out', direction: 'out',
@ -98,11 +96,18 @@ export default async (user: ILocalUser, url: string, object: any) => {
* Resolve host (with cached, asynchrony) * Resolve host (with cached, asynchrony)
*/ */
async function resolveAddr(domain: string) { async function resolveAddr(domain: string) {
const af = config.outgoingAddressFamily || 'ipv4';
const useV4 = af == 'ipv4' || af == 'dual';
const useV6 = af == 'ipv6' || af == 'dual';
const promises = [];
if (!useV4 && !useV6) throw 'No usable address family available';
if (useV4) promises.push(resolveAddrInner(domain, { family: 4 }));
if (useV6) promises.push(resolveAddrInner(domain, { family: 6 }));
// v4/v6で先に取得できた方を採用する // v4/v6で先に取得できた方を採用する
return await promiseAny([ return await promiseAny(promises);
resolveAddrInner(domain, { family: 4 }),
resolveAddrInner(domain, { family: 6 })
]);
} }
function resolveAddrInner(domain: string, options: IRunOptions = {}): Promise<string> { function resolveAddrInner(domain: string, options: IRunOptions = {}): Promise<string> {

View File

@ -65,6 +65,8 @@ interface IQuestionChoice {
_misskey_votes?: number; _misskey_votes?: number;
} }
export const validActor = ['Person', 'Service'];
export interface IPerson extends IObject { export interface IPerson extends IObject {
type: 'Person'; type: 'Person';
name: string; name: string;

View File

@ -9,7 +9,7 @@ import chalk from 'chalk';
const logger = remoteLogger.createSubLogger('resolve-user'); const logger = remoteLogger.createSubLogger('resolve-user');
export default async (username: string, _host: string, option?: any, resync?: boolean): Promise<IUser> => { export default async (username: string, _host: string, option?: any, resync = false): Promise<IUser> => {
const usernameLower = username.toLowerCase(); const usernameLower = username.toLowerCase();
if (_host == null) { if (_host == null) {
@ -28,7 +28,7 @@ export default async (username: string, _host: string, option?: any, resync?: bo
return await User.findOne({ usernameLower, host: null }); return await User.findOne({ usernameLower, host: null });
} }
const user = await User.findOne({ usernameLower, host }, option); const user = await User.findOne({ usernameLower, host }, option) as IRemoteUser;
const acctLower = `${usernameLower}@${hostAscii}`; const acctLower = `${usernameLower}@${hostAscii}`;
@ -39,14 +39,22 @@ export default async (username: string, _host: string, option?: any, resync?: bo
return await createPerson(self.href); return await createPerson(self.href);
} }
if (resync) { // resyncオプション OR ユーザー情報が古い場合は、WebFilgerからやりなおして返す
if (resync || user.lastFetchedAt == null || Date.now() - user.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) {
// 繋がらないインスタンスに何回も試行するのを防ぐ, 後続の同様処理の連続試行を防ぐ ため 試行前にも更新する
await User.update({ _id: user._id }, {
$set: {
lastFetchedAt: new Date(),
},
});
logger.info(`try resync: ${acctLower}`); logger.info(`try resync: ${acctLower}`);
const self = await resolveSelf(acctLower); const self = await resolveSelf(acctLower);
if ((user as IRemoteUser).uri !== self.href) { if (user.uri !== self.href) {
// if uri mismatch, Fix (user@host <=> AP's Person id(IRemoteUser.uri)) mapping. // if uri mismatch, Fix (user@host <=> AP's Person id(IRemoteUser.uri)) mapping.
logger.info(`uri missmatch: ${acctLower}`); logger.info(`uri missmatch: ${acctLower}`);
logger.info(`recovery missmatch uri for (username=${username}, host=${host}) from ${(user as IRemoteUser).uri} to ${self.href}`); logger.info(`recovery missmatch uri for (username=${username}, host=${host}) from ${user.uri} to ${self.href}`);
// validate uri // validate uri
const uri = new URL(self.href); const uri = new URL(self.href);
@ -79,8 +87,8 @@ export default async (username: string, _host: string, option?: any, resync?: bo
async function resolveSelf(acctLower: string) { async function resolveSelf(acctLower: string) {
logger.info(`WebFinger for ${chalk.yellow(acctLower)}`); logger.info(`WebFinger for ${chalk.yellow(acctLower)}`);
const finger = await webFinger(acctLower).catch(e => { const finger = await webFinger(acctLower).catch(e => {
logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: ${e.message} (${e.status})`); logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: ${ e.statusCode || e.message }`);
throw e; throw new Error(`Failed to WebFinger for ${acctLower}: ${ e.statusCode || e.message }`);
}); });
const self = finger.links.find(link => link.rel && link.rel.toLowerCase() === 'self'); const self = finger.links.find(link => link.rel && link.rel.toLowerCase() === 'self');
if (!self) { if (!self) {

View File

@ -1,6 +1,7 @@
import { WebFinger } from 'webfinger.js'; import config from '../config';
import * as request from 'request-promise-native';
const webFinger = new WebFinger({ }); import { URL } from 'url';
import { query as urlQuery } from '../prelude/url';
type ILink = { type ILink = {
href: string; href: string;
@ -12,12 +13,33 @@ type IWebFinger = {
subject: string; subject: string;
}; };
export default async function resolve(query: any): Promise<IWebFinger> { export default async function(query: string): Promise<IWebFinger> {
return await new Promise((res, rej) => webFinger.lookup(query, (error: Error | string, result: any) => { const url = genUrl(query);
if (error) {
return rej(error);
}
res(result.object); return await request({
})) as IWebFinger; url,
proxy: config.proxy,
timeout: 10 * 1000,
forever: true,
headers: {
'User-Agent': config.userAgent,
Accept: 'application/jrd+json, application/json'
},
json: true
});
}
function genUrl(query: string) {
if (query.match(/^https?:\/\//)) {
const u = new URL(query);
return `${u.protocol}//${u.hostname}/.well-known/webfinger?` + urlQuery({ resource: query });
}
const m = query.match(/^([^@]+)@(.*)/);
if (m) {
const hostname = m[2];
return `https://${hostname}/.well-known/webfinger?` + urlQuery({ resource: `acct:${query}` });
}
throw new Error(`Invalied query (${query})`);
} }

View File

@ -10,6 +10,7 @@ import Resolver from '../../../../remote/activitypub/resolver';
import { ApiError } from '../../error'; import { ApiError } from '../../error';
import Instance from '../../../../models/instance'; import Instance from '../../../../models/instance';
import { extractDbHost } from '../../../../misc/convert-host'; import { extractDbHost } from '../../../../misc/convert-host';
import { validActor } from '../../../../remote/activitypub/type';
export const meta = { export const meta = {
tags: ['federation'], tags: ['federation'],
@ -85,6 +86,17 @@ async function fetchAny(uri: string) {
// /@user のような正規id以外で取得できるURIが指定されていた場合、ここで初めて正規URIが確定する // /@user のような正規id以外で取得できるURIが指定されていた場合、ここで初めて正規URIが確定する
// これはDBに存在する可能性があるため再度DB検索 // これはDBに存在する可能性があるため再度DB検索
if (uri !== object.id) { if (uri !== object.id) {
if (object.id.startsWith(config.url + '/')) {
const id = new mongo.ObjectID(object.id.split('/').pop());
const [user, note] = await Promise.all([
User.findOne({ _id: id }),
Note.findOne({ _id: id })
]);
const packed = await mergePack(user, note);
if (packed !== null) return packed;
}
const [user, note] = await Promise.all([ const [user, note] = await Promise.all([
User.findOne({ uri: object.id }), User.findOne({ uri: object.id }),
Note.findOne({ uri: object.id }) Note.findOne({ uri: object.id })
@ -95,7 +107,7 @@ async function fetchAny(uri: string) {
} }
// それでもみつからなければ新規であるため登録 // それでもみつからなければ新規であるため登録
if (object.type === 'Person') { if (validActor.includes(object.type)) {
const user = await createPerson(object.id); const user = await createPerson(object.id);
return { return {
type: 'User', type: 'User',
@ -104,7 +116,7 @@ async function fetchAny(uri: string) {
} }
if (['Note', 'Question', 'Article'].includes(object.type)) { if (['Note', 'Question', 'Article'].includes(object.type)) {
const note = await createNote(object.id); const note = await createNote(object.id, null, true);
return { return {
type: 'Note', type: 'Note',
object: await packNote(note, null, { detail: true }) object: await packNote(note, null, { detail: true })

View File

@ -1,6 +1,6 @@
import $ from 'cafy'; import $ from 'cafy';
import ID, { transform, transformMany } from '../../../../misc/cafy-id'; import ID, { transform, transformMany } from '../../../../misc/cafy-id';
import User, { pack, isRemoteUser } from '../../../../models/user'; import User, { pack } from '../../../../models/user';
import resolveRemoteUser from '../../../../remote/resolve-user'; import resolveRemoteUser from '../../../../remote/resolve-user';
import define from '../../define'; import define from '../../define';
import { apiLogger } from '../../logger'; import { apiLogger } from '../../logger';
@ -96,13 +96,6 @@ export default define(meta, async (ps, me) => {
throw new ApiError(meta.errors.noSuchUser); throw new ApiError(meta.errors.noSuchUser);
} }
// ユーザー情報更新
if (isRemoteUser(user)) {
if (user.lastFetchedAt == null || Date.now() - user.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) {
resolveRemoteUser(ps.username, ps.host, { }, true);
}
}
return await pack(user, me, { return await pack(user, me, {
detail: true detail: true
}); });

View File

@ -16,7 +16,8 @@ import discord from './service/discord';
import github from './service/github'; import github from './service/github';
import twitter from './service/twitter'; import twitter from './service/twitter';
import Instance from '../../models/instance'; import Instance from '../../models/instance';
import { toASCII } from 'punycode'; import { toApHost } from '../../misc/convert-host';
import { unique } from '../../prelude/array';
// Init app // Init app
const app = new Koa(); const app = new Koa();
@ -72,7 +73,7 @@ router.get('/v1/instance/peers', async ctx => {
host: 1 host: 1
}); });
const punyCodes = instances.map(instance => toASCII(instance.host)); const punyCodes = unique(instances.map(instance => toApHost(instance.host)));
ctx.body = punyCodes; ctx.body = punyCodes;
}); });

View File

@ -23,6 +23,7 @@ export default class extends Channel {
break; break;
} }
case 'mention': { case 'mention': {
if (mutedUserIds.includes(body.userId)) return;
if (body.isHidden) return; if (body.isHidden) return;
break; break;
} }

View File

@ -73,7 +73,7 @@ export default async function(ctx: Koa.BaseContext) {
await sendRaw(); await sendRaw();
} else { } else {
ctx.status = 404; ctx.status = 404;
await send(ctx as any, '/dummy.png', { root: assets }); await send(ctx as any, '/thumbnail-not-available.png', { root: assets });
} }
} }
} else if ('web' in ctx.query) { } else if ('web' in ctx.query) {

View File

@ -25,6 +25,9 @@ block meta
meta(name='twitter:card' content='summary') meta(name='twitter:card' content='summary')
if user.host
meta(name='robots' content='noindex')
if user.twitter if user.twitter
meta(name='twitter:creator' content=`@${user.twitter.screenName}`) meta(name='twitter:creator' content=`@${user.twitter.screenName}`)

View File

@ -24,6 +24,9 @@ block meta
meta(name='twitter:card' content='summary') meta(name='twitter:card' content='summary')
if user.host
meta(name='robots' content='noindex')
if user.twitter if user.twitter
meta(name='twitter:creator' content=`@${user.twitter.screenName}`) meta(name='twitter:creator' content=`@${user.twitter.screenName}`)

View File

@ -34,6 +34,7 @@ import Instance from '../../models/instance';
import extractMentions from '../../misc/extract-mentions'; import extractMentions from '../../misc/extract-mentions';
import extractEmojis from '../../misc/extract-emojis'; import extractEmojis from '../../misc/extract-emojis';
import extractHashtags from '../../misc/extract-hashtags'; import extractHashtags from '../../misc/extract-hashtags';
import { genId } from '../../misc/gen-id';
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention'; type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
@ -434,6 +435,7 @@ async function publish(user: IUser, note: INote, noteObj: any, reply: INote, ren
async function insertNote(user: IUser, data: Option, tags: string[], emojis: string[], mentionedUsers: IUser[]) { async function insertNote(user: IUser, data: Option, tags: string[], emojis: string[], mentionedUsers: IUser[]) {
const insert: any = { const insert: any = {
_id: genId(data.createdAt),
createdAt: data.createdAt, createdAt: data.createdAt,
fileIds: data.files ? data.files.map(file => file._id) : [], fileIds: data.files ? data.files.map(file => file._id) : [],
replyId: data.reply ? data.reply._id : null, replyId: data.reply ? data.reply._id : null,

View File

@ -1,9 +1,12 @@
import Instance, { IInstance } from '../models/instance'; import Instance, { IInstance } from '../models/instance';
import federationChart from '../services/chart/federation'; import federationChart from '../services/chart/federation';
import { toDbHost } from '../misc/convert-host';
export async function registerOrFetchInstanceDoc(host: string): Promise<IInstance> { export async function registerOrFetchInstanceDoc(host: string): Promise<IInstance> {
if (host == null) return null; if (host == null) return null;
host = toDbHost(host);
const index = await Instance.findOne({ host }); const index = await Instance.findOne({ host });
if (index == null) { if (index == null) {