Compare commits

...

125 Commits

Author SHA1 Message Date
Acid Chicken (硫酸鶏) a48336303f
Update create.ts 2020-02-02 01:35:20 +09:00
Acid Chicken (硫酸鶏) 3e7b342cc6
Update create.ts 2020-02-02 01:18:53 +09:00
Acid Chicken (硫酸鶏) 9483bbb386
Update trend.ts 2020-02-02 01:00:49 +09:00
Acid Chicken (硫酸鶏) 535f3ddf13
Update hashtags.ts 2020-02-02 00:55:29 +09:00
Acid Chicken (硫酸鶏) ad9bc299be
Merge pull request #5787 from syuilo/patch/autogen/v10
[AUTOMATED] Update README.md
2020-01-31 18:11:02 +09:00
Acid Chicken (硫酸鶏) b55ddaee09 Update README.md [AUTOGEN] 2020-01-30 06:11:07 +09:00
Acid Chicken (硫酸鶏) 6a7d020f9d
Merge pull request #5778 from syuilo/patch/autogen/v10
[AUTOMATED] Update README.md
2020-01-30 00:00:19 +09:00
Acid Chicken (硫酸鶏) ae672b85c7 Update README.md [AUTOGEN] 2020-01-29 00:03:11 +09:00
Acid Chicken (硫酸鶏) 34f3fdc00e
Update README.md [AUTOGEN] (#5777) 2020-01-29 00:01:14 +09:00
Acid Chicken (硫酸鶏) 43fa4d3016 Update README.md [AUTOGEN] 2020-01-28 00:02:15 +09:00
Acid Chicken (硫酸鶏) bc0df206cd
Update README.md [AUTOGEN] (#5766) 2020-01-28 00:01:09 +09:00
Acid Chicken (硫酸鶏) e52725b11e
Update README.md [AUTOGEN] (#5761) 2020-01-25 22:15:09 +09:00
Acid Chicken (硫酸鶏) 4d989dda48
Update README.md [AUTOGEN] (#5760) 2020-01-25 21:31:56 +09:00
Acid Chicken (硫酸鶏) acd2ed5c79
Update README.md [AUTOGEN] (#5751) 2020-01-25 20:45:10 +09:00
Acid Chicken (硫酸鶏) 1fff5f19fe
Update README.md [AUTOGEN] (#5750) 2020-01-24 02:01:27 +09:00
Acid Chicken (硫酸鶏) 3c67be5b37
Update README.md [AUTOGEN] (#5748) 2020-01-24 02:00:13 +09:00
Acid Chicken (硫酸鶏) 6586e1f792
Update README.md [AUTOGEN] (#5746) 2020-01-23 17:18:31 +09:00
Acid Chicken (硫酸鶏) 545606ea3c
Update README.md [AUTOGEN] (#5744) 2020-01-23 17:17:21 +09:00
Acid Chicken (硫酸鶏) 97b0987ce1
Update README.md [AUTOGEN] (#5697) 2020-01-09 16:50:15 +09:00
Acid Chicken (硫酸鶏) 70447e229e
Update README.md [AUTOGEN] (#5679) 2020-01-05 20:58:33 +09:00
Acid Chicken (硫酸鶏) 9190f6e1c8
Update README.md [AUTOGEN] (#5675) 2020-01-04 22:25:28 +09:00
Acid Chicken (硫酸鶏) 475a3e4a02
Update README.md [AUTOGEN] (#5663) 2019-12-31 02:47:30 +09:00
Acid Chicken (硫酸鶏) 480a49b2b6
Update README.md [AUTOGEN] (#5660) 2019-12-27 06:43:41 +09:00
Acid Chicken (硫酸鶏) 6d4cd81556
Update README.md [AUTOGEN] (#5646) 2019-12-20 09:48:21 +09:00
Acid Chicken (硫酸鶏) ddd62dee76
Update README.md [AUTOGEN] (#5644) 2019-12-17 04:43:44 +09:00
Acid Chicken (硫酸鶏) a37b8c8750
Update README.md [AUTOGEN] (#5633) 2019-12-11 21:57:47 +09:00
Acid Chicken (硫酸鶏) 16d7fadb28
Update README.md [AUTOGEN] (#5630) 2019-12-10 21:40:39 +09:00
Acid Chicken (硫酸鶏) 7c360ae313
Update README.md [AUTOGEN] (#5625) 2019-12-10 21:25:33 +09:00
Acid Chicken (硫酸鶏) 140c426c90
Update README.md [AUTOGEN] (#5617) 2019-12-03 08:53:17 +09:00
Acid Chicken (硫酸鶏) 3c6e0403fa
Update README.md [AUTOGEN] (#5614) 2019-12-02 15:43:10 +09:00
Acid Chicken (硫酸鶏) 62b9c4c960
Update README.md [AUTOGEN] (#5605) 2019-12-01 17:39:26 +09:00
Acid Chicken (硫酸鶏) 97a5f15902
Update README.md [AUTOGEN] (#5600) 2019-11-25 18:26:58 +09:00
Acid Chicken (硫酸鶏) d9b447bc60
Update README.md [AUTOGEN] (#5575) 2019-11-03 04:22:48 +09:00
Acid Chicken (硫酸鶏) 0c556e6243
Update README.md [AUTOGEN] (#5571) 2019-11-02 13:47:27 +09:00
Acid Chicken (硫酸鶏) e5a877e316
Update README.md [AUTOGEN] (#5565) 2019-10-31 18:54:04 +09:00
Acid Chicken (硫酸鶏) f88dc9605a
Update README.md [AUTOGEN] (#5555) 2019-10-29 01:06:26 +09:00
Acid Chicken (硫酸鶏) e07d11e15f
Update README.md [AUTOGEN] (#5554) 2019-10-28 14:54:37 +09:00
Acid Chicken (硫酸鶏) 6bf888e4e6
Update README.md [AUTOGEN] (#5540) 2019-10-25 22:35:40 +09:00
Acid Chicken (硫酸鶏) 3b86e5d93a
Update README.md [AUTOGEN] (#5525) 2019-10-20 11:36:30 +09:00
Acid Chicken (硫酸鶏) c7517a5e12
Update README.md [AUTOGEN] (#5518) 2019-10-18 14:33:42 +09:00
Acid Chicken (硫酸鶏) 2e9b9693fe
Update README.md [AUTOGEN] (#5516) 2019-10-18 14:32:27 +09:00
Acid Chicken (硫酸鶏) 09a540839c
Update README.md [AUTOGEN] (#5512) 2019-10-17 18:09:46 +09:00
Acid Chicken (硫酸鶏) 85806634d7
Update README.md [AUTOGEN] (#5481) 2019-10-04 05:05:39 +09:00
Acid Chicken (硫酸鶏) 62c3955061
Update README.md [AUTOGEN] (#5477) 2019-10-03 23:44:16 +09:00
Acid Chicken (硫酸鶏) ec6d250cb8
Update README.md [AUTOGEN] (#5473) 2019-10-02 07:52:58 +09:00
Acid Chicken (硫酸鶏) 29f57c3de6
Update README.md [AUTOGEN] (#5472) 2019-10-02 07:27:28 +09:00
Acid Chicken (硫酸鶏) 42e35bd1cd Update README.md [AUTOGEN] (#5462) 2019-09-27 05:13:23 +09:00
Acid Chicken (硫酸鶏) ca77a1f7f4
Update README.md [AUTOGEN] (#5401) 2019-09-07 23:43:31 +09:00
Acid Chicken (硫酸鶏) fb3d718310
Update README.md [AUTOGEN] (#5395) 2019-09-05 05:05:20 +09:00
Acid Chicken (硫酸鶏) fab631a539
Update README.md [AUTOGEN] (#5389) 2019-09-04 04:25:19 +09:00
Acid Chicken (硫酸鶏) 83e8dea89b
Update README.md [AUTOGEN] (#5383) 2019-09-03 01:03:00 +09:00
Acid Chicken (硫酸鶏) 17bb4ae389
Update README.md [AUTOGEN] (#5376) 2019-09-02 04:06:50 +09:00
Acid Chicken (硫酸鶏) f343f2dcd6
Update README.md [AUTOGEN] (#5357) 2019-08-29 05:33:58 +09:00
Acid Chicken (硫酸鶏) 0bf5b4a5a9
Update README.md [AUTOGEN] (#5334) 2019-08-23 05:06:18 +09:00
Acid Chicken (硫酸鶏) 01960a658b
Update README.md [AUTOGEN] (#5250) 2019-08-06 23:23:05 +09:00
Acid Chicken (硫酸鶏) 362c831b54
Update README.md [AUTOGEN] (#5245) 2019-08-03 19:56:57 +09:00
Acid Chicken (硫酸鶏) 1a81b4e5d9
Update README.md [AUTOGEN] (#5244) 2019-08-02 22:37:34 +09:00
Acid Chicken (硫酸鶏) 569ae4a10f
Update README.md [AUTOGEN] (#5241) 2019-08-02 21:21:34 +09:00
Acid Chicken (硫酸鶏) 3821ed3d2b
Update README.md [AUTOGEN] (#5238) 2019-08-01 22:26:06 +09:00
Acid Chicken (硫酸鶏) eeebda7fe6
Update README.md [AUTOGEN] (#5235) 2019-08-01 01:31:57 +09:00
Acid Chicken (硫酸鶏) bfcd828fc4
Update README.md [AUTOGEN] (#5198) 2019-07-20 01:53:05 +09:00
Acid Chicken (硫酸鶏) 8f4dd03d3c
Update README.md [AUTOGEN] (#5192) 2019-07-20 00:40:05 +09:00
Acid Chicken (硫酸鶏) 5cac017d93
Update README.md [AUTOGEN] (#5164) 2019-07-17 04:28:14 +09:00
Acid Chicken (硫酸鶏) 9b90022a72
Update README.md [AUTOGEN] (#5095) 2019-07-02 15:37:26 +09:00
MeiMei f9f1a654c3
Block all activities from suspended users (#5076) 2019-06-22 11:28:56 +09:00
Satsuki Yanagi 41430288d3
Resolve #5069 (#5070) 2019-06-21 17:37:37 +09:00
MeiMei 0903704957
Fix: オートコンプリートにアニメーション停止が効かない (#5061) 2019-06-21 17:29:44 +09:00
MeiMei 987f7dc89d
Support Unicode 12.0 Emoji (#5062) 2019-06-21 17:28:46 +09:00
MeiMei 2c2de1282a
image以外はproxyしないように (#5051) 2019-06-21 17:27:23 +09:00
Acid Chicken (硫酸鶏) e274c73177
Update README.md [AUTOGEN] (#5045) 2019-06-11 16:58:08 +09:00
syuilo 69cc97ca83
Fix bug 2019-06-10 23:17:42 +09:00
syuilo ea60565b0d
Update package.json 2019-06-10 21:09:06 +09:00
Acid Chicken (硫酸鶏) a26585dcc7
Add @rinsuki into collaborators (#4762) 2019-06-10 14:23:39 +09:00
Acid Chicken (硫酸鶏) 008e5bd24c
Merge branch 'riamu' into v10 2019-06-10 14:01:51 +09:00
Acid Chicken (硫酸鶏) ebc6564fbb
Update packages 2019-06-10 14:01:36 +09:00
Acid Chicken (硫酸鶏) 06b5078245
Add resolitions 2019-06-10 13:19:04 +09:00
Acid Chicken (硫酸鶏) 68337f95ff
Update README.md [AUTOGEN] (#5026) 2019-06-04 18:19:19 +09:00
Acid Chicken (硫酸鶏) 72e148002a
Update README.md [AUTOGEN] (#5018) 2019-06-02 10:13:07 +09:00
Acid Chicken (硫酸鶏) f0a03f71eb
Update README.md [AUTOGEN] (#5012) 2019-06-01 20:38:43 +09:00
Acid Chicken (硫酸鶏) 6049473e46
Update README.md [AUTOGEN] (#5004) 2019-05-27 23:44:49 +09:00
Acid Chicken (硫酸鶏) f9f2787dfc
Update README.md [AUTOGEN] (#5002) 2019-05-27 23:15:51 +09:00
MeiMei b0a068e269 Fix: Firefoxでトークの下が隠れてしまう (#4973) 2019-05-25 09:11:09 +09:00
Acid Chicken (硫酸鶏) dd5546690a
Update README.md [AUTOGEN] (#4939) 2019-05-19 15:08:06 +09:00
MeiMei d87b6d38ab Update ObjectStorage example (#4919) 2019-05-14 02:53:27 +09:00
MeiMei 4069bb170a Fix meta tags (#4917) 2019-05-14 02:50:53 +09:00
Acid Chicken (硫酸鶏) 600c009549
Update README.md [AUTOGEN] (#4915) 2019-05-13 18:03:24 +09:00
syuilo c953a28201
10.102.3 2019-05-12 11:40:51 +09:00
Acid Chicken (硫酸鶏) f6541df42a
Update README.md [AUTOGEN] (#4887) 2019-05-09 22:04:15 +09:00
MeiMei a57e9460c8 Fix: IPv4 onlyホストからDualstackホストにAP deliverできない (#4879) 2019-05-09 15:43:49 +09:00
MeiMei 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
MeiMei a397c040fe エクスポートファイルでは同一ハッシュチェックをしないように (#4841) 2019-05-03 18:34:45 +09:00
MeiMei e311d73ffc Fix: user menu (#4845)
* Fix: Firefoxで自分のメニューが開けないなど

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

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

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

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

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

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

* confirm silence

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

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

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

* fix releaseQueue
2019-04-08 15:18:44 +09:00
MeiMei fceebf7388 Fix #4562 (#4563) 2019-04-08 15:17:39 +09:00
Acid Chicken (硫酸鶏) 13caf37991
Update README.md [AUTOGEN] (#4639) 2019-04-06 12:47:02 +09:00
96 changed files with 1042 additions and 599 deletions

View File

@ -53,40 +53,54 @@ mongodb:
drive:
storage: 'db'
# OR
# OR
# storage: 'minio'
# bucket:
# prefix:
# config:
# endPoint:
# port:
# useSSL:
# accessKey:
# secretKey:
#drive:
# storage: 'minio'
# bucket:
# prefix:
# config:
# endPoint:
# port:
# useSSL:
# accessKey:
# secretKey:
# S3 example
# storage: 'minio'
# bucket: bucket-name
# prefix: files
# config:
# endPoint: s3-us-west-2.amazonaws.com
# region: us-west-2
# useSSL: true
# accessKey: XXX
# secretKey: YYY
# S3/GCS example
#
# * Replace <endpoint> to
# S3: see https://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region
# GCS: use 'storage.googleapis.com'
#
# * Replace <region> to
# S3: see https://docs.aws.amazon.com/general/latest/gr/rande.html#s3_region
# GCS: not needed (just delete the region line)
#
#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)
# storage: 'minio'
# bucket: drive.example.com
# prefix: files
# baseUrl: https://drive.example.com
# config:
# endPoint: s3-us-west-2.amazonaws.com
# region: us-west-2
# useSSL: true
# accessKey: XXX
# secretKey: YYY
# S3/GCS example (with CDN, custom domain)
#
#drive:
# storage: 'minio'
# bucket: drive.example.com
# prefix: files
# baseUrl: https://drive.example.com
# config:
# endPoint: <endpoint>
# region: <region>
# useSSL: true
# accessKey: XXX
# secretKey: YYY
# If enabled:
# The first account created is automatically marked as Admin.
@ -113,3 +127,6 @@ autoAdmin: true
# Clustering
#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
/.autogen/ @acid-chicken
/.circleci/ @syuilo @acid-chicken
/.config/ @syuilo @AyaMorisawa @mei23 @acid-chicken
/.config/ @syuilo @AyaMorisawa @mei23 @acid-chicken @rinsuki
# /.config/mongo_initdb_example.js @khws4v1
/.github/ @syuilo @AyaMorisawa @acid-chicken
/.vscode/ @acid-chicken
@ -12,7 +12,7 @@
# /docs/*.fr.md @BoFFire
# /docs/docker.*.md @khws4v1
/locales/ @syuilo
/src/ @syuilo @AyaMorisawa @mei23 @acid-chicken
/src/ @syuilo @AyaMorisawa @mei23 @acid-chicken @rinsuki
# /src/crypto_key.cc @akihikodaki
# /src/crypto_key.d.ts @akihikodaki
/.dockerignore @syuilo # @khws4v1

1
.gitignore vendored
View File

@ -19,3 +19,4 @@ api-docs.json
*.code-workspace
yarn.lock
.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`
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
----------
* manifest.json にインスタンス名を反映させるように

106
README.md
View File

@ -88,12 +88,14 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
<td><img src="https://avatars0.githubusercontent.com/u/10798641?s=460&v=4" alt="AyaMorisawa" width="100"></td>
<td><img src="https://avatars1.githubusercontent.com/u/30769358?s=460&v=4" alt="mei23" width="100"></td>
<td><img src="https://avatars2.githubusercontent.com/u/20679825?s=460&v=4" alt="acid-chicken" width="100"></td>
<td><img src="https://avatars2.githubusercontent.com/u/6533808?s=460&v=4" alt="rinsuki" width="100"></td>
</tr>
<tr>
<td align="center"><a href="https://github.com/syuilo">@syuilo</a></td>
<td align="center"><a href="https://github.com/AyaMorisawa">@AyaMorisawa</a></td>
<td align="center"><a href="https://github.com/mei23">@mei23</a></td>
<td align="center"><a href="https://github.com/acid-chicken">@acid-chicken</a></td>
<td align="center"><a href="https://github.com/rinsuki">@rinsuki</a></td>
</tr>
</table>
@ -101,62 +103,92 @@ Please see the [Contribution Guide](./CONTRIBUTING.md).
----------------------------------------------------------------
<!-- PATREON_START -->
<table><tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5888816/36da0f7c15954df0ab13f9abdf227f66/1?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/12190916/fb7fa7983c14425f890369535b1506a4/1?token-time=2145916800&token-hash=WeuDzzz24cRXJogyIkU-mxARqkdyms-rcZKbO-GpGjw%3D" alt="weep" 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/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/16869916" alt="見当かなみ" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/20832595" alt="Roujo" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/27956229" alt="Oliver Maximilian Seidel" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12190916/fb7fa7983c14425f890369535b1506a4/3.png?token-time=2145916800&token-hash=oH_i7gJjNT7Ot6j9JiVwy7ZJIBqACVnzLqlz4YrDAZA%3D" alt="weepjp" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/605366/c9dc408fdcbf412fb183ca5b06235f8d/1.jpeg?token-time=2145916800&token-hash=oaqsjLqOFjWN5I9hm2epOaTXaEtKwQUy5OW-EpAz6-g%3D" alt="Jon Leibowitz" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19045173/cb91c0f345c24d4ebfd05f19906d5e26/1.png?token-time=2145916800&token-hash=o_zKBytJs_AxHwSYw_5R8eD0eSJe3RoTR3kR3Q0syN0%3D" alt="kiritan" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/24430516/b1964ac5b9f746d2a12ff53dbc9aa40a/1.jpg?token-time=2145916800&token-hash=bmEiMGYpp3bS7hCCbymjGGsHBZM3AXuBOFO3Kro37PU%3D" alt="Eduardo Quiros" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/14215107/1cbe1912c26143919fa0faca16f12ce1/3.png?token-time=2145916800&token-hash=Zq1TCK2tdY7xudEm_aV70bc_wxmol6pNj3ZWbpFUNbI%3D" alt="Nesakko" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/776209" alt="Denshi" width="100"></td>
</tr><tr>
<td><a href="https://www.patreon.com/rane_hs">Hiroshi Seki</a></td>
<td><a href="https://www.patreon.com/weepjp">weep</a></td>
<td><a href="https://www.patreon.com/user?u=12059069">naga_rus</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/user?u=20832595">Roujo</a></td>
<td><a href="https://www.patreon.com/user?u=27956229">Oliver Maximilian Seidel</a></td>
<td><a href="https://www.patreon.com/weepjp">weepjp</a></td>
<td><a href="https://www.patreon.com/jonleibowitz">Jon Leibowitz</a></td>
<td><a href="https://www.patreon.com/user?u=19045173">kiritan</a></td>
<td><a href="https://www.patreon.com/user?u=24430516">Eduardo Quiros</a></td>
<td><a href="https://www.patreon.com/Nesakko">Nesakko</a></td>
<td><a href="https://www.patreon.com/user?u=776209">Denshi</a></td>
</tr></table>
<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://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://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?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://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://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/17195955/be45e5e14c3e48b2bee0456c84e19df4/4?token-time=2145916800&token-hash=SbdZeN5SmsuT9stD6v0jN1z0hftg0FmRiCTxysU0Ihw%3D" alt="Damillora" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/3075183/c2ae575c604e420297f000ccc396e395/1.jpeg?token-time=2145916800&token-hash=O9qmPtpo6wWb0OuvnkEekhk_1WO2MTdytLr7ZgsAr80%3D" alt="Liaizon Wakest" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/557245" alt="mkatze" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/23915207/25428766ecd745478e600b3d7f871eb2/1.png?token-time=2145916800&token-hash=urCLLA4KjJZX92Y1CxcBP4d8bVTHGkiaPnQZp-Tqz68%3D" alt="kabo2468y" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/8249688/4aacf36b6b244ab1bc6653591b6640df/2.png?token-time=2145916800&token-hash=1ZEf2w6L34253cZXS_HlVevLEENWS9QqrnxGUAYblPo%3D" alt="AureoleArk" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5670915/ee175f0bfb6347ffa4ea101a8c097bff/1.jpg?token-time=2145916800&token-hash=mPLM9CA-riFHx-myr3bLZJuH2xBRHA9se5VbHhLIOuA%3D" alt="osapon" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/16869916" alt="見当かなみ" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18899730/6a22797f68254034a854d69ea2445fc8/1.png?token-time=2145916800&token-hash=b_uj57yxo5VzkSOUS7oXE_762dyOTB_oxzbO6lFNG3k%3D" alt="YuzuRyo61" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5788159/af42076ab3354bb49803cfba65f94bee/1.jpg?token-time=2145916800&token-hash=iSaxp_Yr2-ZiU2YVi9rcpZZj9mj3UvNSMrZr4CU4qtA%3D" alt="mewl hayabusa" width="100"></td>
<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>
</tr><tr>
<td><a href="https://www.patreon.com/gutfuckllc">gutfuckllc</a></td>
<td><a href="https://www.patreon.com/wakest">Liaizon Wakest</a></td>
<td><a href="https://www.patreon.com/user?u=557245">mkatze</a></td>
<td><a href="https://www.patreon.com/user?u=23915207">kabo2468y</a></td>
<td><a href="https://www.patreon.com/AureoleArk">AureoleArk</a></td>
<td><a href="https://www.patreon.com/osapon">osapon</a></td>
<td><a href="https://www.patreon.com/user?u=16869916">見当かなみ</a></td>
<td><a href="https://www.patreon.com/Yuzulia">YuzuRyo61</a></td>
<td><a href="https://www.patreon.com/hs_sh_net">mewl hayabusa</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=18833336">itiradi</a></td>
<td><a href="https://www.patreon.com/user?u=13039004">nemu</a></td>
</tr></table>
<table><tr>
<td><img src="https://c8.patreon.com/2/200/16542964" alt="Takumi Sugita" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/17866454" alt="sikyosyounin" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5881381/6235ca5d3fb04c8e95ef5b4ff2abcc18/3.png?token-time=2145916800&token-hash=KjfQL8nf3AIf6WqzLshBYAyX44piAqOAZiYXgZS_H6A%3D" alt="YUKIMOCHI" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/26340354/08834cf767b3449e93098ef73a434e2f/2.png?token-time=2145916800&token-hash=nyM8DnKRL8hR47HQ619mUzsqVRpkWZjgtgBU9RY15Uc%3D" alt="totokoro" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/19356899/496b4681d33b4520bd7688e0fd19c04d/2.jpeg?token-time=2145916800&token-hash=_sTj3dUBOhn9qwiJ7F19Qd-yWWfUqJC_0jG1h0agEqQ%3D" alt="sheeta.s" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5827393/59893c191dda408f9cabd0f20a3a5627/1.jpeg?token-time=2145916800&token-hash=i9N05vOph-eP1LTLb9_npATjYOpntL0ZsHNaZFSsPmE%3D" alt="motcha" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13737140/1adf7835017d479280d90fe8d30aade2/1.png?token-time=2145916800&token-hash=0pdle8h5pDZrww0BDOjdz6zO-HudeGTh36a3qi1biVU%3D" alt="Satsuki Yanagi" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/17880724/311738c8a48f4a6b9443c2445a75adde/1.jpe?token-time=2145916800&token-hash=CPxGQhKIlEaa6WUcgbyHixyKEhakiw9RFdOhsIJBQ_o%3D" alt="takimura" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/13100201/fc5be4fa90444f09a9c8a06f72385272/1.png?token-time=2145916800&token-hash=i8PjlgfOB2LPEdbtWyx8ZPsBKhGcNZqcw_FQmH71UGU%3D" alt="aqz tamaina" width="100"></td>
</tr><tr>
<td><a href="https://www.patreon.com/user?u=16542964">Takumi Sugita</a></td>
<td><a href="https://www.patreon.com/user?u=17866454">sikyosyounin</a></td>
<td><a href="https://www.patreon.com/yukimochi">YUKIMOCHI</a></td>
<td><a href="https://www.patreon.com/user?u=17463605">Sampot</a></td>
<td><a href="https://www.patreon.com/user?u=26340354">totokoro</a></td>
<td><a href="https://www.patreon.com/user?u=19356899">sheeta.s</a></td>
<td><a href="https://www.patreon.com/user?u=5827393">motcha</a></td>
<td><a href="https://www.patreon.com/user?u=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/aqz">aqz tamaina</a></td>
</tr></table>
<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/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/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/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/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/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/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/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/16900731/619ab87cc08448439222631ebb26802f/1.gif?token-time=2145916800&token-hash=o27K7M02s1z-LkDUEO5Oa7cu-GviRXeOXxryi4o_6VU%3D" alt="Atsuko Tominaga" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/4389829/9f709180ac714651a70f74a82f3ffdb9/3.png?token-time=2145916800&token-hash=FTm3WVom4dJ9NwWMU4OpCL_8Yc13WiwEbKrDPyTZTPs%3D" alt="natalie" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/5923936/2a743cbfbff946c2af3f09026047c0da/2.png?token-time=2145916800&token-hash=h6yphW1qnM0n_NOWaf8qtszMRLXEwIxfk5beu4RxdT0%3D" alt="noellabo" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/2384390/5681180e1efb46a8b28e0e8d4c8b9037/1.jpg?token-time=2145916800&token-hash=SJcMy-Q1BcS940-LFUVOMfR7-5SgrzsEQGhYb3yowFk%3D" alt="CG" width="100"></td>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/18072312/98e894d960314fa7bc236a72a39488fe/1.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/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/5731881/4b6038e6cda34c04b83a5fcce3806a93/1.png?token-time=2145916800&token-hash=hBayGfOmQH3kRMdNnDe4oCZD_9fsJWSt29xXR3KRMVk%3D" alt="Nokotaro Takeda" width="100"></td>
<td><img src="https://c8.patreon.com/2/200/23932002" alt="nenohi" width="100"></td>
</tr><tr>
<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/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/dansup">dansup</a></td>
<td><a href="https://www.patreon.com/mastodon">Gargron</a></td>
<td><a href="https://www.patreon.com/takenoko">Nokotaro Takeda</a></td>
<td><a href="https://www.patreon.com/user?u=23932002">nenohi</a></td>
</tr></table>
<table><tr>
<td><img src="https://c10.patreonusercontent.com/3/eyJ3IjoyMDB9/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1.jpeg?token-time=2145916800&token-hash=vGe7wXGqmA8Q7m-kDNb6fyGdwk-Dxk4F-ut8ZZu51RM%3D" alt="Takashi Shibuya" width="100"></td>
</tr><tr>
<td><a href="https://www.patreon.com/user?u=12531784">Takashi Shibuya</a></td>
</tr></table>
**Last updated:** Fri, 05 Apr 2019 09:39:06 UTC
**Last updated:** Wed, 29 Jan 2020 21:11:07 UTC
<!-- PATREON_END -->
: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. `git clone -b master git://github.com/syuilo/misskey.git` 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.
1. Clone Misskey repository's master branch.
`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
----------------------------------------------------------------
@ -39,7 +53,15 @@ Just `docker-compose up -d`. GLHF!
### How to update your Misskey server to the latest version
1. `git fetch`
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`
5. `docker-compose build`
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. `git clone -b master git://github.com/syuilo/misskey.git` 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).
1. Clone le dépôt de Misskey sur la branche master.
`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
----------------------------------------------------------------
@ -40,7 +54,15 @@ Utilisez la commande `docker-compose up -d`. GLHF!
### How to update your Misskey server to the latest version
1. `git fetch`
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`
5. `docker-compose build`
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)
*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
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)
1. Permet de créer le dossier d'accueil de la base ElasticSearch aves les bons droits
`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
----------------------------------------------------------------
1. `docker-compose -it web /bin/sh` 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
1. Connexion dans le conteneur web
`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`
----------------------------------------------------------------

View File

@ -9,9 +9,23 @@ Dockerを使ったMisskey構築方法
*1.* Misskeyのダウンロード
----------------------------------------------------------------
1. `git clone -b master git://github.com/syuilo/misskey.git` 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)を確認
1. masterブランチからMisskeyレポジトリをクローン
`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.* 設定ファイルを作成する
----------------------------------------------------------------
@ -39,7 +53,15 @@ Dockerを使ったMisskey構築方法
### Misskeyを最新バージョンにアップデートする方法:
1. `git fetch`
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`
5. `docker-compose build`
6. [ChangeLog](../CHANGELOG.md)でマイグレーション情報を確認する

View File

@ -41,15 +41,38 @@ As root:
*4.* Install Misskey
----------------------------------------------------------------
1. `su - misskey` 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
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.
1. Connect to misskey user.
`su - misskey`
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
----------------------------------------------------------------
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`
*6.* Build Misskey
@ -77,37 +100,53 @@ Just `NODE_ENV=production npm start`. GLHF!
### 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:
```
[Unit]
Description=Misskey daemon
```
[Unit]
Description=Misskey daemon
[Service]
Type=simple
User=misskey
ExecStart=/usr/bin/npm start
WorkingDirectory=/home/misskey/misskey
Environment="NODE_ENV=production"
TimeoutSec=60
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=misskey
Restart=always
[Service]
Type=simple
User=misskey
ExecStart=/usr/bin/npm start
WorkingDirectory=/home/misskey/misskey
Environment="NODE_ENV=production"
TimeoutSec=60
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=misskey
Restart=always
[Install]
WantedBy=multi-user.target
```
[Install]
WantedBy=multi-user.target
```
3. `systemctl daemon-reload ; systemctl enable misskey` Reload systemd and enable the misskey service.
4. `systemctl start misskey` Start the misskey service.
3. Reload systemd and enable 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`.
### How to update your Misskey server to the latest version
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`
4. `NODE_ENV=production npm run build`
5. Check [ChangeLog](../CHANGELOG.md) for migration information

View File

@ -41,15 +41,38 @@ En root :
*4.* Installation de Misskey
----------------------------------------------------------------
1. `su - misskey` 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.
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.
1. Basculez vers l'utilisateur misskey.
`su - 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
----------------------------------------------------------------
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`
*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
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 :
```
[Unit]
Description=Misskey daemon
```
[Unit]
Description=Misskey daemon
[Service]
Type=simple
User=misskey
ExecStart=/usr/bin/npm start
WorkingDirectory=/home/misskey/misskey
Environment="NODE_ENV=production"
TimeoutSec=60
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=misskey
Restart=always
[Service]
Type=simple
User=misskey
ExecStart=/usr/bin/npm start
WorkingDirectory=/home/misskey/misskey
Environment="NODE_ENV=production"
TimeoutSec=60
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=misskey
Restart=always
[Install]
WantedBy=multi-user.target
```
[Install]
WantedBy=multi-user.target
```
3. `systemctl daemon-reload ; systemctl enable misskey` Redémarre systemd et active le service misskey.
4. `systemctl start misskey` Démarre le service misskey.
3. Redémarre systemd et active 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`.
### Méthode de mise à jour vers la plus récente version de Misskey
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`
4. `NODE_ENV=production npm run build`
5. Consultez [ChangeLog](../CHANGELOG.md) pour les information de migration.

View File

@ -48,15 +48,38 @@ adduser --disabled-password --disabled-login misskey
*4.* Misskeyのインストール
----------------------------------------------------------------
1. `su - misskey` misskeyユーザーを使用
2. `git clone -b master git://github.com/syuilo/misskey.git` masterブランチからMisskeyレポジトリをクローン
3. `cd misskey` 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の依存パッケージをインストール
1. misskeyユーザーを使用
`su - 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.* 設定ファイルを作成する
----------------------------------------------------------------
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` を編集する。
*6.* Misskeyのビルド
@ -82,38 +105,56 @@ Debianをお使いであれば、`build-essential`パッケージをインスト
`NODE_ENV=production npm start`するだけです。GLHF!
### systemdを用いた起動
1. systemdサービスのファイルを作成: `/etc/systemd/system/misskey.service`
1. systemdサービスのファイルを作成
`/etc/systemd/system/misskey.service`
2. エディタで開き、以下のコードを貼り付けて保存:
```
[Unit]
Description=Misskey daemon
```
[Unit]
Description=Misskey daemon
[Service]
Type=simple
User=misskey
ExecStart=/usr/bin/npm start
WorkingDirectory=/home/misskey/misskey
Environment="NODE_ENV=production"
TimeoutSec=60
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=misskey
Restart=always
[Service]
Type=simple
User=misskey
ExecStart=/usr/bin/npm start
WorkingDirectory=/home/misskey/misskey
Environment="NODE_ENV=production"
TimeoutSec=60
StandardOutput=syslog
StandardError=syslog
SyslogIdentifier=misskey
Restart=always
[Install]
WantedBy=multi-user.target
```
CentOSで1024以下のポートを使用してMisskeyを使用する場合は`ExecStart=/usr/bin/sudo /usr/bin/npm start`に変更する必要があります。
[Install]
WantedBy=multi-user.target
```
3. `systemctl daemon-reload ; systemctl enable misskey` systemdを再読み込みしmisskeyサービスを有効化
4. `systemctl start misskey` misskeyサービスの起動
CentOSで1024以下のポートを使用してMisskeyを使用する場合は`ExecStart=/usr/bin/sudo /usr/bin/npm start`に変更する必要があります。
3. systemdを再読み込みしmisskeyサービスを有効化
`systemctl daemon-reload ; systemctl enable misskey`
4. misskeyサービスの起動
`systemctl start misskey`
`systemctl status misskey`と入力すると、サービスの状態を調べることができます。
### Misskeyを最新バージョンにアップデートする方法:
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`
4. `NODE_ENV=production npm run build`
5. [ChangeLog](../CHANGELOG.md)でマイグレーション情報を確認する

View File

@ -877,7 +877,6 @@ desktop/views/components/post-form.vue:
posting: "Posting"
attach-media-from-local: "Attach media from your device"
attach-media-from-drive: "Attach media from your Drive"
attach-cancel: "Cancel attachment"
insert-a-kao: "v('ω')v"
create-poll: "Create a poll"
text-remain: "{} characters remaining"
@ -970,6 +969,10 @@ common/views/components/password-settings.vue:
not-match: "The new passwords do not match"
changed: "Password changed"
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:
private: "This post is private"
deleted: "This post has been deleted"

View File

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

View File

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

View File

@ -130,7 +130,7 @@
<span>{{ $t('status') }}</span>
</header>
<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.usersCount | number }}</span>
<span>{{ instance.followingCount | number }}</span>

View File

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

View File

@ -16,10 +16,10 @@
</ol>
<ol class="emojis" ref="suggests" v-if="emojis.length > 0">
<li v-for="emoji in emojis" @click="complete(type, emoji.emoji)" @keydown="onKeydown" tabindex="-1">
<span class="emoji" v-if="emoji.isCustomEmoji"><img :src="emoji.url" :alt="emoji.emoji"/></span>
<span class="emoji" v-if="emoji.isCustomEmoji"><img :src="$store.state.device.disableShowingAnimatedImages ? getStaticImageUrl(emoji.url) : emoji.url" :alt="emoji.emoji"/></span>
<span class="emoji" v-else-if="!useOsDefaultEmojis"><img :src="emoji.url" :alt="emoji.emoji"/></span>
<span class="emoji" v-else>{{ emoji.emoji }}</span>
<span class="name">{{ beforeQ }}<b>{{ q }}</b>{{ afterQ }}</span>
<span class="name" v-html="emoji.name.replace(q, `<b>${q}</b>`)"></span>
<span class="alias" v-if="emoji.aliasOf">({{ emoji.aliasOf }})</span>
</li>
</ol>
@ -31,6 +31,7 @@ import Vue from 'vue';
import * as emojilib from 'emojilib';
import contains from '../../../common/scripts/contains';
import { twemojiBase } from '../../../../../misc/twemoji-base';
import { getStaticImageUrl } from '../../../common/scripts/get-static-image-url';
type EmojiDef = {
emoji: string;
@ -78,6 +79,7 @@ export default Vue.extend({
data() {
return {
getStaticImageUrl,
fetching: true,
users: [],
hashtags: [],
@ -89,14 +91,6 @@ export default Vue.extend({
},
computed: {
beforeQ(): string {
return this.emoji.name.split(this.q)[0];
},
afterQ(): string {
return this.emoji.name.split(this.q)[1] || '';
},
items(): HTMLCollection {
return (this.$refs.suggests as Element).children;
},

View File

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

View File

@ -1,5 +1,5 @@
<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">
<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>

View File

@ -1,5 +1,5 @@
<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>
</a>
</template>

View File

@ -9,7 +9,7 @@
<div class="content" v-if="!message.isDeleted">
<mfm class="text" v-if="message.text" ref="text" :text="message.text" :i="$store.state.i"/>
<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"
: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>

View File

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

View File

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

View File

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

View File

@ -89,9 +89,7 @@ export default Vue.extend({
get() {
const at = () => {
const [date] = moment(this.atDate).toISOString().split('T');
const [hour, minute] = this.atTime.split(':');
return moment(`${date}T${hour}:${minute}Z`).valueOf();
return moment(`${this.atDate} ${this.atTime}`).valueOf();
};
const after = () => {

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>
<div v-if="data && !$store.state.i.twoFactorEnabled">
<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('done') }}<br>
<ui-input v-model="token">{{ $t('token') }}</ui-input>

View File

@ -4,21 +4,21 @@
<section v-if="enableTwitterIntegration">
<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-else @click="connectTwitter">{{ $t('connect') }}</ui-button>
</section>
<section v-if="enableDiscordIntegration">
<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-else @click="connectDiscord">{{ $t('connect') }}</ui-button>
</section>
<section v-if="enableGithubIntegration">
<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-else @click="connectGithub">{{ $t('connect') }}</ui-button>
</section>

View File

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

View File

@ -45,7 +45,7 @@
</ui-select>
</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">
<summary><fa icon="palette"/> {{ $t('create-a-theme') }}</summary>

View File

@ -9,7 +9,7 @@
</blockquote>
</div>
<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}')`">
<button v-if="!playerEnabled && player.url" @click.prevent="playerEnabled = true" :title="$t('enable-player')"><fa :icon="['far', 'play-circle']"/></button>
</div>

View File

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

View File

@ -8,7 +8,7 @@
<div class="no-users" v-if="inited && us.length == 0">
<p>{{ $t('no-users') }}</p>
</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"/>
<div class="body" v-if="!iconOnly">
<div class="name">
@ -18,6 +18,7 @@
<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"/>
</div>
<mk-follow-button class="follow-button" v-if="$store.getters.isSignedIn && user.id != $store.state.i.id" :user="user" mini/>
</div>
</div>
<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
opacity 0.7
font-size 14px
padding-right 40px
> .follow-button
position absolute
top 8px
right 0px
> .more
display block

View File

@ -7,7 +7,6 @@
<script lang="ts">
import Vue from 'vue';
import i18n from '../../../i18n';
import copyToClipboard from '../../../common/scripts/copy-to-clipboard';
import { faExclamationCircle, faMicrophoneSlash } from '@fortawesome/free-solid-svg-icons';
import { faSnowflake } from '@fortawesome/free-regular-svg-icons';
@ -27,19 +26,23 @@ export default Vue.extend({
icon: ['fas', 'list'],
text: this.$t('push-to-list'),
action: this.pushList
}, null, {
icon: this.user.isMuted ? ['fas', 'eye'] : ['far', 'eye-slash'],
text: this.user.isMuted ? this.$t('unmute') : this.$t('mute'),
action: this.toggleMute
}, {
icon: 'ban',
text: this.user.isBlocking ? this.$t('unblock') : this.$t('block'),
action: this.toggleBlock
}, null, {
icon: faExclamationCircle,
text: this.$t('report-abuse'),
action: this.reportAbuse
}];
}] as any;
if (this.$store.getters.isSignedIn && this.$store.state.i.id != this.user.id) {
menu = menu.concat([null, {
icon: this.user.isMuted ? ['fas', 'eye'] : ['far', 'eye-slash'],
text: this.user.isMuted ? this.$t('unmute') : this.$t('mute'),
action: this.toggleMute
}, {
icon: 'ban',
text: this.user.isBlocking ? this.$t('unblock') : this.$t('block'),
action: this.toggleBlock
}, 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)) {
menu = menu.concat([null, {
@ -89,8 +92,10 @@ export default Vue.extend({
});
},
toggleMute() {
async toggleMute() {
if (this.user.isMuted) {
if (!await this.getConfirmed(this.$t('unmute-confirm'))) return;
this.$root.api('mute/delete', {
userId: this.user.id
}).then(() => {
@ -102,6 +107,8 @@ export default Vue.extend({
});
});
} else {
if (!await this.getConfirmed(this.$t('mute-confirm'))) return;
this.$root.api('mute/create', {
userId: this.user.id
}).then(() => {
@ -115,8 +122,10 @@ export default Vue.extend({
}
},
toggleBlock() {
async toggleBlock() {
if (this.user.isBlocking) {
if (!await this.getConfirmed(this.$t('unblock-confirm'))) return;
this.$root.api('blocking/delete', {
userId: this.user.id
}).then(() => {
@ -128,6 +137,8 @@ export default Vue.extend({
});
});
} else {
if (!await this.getConfirmed(this.$t('block-confirm'))) return;
this.$root.api('blocking/create', {
userId: this.user.id
}).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', {
userId: this.user.id
}).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', {
userId: this.user.id
}).then(() => {
@ -196,7 +211,18 @@ export default Vue.extend({
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>

View File

@ -8,7 +8,7 @@
<div class="is-remote" v-if="note.user.host != null">
<details>
<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>
</div>
<mk-note :note="note" :detail="true" :key="note.id"/>

View File

@ -157,6 +157,7 @@ export default Vue.extend({
// 稿
if (this.notes.length >= displayLimit) {
this.notes = this.notes.slice(0, displayLimit);
this.cursor = this.notes[this.notes.length - 1].id
}
} else {
this.queue.push(note);
@ -165,6 +166,7 @@ export default Vue.extend({
append(note) {
this.notes.push(note);
this.cursor = this.notes[this.notes.length - 1].id
},
releaseQueue() {

View File

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

View File

@ -8,7 +8,7 @@
<div class="is-remote" v-if="user.host != null">
<details>
<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>
</div>
<header :style="bannerStyle">

View File

@ -21,14 +21,7 @@
<fa :icon="['far', 'laugh']"/>
</button>
</div>
<div class="files" v-show="files.length != 0">
<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>
<x-post-form-attaches class="files" :files="files" :detachMediaFn="detachMedia"/>
<input ref="file" type="file" multiple="multiple" tabindex="-1" @change="onChangeFile"/>
<mk-uploader ref="uploader" @uploaded="attachMedia"/>
<footer>
@ -45,7 +38,7 @@
import define from '../../../common/define-widget';
import i18n from '../../../i18n';
import insertTextAtCursor from 'insert-text-at-cursor';
import * as XDraggable from 'vuedraggable';
import XPostFormAttaches from '../components/post-form-attaches.vue';
export default define({
name: 'post-form',
@ -56,7 +49,7 @@ export default define({
i18n: i18n('desktop/views/widgets/post-form.vue'),
components: {
XDraggable
XPostFormAttaches
},
data() {
@ -176,10 +169,22 @@ export default define({
post() {
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', {
text: this.text == '' ? undefined : this.text,
fileIds: this.files.length > 0 ? this.files.map(f => f.id) : undefined,
visibility: this.$store.state.settings.defaultNoteVisibility
visibility,
localOnly,
}).then(data => {
this.clear();
}).catch(err => {
@ -237,38 +242,6 @@ export default define({
& + .emoji
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]
display none

View File

@ -7,7 +7,7 @@
<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>
<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>
</ui-container>

View File

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

View File

@ -54,7 +54,7 @@
</div>
<mk-poll v-if="appearNote.poll" :note="appearNote"/>
<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="renote" v-if="appearNote.renote">
<mk-note-preview :note="appearNote.renote"/>

View File

@ -32,7 +32,7 @@
<mk-media-list :media-list="appearNote.files"/>
</div>
<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>
<mk-url-preview v-for="url in urls" :url="url" :key="url" :compact="compact"/>
</div>

View File

@ -157,6 +157,7 @@ export default Vue.extend({
// 稿
if (this.notes.length >= displayLimit) {
this.notes = this.notes.slice(0, displayLimit);
this.cursor = this.notes[this.notes.length - 1].id
}
} else {
this.queue.push(note);
@ -165,6 +166,7 @@ export default Vue.extend({
append(note) {
this.notes.push(note);
this.cursor = this.notes[this.notes.length - 1].id
},
releaseQueue() {

View File

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

View File

@ -4,7 +4,7 @@
<fa icon="exclamation-triangle"/> {{ $t('@.user-suspended') }}
</div>
<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 class="main">
<x-header class="header" :user="user"/>

View File

@ -1,7 +1,7 @@
<template>
<div class="header" :class="{ shadow: $store.state.device.useShadow, round: $store.state.device.roundedCorners }">
<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="title">
<p class="name">
@ -105,14 +105,6 @@ export default Vue.extend({
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() {
this.$root.new(XUserMenu, {
source: this.$refs.menu,
@ -171,9 +163,6 @@ export default Vue.extend({
> .menu
height 100%
display block
position absolute
left -42px
padding 0 14px
color #fff
text-shadow 0 0 8px #000

View File

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

View File

@ -11,7 +11,9 @@
<div class="mkw-polls--body">
<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>
<mk-poll :note="poll"/>
</div>

View File

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

View File

@ -40,7 +40,7 @@
</div>
<mk-poll v-if="appearNote.poll" :note="appearNote"/>
<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="renote" v-if="appearNote.renote">
<mk-note-preview :note="appearNote.renote"/>

View File

@ -32,7 +32,7 @@
</div>
<mk-poll v-if="appearNote.poll" :note="appearNote" ref="pollViewer"/>
<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>
<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) {
this.notes = this.notes.slice(0, displayLimit);
this.cursor = this.notes[this.notes.length - 1].id
}
} else {
this.queue.push(note);
@ -159,6 +160,7 @@ export default Vue.extend({
append(note) {
this.notes.push(note);
this.cursor = this.notes[this.notes.length - 1].id
},
releaseQueue() {

View File

@ -21,13 +21,7 @@
</div>
<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>
<div class="attaches" v-show="files.length != 0">
<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>
<x-post-form-attaches class="attaches" :files="files"/>
<mk-poll-editor v-if="poll" ref="poll" @destroyed="poll = false" @updated="onPollUpdate()"/>
<mk-uploader ref="uploader" @uploaded="attachMedia" @change="onChangeUploadings"/>
<footer>
@ -57,7 +51,6 @@
import Vue from 'vue';
import i18n from '../../../i18n';
import insertTextAtCursor from 'insert-text-at-cursor';
import * as XDraggable from 'vuedraggable';
import MkVisibilityChooser from '../../../common/views/components/visibility-chooser.vue';
import getFace from '../../../common/scripts/get-face';
import { parse } from '../../../../../mfm/parse';
@ -66,11 +59,12 @@ import { erase, unique } from '../../../../../prelude/array';
import { length } from 'stringz';
import { toASCII } from 'punycode';
import extractMentions from '../../../../../misc/extract-mentions';
import XPostFormAttaches from '../../../common/views/components/post-form-attaches.vue';
export default Vue.extend({
i18n: i18n('mobile/views/components/post-form.vue'),
components: {
XDraggable
XPostFormAttaches
},
props: {
@ -264,8 +258,8 @@ export default Vue.extend({
this.$emit('change-attached-files', this.files);
},
detachMedia(file) {
this.files = this.files.filter(x => x.id != file.id);
detachMedia(id) {
this.files = this.files.filter(x => x.id != id);
this.$emit('change-attached-files', this.files);
},
@ -481,32 +475,6 @@ export default Vue.extend({
min-width 100%
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
margin 8px 0 0 0
padding 8px

View File

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

View File

@ -5,7 +5,7 @@
</template>
<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-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>
<div class="banner" :style="style"></div>
<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;
clusterLimit?: number;
outgoingAddressFamily?: 'ipv4' | 'ipv6' | 'dual';
};
/**

View File

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

View File

@ -6,4 +6,4 @@ block main
block footer
p
= 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)));
//#endregion
console.log(' Misskey is maintained by @syuilo, @AyaMorisawa, @mei23, and @acid-chicken.');
console.log(' Misskey is maintained by @syuilo, @AyaMorisawa, @mei23, @acid-chicken, and @rinsuki.');
console.log(chalk.keyword('orange')(' If you like Misskey, please donate to support development. https://www.patreon.com/syuilo'));
console.log('');

View File

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

File diff suppressed because one or more lines are too long

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,4 +1,4 @@
export const twemojiBase = 'https://cdn.jsdelivr.net/npm/twemoji@11.3.0';
// https://cdn.jsdelivr.net/npm/twemoji@11.3.0
// https://cdnjs.cloudflare.com/ajax/libs/twemoji/11.3.0
export const twemojiBase = 'https://cdn.jsdelivr.net/npm/twemoji@12.0.1';
// https://cdn.jsdelivr.net/npm/twemoji@12.0.1
// https://cdnjs.cloudflare.com/ajax/libs/twemoji/12.0.1
// https://twemoji.maxcdn.com

View File

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

View File

@ -16,10 +16,9 @@ export async function deleteDriveFiles(job: Bull.Job, done: any): Promise<void>
});
let deletedCount = 0;
let ended = false;
let cursor: any = null;
while (!ended) {
while (true) {
const files = await DriveFile.find({
userId: user._id,
...(cursor ? { _id: { $gt: cursor } } : {})
@ -31,7 +30,6 @@ export async function deleteDriveFiles(job: Bull.Job, done: any): Promise<void>
});
if (files.length === 0) {
ended = true;
job.progress(100);
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' });
let exportedCount = 0;
let ended = false;
let cursor: any = null;
while (!ended) {
while (true) {
const blockings = await Blocking.find({
blockerId: user._id,
...(cursor ? { _id: { $gt: cursor } } : {})
@ -47,7 +46,6 @@ export async function exportBlocking(job: Bull.Job, done: any): Promise<void> {
});
if (blockings.length === 0) {
ended = true;
job.progress(100);
break;
}
@ -81,7 +79,7 @@ export async function exportBlocking(job: Bull.Job, done: any): Promise<void> {
logger.succ(`Exported to: ${path}`);
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}`);
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' });
let exportedCount = 0;
let ended = false;
let cursor: any = null;
while (!ended) {
while (true) {
const followings = await Following.find({
followerId: user._id,
...(cursor ? { _id: { $gt: cursor } } : {})
@ -47,7 +46,6 @@ export async function exportFollowing(job: Bull.Job, done: any): Promise<void> {
});
if (followings.length === 0) {
ended = true;
job.progress(100);
break;
}
@ -81,7 +79,7 @@ export async function exportFollowing(job: Bull.Job, done: any): Promise<void> {
logger.succ(`Exported to: ${path}`);
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}`);
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' });
let exportedCount = 0;
let ended = false;
let cursor: any = null;
while (!ended) {
while (true) {
const mutes = await Mute.find({
muterId: user._id,
...(cursor ? { _id: { $gt: cursor } } : {})
@ -47,7 +46,6 @@ export async function exportMute(job: Bull.Job, done: any): Promise<void> {
});
if (mutes.length === 0) {
ended = true;
job.progress(100);
break;
}
@ -81,7 +79,7 @@ export async function exportMute(job: Bull.Job, done: any): Promise<void> {
logger.succ(`Exported to: ${path}`);
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}`);
cleanup();

View File

@ -42,10 +42,9 @@ export async function exportNotes(job: Bull.Job, done: any): Promise<void> {
});
let exportedNotesCount = 0;
let ended = false;
let cursor: any = null;
while (!ended) {
while (true) {
const notes = await Note.find({
userId: user._id,
...(cursor ? { _id: { $gt: cursor } } : {})
@ -57,7 +56,6 @@ export async function exportNotes(job: Bull.Job, done: any): Promise<void> {
});
if (notes.length === 0) {
ended = true;
job.progress(100);
break;
}
@ -101,7 +99,7 @@ export async function exportNotes(job: Bull.Job, done: any): Promise<void> {
logger.succ(`Exported to: ${path}`);
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}`);
cleanup();

View File

@ -65,7 +65,7 @@ export async function exportUserLists(job: Bull.Job, done: any): Promise<void> {
logger.succ(`Exported to: ${path}`);
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}`);
cleanup();

View File

@ -34,7 +34,8 @@ export async function importFollowing(job: Bull.Job, done: any): Promise<void> {
linenum++;
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({
host: null,

View File

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

View File

@ -15,6 +15,8 @@ import block from './block';
import { apLogger } from '../logger';
const self = async (actor: IRemoteUser, activity: Object): Promise<void> => {
if (actor.isSuspended) return;
switch (activity.type) {
case 'Create':
await create(actor, activity);

View File

@ -20,10 +20,32 @@ import { apLogger } from '../logger';
import { IDriveFile } from '../../../models/drive-file';
import { deliverQuestionUpdate } from '../../../services/note/polls/update';
import Instance from '../../../models/instance';
import { extractDbHost } from '../../../misc/convert-host';
import { extractDbHost, extractApHost } from '../../../misc/convert-host';
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をフェッチします
*
@ -57,8 +79,10 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
const object: any = await resolver.resolve(value);
if (!object || !['Note', 'Question', 'Article'].includes(object.type)) {
logger.error(`invalid note: ${value}`, {
const entryUri = value.id || value;
const err = validateNote(object, entryUri);
if (err) {
logger.error(`${err.message}`, {
resolver: {
history: resolver.getHistory()
},
@ -241,7 +265,7 @@ export async function resolveNote(value: string | IObject, resolver?: Resolver):
// リモートサーバーからフェッチしてきて登録
// ここでuriの代わりに添付されてきたNote Objectが指定されていると、サーバーフェッチを経ずにートが生成されるが
// 添付されてきたNote Objectは偽装されている可能性があるため、常にuriを指定してサーバーフェッチを行う。
return await createNote(uri, resolver);
return await createNote(uri, resolver, true);
}
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 Resolver from '../resolver';
import { resolveImage } from './image';
import { isCollectionOrOrderedCollection, isCollection, IPerson } from '../type';
import { isCollectionOrOrderedCollection, isCollection, IPerson, validActor } from '../type';
import { IDriveFile } from '../../../models/drive-file';
import Meta from '../../../models/meta';
import { fromHtml } from '../../../mfm/fromHtml';
@ -38,7 +38,7 @@ function validatePerson(x: any, uri: string) {
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}'`);
}
@ -294,13 +294,6 @@ export async function updatePerson(uri: string, resolver?: Resolver, hint?: obje
}
//#endregion
// 繋がらないインスタンスに何回も試行するのを防ぐ, 後続の同様処理の連続試行を防ぐ ため 試行前にも更新する
await User.update({ _id: exist._id }, {
$set: {
lastFetchedAt: new Date(),
},
});
if (resolver == null) resolver = new Resolver();
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 { lookup, IRunOptions } from 'lookup-dns-cache';
import * as promiseAny from 'promise-any';
import { toUnicode } from 'punycode';
import config from '../../config';
import { ILocalUser } from '../../models/user';
import { publishApLogStream } from '../../services/stream';
import { apLogger } from './logger';
import Instance from '../../models/instance';
import { toDbHost } from '../../misc/convert-host';
export const logger = apLogger.createSubLogger('deliver');
@ -23,7 +23,7 @@ export default async (user: ILocalUser, url: string, object: any) => {
// ブロックしてたら中断
// TODO: いちいちデータベースにアクセスするのはコスト高そうなのでどっかにキャッシュしておく
const instance = await Instance.findOne({ host: toUnicode(host) });
const instance = await Instance.findOne({ host: toDbHost(host) });
if (instance && instance.isBlocked) return;
const data = JSON.stringify(object);
@ -35,7 +35,7 @@ export default async (user: ILocalUser, url: string, object: any) => {
const addr = await resolveAddr(hostname);
if (!addr) return;
const _ = new Promise((resolve, reject) => {
await new Promise((resolve, reject) => {
const req = request({
protocol,
hostname: addr,
@ -82,8 +82,6 @@ export default async (user: ILocalUser, url: string, object: any) => {
req.end(data);
});
await _;
//#region Log
publishApLogStream({
direction: 'out',
@ -98,11 +96,18 @@ export default async (user: ILocalUser, url: string, object: any) => {
* Resolve host (with cached, asynchrony)
*/
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で先に取得できた方を採用する
return await promiseAny([
resolveAddrInner(domain, { family: 4 }),
resolveAddrInner(domain, { family: 6 })
]);
return await promiseAny(promises);
}
function resolveAddrInner(domain: string, options: IRunOptions = {}): Promise<string> {

View File

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

View File

@ -9,7 +9,7 @@ import chalk from 'chalk';
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();
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 });
}
const user = await User.findOne({ usernameLower, host }, option);
const user = await User.findOne({ usernameLower, host }, option) as IRemoteUser;
const acctLower = `${usernameLower}@${hostAscii}`;
@ -39,14 +39,22 @@ export default async (username: string, _host: string, option?: any, resync?: bo
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}`);
const self = await resolveSelf(acctLower);
if ((user as IRemoteUser).uri !== self.href) {
if (user.uri !== self.href) {
// if uri mismatch, Fix (user@host <=> AP's Person id(IRemoteUser.uri)) mapping.
logger.info(`uri missmatch: ${acctLower}`);
logger.info(`recovery missmatch uri for (username=${username}, host=${host}) from ${(user as IRemoteUser).uri} to ${self.href}`);
logger.info(`recovery missmatch uri for (username=${username}, host=${host}) from ${user.uri} to ${self.href}`);
// validate uri
const uri = new URL(self.href);
@ -79,8 +87,8 @@ export default async (username: string, _host: string, option?: any, resync?: bo
async function resolveSelf(acctLower: string) {
logger.info(`WebFinger for ${chalk.yellow(acctLower)}`);
const finger = await webFinger(acctLower).catch(e => {
logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: ${e.message} (${e.status})`);
throw e;
logger.error(`Failed to WebFinger for ${chalk.yellow(acctLower)}: ${ e.statusCode || e.message }`);
throw new Error(`Failed to WebFinger for ${acctLower}: ${ e.statusCode || e.message }`);
});
const self = finger.links.find(link => link.rel && link.rel.toLowerCase() === 'self');
if (!self) {

View File

@ -1,6 +1,7 @@
import { WebFinger } from 'webfinger.js';
const webFinger = new WebFinger({ });
import config from '../config';
import * as request from 'request-promise-native';
import { URL } from 'url';
import { query as urlQuery } from '../prelude/url';
type ILink = {
href: string;
@ -12,12 +13,33 @@ type IWebFinger = {
subject: string;
};
export default async function resolve(query: any): Promise<IWebFinger> {
return await new Promise((res, rej) => webFinger.lookup(query, (error: Error | string, result: any) => {
if (error) {
return rej(error);
}
export default async function(query: string): Promise<IWebFinger> {
const url = genUrl(query);
res(result.object);
})) as IWebFinger;
return await request({
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

@ -22,6 +22,9 @@ export default define(meta, async (ps) => {
createdAt: {
$gt: new Date(Date.now() - span)
},
visibility: {
$in: ['public', 'home']
},
tagsLower: {
$exists: true,
$ne: []

View File

@ -10,6 +10,7 @@ import Resolver from '../../../../remote/activitypub/resolver';
import { ApiError } from '../../error';
import Instance from '../../../../models/instance';
import { extractDbHost } from '../../../../misc/convert-host';
import { validActor } from '../../../../remote/activitypub/type';
export const meta = {
tags: ['federation'],
@ -85,6 +86,17 @@ async function fetchAny(uri: string) {
// /@user のような正規id以外で取得できるURIが指定されていた場合、ここで初めて正規URIが確定する
// これはDBに存在する可能性があるため再度DB検索
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([
User.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);
return {
type: 'User',
@ -104,7 +116,7 @@ async function fetchAny(uri: string) {
}
if (['Note', 'Question', 'Article'].includes(object.type)) {
const note = await createNote(object.id);
const note = await createNote(object.id, null, true);
return {
type: 'Note',
object: await packNote(note, null, { detail: true })

View File

@ -31,6 +31,9 @@ export default define(meta, async () => {
createdAt: {
$gt: new Date(Date.now() - rangeA)
},
visibility: {
$in: ['public', 'home']
},
tagsLower: {
$exists: true,
$ne: []
@ -79,6 +82,9 @@ export default define(meta, async () => {
const hotsPromises = limitedTags.map(async tag => {
const passedCount = (await Note.distinct('userId', {
tagsLower: tag.name,
visibility: {
$in: ['public', 'home']
},
createdAt: {
$lt: new Date(Date.now() - rangeA),
$gt: new Date(Date.now() - rangeB)
@ -120,6 +126,9 @@ export default define(meta, async () => {
for (let i = 0; i < range; i++) {
countPromises.push(Promise.all(hots.map(tag => Note.distinct('userId', {
tagsLower: tag,
visibility: {
$in: ['public', 'home']
},
createdAt: {
$lt: new Date(Date.now() - (interval * i)),
$gt: new Date(Date.now() - (interval * (i + 1)))
@ -131,6 +140,9 @@ export default define(meta, async () => {
const totalCounts: any = await Promise.all(hots.map(tag => Note.distinct('userId', {
tagsLower: tag,
visibility: {
$in: ['public', 'home']
},
createdAt: {
$gt: new Date(Date.now() - (interval * range))
}

View File

@ -1,6 +1,6 @@
import $ from 'cafy';
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 define from '../../define';
import { apiLogger } from '../../logger';
@ -96,13 +96,6 @@ export default define(meta, async (ps, me) => {
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, {
detail: true
});

View File

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

View File

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

View File

@ -73,7 +73,7 @@ export default async function(ctx: Koa.BaseContext) {
await sendRaw();
} else {
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) {

View File

@ -17,6 +17,8 @@ export async function proxyMedia(ctx: Koa.BaseContext) {
const [type, ext] = await detectMine(path);
if (!type.startsWith('image/')) throw 403;
let image: IImage;
if ('static' in ctx.query && ['image/png', 'image/gif'].includes(type)) {

View File

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

View File

@ -24,6 +24,9 @@ block meta
meta(name='twitter:card' content='summary')
if user.host
meta(name='robots' content='noindex')
if user.twitter
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 extractEmojis from '../../misc/extract-emojis';
import extractHashtags from '../../misc/extract-hashtags';
import { genId } from '../../misc/gen-id';
type NotificationType = 'reply' | 'renote' | 'quote' | 'mention';
@ -237,7 +238,9 @@ export default async (user: IUser, data: Option, silent = false) => new Promise<
}
// ハッシュタグ更新
for (const tag of tags) updateHashtag(user, tag);
if (data.visibility === 'public' || data.visibility === 'home') {
for (const tag of tags) updateHashtag(user, tag);
}
// ファイルが添付されていた場合ドライブのファイルの「このファイルが添付された投稿一覧」プロパティにこの投稿を追加
if (data.files) {
@ -434,6 +437,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[]) {
const insert: any = {
_id: genId(data.createdAt),
createdAt: data.createdAt,
fileIds: data.files ? data.files.map(file => file._id) : [],
replyId: data.reply ? data.reply._id : null,

View File

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