Compare commits

...

73 Commits
5.0.0 ... 5.6.1

Author SHA1 Message Date
12f63db62e 5.6.1 2018-07-27 18:52:07 +09:00
08e1c87fa6 oops 2018-07-27 18:50:15 +09:00
8ee962b729 5.6.0 2018-07-27 18:43:36 +09:00
3d8b45ecdd Use os-utils 2018-07-27 18:42:58 +09:00
2347d9cea2 Merge branch 'master' of https://github.com/syuilo/misskey 2018-07-27 18:40:41 +09:00
8a57f490ce バギーなのでジョブキュー無効化 2018-07-27 18:40:38 +09:00
a880f5cbb8 Merge pull request #2009 from acid-chicken/acid-chicken-patch-1
Minify Mk-II
2018-07-27 18:29:24 +09:00
df5a7c7e0c Update calendar.vue 2018-07-27 18:28:06 +09:00
b7b82456d8 Merge branch 'master' of https://github.com/syuilo/misskey 2018-07-27 18:18:49 +09:00
6b19e54c23 Fix bug 2018-07-27 18:18:05 +09:00
75d04858e6 2018-07-27 17:58:19 +09:00
9332551791 2018-07-27 17:51:40 +09:00
32117a573b Fix bug 2018-07-27 17:47:10 +09:00
d4d3316d18 2018-07-27 17:43:04 +09:00
2vg
43a7eb233c fix: critical memory leak. 2018-07-27 17:33:21 +09:00
2vg
178093861b memory usage excludes buffer and cache. 2018-07-27 17:31:19 +09:00
3fb26534b7 Merge pull request #2004 from syuilo/greenkeeper/jsdom-11.12.0
Update jsdom to the latest version 🚀
2018-07-27 14:48:59 +09:00
19a9fdfd38 fix(package): update jsdom to version 11.12.0 2018-07-27 04:58:58 +00:00
6438e97324 Merge pull request #2002 from yuzulabo/fix/visibility-icon-mobile
#1993 をモバイル版に対応
2018-07-27 13:37:45 +09:00
b29492e8eb Change VisibilityButton's icon in mobile view 2018-07-27 13:30:46 +09:00
5ab4f10230 fix(package): update typescript-eslint-parser to version 17.0.1 2018-07-27 09:39:21 +09:00
80b251e12c Merge branch 'master' of https://github.com/syuilo/misskey 2018-07-27 07:29:26 +09:00
bfd8b12a4f Clean up 2018-07-27 07:29:24 +09:00
1c2e94658b Update README.md 2018-07-27 07:21:03 +09:00
286da28cd6 5.5.0 2018-07-27 07:05:33 +09:00
a4ee93a355 Fix bug 2018-07-27 07:05:12 +09:00
ab56cb1788 Update doc 2018-07-27 06:07:13 +09:00
32435e4d8e Update docs 2018-07-27 05:58:52 +09:00
900cdf9d9a Update doc 2018-07-27 05:56:00 +09:00
e79019266f Update doc 2018-07-27 05:50:37 +09:00
deee7361f0 5.4.0 2018-07-27 04:26:48 +09:00
bdcf09c618 Update doc 2018-07-27 04:25:38 +09:00
7b5d6dcd9b Update doc 2018-07-27 04:21:48 +09:00
0595d87759 Update doc 2018-07-27 04:10:16 +09:00
fab0a0d6e2 ログインしていないとリバーシを観戦できない問題を修正 2018-07-27 04:01:12 +09:00
3eb6b36866 Fix bug 2018-07-27 03:46:12 +09:00
50327158e2 wip doc 2018-07-27 03:43:23 +09:00
a99756ef85 ✌️ 2018-07-27 03:34:28 +09:00
1c25dbed66 5.3.0 2018-07-27 03:23:17 +09:00
7e8c5c0c3c Improve readability 2018-07-27 03:21:50 +09:00
0b747b901c Merge pull request #1998 from syuilo/greenkeeper/typescript-eslint-parser-17.0.0
Update typescript-eslint-parser to the latest version 🚀
2018-07-27 01:54:09 +09:00
8dd5051201 Merge pull request #1990 from mei23/mei-osurl
オブジェクトストレージの参照URLを上書きできるようにする
2018-07-27 01:53:42 +09:00
f7b0fedc9d Merge pull request #1989 from mei23/mei-oscc
オブジェクトストレージ格納時にCache-Controlを指定する
2018-07-27 01:52:19 +09:00
0411d0b242 fix(package): update typescript-eslint-parser to version 17.0.0 2018-07-26 13:37:52 +00:00
3fcc793269 Fix problem displaying button in profile page 2018-07-26 21:47:02 +09:00
fd27a0efef Hide follow button of my account 2018-07-26 21:45:43 +09:00
4474a2568e Change VisibilityButton's icon when changing visibility 2018-07-26 21:44:21 +09:00
9d944243a3 Add S3 examples 2018-07-26 17:42:08 +09:00
8ef38ebab1 Add config.drive.baseUrl 2018-07-26 17:29:05 +09:00
f457a23eab Merge pull request #1988 from syuilo/greenkeeper/element-ui-2.4.5
Update element-ui to the latest version 🚀
2018-07-26 17:16:45 +09:00
5d1eeaf1d8 fix(package): update element-ui to version 2.4.5 2018-07-26 08:16:21 +00:00
77f732c6a4 5.2.1 2018-07-26 17:15:20 +09:00
ac07f04ad8 Update job queue setting 2018-07-26 17:15:00 +09:00
dddd760efd Fix bug 2018-07-26 17:13:55 +09:00
0f7fbacb17 Fix bug 2018-07-26 17:10:43 +09:00
2697107770 5.2.0 2018-07-26 17:04:33 +09:00
e1e1cd0574 Update job queue settings 2018-07-26 17:02:34 +09:00
93786aa510 Merge branch 'master' of https://github.com/syuilo/misskey 2018-07-26 16:51:00 +09:00
d8b9a8715b ✌️ 2018-07-26 16:50:50 +09:00
e8783b15b1 Set Cache-Control to object-storage 2018-07-26 16:06:43 +09:00
0995d5c5a2 Merge pull request #1986 from acid-chicken/master
Resolves #1985
2018-07-26 13:49:24 +09:00
0852045928 Update misskey-flavored-markdown.ts 2018-07-26 13:48:08 +09:00
04de0e9a50 Update hashtags.vue 2018-07-26 13:47:06 +09:00
951b693d17 Merge pull request #1983 from mei23/mei-osct 2018-07-26 11:56:00 +09:00
308f357c4f Set Content-Type to object-storage 2018-07-26 10:47:12 +09:00
206ddd6d36 5.1.0 2018-07-26 09:22:33 +09:00
b4cf963bd6 Merge branch 'master' of https://github.com/syuilo/misskey 2018-07-26 08:11:49 +09:00
77b493c9b0 Use bee-queue instead of Kue 2018-07-26 08:11:47 +09:00
95a5ff5625 Merge pull request #1981 from syuilo/l10n_master
New Crowdin translations
2018-07-26 05:41:50 +09:00
190753aa99 New translations ja.yml (Polish) 2018-07-26 05:41:28 +09:00
f778696a76 ✌️ 2018-07-26 05:27:27 +09:00
ce4fb49d4c Improve tweet embed 2018-07-26 04:55:39 +09:00
91b89b79d2 Fix bug 2018-07-26 04:29:09 +09:00
40 changed files with 468 additions and 206 deletions

View File

@ -68,6 +68,29 @@ drive:
# accessKey:
# secretKey:
# S3 example
# storage: 'minio'
# bucket: bucket-name
# prefix: files
# config:
# endPoint: s3-us-west-2.amazonaws.com
# region: us-west-2
# secure: 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
# secure: true
# accessKey: XXX
# secretKey: YYY
#
# Below settings are optional
#

View File

@ -43,9 +43,9 @@ If you want to...
:heart: Backers & Sponsors
----------------------------------------------------------------
| <img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/619786/32cf01444db24e578cd1982c197f6fc6/1?token-time=2145916800&token-hash=tB1e_r8RlZ5sFL0KV_e8dugapxatNBRK1Z3h67TO1g8%3D"> | <img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12378075/0156f769e20f412594fa6b87d85fe228/1?token-time=2145916800&token-hash=IsIJRUXszzoD6-7pDnRY8I05T9nSznc4GTaxj7C9SwU%3D"> | <img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1?token-time=2145916800&token-hash=S1zP0QyLU52Dqq6dtc9qNYyWfW86XrYHiR4NMbeOrnA%3D"> |
|:-:|:-:|:-:|
| [Gargron](https://www.patreon.com/mastodon) | [39ff](https://www.patreon.com/user/creators?u=12378075) | [dansup](https://www.patreon.com/dansup) |
| <img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/619786/32cf01444db24e578cd1982c197f6fc6/1?token-time=2145916800&token-hash=tB1e_r8RlZ5sFL0KV_e8dugapxatNBRK1Z3h67TO1g8%3D"> | <img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12378075/0156f769e20f412594fa6b87d85fe228/1?token-time=2145916800&token-hash=IsIJRUXszzoD6-7pDnRY8I05T9nSznc4GTaxj7C9SwU%3D"> | <img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/4503830/ccf2cc867ea64de0b524bb2e24b9a1cb/1?token-time=2145916800&token-hash=S1zP0QyLU52Dqq6dtc9qNYyWfW86XrYHiR4NMbeOrnA%3D"> | <img src="https://c10.patreonusercontent.com/3/eyJoIjoxMDAsInciOjEwMH0%3D/patreon-media/p/user/12531784/93a45137841849329ba692da92ac7c60/1?token-time=2145916800&token-hash=tMosUojzUYJCH_3t--tvYA-SMCyrS__hzSndyaRSnbo%3D"> |
|:-:|:-:|:-:|:-:|
| [Gargron](https://www.patreon.com/mastodon) | [39ff](https://www.patreon.com/user/creators?u=12378075) | [dansup](https://www.patreon.com/dansup) | [Takashi Shibuya](https://www.patreon.com/user/creators?u=12531784) |
:four_leaf_clover: Copyright
----------------------------------------------------------------

View File

@ -1,11 +1,7 @@
# Management guide
## Check the status of the job queue
In the directory of Misskey:
``` shell
node_modules/kue/bin/kue-dashboard -p 3050
```
When you access port 3050, you will see the UI.
coming soon
## Mark as 'admin' user
``` shell

View File

@ -1,11 +1,7 @@
# 運営ガイド
## ジョブキューの状態を調べる
Misskeyのディレクトリで:
``` shell
node_modules/kue/bin/kue-dashboard -p 3050
```
ポート3050にアクセスするとUIが表示されます
coming soon
## 管理者ユーザーを設定する
``` shell

View File

@ -21,7 +21,7 @@ const langs = {
Object.values(langs).forEach(locale => {
// Extend native language (Japanese)
Object.assign(locale, native);
locale = Object.assign({}, native, locale);
});
module.exports = langs;

View File

@ -51,7 +51,7 @@ common:
my-token-regenerated: "Twój token został wygenerowany. Zostaniesz wylogowany."
i-like-sushi: "Wolę sushi od puddingu"
show-reversi-board-labels: "Pokazuj podpisy wierszy i kolumn w Reversi"
verified-user: "認証済みのユーザー"
verified-user: "Zweryfikowany użytkownik"
reversi:
drawn: "Remis"
my-turn: "Twoja kolej"
@ -289,8 +289,8 @@ desktop/views/components/drive.file.vue:
banner: "Baner"
contextmenu:
rename: "Zmień nazwę"
mark-as-sensitive: "閲覧注意に設定"
unmark-as-sensitive: "閲覧注意を解除"
mark-as-sensitive: "Oznacz jako zawartość wrażliwą"
unmark-as-sensitive: "Cofnij oznaczenie jako zawartość wrażliwą"
copy-url: "Skopiuj adres"
download: "Pobierz"
else-files: "Inne"
@ -335,11 +335,11 @@ desktop/views/components/drive.vue:
upload: "Wyślij plik"
url-upload: "Wyślij z adresu URL"
desktop/views/components/media-image.vue:
sensitive: "閲覧注意"
click-to-show: "クリックして表示"
sensitive: "To jest zawartość NSFW"
click-to-show: "Naciśnij aby wyświetlić"
desktop/views/components/media-video.vue:
sensitive: "閲覧注意"
click-to-show: "クリックして表示"
sensitive: "To jest zawartość NSFW"
click-to-show: "Naciśnij aby wyświetlić"
desktop/views/components/follow-button.vue:
following: "Śledzisz"
follow: "Śledź"
@ -414,8 +414,8 @@ desktop/views/components/post-form.vue:
insert-a-kao: "v('ω')v"
create-poll: "Utwórz ankietę"
text-remain: "pozostałe znaki: {}"
recent-tags: "最近"
click-to-tagging: "クリックでタグ付け"
recent-tags: "Ostatnie"
click-to-tagging: "Naciśnij aby oznaczyć"
desktop/views/components/post-form-window.vue:
note: "Nowy wpis"
reply: "Odpowiedz"
@ -737,11 +737,11 @@ mobile/views/components/drive.file-detail.vue:
hash: "Hash (md5)"
exif: "EXIF"
mobile/views/components/media-image.vue:
sensitive: "閲覧注意"
click-to-show: "クリックして表示"
sensitive: "To jest zawartość NSFW"
click-to-show: "Naciśnij aby wyświetlić"
mobile/views/components/media-video.vue:
sensitive: "閲覧注意"
click-to-show: "クリックして表示"
sensitive: "To jest zawartość NSFW"
click-to-show: "Naciśnij aby wyświetlić"
mobile/views/components/follow-button.vue:
following: "Śledzisz"
follow: "Śledź"
@ -951,14 +951,14 @@ docs:
properties: "Właściwości"
endpoints:
params: "Parametry"
no-params: "パラメータはありません"
no-params: "Brak parametrów."
res: "Odpowiedź"
require-credential: "このエンドポイントは認証情報が必須です。"
require-permission: "このエンドポイントは{permission}の権限を必要とします。"
has-limit: "レートリミットがあります。"
require-credential: "Punkt końcowy wymaga informacji o uwierzytelnieniu."
require-permission: "Ten punkt końcowy wymaga uprawnienia {permission}."
has-limit: "Istnieje limit częstotliwości."
duration-limit: "直近{duration}ミリ秒の間のこのエンドポイントへのリクエスト数の合計が{max}を超える場合はリクエストできません。"
min-interval-limit: "前回のリクエストから{interval}ミリ秒経っていない場合はリクエストできません。"
show-src: "このエンドポイントのソースコードも閲覧できます。"
min-interval-limit: "Nie możesz wykonać żądania przed upłynięciem {interval} od ostatniego żądania."
show-src: "Możesz zobaczyć kod źródłowy tego punktu końcowego."
show-src-link: "Zobacz kod na GitHubie"
generated: "このドキュメントはAPI定義に基づき自動生成されています。"
props:

View File

@ -1,8 +1,8 @@
{
"name": "misskey",
"author": "syuilo <i@syuilo.com>",
"version": "5.0.0",
"clientVersion": "1.0.7553",
"version": "5.6.1",
"clientVersion": "1.0.7624",
"codename": "nighthike",
"main": "./built/index.js",
"private": true,
@ -55,7 +55,6 @@
"@types/koa-send": "4.1.1",
"@types/koa-views": "2.0.3",
"@types/koa__cors": "2.2.2",
"@types/kue": "0.11.9",
"@types/minio": "6.0.2",
"@types/mkdirp": "0.5.2",
"@types/mocha": "5.2.3",
@ -76,6 +75,7 @@
"@types/showdown": "1.7.5",
"@types/single-line-log": "1.1.0",
"@types/speakeasy": "2.0.2",
"@types/systeminformation": "3.23.0",
"@types/tmp": "0.0.33",
"@types/uuid": "3.4.3",
"@types/webpack": "4.4.8",
@ -86,6 +86,7 @@
"autosize": "4.0.2",
"autwh": "0.1.0",
"bcryptjs": "2.4.3",
"bee-queue": "1.2.2",
"bootstrap-vue": "2.0.0-rc.11",
"cafy": "11.3.0",
"chalk": "2.4.1",
@ -98,7 +99,7 @@
"diskusage": "0.2.4",
"dompurify": "1.0.5",
"elasticsearch": "15.1.1",
"element-ui": "2.4.4",
"element-ui": "2.4.5",
"emojilib": "2.3.0",
"escape-regexp": "0.0.1",
"eslint": "5.0.1",
@ -131,7 +132,7 @@
"is-url": "1.2.4",
"jquery": "3.3.1",
"js-yaml": "3.12.0",
"jsdom": "11.11.0",
"jsdom": "11.12.0",
"koa": "2.5.1",
"koa-bodyparser": "4.2.1",
"koa-compress": "3.0.0",
@ -144,7 +145,6 @@
"koa-send": "5.0.0",
"koa-slow": "2.1.0",
"koa-views": "6.1.4",
"kue": "0.11.6",
"loader-utils": "1.1.0",
"mecab-async": "0.1.2",
"minio": "6.0.0",
@ -188,6 +188,7 @@
"stylus": "0.54.5",
"stylus-loader": "3.0.2",
"summaly": "2.0.6",
"systeminformation": "3.42.4",
"syuilo-password-strength": "0.0.1",
"textarea-caret": "3.1.0",
"tmp": "0.0.33",
@ -195,7 +196,7 @@
"ts-node": "7.0.0",
"tslint": "5.10.0",
"typescript": "2.9.2",
"typescript-eslint-parser": "16.0.1",
"typescript-eslint-parser": "17.0.1",
"uglify-es": "3.3.9",
"url-loader": "1.0.1",
"uuid": "3.3.2",

View File

@ -105,7 +105,8 @@ export default Vue.extend({
}
},
isMyTurn(): boolean {
if (this.turnUser == null) return null;
if (!this.iAmPlayer) return false;
if (this.turnUser == null) return false;
return this.turnUser.id == this.$store.state.i.id;
},
cellsStyle(): any {

View File

@ -67,7 +67,9 @@ export default Vue.extend({
components: {
XGameroom
},
props: ['initGame'],
data() {
return {
game: null,
@ -82,17 +84,21 @@ export default Vue.extend({
pingClock: null
};
},
watch: {
game(g) {
this.$emit('gamed', g);
}
},
created() {
if (this.initGame) {
this.game = this.initGame;
}
},
mounted() {
if (this.$store.getters.isSignedIn) {
this.connection = (this as any).os.streams.reversiStream.getConnection();
this.connectionId = (this as any).os.streams.reversiStream.use();
@ -105,11 +111,6 @@ export default Vue.extend({
this.myGames = games;
});
(this as any).api('games/reversi/games').then(games => {
this.games = games;
this.gamesFetching = false;
});
(this as any).api('games/reversi/invitations').then(invitations => {
this.invitations = this.invitations.concat(invitations);
});
@ -122,14 +123,24 @@ export default Vue.extend({
});
}
}, 3000);
}
(this as any).api('games/reversi/games').then(games => {
this.games = games;
this.gamesFetching = false;
});
},
beforeDestroy() {
if (this.connection) {
this.connection.off('matched', this.onMatched);
this.connection.off('invited', this.onInvited);
(this as any).os.streams.reversiStream.dispose(this.connectionId);
clearInterval(this.pingClock);
}
},
methods: {
go(game) {
(this as any).api('games/reversi/games/show', {
@ -139,6 +150,7 @@ export default Vue.extend({
this.game = game;
});
},
match() {
(this as any).apis.input({
title: 'ユーザー名を入力してください'
@ -158,10 +170,12 @@ export default Vue.extend({
});
});
},
cancel() {
this.matching = null;
(this as any).api('games/reversi/match/cancel');
},
accept(invitation) {
(this as any).api('games/reversi/match', {
userId: invitation.parent.id
@ -172,10 +186,12 @@ export default Vue.extend({
}
});
},
onMatched(game) {
this.matching = null;
this.game = game;
},
onInvited(invite) {
this.invitations.unshift(invite);
}

View File

@ -92,7 +92,7 @@ export default Vue.component('misskey-flavored-markdown', {
case 'hashtag':
return createElement('a', {
attrs: {
href: `${url}/tags/${token.hashtag}`,
href: `${url}/tags/${encodeURIComponent(token.hashtag)}`,
target: '_blank'
}
}, token.content);

View File

@ -2,9 +2,11 @@
<iframe v-if="youtubeId" type="text/html" height="250"
:src="`https://www.youtube.com/embed/${youtubeId}?origin=${misskeyUrl}`"
frameborder="0"/>
<blockquote v-else-if="tweetUrl" class="twitter-tweet" ref="tweet">
<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>
</blockquote>
</blockquote>
</div>
<div v-else class="mk-url-preview">
<a :href="url" target="_blank" :title="url" v-if="!fetching">
<div class="thumbnail" v-if="thumbnail" :style="`background-image: url(${thumbnail})`"></div>
@ -27,7 +29,17 @@ import Vue from 'vue';
import { url as misskeyUrl } from '../../../config';
export default Vue.extend({
props: ['url'],
props: {
url: {
type: String,
require: true
},
detail: {
type: Boolean,
required: false,
default: false
}
},
data() {
return {
fetching: true,
@ -48,7 +60,7 @@ export default Vue.extend({
this.youtubeId = url.searchParams.get('v');
} else if (url.hostname == 'youtu.be') {
this.youtubeId = url.pathname;
} else if (url.hostname == 'twitter.com' && /^\/.+\/status(es)?\/\d+/.test(url.pathname)) {
} else 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);

View File

@ -11,7 +11,7 @@
<div>
<div v-for="stat in stats" :key="stat.tag">
<div class="tag">
<router-link :to="`/tags/${ stat.tag }`" :title="stat.tag">#{{ stat.tag }}</router-link>
<router-link :to="`/tags/${ encodeURIComponent(stat.tag) }`" :title="stat.tag">#{{ stat.tag }}</router-link>
<p>{{ '%i18n:@count%'.replace('{}', stat.usersCount) }}</p>
</div>
<x-chart class="chart" :src="stat.chart"/>

View File

@ -102,7 +102,6 @@ export default Vue.extend({
},
methods: {
onStats(stats) {
stats.mem.used = stats.mem.total - stats.mem.free;
this.stats.push(stats);
if (this.stats.length > 50) this.stats.shift();

View File

@ -35,7 +35,7 @@ export default Vue.extend({
},
methods: {
onStats(stats) {
stats.mem.used = stats.mem.total - stats.mem.free;
stats.mem.free = stats.mem.total - stats.mem.used;
this.usage = stats.mem.used / stats.mem.total;
this.total = stats.mem.total;
this.used = stats.mem.used;

View File

@ -35,7 +35,7 @@ import Vue from 'vue';
const eachMonthDays = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
function isLeapYear(year) {
return !(year % (year % 25 ? 4 : 16));
return !(year & (year % 25 ? 3 : 5));
}
export default Vue.extend({

View File

@ -46,7 +46,7 @@
<mk-media-list :media-list="p.media" :raw="true"/>
</div>
<mk-poll v-if="p.poll" :note="p"/>
<mk-url-preview v-for="url in urls" :url="url" :key="url"/>
<mk-url-preview v-for="url in urls" :url="url" :key="url" :detail="true"/>
<a class="location" v-if="p.geo" :href="`http://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% %i18n:@location%</a>
<div class="map" v-if="p.geo" ref="map"></div>
<div class="renote" v-if="p.renote">

View File

@ -38,7 +38,13 @@
<button class="poll" title="%i18n:@create-poll%" @click="poll = true">%fa:chart-pie%</button>
<button class="poll" title="内容を隠す" @click="useCw = !useCw">%fa:eye-slash%</button>
<button class="geo" title="位置情報を添付する" @click="geo ? removeGeo() : setGeo()">%fa:map-marker-alt%</button>
<button class="visibility" title="公開範囲" @click="setVisibility" ref="visibilityButton">%fa:lock%</button>
<button class="visibility" title="公開範囲" @click="setVisibility" ref="visibilityButton">
<span v-if="visibility === 'public'">%fa:globe%</span>
<span v-if="visibility === 'home'">%fa:home%</span>
<span v-if="visibility === 'followers'">%fa:unlock%</span>
<span v-if="visibility === 'specified'">%fa:envelope%</span>
<span v-if="visibility === 'private'">%fa:lock%</span>
</button>
<p class="text-count" :class="{ over: text.length > 1000 }">{{ 1000 - text.length }}</p>
<button :class="{ posting }" class="submit" :disabled="!canPost" @click="post">
{{ posting ? '%i18n:@posting%' : submitText }}<mk-ellipsis v-if="posting"/>

View File

@ -84,8 +84,7 @@ export default Vue.extend({
(this as any).os.new(MkGameWindow);
},
goToTop(e: HTMLElement) {
if (e.classList.contains('active'))
goToTop() {
window.scrollTo({
top: 0,
behavior: 'smooth'

View File

@ -45,14 +45,7 @@ export default Vue.extend({
XPost,
XClock,
},
methods: {
goToTop() {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
}
},
mounted() {
this.$store.commit('setUiHeaderHeight', 48);
@ -104,7 +97,16 @@ export default Vue.extend({
}, 2500);
}
}
},
methods: {
goToTop() {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
}
},
});
</script>

View File

@ -19,7 +19,7 @@
<p>%i18n:@followers%</p><a>{{ u.followersCount }}</a>
</div>
</div>
<mk-follow-button v-if="$store.getters.isSignedIn && user.id != $store.state.i.id" :user="u"/>
<mk-follow-button v-if="$store.getters.isSignedIn && u.id != $store.state.i.id" :user="u"/>
</template>
</div>
</template>

View File

@ -1,6 +1,6 @@
<template>
<div class="profile">
<div class="friend-form" v-if="$store.getters.isSignedIn && $store.state.i.id != user.id">
<div class="profile" v-if="$store.getters.isSignedIn">
<div class="friend-form" v-if="$store.state.i.id != user.id">
<mk-follow-button :user="user" size="big"/>
<p class="followed" v-if="user.isFollowed">%i18n:@follows-you%</p>
<p class="stalk" v-if="user.isFollowing">
@ -9,7 +9,7 @@
</p>
</div>
<div class="action-form">
<button class="mute ui" @click="user.isMuted ? unmute() : mute()">
<button class="mute ui" @click="user.isMuted ? unmute() : mute()" v-if="$store.state.i.id != user.id">
<span v-if="user.isMuted">%fa:eye% %i18n:@unmute%</span>
<span v-if="!user.isMuted">%fa:eye-slash% %i18n:@mute%</span>
</button>

View File

@ -44,7 +44,7 @@
<mk-media-list :media-list="p.media" :raw="true"/>
</div>
<mk-poll v-if="p.poll" :note="p"/>
<mk-url-preview v-for="url in urls" :url="url" :key="url"/>
<mk-url-preview v-for="url in urls" :url="url" :key="url" :detail="true"/>
<a class="location" v-if="p.geo" :href="`http://maps.google.com/maps?q=${p.geo.coordinates[1]},${p.geo.coordinates[0]}`" target="_blank">%fa:map-marker-alt% %i18n:@location%</a>
<div class="map" v-if="p.geo" ref="map"></div>
<div class="renote" v-if="p.renote">

View File

@ -34,7 +34,13 @@
<button class="poll" @click="poll = true">%fa:chart-pie%</button>
<button class="poll" @click="useCw = !useCw">%fa:eye-slash%</button>
<button class="geo" @click="geo ? removeGeo() : setGeo()">%fa:map-marker-alt%</button>
<button class="visibility" @click="setVisibility" ref="visibilityButton">%fa:lock%</button>
<button class="visibility" @click="setVisibility" ref="visibilityButton">
<span v-if="visibility === 'public'">%fa:globe%</span>
<span v-if="visibility === 'home'">%fa:home%</span>
<span v-if="visibility === 'followers'">%fa:unlock%</span>
<span v-if="visibility === 'specified'">%fa:envelope%</span>
<span v-if="visibility === 'private'">%fa:lock%</span>
</button>
</footer>
<input ref="file" class="file" type="file" accept="image/*" multiple="multiple" @change="onChangeFile"/>
</div>

View File

@ -53,6 +53,7 @@ export type Source = {
storage: string;
bucket?: string;
prefix?: string;
baseUrl?: string;
config?: any;
};

View File

@ -1,7 +1,8 @@
import * as os from 'os';
const osUtils = require('os-utils');
import * as sysUtils from 'systeminformation';
import * as diskusage from 'diskusage';
import Xev from 'xev';
const osUtils = require('os-utils');
const ev = new Xev();
@ -18,13 +19,16 @@ export default function() {
});
async function tick() {
osUtils.cpuUsage((cpuUsage: number) => {
const cpu = await cpuUsage();
const usedmem = await usedMem();
const totalmem = await totalMem();
const disk = diskusage.checkSync(os.platform() == 'win32' ? 'c:' : '/');
const stats = {
cpu_usage: cpuUsage,
cpu_usage: cpu,
mem: {
total: os.totalmem(),
free: os.freemem()
total: totalmem,
used: usedmem
},
disk,
os_uptime: os.uptime(),
@ -33,10 +37,40 @@ export default function() {
ev.emit('serverStats', stats);
log.push(stats);
if (log.length > 50) log.shift();
});
}
tick();
setInterval(tick, interval);
}
// CPU STAT
function cpuUsage() {
return new Promise((res, rej) => {
osUtils.cpuUsage((cpuUsage: number) => {
res(cpuUsage);
});
});
}
// MEMORY(excl buffer + cache) STAT
async function usedMem() {
try {
const data = await sysUtils.mem();
return data.active;
} catch (error) {
console.error(error);
throw error;
}
}
// TOTAL MEMORY STAT
async function totalMem() {
try {
const data = await sysUtils.mem();
return data.total;
} catch (error) {
console.error(error);
throw error;
}
}

View File

@ -27,7 +27,7 @@ const nativeDbConn = async (): Promise<mongodb.Db> => {
if (mdb) return mdb;
const db = await ((): Promise<mongodb.Db> => new Promise((resolve, reject) => {
mongodb.MongoClient.connect(uri, (e: Error, client: any) => {
mongodb.MongoClient.connect(uri, { useNewUrlParser: true }, (e: Error, client: any) => {
if (e) return reject(e);
resolve(client.db(config.mongodb.db));
});

View File

@ -71,6 +71,9 @@ APIの詳しい使用法は「Misskey APIの利用」セクションをご覧く
## Misskey APIの利用
APIはすべてリクエストのパラメータ・レスポンスともにJSON形式です。また、すべてのエンドポイントはPOSTメソッドのみ受け付けます。
ストリーミングAPIも提供しています。
APIリファレンスもご確認ください。
### レートリミット

183
src/docs/stream.ja.md Normal file
View File

@ -0,0 +1,183 @@
# ストリーミングAPI
ストリーミングAPIを使うと、リアルタイムで様々な情報(例えばタイムラインに新しい投稿が流れてきた、メッセージが届いた、フォローされた、など)を受け取ったり、HTTPリクエストを発生させることなくAPIにアクセスしたりすることができます。
ストリーミングAPIは複数の種類がありますが、ここではメインとなる「ホームストリーム」について説明します。
## ストリームに接続する
以下のURLに**websocket**接続します。
```
%URL%
```
接続する際は、`i`というパラメータ名で認証情報を含めます。例:
```
%URL%/?i=xxxxxxxxxxxxxxx
```
認証情報は、自分のAPIキーや、アプリケーションからストリームに接続する際はユーザーのアクセストークンのことを指します。
<div class="ui info">
<p><i class="fas fa-info-circle"></i> 認証情報の取得については、<a href="./api">こちらのドキュメント</a>をご確認ください。</p>
</div>
## ストリームを経由してAPIリクエストする
ストリームを経由してAPIリクエストすると、HTTPリクエストを発生させずにAPIを利用できます。そのため、コードを簡潔にできたり、パフォーマンスの向上を見込めるかもしれません。
ストリームを経由してAPIリクエストするには、次のようなメッセージをストリームに送信します:
```json
{
type: 'api',
id: 'xxxxxxxxxxxxxxxx',
endpoint: 'notes/create',
data: {
text: 'yee haw!'
}
}
```
`id`には、APIのレスポンスを識別するための、APIリクエストごとの一意なIDを設定する必要があります。UUIDや、簡単な乱数のようなもので構いません。
`endpoint`には、あなたがリクエストしたいAPIのエンドポイントを指定します。
`data`には、エンドポイントのパラメータを含めます。
<div class="ui info">
<p><i class="fas fa-info-circle"></i> APIのエンドポイントやパラメータについてはAPIリファレンスをご確認ください。</p>
</div>
### レスポンスの受信
APIへリクエストすると、レスポンスがストリームから次のような形式で流れてきます。
```json
{
type: 'api-res:xxxxxxxxxxxxxxxx',
body: {
...
}
}
```
`xxxxxxxxxxxxxxxx`の部分には、リクエストの際に設定された`id`が含まれています。これにより、どのリクエストに対するレスポンスなのか判別することができます。
`body`には、レスポンスが含まれています。
## 投稿のキャプチャ
Misskeyは投稿のキャプチャと呼ばれる仕組みを提供しています。これは、指定した投稿のイベントをストリームで受け取る機能です。
例えばタイムラインを取得してユーザーに表示したとします。ここで誰かがそのタイムラインに含まれるどれかの投稿に対してリアクションしたとします。
しかし、クライアントからするとある投稿にリアクションが付いたことなどは知る由がないため、リアルタイムでリアクションをタイムライン上の投稿に反映して表示するといったことができません。
この問題を解決するために、Misskeyは投稿のキャプチャ機構を用意しています。投稿をキャプチャすると、その投稿に関するイベントを受け取ることができるため、リアルタイムでリアクションを反映させたりすることが可能になります。
### 投稿をキャプチャする
投稿をキャプチャするには、ストリームに次のようなメッセージを送信します:
```json
{
type: 'capture',
id: 'xxxxxxxxxxxxxxxx'
}
```
`id`には、キャプチャしたい投稿の`id`を設定します。
このメッセージを送信すると、Misskeyにキャプチャを要請したことになり、以後、その投稿に関するイベントが流れてくるようになります。
例えば投稿にリアクションが付いたとすると、次のようなメッセージが流れてきます:
```json
{
type: 'note-updated',
body: {
note: {
...
}
}
}
```
`body`内の`note`には、その投稿の最新の情報が含まれています。
---
このように、投稿の情報が更新されると、`note-updated`イベントが流れてくるようになります。`note-updated`イベントが発生するのは、以下の場合です:
- 投稿にリアクションが付いた
- 投稿に添付されたアンケートに投票がされた
- 投稿が削除された
### 投稿のキャプチャを解除する
その投稿がもう画面に表示されなくなったりして、その投稿に関するイベントをもう受け取る必要がなくなったときは、キャプチャの解除を申請してください。
次のメッセージを送信します:
```json
{
type: 'decapture',
id: 'xxxxxxxxxxxxxxxx'
}
```
`id`には、キャプチャを解除したい投稿の`id`を設定します。
このメッセージを送信すると、以後、その投稿に関するイベントは流れてこないようになります。
## 流れてくるイベント一覧
流れてくるすべてのメッセージはJSON形式で、必ず`type`というプロパティが含まれています。これにより、メッセージの種類(イベント)を判別することができます。
### `note`
タイムラインに新しい投稿が流れてきたときに発生するイベントです。
`body`プロパティの中に、投稿情報が含まれています。
### `renote`
自分の投稿がRenoteされた時に発生するイベントです。自分自身の投稿をRenoteしたときは発生しません。
`body`プロパティの中に、Renoteされた投稿情報が含まれています。
### `mention`
誰かからメンションされたときに発生するイベントです。
`body`プロパティの中に、投稿情報が含まれています。
### `read_all_notifications`
自分宛ての通知がすべて既読になったことを表すイベントです。このイベントを利用して、「通知があることを示すアイコン」のようなものをオフにしたりする等のケースが想定されます。
### `meUpdated`
自分の情報が更新されたことを表すイベントです。
`body`プロパティの中に、最新の自分のアカウントの情報が含まれています。
### `follow`
自分が誰かをフォローしたときに発生するイベントです。
`body`プロパティの中に、フォローしたユーザーの情報が含まれています。
### `unfollow`
自分が誰かのフォローを解除したときに発生するイベントです。
`body`プロパティの中に、フォロー解除したユーザーの情報が含まれています。
### `followed`
自分が誰かにフォローされたときに発生するイベントです。
`body`プロパティの中に、フォローしてきたユーザーの情報が含まれています。

View File

@ -36,6 +36,10 @@ main
margin 1em 0
line-height 1.6em
hr
border none
border-bottom solid 2px #eee
footer
margin 32px 0 0 0
border-top solid 2px #eee

View File

@ -31,9 +31,6 @@ if (process.env.NODE_ENV != 'production') {
process.env.DEBUG = 'misskey:*';
}
// https://github.com/Automattic/kue/issues/822
require('events').EventEmitter.prototype._maxListeners = 512;
// Start app
main();
@ -82,9 +79,6 @@ async function workerMain() {
// start server
await require('./server').default();
// start processor
require('./queue').default();
// Send a 'ready' message to parent process
process.send('ready');
}

View File

@ -1,45 +1,15 @@
import { createQueue } from 'kue';
import config from '../config';
import http from './processors/http';
import { ILocalUser } from '../models/user';
const queue = createQueue({
redis: {
port: config.redis.port,
host: config.redis.host,
auth: config.redis.pass
}
});
export function createHttp(data: any) {
return queue
.create('http', data)
.removeOnComplete(true)
.events(false)
.attempts(8)
.backoff({ delay: 16384, type: 'exponential' });
export function createHttpJob(data: any) {
return http({ data }, () => {});
}
export function deliver(user: ILocalUser, content: any, to: any) {
createHttp({
title: 'deliver',
createHttpJob({
type: 'deliver',
user,
content,
to
}).save();
}
export default function() {
/*
256 is the default concurrency limit of Mozilla Firefox and Google
Chromium.
a8af215e691f3a2205a3758d2d96e9d328e100ff - chromium/src.git - Git at Google
https://chromium.googlesource.com/chromium/src.git/+/a8af215e691f3a2205a3758d2d96e9d328e100ff
Network.http.max-connections - MozillaZine Knowledge Base
http://kb.mozillazine.org/Network.http.max-connections
*/
//queue.process('http', 256, http);
queue.process('http', 128, http);
});
}

View File

@ -1,8 +1,8 @@
import * as kue from 'kue';
import * as bq from 'bee-queue';
import request from '../../../remote/activitypub/request';
export default async (job: kue.Job, done: any): Promise<void> => {
export default async (job: bq.Job, done: any): Promise<void> => {
try {
await request(job.data.user, job.data.to, job.data.content);
done();

View File

@ -1,4 +1,4 @@
import * as kue from 'kue';
import * as bq from 'bee-queue';
import * as debug from 'debug';
const httpSignature = require('http-signature');
@ -10,7 +10,7 @@ import { resolvePerson } from '../../../remote/activitypub/models/person';
const log = debug('misskey:queue:inbox');
// ユーザーのinboxにアクティビティが届いた時の処理
export default async (job: kue.Job, done: any): Promise<void> => {
export default async (job: bq.Job, done: any): Promise<void> => {
const signature = job.data.signature;
const activity = job.data.activity;

View File

@ -14,6 +14,34 @@ import htmlToMFM from '../../../mfm/html-to-mfm';
const log = debug('misskey:activitypub');
function validatePerson(x: any) {
if (x == null) {
return new Error('invalid person: object is null');
}
if (x.type != 'Person' && x.type != 'Service') {
return new Error(`invalid person: object is not a person or service '${x.type}'`);
}
if (typeof x.preferredUsername !== 'string') {
return new Error('invalid person: preferredUsername is not a string');
}
if (typeof x.inbox !== 'string') {
return new Error('invalid person: inbox is not a string');
}
if (!validateUsername(x.preferredUsername)) {
return new Error('invalid person: invalid username');
}
if (!isValidName(x.name == '' ? null : x.name)) {
return new Error('invalid person: invalid name');
}
return null;
}
/**
* Personをフェッチします。
*
@ -47,28 +75,10 @@ export async function createPerson(value: any, resolver?: Resolver): Promise<IUs
const object = await resolver.resolve(value) as any;
if (object == null) {
throw new Error('invalid person: object is null');
}
const err = validatePerson(object);
if (object.type != 'Person' && object.type != 'Service') {
throw new Error(`invalid person: object is not a person or service '${object.type}'`);
}
if (typeof object.preferredUsername !== 'string') {
throw new Error('invalid person: preferredUsername is not a string');
}
if (typeof object.inbox !== 'string') {
throw new Error('invalid person: inbox is not a string');
}
if (!validateUsername(object.preferredUsername)) {
throw new Error('invalid person: invalid username');
}
if (!isValidName(object.name == '' ? null : object.name)) {
throw new Error('invalid person: invalid name');
if (err) {
throw err;
}
const person: IPerson = object;
@ -198,12 +208,10 @@ export async function updatePerson(value: string | IObject, resolver?: Resolver)
const object = await resolver.resolve(value) as any;
if (
object == null ||
object.type !== 'Person'
) {
log(`invalid person: ${JSON.stringify(object, null, 2)}`);
throw new Error('invalid person');
const err = validatePerson(object);
if (err) {
throw err;
}
const person: IPerson = object;

View File

@ -3,7 +3,7 @@ import * as Router from 'koa-router';
const json = require('koa-json-body');
const httpSignature = require('http-signature');
import { createHttp } from '../queue';
import { createHttpJob } from '../queue';
import pack from '../remote/activitypub/renderer';
import Note from '../models/note';
import User, { isLocalUser, ILocalUser, IUser } from '../models/user';
@ -30,11 +30,11 @@ function inbox(ctx: Router.IRouterContext) {
return;
}
createHttp({
createHttpJob({
type: 'processInbox',
activity: ctx.request.body,
signature
}).save();
});
ctx.status = 202;
}

View File

@ -52,7 +52,7 @@ export default (endpoint: string, user: IUser, app: IApp, data: any, file?: any)
const time = after - before;
if (time > 500) {
if (time > 1000) {
console.warn(`SLOW API CALL DETECTED: ${ep.name} (${ time }ms)`);
}
} catch (e) {

View File

@ -3,7 +3,6 @@ import ReversiGame, { pack } from '../../../../../models/games/reversi/game';
import { ILocalUser } from '../../../../../models/user';
export const meta = {
requireCredential: true
};
export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {

View File

@ -25,7 +25,14 @@ async function save(readable: stream.Readable, name: string, type: string, hash:
const minio = new Minio.Client(config.drive.config);
const id = uuid.v4();
const obj = `${config.drive.prefix}/${id}`;
await minio.putObject(config.drive.bucket, obj, readable);
const baseUrl = config.drive.baseUrl
|| `${ config.drive.config.secure ? 'https' : 'http' }://${ config.drive.config.endPoint }${ config.drive.config.port ? ':' + config.drive.config.port : '' }/${ config.drive.bucket }`;
await minio.putObject(config.drive.bucket, obj, readable, size, {
'Content-Type': type,
'Cache-Control': 'max-age=31536000, immutable'
});
Object.assign(metadata, {
withoutChunks: true,
@ -33,7 +40,7 @@ async function save(readable: stream.Readable, name: string, type: string, hash:
storageProps: {
id: id
},
url: `${ config.drive.config.secure ? 'https' : 'http' }://${ config.drive.config.endPoint }${ config.drive.config.port ? ':' + config.drive.config.port : '' }/${ config.drive.bucket }/${ obj }`
url: `${ baseUrl }/${ obj }`
});
const file = await DriveFile.insert({
@ -242,6 +249,7 @@ export default async function(
const calcAvg = async () => {
log('calculate average color...');
try {
const info = await (img as any).stats();
const r = Math.round(info.channels[0].mean);
@ -253,6 +261,7 @@ export default async function(
const value = info.isOpaque ? [r, g, b] : [r, g, b, 255];
properties['avgColor'] = value;
} catch (e) { }
};
propPromises = [calcWh(), calcAvg()];

View File

@ -22,7 +22,8 @@ export default async function(user: IUser, note: INote) {
text: null,
tags: [],
mediaIds: [],
poll: null
poll: null,
geo: null
}
});

View File

@ -10,8 +10,7 @@ export default async (me: mongodb.ObjectID, note: object) => {
// if watching now
const exist = await Watching.findOne({
noteId: (note as any)._id,
userId: me,
deletedAt: { $exists: false }
userId: me
});
if (exist !== null) {