Compare commits

...

162 Commits

Author SHA1 Message Date
cd6a1d3446 5.24.0 2018-08-15 02:14:57 +09:00
4a9fc0c8ed Better query 2018-08-15 02:08:18 +09:00
fada899b30 Merge pull request #2210 from mei23/mei-0814-ap3
ActivityPub Followers/Following/Outbox の実装
2018-08-15 02:04:08 +09:00
22b099fa8a Merge branch 'master' of https://github.com/syuilo/misskey 2018-08-15 02:01:52 +09:00
e0bc0d2830 Add new kao 2018-08-15 02:01:49 +09:00
90768d30aa fix(package): update seedrandom to version 2.4.4 2018-08-15 01:53:48 +09:00
177c549493 fix(package): update url-loader to version 1.1.0 2018-08-15 01:53:40 +09:00
5e1ee68189 Merge branch 'master' of https://github.com/syuilo/misskey 2018-08-15 01:51:46 +09:00
175f6303bc Update theme color 2018-08-15 01:51:43 +09:00
cf9f2a5562 Merge pull request #2211 from skid9000/patch-2
Update doc for twitter integration.
2018-08-14 22:55:02 +09:00
f04526baca Update example.yml 2018-08-14 15:45:36 +02:00
f085ecedb3 Update setup.en.md 2018-08-14 15:41:55 +02:00
0986301788 Implement ActivityPub Followers/Following/Outbox 2018-08-14 20:13:32 +09:00
fe418d8d9a fix(package): update @types/ws to version 6.0.0 2018-08-14 17:07:35 +09:00
6009be34dc fix(package): update @types/node to version 10.7.0 2018-08-14 17:07:23 +09:00
01a0a54a2c fix(package): update @types/mongodb to version 3.1.4 2018-08-14 17:07:15 +09:00
cfcaf77e21 fix(package): update mongodb to version 3.1.3 2018-08-14 17:07:07 +09:00
3b37bdc0b9 fix(package): update vue-style-loader to version 4.1.2 2018-08-14 17:06:54 +09:00
ec07112f94 Fix bug 2018-08-14 16:53:57 +09:00
464faf2673 Merge pull request #2200 from syuilo/use-deque
Use deque instead of linked list
2018-08-14 08:22:23 +09:00
bde20a1a65 Use deque instead of linked list 2018-08-14 08:21:25 +09:00
86c7276da9 Merge branch 'master' of https://github.com/syuilo/misskey 2018-08-14 08:16:24 +09:00
fec988bb79 Provide isFirstNote flag 2018-08-14 08:16:21 +09:00
0702d0974b Merge pull request #2199 from syuilo/patch-2176
Resolve #2176
2018-08-14 07:51:45 +09:00
f443d36dbb Resolve #2176 2018-08-14 07:49:59 +09:00
dc02168f33 Merge #2182 2018-08-14 05:25:02 +09:00
cc5c32b4d2 Clean up 2018-08-14 05:24:51 +09:00
d35f62d0e4 Merge pull request #2195 from syuilo/instance-management-system
管理画面
2018-08-14 04:42:16 +09:00
09b8e81a77 wip 2018-08-14 04:39:37 +09:00
3b38979a34 wip 2018-08-14 04:30:42 +09:00
0fd8c86c24 fix(package): update mongodb to version 3.1.2 2018-08-14 03:38:01 +09:00
58d0ed1a2e 5.23.2 2018-08-14 02:36:06 +09:00
8939452036 Merge pull request #2192 from acid-chicken/acid-chicken-patch-3
Re: Re: Fix #2177
2018-08-14 02:31:00 +09:00
a2931d6f7e Update ui.header.vue 2018-08-14 02:26:58 +09:00
38b75ad977 Update avatar.vue 2018-08-14 02:10:06 +09:00
dc4f585954 Merge pull request #2191 from syuilo/greenkeeper/parse5-5.1.0
Update parse5 to the latest version 🚀
2018-08-14 01:58:01 +09:00
1fbe5365f7 Update example.yml 2018-08-14 01:57:52 +09:00
04257db938 fix(package): update parse5 to version 5.1.0 2018-08-13 16:54:58 +00:00
ba08d1aa53 wip 2018-08-14 01:48:11 +09:00
c29cb5bfb9 Update package.json 2018-08-14 01:38:29 +09:00
fb1e2efbdd wip 2018-08-14 01:37:23 +09:00
131a454e7c Merge pull request #2190 from mei23/mei-apsendvis2
ActivityPub送信時の公開範囲の実装
2018-08-14 01:36:56 +09:00
92e5cff285 wip 2018-08-14 01:35:36 +09:00
b24e32e14e 5.23.0 2018-08-14 01:32:49 +09:00
943805bdcd Merge pull request #2180 from syuilo/l10n_master
New Crowdin translations
2018-08-14 01:32:11 +09:00
ba9340a26b wip 2018-08-14 01:24:46 +09:00
00119328f2 wip 2018-08-14 01:19:05 +09:00
a73c65da07 New translations ja.yml (French) 2018-08-14 01:11:32 +09:00
9021bb5694 wip 2018-08-14 01:05:58 +09:00
a3cf63823f Merge pull request #2189 from syuilo/patch-1
Hide unimplemented button
2018-08-13 23:53:15 +09:00
f15878cc6f Update avatar.vue
refs: https://github.com/syuilo/misskey/pull/2182#discussion_r209609541
2018-08-13 22:49:32 +09:00
33469ff87a Hide Unimplemented button 2018-08-13 22:08:59 +09:00
23b0723168 Merge pull request #2188 from Tosuke/patch1
Create apps without authentication(#2025)
2018-08-13 12:38:02 +09:00
fda1ab3e05 Create apps without authentication(#2025) 2018-08-13 12:30:32 +09:00
490c05a869 New translations ja.yml (English) 2018-08-13 06:32:08 +09:00
f0137daebe New translations ja.yml (English) 2018-08-13 04:51:11 +09:00
b9fc0e6d71 Merge pull request #2183 from acid-chicken/patch-1
Make player height taller
2018-08-13 04:42:49 +09:00
979efee412 Update url-preview.vue 2018-08-13 04:41:12 +09:00
f079041827 ActivityPub visibility on send 2018-08-13 03:49:17 +09:00
4edd9efc0b Update avatar.vue
refs: https://github.com/syuilo/misskey/pull/2182#discussion_r209464350
2018-08-13 03:47:56 +09:00
2913c7ccfb Update avatar.vue 2018-08-13 03:42:12 +09:00
e1f460f90f Update user.header.vue 2018-08-13 03:40:50 +09:00
70f927ea43 Update avatar.vue 2018-08-13 03:39:32 +09:00
80d343bb0b Update avatar.vue 2018-08-13 03:36:42 +09:00
9e41fddea3 Merge pull request #2130 from acid-chicken/patch-player
WIP: Add player
2018-08-13 03:25:22 +09:00
8384efc8c7 Create whitelist 2018-08-13 03:23:36 +09:00
7797c86581 Merge pull request #2181 from acid-chicken/patch-1
Re: Fix #2177
2018-08-13 01:59:11 +09:00
4da8cc478f Update ui.header.vue 2018-08-13 01:58:15 +09:00
285deeec52 New translations ja.yml (Catalan) 2018-08-13 01:31:20 +09:00
2916e49422 New translations ja.yml (Portuguese) 2018-08-13 01:31:18 +09:00
41e5b9134b New translations ja.yml (Korean) 2018-08-13 01:31:16 +09:00
d0d853dcb2 New translations ja.yml (Polish) 2018-08-13 01:31:13 +09:00
be46c7e4c5 New translations ja.yml (Chinese Simplified) 2018-08-13 01:31:11 +09:00
8e0f41d608 New translations ja.yml (Italian) 2018-08-13 01:31:09 +09:00
a7b438072c New translations ja.yml (Russian) 2018-08-13 01:31:06 +09:00
99958e2fce New translations ja.yml (English) 2018-08-13 01:31:04 +09:00
b82843d359 New translations ja.yml (Spanish) 2018-08-13 01:31:02 +09:00
4dfc2dfa89 New translations ja.yml (German) 2018-08-13 01:30:59 +09:00
50c945607f New translations ja.yml (French) 2018-08-13 01:30:57 +09:00
01f28b21dd #1211 2018-08-13 01:25:50 +09:00
2cb39a8882 Fix #2097 2018-08-13 00:59:36 +09:00
6ddb6bc160 Add .vsls.json 2018-08-13 00:24:45 +09:00
92befbb4cc Fix #2177
Resolves #2177
2018-08-13 00:18:02 +09:00
ab701bb93e Merge pull request #2159 from syuilo/l10n_master
New Crowdin translations
2018-08-12 23:53:22 +09:00
7f9a88fd1c fix(package): update vue-js-modal to version 1.3.17 2018-08-12 23:13:52 +09:00
c5073b33ef Fix ActivityPub followers/specified detection 2018-08-12 20:37:35 +09:00
765b922a8b fix(package): update ts-node to version 7.0.1 2018-08-12 16:22:43 +09:00
9b7d6274fa New translations ja.yml (French) 2018-08-12 07:40:57 +09:00
26b384aef1 New translations ja.yml (French) 2018-08-12 07:30:58 +09:00
0b1e5e3e08 New translations ja.yml (French) 2018-08-12 07:20:58 +09:00
5b7506756e New translations ja.yml (French) 2018-08-12 07:11:02 +09:00
1f28a0dfeb Improve nya 2018-08-12 05:20:31 +09:00
f56ec82f6b Fix ActivityPub attachment url 2018-08-12 04:42:14 +09:00
8ccbabf5ca 5.22.1 2018-08-12 03:09:45 +09:00
7c763600b7 Fix #2162 2018-08-12 03:09:22 +09:00
499491003b New translations ja.yml (Polish) 2018-08-12 00:31:30 +09:00
3a77d871d5 Stop supporting docker 2018-08-12 00:30:58 +09:00
4ee6d0b549 Refactoring (#2160) 2018-08-12 00:01:07 +09:00
64aa733b16 New translations ja.yml (Polish) 2018-08-11 22:51:08 +09:00
c8c4ec6ad4 5.22.0 2018-08-11 21:35:22 +09:00
c9ee737078 Fix bug 2018-08-11 21:34:12 +09:00
ebc2cca0b4 New Crowdin translations (#2158) 2018-08-11 21:29:07 +09:00
fc94df06eb New Crowdin translations (#2157) 2018-08-11 21:13:38 +09:00
190a03103e New translations ja.yml (Catalan) 2018-08-11 21:11:31 +09:00
0d75ae9d9a New translations ja.yml (Portuguese) 2018-08-11 21:11:29 +09:00
3129f8f073 New translations ja.yml (Korean) 2018-08-11 21:11:27 +09:00
7f751d3f20 New translations ja.yml (Polish) 2018-08-11 21:11:25 +09:00
0b5b834f8b New translations ja.yml (Chinese Simplified) 2018-08-11 21:11:23 +09:00
0f649f7d37 New translations ja.yml (Italian) 2018-08-11 21:11:21 +09:00
a1b100d412 New translations ja.yml (Russian) 2018-08-11 21:11:19 +09:00
dd4ee1627e New translations ja.yml (English) 2018-08-11 21:11:16 +09:00
1da0fdcf78 New translations ja.yml (Spanish) 2018-08-11 21:11:14 +09:00
800eec73b8 New translations ja.yml (German) 2018-08-11 21:11:12 +09:00
166cb5e179 New translations ja.yml (French) 2018-08-11 21:11:10 +09:00
4a5e145048 Add new reaction: rip 😇 2018-08-11 21:08:34 +09:00
be68f42220 Implement messaging/messages/read 2018-08-11 21:01:39 +09:00
7942aa677f Update doc 2018-08-11 21:01:26 +09:00
2fcf9288a5 Refactoring 2018-08-11 20:53:03 +09:00
9bc17974f2 未利用時はTwitterでログインを表示しない (#2156) 2018-08-11 20:20:35 +09:00
19c872a1f3 Update url-preview.vue 2018-08-11 19:56:53 +09:00
9252c59d90 Merge branch 'master' into patch-player 2018-08-11 19:56:05 +09:00
a8f142096c Cache failed url-preview (apply to proxy/client) (#2154) 2018-08-11 18:04:59 +09:00
0b154ac7ba Merge branch 'master' into l10n_master 2018-08-11 12:21:06 +09:00
d4eb0c8df9 New translations ja.yml (Spanish) 2018-08-11 01:42:11 +09:00
5ba6f20701 New translations ja.yml (Spanish) 2018-08-11 01:31:44 +09:00
695a082582 New translations ja.yml (Spanish) 2018-08-11 01:21:58 +09:00
1b9f293959 Update url-preview.vue
多分もう折り返す必要はないのだわ
2018-08-09 23:34:54 +09:00
7289d5b401 Add player 2018-08-09 16:44:34 +09:00
5bdbf98f8c New translations ja.yml (English) 2018-08-09 04:21:24 +09:00
4e915e96a5 New translations ja.yml (French) 2018-08-09 02:52:24 +09:00
e148f6ce5e New translations ja.yml (French) 2018-08-09 02:41:44 +09:00
652d7d2c05 New translations ja.yml (French) 2018-08-09 02:36:35 +09:00
0b0111fe23 New translations ja.yml (French) 2018-08-09 02:31:11 +09:00
5d097fb29d New translations ja.yml (French) 2018-08-08 00:51:48 +09:00
f29fe986af New translations ja.yml (French) 2018-08-08 00:43:05 +09:00
0c774979c0 New translations ja.yml (French) 2018-08-08 00:32:08 +09:00
f0bc2ed1d7 New translations ja.yml (French) 2018-08-08 00:22:38 +09:00
b174e5e57a New translations ja.yml (English) 2018-08-07 23:21:52 +09:00
ae50b71c07 New translations ja.yml (Polish) 2018-08-07 20:53:54 +09:00
b2eb50f260 New translations ja.yml (Catalan) 2018-08-07 13:31:40 +09:00
790c7f2249 New translations ja.yml (Portuguese) 2018-08-07 13:31:38 +09:00
6a0f34c283 New translations ja.yml (Korean) 2018-08-07 13:31:36 +09:00
d151445db7 New translations ja.yml (Polish) 2018-08-07 13:31:34 +09:00
18a3007273 New translations ja.yml (Chinese Simplified) 2018-08-07 13:31:31 +09:00
bea8c3c65c New translations ja.yml (Italian) 2018-08-07 13:31:29 +09:00
391ee01fe3 New translations ja.yml (Russian) 2018-08-07 13:31:26 +09:00
be7cf9f731 New translations ja.yml (English) 2018-08-07 13:31:24 +09:00
b92c2aa40e New translations ja.yml (Spanish) 2018-08-07 13:31:21 +09:00
45ebcbf785 New translations ja.yml (German) 2018-08-07 13:31:19 +09:00
5f32484be0 New translations ja.yml (French) 2018-08-07 13:31:17 +09:00
e245122f12 New translations ja.yml (English) 2018-08-07 12:41:03 +09:00
21570e2111 New translations ja.yml (Catalan) 2018-08-07 11:12:10 +09:00
63e1165a01 New translations ja.yml (Portuguese) 2018-08-07 11:12:07 +09:00
89b5a69127 New translations ja.yml (Korean) 2018-08-07 11:12:04 +09:00
1152a9d03a New translations ja.yml (Polish) 2018-08-07 11:12:02 +09:00
10092d4570 New translations ja.yml (Chinese Simplified) 2018-08-07 11:12:00 +09:00
429b4bec64 New translations ja.yml (Italian) 2018-08-07 11:11:58 +09:00
99f96583b6 New translations ja.yml (Russian) 2018-08-07 11:11:56 +09:00
5adb765f85 New translations ja.yml (English) 2018-08-07 11:11:54 +09:00
0eb787e0d0 New translations ja.yml (Spanish) 2018-08-07 11:11:52 +09:00
4493c856a9 New translations ja.yml (German) 2018-08-07 11:11:50 +09:00
59d3d4a749 New translations ja.yml (French) 2018-08-07 11:11:47 +09:00
69 changed files with 966 additions and 238 deletions

View File

@ -64,7 +64,7 @@ drive:
# config:
# endPoint:
# port:
# secure:
# useSSL:
# accessKey:
# secretKey:
@ -75,7 +75,7 @@ drive:
# config:
# endPoint: s3-us-west-2.amazonaws.com
# region: us-west-2
# secure: true
# useSSL: true
# accessKey: XXX
# secretKey: YYY
@ -87,7 +87,7 @@ drive:
# config:
# endPoint: s3-us-west-2.amazonaws.com
# region: us-west-2
# secure: true
# useSSL: true
# accessKey: XXX
# secretKey: YYY
@ -123,6 +123,7 @@ drive:
# google_maps_api_key: example-google-maps-api-key
# Twitter integration
# You need to set the oauth callback url as : https://<your-misskey-instance>/api/tw/cb
# twitter:
# consumer_key: example-twitter-consumer-key
# consumer_secret: example-twitter-consumer-secret-key

4
.vsls.json Normal file
View File

@ -0,0 +1,4 @@
{
"$schema": "http://json.schemastore.org/vsls",
"gitignore": "exclude"
}

View File

@ -1,26 +0,0 @@
FROM base/archlinux
MAINTAINER Aya Morisawa
RUN rm /etc/pacman.d/mirrorlist
RUN echo 'Server = http://ftp.jaist.ac.jp/pub/Linux/ArchLinux/$repo/os/$arch' >> /etc/pacman.d/mirrorlist
RUN echo 'Server = http://ftp.tsukuba.wide.ad.jp/Linux/archlinux/$repo/os/$arch' >> /etc/pacman.d/mirrorlist
RUN rm /etc/localtime
RUN ln -s /usr/share/zoneinfo/Asia/Tokyo /etc/localtime
RUN pacman -Sy --noconfirm
RUN pacman -S --noconfirm pacman
RUN pacman-db-upgrade
RUN pacman -S --noconfirm archlinux-keyring
RUN pacman -Syyu --noconfirm
RUN pacman -S --noconfirm git nodejs npm mongodb redis
COPY misskey.sh /root/misskey.sh
RUN chmod u+x /root/misskey.sh
EXPOSE 80
EXPOSE 443
EXPOSE 27017
CMD ["/root/misskey.sh"]

View File

@ -1,6 +0,0 @@
#!/bin/sh
redis-server --daemonize yes
mongod > /dev/null &
cd /root/misskey
npm start
tail -f /dev/null

View File

@ -1,29 +0,0 @@
Setup with Docker :whale:
================================================================
Ensure that the working directory is the repository root directory.
To create misskey image:
``` console
$ sudo docker build -t misskey ./docker
```
To run misskey:
``` console
$ sudo docker run --rm -i -t -p $PORT:80 -v $(pwd):/root/misskey -v $DBPATH:/data/db misskey
```
where `$PORT` is the port used to access Misskey Web from host browser
and `$DBPATH` is the path of MongoDB database on the host for data persistence.
ex:
``` console
$ sudo docker run --rm -i -t -p 80:80 -v $(pwd):/root/misskey -v /data/db:/data/db misskey
```
If you want to run misskey in production mode, add `--env NODE_ENV=production` like this:
``` console
$ sudo docker run --rm -i -t -p 80:80 -v $(pwd):/root/misskey -v /data/db:/data/db --env NODE_ENV=production misskey
```
Note that `$(pwd)` is the working directory.

View File

@ -62,6 +62,13 @@ npm install web-push -g
web-push generate-vapid-keys
```
*(optional)* Create a twitter application
----------------------------------------------------------------
If you want to enable the twitter integration, you need to create a twitter app at [apps.twitter.com](https://apps.twitter.com/).
In the app you need to set the oauth callback url as : https://misskey-instance/api/tw/cb
*5.* Make configuration file
----------------------------------------------------------------
1. `cp .config/example.yml .config/default.yml` Copy the `.config/example.yml` and rename it to `default.yml`.

View File

@ -66,6 +66,7 @@ common:
congrats: "おめでとう"
angry: "おこ"
confused: "こまこまのこまり"
rip: "RIP"
pudding: "Pudding"
note-placeholders:
a: "今どうしてる?"
@ -168,6 +169,9 @@ common/views/components/games/reversi/reversi.vue:
common/views/components/games/reversi/reversi.game.vue:
surrender: "投了"
surrendered: "投了により"
is-llotheo: "石の少ない方が勝ち(ロセオ)"
looped-map: "ループマップ"
can-put-everywhere: "どこでも置けるモード"
common/views/components/games/reversi/reversi.index.vue:
title: "Misskey Reversi"
sub-title: "他のMisskeyユーザーとリバーシで対戦しよう"

View File

@ -66,6 +66,7 @@ common:
congrats: "Glückwunsch!"
angry: "Wütend"
confused: "Verwirrt"
rip: "RIP"
pudding: "Pudding"
note-placeholders:
a: "Was machst du gerade?"
@ -168,6 +169,9 @@ common/views/components/games/reversi/reversi.vue:
common/views/components/games/reversi/reversi.game.vue:
surrender: "投了"
surrendered: "投了により"
is-llotheo: "石の少ない方が勝ち(ロセオ)"
looped-map: "ループマップ"
can-put-everywhere: "どこでも置けるモード"
common/views/components/games/reversi/reversi.index.vue:
title: "Misskey Reversi"
sub-title: "他のMisskeyユーザーとリバーシで対戦しよう"

View File

@ -11,7 +11,7 @@ common:
warning: "<strong>Misskey is not running ads</strong>, but some features may be unavailable or malfunctioning if ad blocking features are enabled."
application-authorization: "Application authorizations."
close: "Close"
do-not-copy-paste: "ここにコードを入力したり張り付けたりしないでください。アカウントが不正利用される可能性があります。"
do-not-copy-paste: "Please do not enter or paste the code here. Account may be compromised."
got-it: "Got it!"
customization-tips:
title: "Customization tips"
@ -66,6 +66,7 @@ common:
congrats: "Congrats!"
angry: "Angry"
confused: "Confused"
rip: "RIP"
pudding: "Pudding"
note-placeholders:
a: "What are you doing?"
@ -168,6 +169,9 @@ common/views/components/games/reversi/reversi.vue:
common/views/components/games/reversi/reversi.game.vue:
surrender: "Surrender"
surrendered: "By surrender"
is-llotheo: "The lesser one wins"
looped-map: "Looped map"
can-put-everywhere: "Can put everywhere"
common/views/components/games/reversi/reversi.index.vue:
title: "Misskey Reversi"
sub-title: "Play reversi with your friends!"
@ -235,13 +239,13 @@ common/views/components/messaging-room.vue:
no-history: "There is no further history"
resize-form: "Drag to resize"
new-message: "New message"
only-one-file-attached: "メッセージに添付できるのはひとつのファイルのみです"
only-one-file-attached: "Only one file can be attached to a message."
common/views/components/messaging-room.form.vue:
input-message-here: "Enter message here"
send: "Send"
attach-from-local: "Attach files from your device"
attach-from-drive: "Attach files from your Drive"
only-one-file-attached: "メッセージに添付できるのはひとつのファイルのみです"
only-one-file-attached: "Only one file can be attached to a message."
common/views/components/messaging-room.message.vue:
is-read: "Read"
deleted: "This message has been deleted"
@ -736,7 +740,7 @@ desktop/views/components/timeline.vue:
list: "Lists"
desktop/views/components/ui.header.vue:
welcome-back: "Welcome back,"
adjective: "さん"
adjective: "Ms."
desktop/views/components/ui.header.account.vue:
profile: "Your profile"
drive: "Media storage"
@ -977,7 +981,7 @@ mobile/views/components/timeline.vue:
load-more: "More"
mobile/views/components/ui.header.vue:
welcome-back: "Welcome back, "
adjective: "さん"
adjective: "Ms."
mobile/views/components/ui.nav.vue:
timeline: "Timeline"
notifications: "Notifications"
@ -1066,7 +1070,7 @@ mobile/views/pages/settings/settings.profile.vue:
mobile/views/pages/search.vue:
search: "Search"
empty: "No posts were found for '{}'"
not-found: "「{}」に関する投稿は見つかりませんでした。"
not-found: "No posts were found for \"{}\"."
mobile/views/pages/selectdrive.vue:
select-file: "Choose files"
mobile/views/pages/settings.vue:

View File

@ -66,6 +66,7 @@ common:
congrats: "felicidades"
angry: "enfadado"
confused: "confundido"
rip: "RIP"
pudding: "Chafado"
note-placeholders:
a: "¿Qué haces?"
@ -168,6 +169,9 @@ common/views/components/games/reversi/reversi.vue:
common/views/components/games/reversi/reversi.game.vue:
surrender: "Rendirse"
surrendered: "Por rendirse"
is-llotheo: "石の少ない方が勝ち(ロセオ)"
looped-map: "ループマップ"
can-put-everywhere: "どこでも置けるモード"
common/views/components/games/reversi/reversi.index.vue:
title: "Misskey Reversi"
sub-title: "¡Juega Reversi con tus amigos!"

View File

@ -11,7 +11,7 @@ common:
warning: "<strong>Misskey n'utilise pas de publicités</strong>, mais quelques options peuvent être non disponibles ou fonctionneraient mal si un bloqueur de publicités est activé."
application-authorization: "Permissions de l'application"
close: "Fermer"
do-not-copy-paste: "ここにコードを入力したり張り付けたりしないでください。アカウントが不正利用される可能性があります。"
do-not-copy-paste: "Veuillez ne pas entrer ou coller le code ici. Le compte peut être compromis."
got-it: "J'ai compris !"
customization-tips:
title: "Conseils de personnalisation"
@ -66,6 +66,7 @@ common:
congrats: "Félicitations !"
angry: "En colère"
confused: "Confus"
rip: "RIP"
pudding: "Pudding"
note-placeholders:
a: "Que faîtes vous maintenant ?"
@ -168,6 +169,9 @@ common/views/components/games/reversi/reversi.vue:
common/views/components/games/reversi/reversi.game.vue:
surrender: "Se rendre"
surrendered: "Par abandon"
is-llotheo: "石の少ない方が勝ち(ロセオ)"
looped-map: "Carte en boucle"
can-put-everywhere: "どこでも置けるモード"
common/views/components/games/reversi/reversi.index.vue:
title: "Misskey Reversi"
sub-title: "Jouer à Reversi avec vos amis·es !"
@ -817,7 +821,7 @@ desktop/views/pages/search.vue:
desktop/views/pages/share.vue:
share-with: "Partager avec {}"
desktop/views/pages/tag.vue:
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
no-posts-found: "Pas de message avec un hashtag {} trouvé."
desktop/views/pages/user-list.users.vue:
users: "Utilisateurs"
add-user: "Ajouter un utilisateur"
@ -842,7 +846,7 @@ desktop/views/pages/user/user.photos.vue:
no-photos: "Pas de photos"
desktop/views/pages/user/user.profile.vue:
follows-you: "Vous suis"
stalk: "ストークする"
stalk: "Traquer"
stalking: "ストーキングしています"
unstalk: "ストーク解除"
mute: "Mettre en sourdine"
@ -892,11 +896,11 @@ mobile/views/components/drive.vue:
load-more: "Charger plus"
nothing-in-drive: "Rien"
folder-is-empty: "Ce dossier est vide"
prompt: "何をしますか?(数字を入力してください): <1 → ファイルをアップロード | 2 → ファイルをURLでアップロード | 3 → フォルダ作成 | 4 → このフォルダ名を変更 | 5 → このフォルダを移動 | 6 → このフォルダを削除>"
prompt: "Que veux-tu faire ? (Entrez un nombre): <1 → Télécharger le fichier | 2 → Télécharger le fichier avec l'URL | 3 → Créer le dossier | 4 → Modifier le nom du dossier | 5 → Déplacer ce dossier | 6 → Supprimer ce dossier >"
deletion-alert: "Désolé ! La suppression dun dossier nest pas encore implémentée."
folder-name: "Nom du dossier"
root-rename-alert: "現在いる場所はルートで、フォルダではないため名前の変更はできません。名前を変更したいフォルダに移動してからやってください。"
root-move-alert: "現在いる場所はルートで、フォルダではないため移動はできません。移動したいフォルダに移動してからやってください。"
root-rename-alert: "L'emplacement actuel est la racine, pas le dossier, vous ne pouvez donc pas le renommer. Veuillez vous déplacer dans le dossier dont vous souhaitez modifier le nom."
root-move-alert: "L'emplacement actuel est la racine, ce n'est pas un dossier et il ne peut pas être déplacé. Veuillez vous déplacer dans le dossier que vous souhaitez déplacer."
url-prompt: "URL du fichier que vous souhaitez téléverser"
uploading: "アップロードをリクエストしました。アップロードが完了するまで時間がかかる場合があります。"
mobile/views/components/drive-file-detail.vue:
@ -976,7 +980,7 @@ mobile/views/components/timeline.vue:
empty: "Pas de notes"
load-more: "Afficher plus"
mobile/views/components/ui.header.vue:
welcome-back: "Bon retour parmi nous !"
welcome-back: "Content de vous revoir ! "
adjective: "さん"
mobile/views/components/ui.nav.vue:
timeline: "Fil d'actualité"
@ -1020,7 +1024,7 @@ mobile/views/pages/home.vue:
hybrid: "Social"
global: "Global"
mobile/views/pages/tag.vue:
no-posts-found: "ハッシュタグ「{}」が付けられた投稿は見つかりませんでした。"
no-posts-found: "Pas de message avec un hashtag {} trouvé."
mobile/views/pages/welcome.vue:
signup: "S'enregistrer"
mobile/views/pages/widgets.vue:
@ -1066,7 +1070,7 @@ mobile/views/pages/settings/settings.profile.vue:
mobile/views/pages/search.vue:
search: "Chercher"
empty: "Aucun message trouvé pour '{}' "
not-found: "「{}」に関する投稿は見つかりませんでした。"
not-found: "Aucun post pour {} n'a été trouvé."
mobile/views/pages/selectdrive.vue:
select-file: "Choisissez un fichier"
mobile/views/pages/settings.vue:

View File

@ -66,6 +66,7 @@ common:
congrats: "おめでとう"
angry: "おこ"
confused: "こまこまのこまり"
rip: "RIP"
pudding: "Pudding"
note-placeholders:
a: "今どうしてる?"
@ -168,6 +169,9 @@ common/views/components/games/reversi/reversi.vue:
common/views/components/games/reversi/reversi.game.vue:
surrender: "投了"
surrendered: "投了により"
is-llotheo: "石の少ない方が勝ち(ロセオ)"
looped-map: "ループマップ"
can-put-everywhere: "どこでも置けるモード"
common/views/components/games/reversi/reversi.index.vue:
title: "Misskey Reversi"
sub-title: "他のMisskeyユーザーとリバーシで対戦しよう"

View File

@ -70,6 +70,7 @@ common:
congrats: "おめでとう"
angry: "おこ"
confused: "こまこまのこまり"
rip: "RIP"
pudding: "Pudding"
note-placeholders:
@ -181,6 +182,9 @@ common/views/components/games/reversi/reversi.vue:
common/views/components/games/reversi/reversi.game.vue:
surrender: "投了"
surrendered: "投了により"
is-llotheo: "石の少ない方が勝ち(ロセオ)"
looped-map: "ループマップ"
can-put-everywhere: "どこでも置けるモード"
common/views/components/games/reversi/reversi.index.vue:
title: "Misskey Reversi"
@ -893,6 +897,24 @@ desktop/views/components/window.vue:
popout: "ポップアウト"
close: "閉じる"
desktop/views/pages/admin/admin.vue:
dashboard: "ダッシュボード"
drive: "ドライブ"
users: "ユーザー"
update: "更新"
desktop/views/pages/admin/admin.dashboard.vue:
dashboard: "ダッシュボード"
all-users: "全てのユーザー"
original-users: "このインスタンスのユーザー"
all-notes: "全てのノート"
original-notes: "このインスタンスのノート"
desktop/views/pages/admin/admin.suspend-user.vue:
suspend-user: "ユーザーの凍結"
suspend: "凍結"
suspended: "凍結しました"
desktop/views/pages/deck/deck.tl-column.vue:
is-media-only: "メディア投稿のみ"
is-media-view: "メディアビュー"

View File

@ -66,6 +66,7 @@ common:
congrats: "받으세요"
angry: "화냈어"
confused: "곤란하고 있어"
rip: "RIP"
pudding: "Pudding"
note-placeholders:
a: "지금 어떻게하고있어?"
@ -168,6 +169,9 @@ common/views/components/games/reversi/reversi.vue:
common/views/components/games/reversi/reversi.game.vue:
surrender: "投了"
surrendered: "投了により"
is-llotheo: "石の少ない方が勝ち(ロセオ)"
looped-map: "ループマップ"
can-put-everywhere: "どこでも置けるモード"
common/views/components/games/reversi/reversi.index.vue:
title: "Misskey Reversi"
sub-title: "他のMisskeyユーザーとリバーシで対戦しよう"

View File

@ -39,7 +39,7 @@ common:
weeks_ago: "{} tyg. temu"
months_ago: "{} mies. temu"
years_ago: "{} lat temu"
month-and-day: "{month}{day}"
month-and-day: "{month}-{day}"
trash: "Kosz"
weekday-short:
sunday: "N"
@ -66,6 +66,7 @@ common:
congrats: "Gratuluję!"
angry: "Wściekły"
confused: "Zmieszany"
rip: "RIP"
pudding: "Pudding"
note-placeholders:
a: "Co robisz?"
@ -168,6 +169,9 @@ common/views/components/games/reversi/reversi.vue:
common/views/components/games/reversi/reversi.game.vue:
surrender: "投了"
surrendered: "投了により"
is-llotheo: "石の少ない方が勝ち(ロセオ)"
looped-map: "ループマップ"
can-put-everywhere: "どこでも置けるモード"
common/views/components/games/reversi/reversi.index.vue:
title: "Misskey Reversi"
sub-title: "Zagraj w Reversi ze znajomymi!"
@ -394,15 +398,15 @@ common/views/pages/follow.vue:
follow-request: "Poproś o śledzenie"
desktop:
banner-crop-title: "バナーとして表示する部分を選択"
banner: "バナー"
uploading-banner: "新しいバナーをアップロードしています"
banner-updated: "バナーを更新しました"
choose-banner: "バナーにする画像を選択"
avatar-crop-title: "アバターとして表示する部分を選択"
avatar: "アバター"
uploading-avatar: "新しいアバターをアップロードしています"
avatar-updated: "アバターを更新しました"
choose-avatar: "アバターにする画像を選択"
banner: "Baner"
uploading-banner: "Wysyłanie baneru"
banner-updated: "Zmieniono baner"
choose-banner: "Wybierz baner"
avatar-crop-title: "Wybierz część obrazu, która zostanie użyta jako awatar"
avatar: "Awatar"
uploading-avatar: "Wysyłanie awatara"
avatar-updated: "Wysłano awatar"
choose-avatar: "Wybierz awatar"
desktop/views/components/activity.chart.vue:
total: "Czarny … Łącznie"
notes: "Niebieski … Wpisy"
@ -1162,4 +1166,4 @@ docs:
type: "Rodzaj"
description: "Opis"
dev/views/index.vue:
manage-apps: "アプリの管理"
manage-apps: "Zarządzaj aplikacjami"

View File

@ -66,6 +66,7 @@ common:
congrats: "おめでとう"
angry: "おこ"
confused: "こまこまのこまり"
rip: "RIP"
pudding: "Pudding"
note-placeholders:
a: "今どうしてる?"
@ -168,6 +169,9 @@ common/views/components/games/reversi/reversi.vue:
common/views/components/games/reversi/reversi.game.vue:
surrender: "投了"
surrendered: "投了により"
is-llotheo: "石の少ない方が勝ち(ロセオ)"
looped-map: "ループマップ"
can-put-everywhere: "どこでも置けるモード"
common/views/components/games/reversi/reversi.index.vue:
title: "Misskey Reversi"
sub-title: "他のMisskeyユーザーとリバーシで対戦しよう"

View File

@ -66,6 +66,7 @@ common:
congrats: "おめでとう"
angry: "おこ"
confused: "こまこまのこまり"
rip: "RIP"
pudding: "Pudding"
note-placeholders:
a: "今どうしてる?"
@ -168,6 +169,9 @@ common/views/components/games/reversi/reversi.vue:
common/views/components/games/reversi/reversi.game.vue:
surrender: "投了"
surrendered: "投了により"
is-llotheo: "石の少ない方が勝ち(ロセオ)"
looped-map: "ループマップ"
can-put-everywhere: "どこでも置けるモード"
common/views/components/games/reversi/reversi.index.vue:
title: "Misskey Reversi"
sub-title: "他のMisskeyユーザーとリバーシで対戦しよう"

View File

@ -66,6 +66,7 @@ common:
congrats: "おめでとう"
angry: "おこ"
confused: "こまこまのこまり"
rip: "RIP"
pudding: "Pudding"
note-placeholders:
a: "今どうしてる?"
@ -168,6 +169,9 @@ common/views/components/games/reversi/reversi.vue:
common/views/components/games/reversi/reversi.game.vue:
surrender: "投了"
surrendered: "投了により"
is-llotheo: "石の少ない方が勝ち(ロセオ)"
looped-map: "ループマップ"
can-put-everywhere: "どこでも置けるモード"
common/views/components/games/reversi/reversi.index.vue:
title: "Misskey Reversi"
sub-title: "他のMisskeyユーザーとリバーシで対戦しよう"

View File

@ -1,8 +1,8 @@
{
"name": "misskey",
"author": "syuilo <i@syuilo.com>",
"version": "5.21.0",
"clientVersion": "1.0.8117",
"version": "5.24.0",
"clientVersion": "1.0.8279",
"codename": "nighthike",
"main": "./built/index.js",
"private": true,
@ -31,6 +31,7 @@
"@types/dateformat": "1.0.1",
"@types/debug": "0.0.30",
"@types/deep-equal": "1.0.1",
"@types/double-ended-queue": "2.1.0",
"@types/elasticsearch": "5.0.25",
"@types/file-type": "5.2.1",
"@types/gulp": "3.8.36",
@ -57,9 +58,9 @@
"@types/minio": "6.0.2",
"@types/mkdirp": "0.5.2",
"@types/mocha": "5.2.3",
"@types/mongodb": "3.1.3",
"@types/mongodb": "3.1.4",
"@types/ms": "0.7.30",
"@types/node": "10.5.8",
"@types/node": "10.7.0",
"@types/portscanner": "2.1.0",
"@types/pug": "2.0.4",
"@types/qrcode": "1.2.0",
@ -79,7 +80,7 @@
"@types/webpack": "4.4.9",
"@types/webpack-stream": "3.2.10",
"@types/websocket": "0.0.39",
"@types/ws": "5.1.2",
"@types/ws": "6.0.0",
"animejs": "2.2.0",
"autosize": "4.0.2",
"autwh": "0.1.0",
@ -97,6 +98,7 @@
"deepcopy": "0.6.3",
"diskusage": "0.2.4",
"dompurify": "1.0.5",
"double-ended-queue": "2.1.0-0",
"elasticsearch": "15.1.1",
"element-ui": "2.4.6",
"emojilib": "2.3.0",
@ -145,12 +147,13 @@
"koa-slow": "2.1.0",
"koa-views": "6.1.4",
"loader-utils": "1.1.0",
"lodash.assign": "4.2.0",
"mecab-async": "0.1.2",
"minio": "6.0.0",
"mkdirp": "0.5.1",
"mocha": "5.2.0",
"moji": "0.5.1",
"mongodb": "3.1.1",
"mongodb": "3.1.3",
"monk": "6.0.6",
"ms": "2.1.1",
"nan": "2.10.0",
@ -160,7 +163,7 @@
"object-assign-deep": "0.4.0",
"on-build-webpack": "0.1.0",
"os-utils": "0.0.14",
"parse5": "5.0.0",
"parse5": "5.1.0",
"portscanner": "2.2.0",
"progress-bar-webpack-plugin": "1.11.0",
"promise-sequential": "1.1.1",
@ -177,7 +180,7 @@
"rndstr": "1.0.0",
"s-age": "1.1.2",
"sass-loader": "7.1.0",
"seedrandom": "2.4.3",
"seedrandom": "2.4.4",
"sharp": "0.20.5",
"showdown": "1.8.6",
"showdown-highlightjs-extension": "0.1.2",
@ -193,21 +196,21 @@
"textarea-caret": "3.1.0",
"tmp": "0.0.33",
"ts-loader": "4.4.1",
"ts-node": "7.0.0",
"ts-node": "7.0.1",
"tslint": "5.10.0",
"typescript": "2.9.2",
"typescript-eslint-parser": "18.0.0",
"uglify-es": "3.3.9",
"url-loader": "1.0.1",
"url-loader": "1.1.0",
"uuid": "3.3.2",
"v-animate-css": "0.0.2",
"vue": "2.5.17",
"vue-cropperjs": "2.2.1",
"vue-js-modal": "1.3.16",
"vue-js-modal": "1.3.17",
"vue-json-tree-view": "2.1.4",
"vue-loader": "15.3.0",
"vue-router": "3.0.1",
"vue-style-loader": "4.1.1",
"vue-style-loader": "4.1.2",
"vue-template-compiler": "2.5.17",
"vuedraggable": "2.16.0",
"vuex": "3.0.1",

View File

@ -1,5 +1,6 @@
export default () => [
'(=^・・^=)',
'v(\'ω\')v',
'🐡( \'-\' 🐡 )フグパンチ!!!!'
][Math.floor(Math.random() * 3)];
'🐡( \'-\' 🐡 )フグパンチ!!!!',
'🖕(´・_・`)🖕'
][Math.floor(Math.random() * 4)];

View File

@ -1,8 +1,16 @@
<template>
<span class="mk-avatar" :title="user | acct" :style="style" v-if="disableLink && !disablePreview" v-user-preview="user.id" @click="onClick"></span>
<span class="mk-avatar" :title="user | acct" :style="style" v-else-if="disableLink && disablePreview" @click="onClick"></span>
<router-link class="mk-avatar" :to="user | userPage" :title="user | acct" :target="target" :style="style" v-else-if="!disableLink && !disablePreview" v-user-preview="user.id"></router-link>
<router-link class="mk-avatar" :to="user | userPage" :title="user | acct" :target="target" :style="style" v-else-if="!disableLink && disablePreview"></router-link>
<span class="mk-avatar" :class="{ cat }" :title="user | acct" v-if="disableLink && !disablePreview" v-user-preview="user.id" @click="onClick">
<span class="inner" :style="style"></span>
</span>
<span class="mk-avatar" :class="{ cat }" :title="user | acct" v-else-if="disableLink && disablePreview" @click="onClick">
<span class="inner" :style="style"></span>
</span>
<router-link class="mk-avatar" :class="{ cat }" :to="user | userPage" :title="user | acct" :target="target" v-else-if="!disableLink && !disablePreview" v-user-preview="user.id">
<span class="inner" :style="style"></span>
</router-link>
<router-link class="mk-avatar" :class="{ cat }" :to="user | userPage" :title="user | acct" :target="target" v-else-if="!disableLink && disablePreview">
<span class="inner" :style="style"></span>
</router-link>
</template>
<script lang="ts">
@ -30,14 +38,17 @@ export default Vue.extend({
lightmode(): boolean {
return this.$store.state.device.lightmode;
},
cat(): boolean {
return this.user.isCat && this.$store.state.settings.circleIcons;
},
style(): any {
return {
backgroundColor: this.lightmode
? `rgb(${ this.user.avatarColor.slice(0, 3).join(',') })`
? `rgb(${this.user.avatarColor.slice(0, 3).join(',')})`
: this.user.avatarColor && this.user.avatarColor.length == 3
? `rgb(${ this.user.avatarColor.join(',') })`
? `rgb(${this.user.avatarColor.join(',')})`
: null,
backgroundImage: this.lightmode ? null : `url(${ this.user.avatarUrl })`,
backgroundImage: this.lightmode ? null : `url(${this.user.avatarUrl})`,
borderRadius: this.$store.state.settings.circleIcons ? '100%' : null
};
}
@ -51,10 +62,43 @@ export default Vue.extend({
</script>
<style lang="stylus" scoped>
.mk-avatar
root(isDark)
display inline-block
vertical-align bottom
background-size cover
background-position center center
transition border-radius 1s ease
&.cat::before,
&.cat::after
background #df548f
border solid 4px isDark ? #e0eefd : #202224
box-sizing border-box
content ''
display inline-block
height 50%
width 50%
&.cat::before
border-radius 0 75% 75%
transform rotate(37.5deg) skew(30deg)
&.cat::after
border-radius 75% 0 75% 75%
transform rotate(-37.5deg) skew(-30deg)
.inner
background-position center center
background-size cover
bottom 0
left 0
position absolute
right 0
top 0
transition border-radius 1s ease
z-index 1
.mk-avatar[data-darkmode]
root(true)
.mk-avatar:not([data-darkmode])
root(false)
</style>

View File

@ -60,6 +60,12 @@
<el-button type="primary" @click="logPos = logs.length" :disabled="logPos == logs.length">%fa:angle-double-right%</el-button>
</el-button-group>
</div>
<div class="info">
<p v-if="game.settings.isLlotheo">%i18n:@is-llotheo%</p>
<p v-if="game.settings.loopedBoard">%i18n:@looped-map%</p>
<p v-if="game.settings.canPutEverywhere">%i18n:@can-put-everywhere%</p>
</div>
</div>
</template>

View File

@ -8,6 +8,7 @@
<img v-if="reaction == 'congrats'" src="/assets/reactions/congrats.png" alt="%i18n:common.reactions.congrats%">
<img v-if="reaction == 'angry'" src="/assets/reactions/angry.png" alt="%i18n:common.reactions.angry%">
<img v-if="reaction == 'confused'" src="/assets/reactions/confused.png" alt="%i18n:common.reactions.confused%">
<img v-if="reaction == 'rip'" src="/assets/reactions/rip.png" alt="%i18n:common.reactions.rip%">
<template v-if="reaction == 'pudding'">
<img v-if="$store.getters.isSignedIn && $store.state.settings.iLikeSushi" src="/assets/reactions/sushi.png" alt="%i18n:common.reactions.pudding%">
<img v-else src="/assets/reactions/pudding.png" alt="%i18n:common.reactions.pudding%">

View File

@ -10,9 +10,10 @@
<button @click="react('hmm')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="4" title="%i18n:common.reactions.hmm%"><mk-reaction-icon reaction='hmm'/></button>
<button @click="react('surprise')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="5" title="%i18n:common.reactions.surprise%"><mk-reaction-icon reaction='surprise'/></button>
<button @click="react('congrats')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="6" title="%i18n:common.reactions.congrats%"><mk-reaction-icon reaction='congrats'/></button>
<button @click="react('angry')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="4" title="%i18n:common.reactions.angry%"><mk-reaction-icon reaction='angry'/></button>
<button @click="react('confused')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="5" title="%i18n:common.reactions.confused%"><mk-reaction-icon reaction='confused'/></button>
<button @click="react('pudding')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="6" title="%i18n:common.reactions.pudding%"><mk-reaction-icon reaction='pudding'/></button>
<button @click="react('angry')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="7" title="%i18n:common.reactions.angry%"><mk-reaction-icon reaction='angry'/></button>
<button @click="react('confused')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="8" title="%i18n:common.reactions.confused%"><mk-reaction-icon reaction='confused'/></button>
<button @click="react('rip')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="9" title="%i18n:common.reactions.rip%"><mk-reaction-icon reaction='rip'/></button>
<button @click="react('pudding')" @mouseover="onMouseover" @mouseout="onMouseout" tabindex="10" title="%i18n:common.reactions.pudding%"><mk-reaction-icon reaction='pudding'/></button>
</div>
</div>
</div>

View File

@ -9,6 +9,7 @@
<span v-if="reactions.congrats"><mk-reaction-icon reaction="congrats"/><span>{{ reactions.congrats }}</span></span>
<span v-if="reactions.angry"><mk-reaction-icon reaction="angry"/><span>{{ reactions.angry }}</span></span>
<span v-if="reactions.confused"><mk-reaction-icon reaction="confused"/><span>{{ reactions.confused }}</span></span>
<span v-if="reactions.rip"><mk-reaction-icon reaction="rip"/><span>{{ reactions.rip }}</span></span>
<span v-if="reactions.pudding"><mk-reaction-icon reaction="pudding"/><span>{{ reactions.pudding }}</span></span>
</template>
</div>

View File

@ -12,13 +12,13 @@
</ui-input>
<ui-input v-if="user && user.twoFactorEnabled" v-model="token" type="number" required/>
<ui-button type="submit" :disabled="signing">{{ signing ? '%i18n:@signing-in%' : '%i18n:@signin%' }}</ui-button>
<p style="margin: 8px 0;">%i18n:@or%<a :href="`${apiUrl}/signin/twitter`">%i18n:@signin-with-twitter%</a></p>
<p style="margin: 8px 0;" v-if="twitterIntegration">%i18n:@or%<a :href="`${apiUrl}/signin/twitter`">%i18n:@signin-with-twitter%</a></p>
</form>
</template>
<script lang="ts">
import Vue from 'vue';
import { apiUrl, host } from '../../../config';
import { apiUrl, host, twitterIntegration } from '../../../config';
export default Vue.extend({
props: {
@ -36,7 +36,8 @@ export default Vue.extend({
password: '',
token: '',
apiUrl,
host
host,
twitterIntegration
};
},
methods: {

View File

@ -1,13 +1,5 @@
<template>
<iframe v-if="youtubeId" type="text/html" height="250"
:src="`https://www.youtube.com/embed/${youtubeId}?origin=${misskeyUrl}`"
frameborder="0"/>
<iframe v-else-if="spotifyId"
:src="`https://open.spotify.com/embed/track/${spotifyId}`"
frameborder="0" allowtransparency="true" allow="encrypted-media" />
<iframe v-else-if="nicovideoId"
:src="`https://embed.nicovideo.jp/watch/${nicovideoId}?oldScript=1&referer=${misskeyUrl}&from=${position || '0'}&allowProgrammaticFullScreen=1`"
frameborder="0" allow="autoplay; encrypted-media" allowfullscreen />
<iframe v-if="player" :src="player" heigth="250" frameborder="0" allow="autoplay; encrypted-media" allowfullscreen />
<div v-else-if="tweetUrl && detail" class="twitter">
<blockquote ref="tweet" class="twitter-tweet" :data-theme="$store.state.device.darkmode ? 'dark' : null">
<a :href="url"></a>
@ -54,10 +46,7 @@ export default Vue.extend({
thumbnail: null,
icon: null,
sitename: null,
youtubeId: null,
spotifyId: null,
nicovideoId: null,
position: null,
player: null,
tweetUrl: null,
misskeyUrl
};
@ -65,23 +54,7 @@ export default Vue.extend({
created() {
const url = new URL(this.url);
if (url.hostname == 'www.youtube.com') {
this.youtubeId = url.searchParams.get('v');
return;
} else if (url.hostname == 'youtu.be') {
this.youtubeId = url.pathname;
return;
} else if (url.hostname == 'open.spotify.com') {
this.spotifyId = url.pathname.split('/').reverse().filter(x => x !== '')[0];
return;
} else if (['nicovideo.jp', 'www.nicovideo.jp', 'nico.ms'].includes(url.hostname)) {
const id = url.pathname.split('/').reverse().filter(x => x !== '')[0];
if (['sm', 'nm', 'ax', 'ca', 'cd', 'cw', 'fx', 'ig', 'na', 'om', 'sd', 'sk', 'yk', 'yo', 'za', 'zb', 'zc', 'zd', 'ze', 'nl', 'so', ...Array(10).keys()].some(x => id.startsWith(x)) {
this.nicovideoId = id;
this.position = url.searchParams.get('from');
return;
}
} else if (this.detail && url.hostname == 'twitter.com' && /^\/.+\/status(es)?\/\d+/.test(url.pathname)) {
if (this.detail && url.hostname == 'twitter.com' && /^\/.+\/status(es)?\/\d+/.test(url.pathname)) {
this.tweetUrl = url;
const twttr = (window as any).twttr || {};
const loadTweet = () => twttr.widgets.load(this.$refs.tweet);
@ -104,15 +77,92 @@ export default Vue.extend({
}
fetch('/url?url=' + encodeURIComponent(this.url)).then(res => {
res.json().then(info => {
this.title = info.title;
this.description = info.description;
this.thumbnail = info.thumbnail;
this.icon = info.icon;
this.sitename = info.sitename;
this.fetching = false;
if (info.url != null) {
this.title = info.title;
this.description = info.description;
this.thumbnail = info.thumbnail;
this.icon = info.icon;
this.sitename = info.sitename;
this.fetching = false;
if ([ // THIS IS THE WHITELIST FOR THE EMBED PLAYER
'afreecatv.com',
'aparat.com',
'applemusic.com',
'amazon.com',
'awa.fm',
'bandcamp.com',
'bbc.co.uk',
'beatport.com',
'bilibili.com',
'boomstream.com',
'breakers.tv',
'cam4.com',
'cavelis.net',
'chaturbate.com',
'cnn.com',
'cybergame.tv',
'dailymotion.com',
'deezer.com',
'djlive.pl',
'e-onkyo.com',
'eventials.com',
'facebook.com',
'fc2.com',
'gameplank.tv',
'goodgame.ru',
'google.com',
'hardtunes.com',
'instagram.com',
'johnnylooch.com',
'kexp.org',
'lahzenegar.com',
'liveedu.tv',
'livetube.cc',
'livestream.com',
'meridix.com',
'mixcloud.com',
'mixer.com',
'mobcrush.com',
'mylive.in.th',
'myspace.com',
'netflix.com',
'newretrowave.com',
'nhk.or.jp',
'nicovideo.jp',
'noisetrade.com',
'nood.tv',
'npr.org',
'openrec.tv',
'pandora.com',
'pandora.tv',
'picarto.tv',
'pscp.tv',
'restream.io',
'reverbnation.com',
'sermonaudio.com',
'smashcast.tv',
'songkick.com',
'soundcloud.com',
'spinninrecords.com',
'stitcher.com',
'stream.me',
'switchboard.live',
'tunein.com',
'twitcasting.tv',
'twitch.tv',
'twitter.com',
'vaughnlive.tv',
'veoh.com',
'vimeo.com',
'watchpeoplecode.com',
'web.tv',
'youtube.com',
'youtu.be'
].some(x => x == url.hostname || url.hostname.endsWith(`.${x}`))))
this.player = info.player;
}
});
});
}
}
});
</script>

View File

@ -22,6 +22,7 @@ declare const _CODENAME_: string;
declare const _LICENSE_: string;
declare const _GOOGLE_MAPS_API_KEY_: string;
declare const _WELCOME_BG_URL_: string;
declare const _TWITTER_INTEGRATION_: boolean;
export const host = _HOST_;
export const hostname = _HOSTNAME_;
@ -47,3 +48,4 @@ export const codename = _CODENAME_;
export const license = _LICENSE_;
export const googleMapsApiKey = _GOOGLE_MAPS_API_KEY_;
export const welcomeBgUrl = _WELCOME_BG_URL_;
export const twitterIntegration = _TWITTER_INTEGRATION_;

View File

@ -24,6 +24,7 @@ import updateBanner from './api/update-banner';
import MkIndex from './views/pages/index.vue';
import MkDeck from './views/pages/deck/deck.vue';
import MkAdmin from './views/pages/admin/admin.vue';
import MkUser from './views/pages/user/user.vue';
import MkFavorites from './views/pages/favorites.vue';
import MkSelectDrive from './views/pages/selectdrive.vue';
@ -55,6 +56,7 @@ init(async (launch) => {
routes: [
{ path: '/', name: 'index', component: MkIndex },
{ path: '/deck', name: 'deck', component: MkDeck },
{ path: '/admin', name: 'admin', component: MkAdmin },
{ path: '/i/customize-home', component: MkHomeCustomize },
{ path: '/i/favorites', component: MkFavorites },
{ path: '/i/messaging/:user', component: MkMessagingRoom },

View File

@ -99,7 +99,7 @@ export default Vue.extend({
text: '%i18n:@contextmenu.set-as-banner%',
action: this.setAsBanner
}]
}, {
}, /*{
type: 'nest',
text: '%i18n:@contextmenu.open-in-app%',
menu: [{
@ -107,11 +107,11 @@ export default Vue.extend({
text: '%i18n:@contextmenu.add-app%...',
action: this.addApp
}]
}], {
closed: () => {
this.isContextmenuShowing = false;
}
});
}*/], {
closed: () => {
this.isContextmenuShowing = false;
}
});
},
onDragstart(e) {

View File

@ -67,16 +67,16 @@ export default Vue.extend({
text: '%i18n:@contextmenu.rename%',
icon: '%fa:i-cursor%',
action: this.rename
}, null, {
}/*, null, {
type: 'item',
text: '%i18n:common.delete%',
icon: '%fa:R trash-alt%',
action: this.deleteFolder
}], {
closed: () => {
this.isContextmenuShowing = false;
}
});
}*/], {
closed: () => {
this.isContextmenuShowing = false;
}
});
},
onMouseover() {

View File

@ -567,6 +567,7 @@ export default Vue.extend({
// ファイル一覧取得
(this as any).api('drive/files', {
folderId: this.folder ? this.folder.id : null,
untilId: this.files[this.files.length - 1].id,
limit: max + 1
}).then(files => {
if (files.length == max + 1) {

View File

@ -155,10 +155,15 @@ root(isDark)
max-width 1300px
margin 0 auto
> *
position absolute
height 48px
> .center
margin auto
right 0
> .icon
margin auto
display block
width 48px
height 48px
@ -169,11 +174,12 @@ root(isDark)
opacity 0.3
cursor pointer
> .left
height 48px
> .left,
> .center
left 0
> .right
height 48px
right 0
> *
display inline-block

View File

@ -0,0 +1,37 @@
<template>
<div>
<h1>%i18n:@dashboard%</h1>
<div v-if="stats">
<p><b>%i18n:@all-users%</b>: <span>{{ stats.usersCount | number }}</span></p>
<p><b>%i18n:@original-users%</b>: <span>{{ stats.originalUsersCount | number }}</span></p>
<p><b>%i18n:@all-notes%</b>: <span>{{ stats.notesCount | number }}</span></p>
<p><b>%i18n:@original-notes%</b>: <span>{{ stats.originalNotesCount | number }}</span></p>
</div>
</div>
</template>
<script lang="ts">
import Vue from "vue";
export default Vue.extend({
data() {
return {
stats: null
};
},
created() {
(this as any).api('stats').then(stats => {
this.stats = stats;
});
}
});
</script>
<style lang="stylus" scoped>
h1
margin 0 0 1em 0
padding 0 0 8px 0
font-size 1em
color #555
border-bottom solid 1px #eee
</style>

View File

@ -0,0 +1,39 @@
<template>
<div>
<header>%i18n:@suspend-user%</header>
<input v-model="username"/>
<button @click="suspendUser" :disabled="suspending">%i18n:@suspend%</button>
</div>
</template>
<script lang="ts">
import Vue from "vue";
import parseAcct from "../../../../../../misc/acct/parse";
export default Vue.extend({
data() {
return {
username: null,
suspending: false
};
},
methods: {
async suspendUser() {
this.suspending = true;
const user = await (this as any).os.api(
"users/show",
parseAcct(this.username)
);
await (this as any).os.api("admin/suspend-user", {
userId: user.id
});
this.suspending = false;
(this as any).os.apis.dialog({ text: "%i18n:@suspended%" });
}
}
});
</script>

View File

@ -0,0 +1,90 @@
<template>
<div class="mk-admin">
<nav>
<ul>
<li @click="nav('dashboard')" :class="{ active: page == 'dashboard' }">%fa:chalkboard .fw%%i18n:@dashboard%</li>
<!-- <li @click="nav('users')" :class="{ active: page == 'users' }">%fa:users .fw%%i18n:@users%</li> -->
<!-- <li @click="nav('drive')" :class="{ active: page == 'drive' }">%fa:cloud .fw%%i18n:@drive%</li> -->
<!-- <li @click="nav('update')" :class="{ active: page == 'update' }">%i18n:@update%</li> -->
</ul>
</nav>
<main>
<div v-if="page == 'dashboard'">
<x-dashboard/>
</div>
<div v-if="page == 'users'">
<x-suspend-user/>
</div>
<div v-if="page == 'drive'"></div>
<div v-if="page == 'update'"></div>
</main>
</div>
</template>
<script lang="ts">
import Vue from "vue";
import XDashboard from "./admin.dashboard.vue";
import XSuspendUser from "./admin.suspend-user.vue";
export default Vue.extend({
components: {
XDashboard,
XSuspendUser
},
data() {
return {
page: 'dashboard'
};
},
methods: {
nav(page: string) {
this.page = page;
}
}
});
</script>
<style lang="stylus" scoped>
@import '~const.styl'
.mk-admin
display flex
height 100%
margin 32px
> nav
flex 0 0 250px
width 100%
height 100%
padding 16px 0 0 0
overflow auto
border-right solid 1px #ddd
> ul
list-style none
> li
display block
padding 10px 16px
margin 0
color #666
cursor pointer
user-select none
transition margin-left 0.2s ease
> [data-fa]
margin-right 4px
&:hover
color #555
&.active
margin-left 8px
color $theme-color !important
> main
width 100%
padding 16px 32px
</style>

View File

@ -176,6 +176,10 @@ root(isDark)
height 120px
box-shadow 1px 1px 3px rgba(#000, 0.2)
> &.cat::before,
> &.cat::after
border-width 8px
> .body
padding 16px 16px 16px 154px
color isDark ? #c5ced6 : #555

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

View File

@ -1,5 +1,5 @@
{
"copyright": "Copyright (c) 2014-2018 syuilo",
"themeColor": "#f66e4f",
"themeColor": "#f6584f",
"themeColorForeground": "#fff"
}

View File

@ -1,21 +1,22 @@
import * as childProcess from 'child_process';
import * as Deque from 'double-ended-queue';
import Xev from 'xev';
const ev = new Xev();
export default function() {
const log: any[] = [];
const log = new Deque<any>();
const p = childProcess.fork(__dirname + '/notes-stats-child.js');
p.on('message', stats => {
ev.emit('notesStats', stats);
log.push(stats);
if (log.length > 100) log.shift();
if (log.length > 100) log.pop();
});
ev.on('requestNotesStatsLog', id => {
ev.emit('notesStatsLog:' + id, log);
ev.emit('notesStatsLog:' + id, log.toArray());
});
process.on('exit', code => {

View File

@ -1,6 +1,7 @@
import * as os from 'os';
import * as sysUtils from 'systeminformation';
import * as diskusage from 'diskusage';
import * as Deque from 'double-ended-queue';
import Xev from 'xev';
const osUtils = require('os-utils');
@ -12,10 +13,10 @@ const interval = 1000;
* Report server stats regularly
*/
export default function() {
const log: any[] = [];
const log = new Deque<any>();
ev.on('requestServerStatsLog', id => {
ev.emit('serverStatsLog:' + id, log);
ev.emit('serverStatsLog:' + id, log.toArray());
});
async function tick() {
@ -36,7 +37,7 @@ export default function() {
};
ev.emit('serverStats', stats);
log.push(stats);
if (log.length > 50) log.shift();
if (log.length > 50) log.pop();
}
tick();

View File

@ -3,9 +3,7 @@ import config from '../config';
const u = config.mongodb.user ? encodeURIComponent(config.mongodb.user) : null;
const p = config.mongodb.pass ? encodeURIComponent(config.mongodb.pass) : null;
const uri = u && p
? `mongodb://${u}:${p}@${config.mongodb.host}:${config.mongodb.port}/${config.mongodb.db}`
: `mongodb://${config.mongodb.host}:${config.mongodb.port}/${config.mongodb.db}`;
const uri = `mongodb://${u && p ? `${u}:${p}@` : ''}${config.mongodb.host}:${config.mongodb.port}/${config.mongodb.db}`;
/**
* monk

View File

@ -8,6 +8,7 @@ export default function(reaction: string): string {
case 'congrats': return '🎉';
case 'angry': return '💢';
case 'confused': return '😥';
case 'rip': return '😇';
case 'pudding': return '🍮';
default: return '';
}

View File

@ -13,7 +13,7 @@ export default App;
export type IApp = {
_id: mongo.ObjectID;
createdAt: Date;
userId: mongo.ObjectID;
userId: mongo.ObjectID | null;
secret: string;
name: string;
nameId: string;

View File

@ -10,7 +10,7 @@ import DriveFileThumbnail, { deleteDriveFileThumbnail } from './drive-file-thumb
const DriveFile = monkDb.get<IDriveFile>('driveFiles.files');
DriveFile.createIndex('md5');
DriveFile.createIndex(['metadata.uri', 'metadata.userId'], { sparse: true, unique: true });
DriveFile.createIndex('metadata.uri');
export default DriveFile;
export const DriveFileChunk = monkDb.get('driveFiles.chunks');

View File

@ -26,6 +26,7 @@ export const validateReaction = $.str.or([
'congrats',
'angry',
'confused',
'rip',
'pudding'
]);

View File

@ -340,7 +340,7 @@ export const pack = async (
_note = await rap(_note);
if (_note.user.isCat && _note.text) {
_note.text = _note.text.replace(/な/g, 'にゃ').replace(/ナ/g, 'ニャ');
_note.text = _note.text.replace(/な/g, 'にゃ').replace(/ナ/g, 'ニャ').replace(/ナ/g, 'ニャ');
}
if (hide) {

View File

@ -69,12 +69,13 @@ export async function createNote(value: any, resolver?: Resolver, silent = false
if (!note.to.includes('https://www.w3.org/ns/activitystreams#Public')) {
if (note.cc.includes('https://www.w3.org/ns/activitystreams#Public')) {
visibility = 'home';
} else if (note.to.includes(`${actor.uri}/followers`)) { // TODO: person.followerと照合するべき
visibility = 'followers';
} else {
visibility = 'specified';
visibleUsers = await Promise.all(note.to.map(uri => resolvePerson(uri)));
}
}
if (note.cc.length == 0) visibility = 'followers';
//#endergion
// 添付メディア

View File

@ -4,5 +4,5 @@ import { IDriveFile } from '../../../models/drive-file';
export default (file: IDriveFile) => ({
type: 'Document',
mediaType: file.contentType,
url: `${config.drive_url}/${file._id}`
url: file.metadata.url || `${config.drive_url}/${file._id}`
});

View File

@ -0,0 +1,16 @@
import config from '../../../config';
import * as mongo from 'mongodb';
import User, { isLocalUser } from '../../../models/user';
/**
* Convert (local|remote)(Follower|Followee)ID to URL
* @param id Follower|Followee ID
*/
export default async function renderFollowUser(id: mongo.ObjectID): Promise<any> {
const user = await User.findOne({
_id: id
});
return isLocalUser(user) ? `${config.url}/users/${user._id}` : user.uri;
}

View File

@ -3,6 +3,6 @@ import { IDriveFile } from '../../../models/drive-file';
export default (file: IDriveFile) => ({
type: 'Image',
url: `${config.drive_url}/${file._id}`,
url: file.metadata.url || `${config.drive_url}/${file._id}`,
sensitive: file.metadata.isSensitive
});

View File

@ -50,9 +50,21 @@ export default async function renderNote(note: INote, dive = true): Promise<any>
? note.mentionedRemoteUsers.map(x => x.uri)
: [];
const cc = ['public', 'home', 'followers'].includes(note.visibility)
? [`${attributedTo}/followers`].concat(mentions)
: [];
let to: string[] = [];
let cc: string[] = [];
if (note.visibility == 'public') {
to = ['https://www.w3.org/ns/activitystreams#Public'];
cc = [`${attributedTo}/followers`].concat(mentions);
} else if (note.visibility == 'home') {
to = [`${attributedTo}/followers`];
cc = ['https://www.w3.org/ns/activitystreams#Public'].concat(mentions);
} else if (note.visibility == 'followers') {
to = [`${attributedTo}/followers`];
cc = mentions;
} else {
to = mentions;
}
const mentionedUsers = note.mentions ? await User.find({
_id: {
@ -74,7 +86,7 @@ export default async function renderNote(note: INote, dive = true): Promise<any>
summary: note.cw,
content: toHtml(note),
published: note.createdAt.toISOString(),
to: 'https://www.w3.org/ns/activitystreams#Public',
to,
cc,
inReplyTo,
attachment: (await promisedFiles).map(renderDocument),

View File

@ -0,0 +1,23 @@
/**
* Render OrderedCollectionPage
* @param id URL of self
* @param totalItems Number of total items
* @param orderedItems Items
* @param partOf URL of base
* @param prev URL of prev page (optional)
* @param next URL of next page (optional)
*/
export default function(id: string, totalItems: any, orderedItems: any, partOf: string, prev: string, next: string) {
const page = {
id,
partOf,
type: 'OrderedCollectionPage',
totalItems,
orderedItems
} as any;
if (prev) page.prev = prev;
if (next) page.next = next;
return page;
}

View File

@ -1,6 +1,19 @@
export default (id: string, totalItems: any, orderedItems: any) => ({
id,
type: 'OrderedCollection',
totalItems,
orderedItems
});
/**
* Render OrderedCollection
* @param id URL of self
* @param totalItems Total number of items
* @param first URL of first page (optional)
* @param last URL of last page (optional)
*/
export default function(id: string, totalItems: any, first: string, last: string) {
const page: any = {
id,
type: 'OrderedCollection',
totalItems,
};
if (first) page.first = first;
if (last) page.last = last;
return page;
}

View File

@ -19,6 +19,8 @@ export default async (user: ILocalUser) => {
id,
inbox: `${id}/inbox`,
outbox: `${id}/outbox`,
followers: `${id}/followers`,
following: `${id}/following`,
sharedInbox: `${config.url}/inbox`,
url: `${config.url}/@${user.username}`,
preferredUsername: user.username,

View File

@ -10,8 +10,9 @@ import User, { isLocalUser, ILocalUser, IUser } from '../models/user';
import renderNote from '../remote/activitypub/renderer/note';
import renderKey from '../remote/activitypub/renderer/key';
import renderPerson from '../remote/activitypub/renderer/person';
import renderOrderedCollection from '../remote/activitypub/renderer/ordered-collection';
import config from '../config';
import Outbox from './activitypub/outbox';
import Followers from './activitypub/followers';
import Following from './activitypub/following';
// Init router
const router = new Router();
@ -64,30 +65,14 @@ router.get('/notes/:note', async (ctx, next) => {
ctx.body = pack(await renderNote(note));
});
// outbot
router.get('/users/:user/outbox', async ctx => {
const userId = new mongo.ObjectID(ctx.params.user);
// outbox
router.get('/users/:user/outbox', Outbox);
const user = await User.findOne({
_id: userId,
host: null
});
// followers
router.get('/users/:user/followers', Followers);
if (user === null) {
ctx.status = 404;
return;
}
const notes = await Note.find({ userId: user._id }, {
limit: 10,
sort: { _id: -1 }
});
const renderedNotes = await Promise.all(notes.map(note => renderNote(note)));
const rendered = renderOrderedCollection(`${config.url}/users/${userId}/inbox`, user.notesCount, renderedNotes);
ctx.body = pack(rendered);
});
// following
router.get('/users/:user/following', Following);
// publickey
router.get('/users/:user/publickey', async ctx => {

View File

@ -0,0 +1,80 @@
import * as mongo from 'mongodb';
import * as Koa from 'koa';
import config from '../../config';
import $ from 'cafy'; import ID from '../../misc/cafy-id';
import User from '../../models/user';
import Following from '../../models/following';
import pack from '../../remote/activitypub/renderer';
import renderOrderedCollection from '../../remote/activitypub/renderer/ordered-collection';
import renderOrderedCollectionPage from '../../remote/activitypub/renderer/ordered-collection-page';
import renderFollowUser from '../../remote/activitypub/renderer/follow-user';
export default async (ctx: Koa.Context) => {
const userId = new mongo.ObjectID(ctx.params.user);
// Get 'cursor' parameter
const [cursor = null, cursorErr] = $.type(ID).optional.get(ctx.request.query.cursor);
// Get 'page' parameter
const pageErr = !$.str.optional.or(['true', 'false']).ok(ctx.request.query.page);
const page: boolean = ctx.request.query.page === 'true';
// Validate parameters
if (cursorErr || pageErr) {
ctx.status = 400;
return;
}
// Verify user
const user = await User.findOne({
_id: userId,
host: null
});
if (user === null) {
ctx.status = 404;
return;
}
const limit = 10;
const partOf = `${config.url}/users/${userId}/followers`;
if (page) {
// Construct query
const query = {
followeeId: user._id
} as any;
// カーソルが指定されている場合
if (cursor) {
query._id = {
$lt: cursor
};
}
// Get followers
const followings = await Following
.find(query, {
limit: limit + 1,
sort: { _id: -1 }
});
// 「次のページ」があるかどうか
const inStock = followings.length === limit + 1;
if (inStock) followings.pop();
const renderedFollowers = await Promise.all(followings.map(following => renderFollowUser(following.followerId)));
const rendered = renderOrderedCollectionPage(
`${partOf}?page=true${cursor ? `&cursor=${cursor}` : ''}`,
user.followersCount, renderedFollowers, partOf,
null,
inStock ? `${partOf}?page=true&cursor=${followings[followings.length - 1]._id}` : null
);
ctx.body = pack(rendered);
} else {
// index page
const rendered = renderOrderedCollection(partOf, user.followersCount, `${partOf}?page=true`, null);
ctx.body = pack(rendered);
}
};

View File

@ -0,0 +1,80 @@
import * as mongo from 'mongodb';
import * as Koa from 'koa';
import config from '../../config';
import $ from 'cafy'; import ID from '../../misc/cafy-id';
import User from '../../models/user';
import Following from '../../models/following';
import pack from '../../remote/activitypub/renderer';
import renderOrderedCollection from '../../remote/activitypub/renderer/ordered-collection';
import renderOrderedCollectionPage from '../../remote/activitypub/renderer/ordered-collection-page';
import renderFollowUser from '../../remote/activitypub/renderer/follow-user';
export default async (ctx: Koa.Context) => {
const userId = new mongo.ObjectID(ctx.params.user);
// Get 'cursor' parameter
const [cursor = null, cursorErr] = $.type(ID).optional.get(ctx.request.query.cursor);
// Get 'page' parameter
const pageErr = !$.str.optional.or(['true', 'false']).ok(ctx.request.query.page);
const page: boolean = ctx.request.query.page === 'true';
// Validate parameters
if (cursorErr || pageErr) {
ctx.status = 400;
return;
}
// Verify user
const user = await User.findOne({
_id: userId,
host: null
});
if (user === null) {
ctx.status = 404;
return;
}
const limit = 10;
const partOf = `${config.url}/users/${userId}/following`;
if (page) {
// Construct query
const query = {
followerId: user._id
} as any;
// カーソルが指定されている場合
if (cursor) {
query._id = {
$lt: cursor
};
}
// Get followings
const followings = await Following
.find(query, {
limit: limit + 1,
sort: { _id: -1 }
});
// 「次のページ」があるかどうか
const inStock = followings.length === limit + 1;
if (inStock) followings.pop();
const renderedFollowees = await Promise.all(followings.map(following => renderFollowUser(following.followeeId)));
const rendered = renderOrderedCollectionPage(
`${partOf}?page=true${cursor ? `&cursor=${cursor}` : ''}`,
user.followingCount, renderedFollowees, partOf,
null,
inStock ? `${partOf}?page=true&cursor=${followings[followings.length - 1]._id}` : null
);
ctx.body = pack(rendered);
} else {
// index page
const rendered = renderOrderedCollection(partOf, user.followingCount, `${partOf}?page=true`, null);
ctx.body = pack(rendered);
}
};

View File

@ -0,0 +1,103 @@
import * as mongo from 'mongodb';
import * as Koa from 'koa';
import config from '../../config';
import $ from 'cafy'; import ID from '../../misc/cafy-id';
import User from '../../models/user';
import pack from '../../remote/activitypub/renderer';
import renderOrderedCollection from '../../remote/activitypub/renderer/ordered-collection';
import renderOrderedCollectionPage from '../../remote/activitypub/renderer/ordered-collection-page';
import Note from '../../models/note';
import renderNote from '../../remote/activitypub/renderer/note';
export default async (ctx: Koa.Context) => {
const userId = new mongo.ObjectID(ctx.params.user);
// Get 'sinceId' parameter
const [sinceId, sinceIdErr] = $.type(ID).optional.get(ctx.request.query.since_id);
// Get 'untilId' parameter
const [untilId, untilIdErr] = $.type(ID).optional.get(ctx.request.query.until_id);
// Get 'page' parameter
const pageErr = !$.str.optional.or(['true', 'false']).ok(ctx.request.query.page);
const page: boolean = ctx.request.query.page === 'true';
// Validate parameters
if (sinceIdErr || untilIdErr || pageErr || [sinceId, untilId].filter(x => x != null).length > 1) {
ctx.status = 400;
return;
}
// Verify user
const user = await User.findOne({
_id: userId,
host: null
});
if (user === null) {
ctx.status = 404;
return;
}
const limit = 20;
const partOf = `${config.url}/users/${userId}/outbox`;
if (page) {
//#region Construct query
const sort = {
_id: -1
};
const query = {
userId: user._id,
$and: [{
$or: [ { visibility: 'public' }, { visibility: 'home' } ]
}, { // exclude renote, but include quote
$or: [{
text: { $ne: null }
}, {
mediaIds: { $ne: [] }
}]
}]
} as any;
if (sinceId) {
sort._id = 1;
query._id = {
$gt: sinceId
};
} else if (untilId) {
query._id = {
$lt: untilId
};
}
//#endregion
// Issue query
const notes = await Note
.find(query, {
limit: limit,
sort: sort
});
if (sinceId) notes.reverse();
const renderedNotes = await Promise.all(notes.map(note => renderNote(note)));
const rendered = renderOrderedCollectionPage(
`${partOf}?page=true${sinceId ? `&since_id=${sinceId}` : ''}${untilId ? `&until_id=${untilId}` : ''}`,
user.notesCount, renderedNotes, partOf,
notes.length > 0 ? `${partOf}?page=true&since_id=${notes[0]._id}` : null,
notes.length > 0 ? `${partOf}?page=true&until_id=${notes[notes.length - 1]._id}` : null
);
ctx.body = pack(rendered);
} else {
// index page
const rendered = renderOrderedCollection(partOf, user.notesCount,
`${partOf}?page=true`,
`${partOf}?page=true&since_id=000000000000000000000000`
);
ctx.body = pack(rendered);
}
};

View File

@ -1,6 +1,6 @@
import { performance } from 'perf_hooks';
import limitter from './limitter';
import { IUser } from '../../models/user';
import { IUser, isLocalUser } from '../../models/user';
import { IApp } from '../../models/app';
import endpoints from './endpoints';
@ -21,6 +21,10 @@ export default (endpoint: string, user: IUser, app: IApp, data: any, file?: any)
return rej('YOUR_ACCOUNT_HAS_BEEN_SUSPENDED');
}
if (ep.meta.requireAdmin && !(isLocalUser(user) && user.isAdmin)) {
return rej('YOU_ARE_NOT_ADMIN');
}
if (app && ep.meta.kind) {
if (!app.permission.some(p => p === ep.meta.kind)) {
return rej('PERMISSION_DENIED');
@ -53,7 +57,7 @@ export default (endpoint: string, user: IUser, app: IApp, data: any, file?: any)
const time = after - before;
if (time > 1000) {
console.warn(`SLOW API CALL DETECTED: ${ep.name} (${ time }ms)`);
console.warn(`SLOW API CALL DETECTED: ${ep.name} (${time}ms)`);
}
} catch (e) {
rej(e);

View File

@ -14,6 +14,11 @@ export interface IEndpointMeta {
*/
requireCredential?: boolean;
/**
* 管理者のみ使えるエンドポイントか否か
*/
requireAdmin?: boolean;
/**
* エンドポイントのリミテーションに関するやつ
* 省略した場合はリミテーションは無いものとして解釈されます。

View File

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

View File

@ -4,7 +4,7 @@ import App, { isValidNameId, pack } from '../../../../models/app';
import { ILocalUser } from '../../../../models/user';
export const meta = {
requireCredential: true
requireCredential: false
};
/**
@ -38,7 +38,7 @@ export default async (params: any, user: ILocalUser) => new Promise(async (res,
// Create account
const app = await App.insert({
createdAt: new Date(),
userId: user._id,
userId: user && user._id,
name: name,
nameId: nameId,
nameIdLower: nameId.toLowerCase(),

View File

@ -40,6 +40,5 @@ export default (params: any, user: ILocalUser) => new Promise(async (res, rej) =
});
// Serialize
res(await Promise.all(history.map(async h =>
await pack(h.messageId, user))));
res(await Promise.all(history.map(h => pack(h.messageId, user))));
});

View File

@ -0,0 +1,43 @@
import $ from 'cafy'; import ID from '../../../../../misc/cafy-id';
import Message from '../../../../../models/messaging-message';
import { ILocalUser } from '../../../../../models/user';
import read from '../../../common/read-messaging-message';
import getParams from '../../../get-params';
export const meta = {
desc: {
ja: '指定した自分宛てのメッセージを既読にします。',
en: 'Mark as read a message of messaging.'
},
requireCredential: true,
kind: 'messaging-write',
params: {
messageId: $.type(ID).note({
desc: {
ja: '既読にするメッセージのID',
en: 'The ID of a message that you want to mark as read'
}
})
}
};
export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) throw psErr;
const message = await Message.findOne({
_id: ps.messageId,
recipientId: user._id
});
if (message == null) {
return rej('message not found');
}
read(user._id, message.userId, message);
res();
});

View File

@ -8,7 +8,8 @@ import getParams from '../../get-params';
export const meta = {
desc: {
ja: 'タイムラインを取得します。'
ja: 'タイムラインを取得します。',
en: 'Get timeline of myself.'
},
requireCredential: true,
@ -67,9 +68,6 @@ export const meta = {
}
};
/**
* Get timeline of myself
*/
export default async (params: any, user: ILocalUser) => {
const [ps, psErr] = getParams(meta, params);
if (psErr) throw psErr;

View File

@ -14,7 +14,9 @@ module.exports = async (ctx: Koa.Context) => {
ctx.body = summary;
} catch (e) {
ctx.status = 500;
ctx.status = 200;
ctx.set('Cache-Control', 'max-age=86400, immutable');
ctx.body = '{}';
}
};

View File

@ -95,6 +95,8 @@ type Option = {
};
export default async (user: IUser, data: Option, silent = false) => new Promise<INote>(async (res, rej) => {
const isFirstNote = user.notesCount === 0;
if (data.createdAt == null) data.createdAt = new Date();
if (data.visibility == null) data.visibility = 'public';
if (data.viaMobile == null) data.viaMobile = false;
@ -164,6 +166,10 @@ export default async (user: IUser, data: Option, silent = false) => new Promise<
// Pack the note
const noteObj = await pack(note);
if (isFirstNote) {
noteObj.isFirstNote = true;
}
const nm = new NotificationManager(user, note);
const nmRelatedPromises = [];
@ -188,6 +194,7 @@ export default async (user: IUser, data: Option, silent = false) => new Promise<
// 通知
if (isLocalUser(data.reply._user)) {
nm.push(data.reply.userId, 'reply');
publishUserStream(data.reply.userId, 'reply', noteObj);
}
}
@ -209,7 +216,7 @@ export default async (user: IUser, data: Option, silent = false) => new Promise<
}
// Publish event
if (!user._id.equals(data.renote.userId)) {
if (!user._id.equals(data.renote.userId) && isLocalUser(data.renote._user)) {
publishUserStream(data.renote.userId, 'renote', noteObj);
}
}

View File

@ -95,7 +95,8 @@ const consts = {
_URL_: config.url,
_LICENSE_: licenseHtml,
_GOOGLE_MAPS_API_KEY_: config.google_maps_api_key,
_WELCOME_BG_URL_: config.welcome_bg_url
_WELCOME_BG_URL_: config.welcome_bg_url,
_TWITTER_INTEGRATION_: config.twitter != null
};
const _consts: { [ key: string ]: any } = {};