Compare commits
43 Commits
Author | SHA1 | Date | |
---|---|---|---|
deee7361f0 | |||
bdcf09c618 | |||
7b5d6dcd9b | |||
0595d87759 | |||
fab0a0d6e2 | |||
3eb6b36866 | |||
50327158e2 | |||
a99756ef85 | |||
1c25dbed66 | |||
7e8c5c0c3c | |||
0b747b901c | |||
8dd5051201 | |||
f7b0fedc9d | |||
0411d0b242 | |||
3fcc793269 | |||
fd27a0efef | |||
4474a2568e | |||
9d944243a3 | |||
8ef38ebab1 | |||
f457a23eab | |||
5d1eeaf1d8 | |||
77f732c6a4 | |||
ac07f04ad8 | |||
dddd760efd | |||
0f7fbacb17 | |||
2697107770 | |||
e1e1cd0574 | |||
93786aa510 | |||
d8b9a8715b | |||
e8783b15b1 | |||
0995d5c5a2 | |||
0852045928 | |||
04de0e9a50 | |||
951b693d17 | |||
308f357c4f | |||
206ddd6d36 | |||
b4cf963bd6 | |||
77b493c9b0 | |||
95a5ff5625 | |||
190753aa99 | |||
f778696a76 | |||
ce4fb49d4c | |||
91b89b79d2 |
@ -68,6 +68,29 @@ drive:
|
|||||||
# accessKey:
|
# accessKey:
|
||||||
# secretKey:
|
# 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
|
# Below settings are optional
|
||||||
#
|
#
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
# Management guide
|
# Management guide
|
||||||
|
|
||||||
## Check the status of the job queue
|
## Check the status of the job queue
|
||||||
In the directory of Misskey:
|
coming soon
|
||||||
``` shell
|
|
||||||
node_modules/kue/bin/kue-dashboard -p 3050
|
|
||||||
```
|
|
||||||
When you access port 3050, you will see the UI.
|
|
||||||
|
|
||||||
## Mark as 'admin' user
|
## Mark as 'admin' user
|
||||||
``` shell
|
``` shell
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
# 運営ガイド
|
# 運営ガイド
|
||||||
|
|
||||||
## ジョブキューの状態を調べる
|
## ジョブキューの状態を調べる
|
||||||
Misskeyのディレクトリで:
|
coming soon
|
||||||
``` shell
|
|
||||||
node_modules/kue/bin/kue-dashboard -p 3050
|
|
||||||
```
|
|
||||||
ポート3050にアクセスするとUIが表示されます
|
|
||||||
|
|
||||||
## 管理者ユーザーを設定する
|
## 管理者ユーザーを設定する
|
||||||
``` shell
|
``` shell
|
||||||
|
@ -51,7 +51,7 @@ common:
|
|||||||
my-token-regenerated: "Twój token został wygenerowany. Zostaniesz wylogowany."
|
my-token-regenerated: "Twój token został wygenerowany. Zostaniesz wylogowany."
|
||||||
i-like-sushi: "Wolę sushi od puddingu"
|
i-like-sushi: "Wolę sushi od puddingu"
|
||||||
show-reversi-board-labels: "Pokazuj podpisy wierszy i kolumn w Reversi"
|
show-reversi-board-labels: "Pokazuj podpisy wierszy i kolumn w Reversi"
|
||||||
verified-user: "認証済みのユーザー"
|
verified-user: "Zweryfikowany użytkownik"
|
||||||
reversi:
|
reversi:
|
||||||
drawn: "Remis"
|
drawn: "Remis"
|
||||||
my-turn: "Twoja kolej"
|
my-turn: "Twoja kolej"
|
||||||
@ -289,8 +289,8 @@ desktop/views/components/drive.file.vue:
|
|||||||
banner: "Baner"
|
banner: "Baner"
|
||||||
contextmenu:
|
contextmenu:
|
||||||
rename: "Zmień nazwę"
|
rename: "Zmień nazwę"
|
||||||
mark-as-sensitive: "閲覧注意に設定"
|
mark-as-sensitive: "Oznacz jako zawartość wrażliwą"
|
||||||
unmark-as-sensitive: "閲覧注意を解除"
|
unmark-as-sensitive: "Cofnij oznaczenie jako zawartość wrażliwą"
|
||||||
copy-url: "Skopiuj adres"
|
copy-url: "Skopiuj adres"
|
||||||
download: "Pobierz"
|
download: "Pobierz"
|
||||||
else-files: "Inne"
|
else-files: "Inne"
|
||||||
@ -335,11 +335,11 @@ desktop/views/components/drive.vue:
|
|||||||
upload: "Wyślij plik"
|
upload: "Wyślij plik"
|
||||||
url-upload: "Wyślij z adresu URL"
|
url-upload: "Wyślij z adresu URL"
|
||||||
desktop/views/components/media-image.vue:
|
desktop/views/components/media-image.vue:
|
||||||
sensitive: "閲覧注意"
|
sensitive: "To jest zawartość NSFW"
|
||||||
click-to-show: "クリックして表示"
|
click-to-show: "Naciśnij aby wyświetlić"
|
||||||
desktop/views/components/media-video.vue:
|
desktop/views/components/media-video.vue:
|
||||||
sensitive: "閲覧注意"
|
sensitive: "To jest zawartość NSFW"
|
||||||
click-to-show: "クリックして表示"
|
click-to-show: "Naciśnij aby wyświetlić"
|
||||||
desktop/views/components/follow-button.vue:
|
desktop/views/components/follow-button.vue:
|
||||||
following: "Śledzisz"
|
following: "Śledzisz"
|
||||||
follow: "Śledź"
|
follow: "Śledź"
|
||||||
@ -414,8 +414,8 @@ desktop/views/components/post-form.vue:
|
|||||||
insert-a-kao: "v('ω')v"
|
insert-a-kao: "v('ω')v"
|
||||||
create-poll: "Utwórz ankietę"
|
create-poll: "Utwórz ankietę"
|
||||||
text-remain: "pozostałe znaki: {}"
|
text-remain: "pozostałe znaki: {}"
|
||||||
recent-tags: "最近"
|
recent-tags: "Ostatnie"
|
||||||
click-to-tagging: "クリックでタグ付け"
|
click-to-tagging: "Naciśnij aby oznaczyć"
|
||||||
desktop/views/components/post-form-window.vue:
|
desktop/views/components/post-form-window.vue:
|
||||||
note: "Nowy wpis"
|
note: "Nowy wpis"
|
||||||
reply: "Odpowiedz"
|
reply: "Odpowiedz"
|
||||||
@ -737,11 +737,11 @@ mobile/views/components/drive.file-detail.vue:
|
|||||||
hash: "Hash (md5)"
|
hash: "Hash (md5)"
|
||||||
exif: "EXIF"
|
exif: "EXIF"
|
||||||
mobile/views/components/media-image.vue:
|
mobile/views/components/media-image.vue:
|
||||||
sensitive: "閲覧注意"
|
sensitive: "To jest zawartość NSFW"
|
||||||
click-to-show: "クリックして表示"
|
click-to-show: "Naciśnij aby wyświetlić"
|
||||||
mobile/views/components/media-video.vue:
|
mobile/views/components/media-video.vue:
|
||||||
sensitive: "閲覧注意"
|
sensitive: "To jest zawartość NSFW"
|
||||||
click-to-show: "クリックして表示"
|
click-to-show: "Naciśnij aby wyświetlić"
|
||||||
mobile/views/components/follow-button.vue:
|
mobile/views/components/follow-button.vue:
|
||||||
following: "Śledzisz"
|
following: "Śledzisz"
|
||||||
follow: "Śledź"
|
follow: "Śledź"
|
||||||
@ -951,14 +951,14 @@ docs:
|
|||||||
properties: "Właściwości"
|
properties: "Właściwości"
|
||||||
endpoints:
|
endpoints:
|
||||||
params: "Parametry"
|
params: "Parametry"
|
||||||
no-params: "パラメータはありません"
|
no-params: "Brak parametrów."
|
||||||
res: "Odpowiedź"
|
res: "Odpowiedź"
|
||||||
require-credential: "このエンドポイントは認証情報が必須です。"
|
require-credential: "Punkt końcowy wymaga informacji o uwierzytelnieniu."
|
||||||
require-permission: "このエンドポイントは{permission}の権限を必要とします。"
|
require-permission: "Ten punkt końcowy wymaga uprawnienia {permission}."
|
||||||
has-limit: "レートリミットがあります。"
|
has-limit: "Istnieje limit częstotliwości."
|
||||||
duration-limit: "直近{duration}ミリ秒の間のこのエンドポイントへのリクエスト数の合計が{max}を超える場合はリクエストできません。"
|
duration-limit: "直近{duration}ミリ秒の間のこのエンドポイントへのリクエスト数の合計が{max}を超える場合はリクエストできません。"
|
||||||
min-interval-limit: "前回のリクエストから{interval}ミリ秒経っていない場合はリクエストできません。"
|
min-interval-limit: "Nie możesz wykonać żądania przed upłynięciem {interval} od ostatniego żądania."
|
||||||
show-src: "このエンドポイントのソースコードも閲覧できます。"
|
show-src: "Możesz zobaczyć kod źródłowy tego punktu końcowego."
|
||||||
show-src-link: "Zobacz kod na GitHubie"
|
show-src-link: "Zobacz kod na GitHubie"
|
||||||
generated: "このドキュメントはAPI定義に基づき自動生成されています。"
|
generated: "このドキュメントはAPI定義に基づき自動生成されています。"
|
||||||
props:
|
props:
|
||||||
|
11
package.json
11
package.json
@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "misskey",
|
"name": "misskey",
|
||||||
"author": "syuilo <i@syuilo.com>",
|
"author": "syuilo <i@syuilo.com>",
|
||||||
"version": "5.0.0",
|
"version": "5.4.0",
|
||||||
"clientVersion": "1.0.7553",
|
"clientVersion": "1.0.7596",
|
||||||
"codename": "nighthike",
|
"codename": "nighthike",
|
||||||
"main": "./built/index.js",
|
"main": "./built/index.js",
|
||||||
"private": true,
|
"private": true,
|
||||||
@ -55,7 +55,6 @@
|
|||||||
"@types/koa-send": "4.1.1",
|
"@types/koa-send": "4.1.1",
|
||||||
"@types/koa-views": "2.0.3",
|
"@types/koa-views": "2.0.3",
|
||||||
"@types/koa__cors": "2.2.2",
|
"@types/koa__cors": "2.2.2",
|
||||||
"@types/kue": "0.11.9",
|
|
||||||
"@types/minio": "6.0.2",
|
"@types/minio": "6.0.2",
|
||||||
"@types/mkdirp": "0.5.2",
|
"@types/mkdirp": "0.5.2",
|
||||||
"@types/mocha": "5.2.3",
|
"@types/mocha": "5.2.3",
|
||||||
@ -86,6 +85,7 @@
|
|||||||
"autosize": "4.0.2",
|
"autosize": "4.0.2",
|
||||||
"autwh": "0.1.0",
|
"autwh": "0.1.0",
|
||||||
"bcryptjs": "2.4.3",
|
"bcryptjs": "2.4.3",
|
||||||
|
"bee-queue": "1.2.2",
|
||||||
"bootstrap-vue": "2.0.0-rc.11",
|
"bootstrap-vue": "2.0.0-rc.11",
|
||||||
"cafy": "11.3.0",
|
"cafy": "11.3.0",
|
||||||
"chalk": "2.4.1",
|
"chalk": "2.4.1",
|
||||||
@ -98,7 +98,7 @@
|
|||||||
"diskusage": "0.2.4",
|
"diskusage": "0.2.4",
|
||||||
"dompurify": "1.0.5",
|
"dompurify": "1.0.5",
|
||||||
"elasticsearch": "15.1.1",
|
"elasticsearch": "15.1.1",
|
||||||
"element-ui": "2.4.4",
|
"element-ui": "2.4.5",
|
||||||
"emojilib": "2.3.0",
|
"emojilib": "2.3.0",
|
||||||
"escape-regexp": "0.0.1",
|
"escape-regexp": "0.0.1",
|
||||||
"eslint": "5.0.1",
|
"eslint": "5.0.1",
|
||||||
@ -144,7 +144,6 @@
|
|||||||
"koa-send": "5.0.0",
|
"koa-send": "5.0.0",
|
||||||
"koa-slow": "2.1.0",
|
"koa-slow": "2.1.0",
|
||||||
"koa-views": "6.1.4",
|
"koa-views": "6.1.4",
|
||||||
"kue": "0.11.6",
|
|
||||||
"loader-utils": "1.1.0",
|
"loader-utils": "1.1.0",
|
||||||
"mecab-async": "0.1.2",
|
"mecab-async": "0.1.2",
|
||||||
"minio": "6.0.0",
|
"minio": "6.0.0",
|
||||||
@ -195,7 +194,7 @@
|
|||||||
"ts-node": "7.0.0",
|
"ts-node": "7.0.0",
|
||||||
"tslint": "5.10.0",
|
"tslint": "5.10.0",
|
||||||
"typescript": "2.9.2",
|
"typescript": "2.9.2",
|
||||||
"typescript-eslint-parser": "16.0.1",
|
"typescript-eslint-parser": "17.0.0",
|
||||||
"uglify-es": "3.3.9",
|
"uglify-es": "3.3.9",
|
||||||
"url-loader": "1.0.1",
|
"url-loader": "1.0.1",
|
||||||
"uuid": "3.3.2",
|
"uuid": "3.3.2",
|
||||||
|
@ -105,7 +105,8 @@ export default Vue.extend({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
isMyTurn(): boolean {
|
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;
|
return this.turnUser.id == this.$store.state.i.id;
|
||||||
},
|
},
|
||||||
cellsStyle(): any {
|
cellsStyle(): any {
|
||||||
|
@ -67,7 +67,9 @@ export default Vue.extend({
|
|||||||
components: {
|
components: {
|
||||||
XGameroom
|
XGameroom
|
||||||
},
|
},
|
||||||
|
|
||||||
props: ['initGame'],
|
props: ['initGame'],
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
game: null,
|
game: null,
|
||||||
@ -82,54 +84,63 @@ export default Vue.extend({
|
|||||||
pingClock: null
|
pingClock: null
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
game(g) {
|
game(g) {
|
||||||
this.$emit('gamed', g);
|
this.$emit('gamed', g);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
created() {
|
created() {
|
||||||
if (this.initGame) {
|
if (this.initGame) {
|
||||||
this.game = this.initGame;
|
this.game = this.initGame;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.connection = (this as any).os.streams.reversiStream.getConnection();
|
if (this.$store.getters.isSignedIn) {
|
||||||
this.connectionId = (this as any).os.streams.reversiStream.use();
|
this.connection = (this as any).os.streams.reversiStream.getConnection();
|
||||||
|
this.connectionId = (this as any).os.streams.reversiStream.use();
|
||||||
|
|
||||||
this.connection.on('matched', this.onMatched);
|
this.connection.on('matched', this.onMatched);
|
||||||
this.connection.on('invited', this.onInvited);
|
this.connection.on('invited', this.onInvited);
|
||||||
|
|
||||||
(this as any).api('games/reversi/games', {
|
(this as any).api('games/reversi/games', {
|
||||||
my: true
|
my: true
|
||||||
}).then(games => {
|
}).then(games => {
|
||||||
this.myGames = games;
|
this.myGames = games;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
(this as any).api('games/reversi/invitations').then(invitations => {
|
||||||
|
this.invitations = this.invitations.concat(invitations);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.pingClock = setInterval(() => {
|
||||||
|
if (this.matching) {
|
||||||
|
this.connection.send({
|
||||||
|
type: 'ping',
|
||||||
|
id: this.matching.id
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
|
||||||
(this as any).api('games/reversi/games').then(games => {
|
(this as any).api('games/reversi/games').then(games => {
|
||||||
this.games = games;
|
this.games = games;
|
||||||
this.gamesFetching = false;
|
this.gamesFetching = false;
|
||||||
});
|
});
|
||||||
|
|
||||||
(this as any).api('games/reversi/invitations').then(invitations => {
|
|
||||||
this.invitations = this.invitations.concat(invitations);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.pingClock = setInterval(() => {
|
|
||||||
if (this.matching) {
|
|
||||||
this.connection.send({
|
|
||||||
type: 'ping',
|
|
||||||
id: this.matching.id
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}, 3000);
|
|
||||||
},
|
},
|
||||||
|
|
||||||
beforeDestroy() {
|
beforeDestroy() {
|
||||||
this.connection.off('matched', this.onMatched);
|
if (this.connection) {
|
||||||
this.connection.off('invited', this.onInvited);
|
this.connection.off('matched', this.onMatched);
|
||||||
(this as any).os.streams.reversiStream.dispose(this.connectionId);
|
this.connection.off('invited', this.onInvited);
|
||||||
|
(this as any).os.streams.reversiStream.dispose(this.connectionId);
|
||||||
|
|
||||||
clearInterval(this.pingClock);
|
clearInterval(this.pingClock);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
methods: {
|
methods: {
|
||||||
go(game) {
|
go(game) {
|
||||||
(this as any).api('games/reversi/games/show', {
|
(this as any).api('games/reversi/games/show', {
|
||||||
@ -139,6 +150,7 @@ export default Vue.extend({
|
|||||||
this.game = game;
|
this.game = game;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
match() {
|
match() {
|
||||||
(this as any).apis.input({
|
(this as any).apis.input({
|
||||||
title: 'ユーザー名を入力してください'
|
title: 'ユーザー名を入力してください'
|
||||||
@ -158,10 +170,12 @@ export default Vue.extend({
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
cancel() {
|
cancel() {
|
||||||
this.matching = null;
|
this.matching = null;
|
||||||
(this as any).api('games/reversi/match/cancel');
|
(this as any).api('games/reversi/match/cancel');
|
||||||
},
|
},
|
||||||
|
|
||||||
accept(invitation) {
|
accept(invitation) {
|
||||||
(this as any).api('games/reversi/match', {
|
(this as any).api('games/reversi/match', {
|
||||||
userId: invitation.parent.id
|
userId: invitation.parent.id
|
||||||
@ -172,10 +186,12 @@ export default Vue.extend({
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
onMatched(game) {
|
onMatched(game) {
|
||||||
this.matching = null;
|
this.matching = null;
|
||||||
this.game = game;
|
this.game = game;
|
||||||
},
|
},
|
||||||
|
|
||||||
onInvited(invite) {
|
onInvited(invite) {
|
||||||
this.invitations.unshift(invite);
|
this.invitations.unshift(invite);
|
||||||
}
|
}
|
||||||
|
@ -92,7 +92,7 @@ export default Vue.component('misskey-flavored-markdown', {
|
|||||||
case 'hashtag':
|
case 'hashtag':
|
||||||
return createElement('a', {
|
return createElement('a', {
|
||||||
attrs: {
|
attrs: {
|
||||||
href: `${url}/tags/${token.hashtag}`,
|
href: `${url}/tags/${encodeURIComponent(token.hashtag)}`,
|
||||||
target: '_blank'
|
target: '_blank'
|
||||||
}
|
}
|
||||||
}, token.content);
|
}, token.content);
|
||||||
|
@ -2,9 +2,11 @@
|
|||||||
<iframe v-if="youtubeId" type="text/html" height="250"
|
<iframe v-if="youtubeId" type="text/html" height="250"
|
||||||
:src="`https://www.youtube.com/embed/${youtubeId}?origin=${misskeyUrl}`"
|
:src="`https://www.youtube.com/embed/${youtubeId}?origin=${misskeyUrl}`"
|
||||||
frameborder="0"/>
|
frameborder="0"/>
|
||||||
<blockquote v-else-if="tweetUrl" class="twitter-tweet" ref="tweet">
|
<div v-else-if="tweetUrl && detail" class="twitter">
|
||||||
<a :href="url"></a>
|
<blockquote ref="tweet" class="twitter-tweet" :data-theme="$store.state.device.darkmode ? 'dark' : null">
|
||||||
</blockquote>
|
<a :href="url"></a>
|
||||||
|
</blockquote>
|
||||||
|
</div>
|
||||||
<div v-else class="mk-url-preview">
|
<div v-else class="mk-url-preview">
|
||||||
<a :href="url" target="_blank" :title="url" v-if="!fetching">
|
<a :href="url" target="_blank" :title="url" v-if="!fetching">
|
||||||
<div class="thumbnail" v-if="thumbnail" :style="`background-image: url(${thumbnail})`"></div>
|
<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';
|
import { url as misskeyUrl } from '../../../config';
|
||||||
|
|
||||||
export default Vue.extend({
|
export default Vue.extend({
|
||||||
props: ['url'],
|
props: {
|
||||||
|
url: {
|
||||||
|
type: String,
|
||||||
|
require: true
|
||||||
|
},
|
||||||
|
detail: {
|
||||||
|
type: Boolean,
|
||||||
|
required: false,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
fetching: true,
|
fetching: true,
|
||||||
@ -48,7 +60,7 @@ export default Vue.extend({
|
|||||||
this.youtubeId = url.searchParams.get('v');
|
this.youtubeId = url.searchParams.get('v');
|
||||||
} else if (url.hostname == 'youtu.be') {
|
} else if (url.hostname == 'youtu.be') {
|
||||||
this.youtubeId = url.pathname;
|
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;
|
this.tweetUrl = url;
|
||||||
const twttr = (window as any).twttr || {};
|
const twttr = (window as any).twttr || {};
|
||||||
const loadTweet = () => twttr.widgets.load(this.$refs.tweet);
|
const loadTweet = () => twttr.widgets.load(this.$refs.tweet);
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
<div>
|
<div>
|
||||||
<div v-for="stat in stats" :key="stat.tag">
|
<div v-for="stat in stats" :key="stat.tag">
|
||||||
<div class="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>
|
<p>{{ '%i18n:@count%'.replace('{}', stat.usersCount) }}</p>
|
||||||
</div>
|
</div>
|
||||||
<x-chart class="chart" :src="stat.chart"/>
|
<x-chart class="chart" :src="stat.chart"/>
|
||||||
|
@ -46,7 +46,7 @@
|
|||||||
<mk-media-list :media-list="p.media" :raw="true"/>
|
<mk-media-list :media-list="p.media" :raw="true"/>
|
||||||
</div>
|
</div>
|
||||||
<mk-poll v-if="p.poll" :note="p"/>
|
<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>
|
<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="map" v-if="p.geo" ref="map"></div>
|
||||||
<div class="renote" v-if="p.renote">
|
<div class="renote" v-if="p.renote">
|
||||||
|
@ -38,7 +38,13 @@
|
|||||||
<button class="poll" title="%i18n:@create-poll%" @click="poll = true">%fa:chart-pie%</button>
|
<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="poll" title="内容を隠す" @click="useCw = !useCw">%fa:eye-slash%</button>
|
||||||
<button class="geo" title="位置情報を添付する" @click="geo ? removeGeo() : setGeo()">%fa:map-marker-alt%</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>
|
<p class="text-count" :class="{ over: text.length > 1000 }">{{ 1000 - text.length }}</p>
|
||||||
<button :class="{ posting }" class="submit" :disabled="!canPost" @click="post">
|
<button :class="{ posting }" class="submit" :disabled="!canPost" @click="post">
|
||||||
{{ posting ? '%i18n:@posting%' : submitText }}<mk-ellipsis v-if="posting"/>
|
{{ posting ? '%i18n:@posting%' : submitText }}<mk-ellipsis v-if="posting"/>
|
||||||
|
@ -84,12 +84,11 @@ export default Vue.extend({
|
|||||||
(this as any).os.new(MkGameWindow);
|
(this as any).os.new(MkGameWindow);
|
||||||
},
|
},
|
||||||
|
|
||||||
goToTop(e: HTMLElement) {
|
goToTop() {
|
||||||
if (e.classList.contains('active'))
|
window.scrollTo({
|
||||||
window.scrollTo({
|
top: 0,
|
||||||
top: 0,
|
behavior: 'smooth'
|
||||||
behavior: 'smooth'
|
});
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -45,14 +45,7 @@ export default Vue.extend({
|
|||||||
XPost,
|
XPost,
|
||||||
XClock,
|
XClock,
|
||||||
},
|
},
|
||||||
methods: {
|
|
||||||
goToTop() {
|
|
||||||
window.scrollTo({
|
|
||||||
top: 0,
|
|
||||||
behavior: 'smooth'
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$store.commit('setUiHeaderHeight', 48);
|
this.$store.commit('setUiHeaderHeight', 48);
|
||||||
|
|
||||||
@ -104,7 +97,16 @@ export default Vue.extend({
|
|||||||
}, 2500);
|
}, 2500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
|
methods: {
|
||||||
|
goToTop() {
|
||||||
|
window.scrollTo({
|
||||||
|
top: 0,
|
||||||
|
behavior: 'smooth'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
@ -19,7 +19,7 @@
|
|||||||
<p>%i18n:@followers%</p><a>{{ u.followersCount }}</a>
|
<p>%i18n:@followers%</p><a>{{ u.followersCount }}</a>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="profile">
|
<div class="profile" v-if="$store.getters.isSignedIn">
|
||||||
<div class="friend-form" v-if="$store.getters.isSignedIn && $store.state.i.id != user.id">
|
<div class="friend-form" v-if="$store.state.i.id != user.id">
|
||||||
<mk-follow-button :user="user" size="big"/>
|
<mk-follow-button :user="user" size="big"/>
|
||||||
<p class="followed" v-if="user.isFollowed">%i18n:@follows-you%</p>
|
<p class="followed" v-if="user.isFollowed">%i18n:@follows-you%</p>
|
||||||
<p class="stalk" v-if="user.isFollowing">
|
<p class="stalk" v-if="user.isFollowing">
|
||||||
@ -9,7 +9,7 @@
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="action-form">
|
<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% %i18n:@unmute%</span>
|
||||||
<span v-if="!user.isMuted">%fa:eye-slash% %i18n:@mute%</span>
|
<span v-if="!user.isMuted">%fa:eye-slash% %i18n:@mute%</span>
|
||||||
</button>
|
</button>
|
||||||
|
@ -44,7 +44,7 @@
|
|||||||
<mk-media-list :media-list="p.media" :raw="true"/>
|
<mk-media-list :media-list="p.media" :raw="true"/>
|
||||||
</div>
|
</div>
|
||||||
<mk-poll v-if="p.poll" :note="p"/>
|
<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>
|
<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="map" v-if="p.geo" ref="map"></div>
|
||||||
<div class="renote" v-if="p.renote">
|
<div class="renote" v-if="p.renote">
|
||||||
|
@ -53,6 +53,7 @@ export type Source = {
|
|||||||
storage: string;
|
storage: string;
|
||||||
bucket?: string;
|
bucket?: string;
|
||||||
prefix?: string;
|
prefix?: string;
|
||||||
|
baseUrl?: string;
|
||||||
config?: any;
|
config?: any;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ const nativeDbConn = async (): Promise<mongodb.Db> => {
|
|||||||
if (mdb) return mdb;
|
if (mdb) return mdb;
|
||||||
|
|
||||||
const db = await ((): Promise<mongodb.Db> => new Promise((resolve, reject) => {
|
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);
|
if (e) return reject(e);
|
||||||
resolve(client.db(config.mongodb.db));
|
resolve(client.db(config.mongodb.db));
|
||||||
});
|
});
|
||||||
|
127
src/docs/stream.ja.md
Normal file
127
src/docs/stream.ja.md
Normal file
@ -0,0 +1,127 @@
|
|||||||
|
# ストリーミングAPI
|
||||||
|
|
||||||
|
ストリーミングAPIを使うと、リアルタイムで様々な情報(例えばタイムラインに新しい投稿が流れてきた、メッセージが届いた、フォローされた、など)を受け取ったり、HTTPリクエストを発生させることなくAPIにアクセスしたりすることができます。
|
||||||
|
|
||||||
|
ストリーミングAPIは複数の種類がありますが、ここではメインとなる「ホームストリーム」について説明します。
|
||||||
|
|
||||||
|
## ストリームに接続する
|
||||||
|
|
||||||
|
以下のURLに**websocket**接続します。
|
||||||
|
```
|
||||||
|
%URL%
|
||||||
|
```
|
||||||
|
|
||||||
|
接続する際は、`i`というパラメータ名で認証情報を含めます。例:
|
||||||
|
```
|
||||||
|
%URL%/?i=xxxxxxxxxxxxxxx
|
||||||
|
```
|
||||||
|
|
||||||
|
## ストリームを経由して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`には、その投稿の最新の情報が含まれています。
|
||||||
|
|
||||||
|
### 投稿のキャプチャを解除する
|
||||||
|
|
||||||
|
その投稿がもう画面に表示されなくなったりして、その投稿に関するイベントをもう受け取る必要がなくなったときは、キャプチャの解除を申請してください。
|
||||||
|
|
||||||
|
次のメッセージを送信します:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
type: 'decapture',
|
||||||
|
id: 'xxxxxxxxxxxxxxxx'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
`id`には、キャプチャを解除したい投稿の`id`を設定します。
|
||||||
|
|
||||||
|
このメッセージを送信すると、以後、その投稿に関するイベントは流れてこないようになります。
|
||||||
|
|
||||||
|
## 流れてくるイベント一覧
|
||||||
|
|
||||||
|
流れてくるすべてのメッセージはJSON形式で、必ず`type`というプロパティが含まれています。これにより、メッセージの種類(イベント)を判別することができます。
|
||||||
|
|
||||||
|
### `note`
|
||||||
|
|
||||||
|
タイムラインに新しい投稿が流れてきたときに発生するイベントです。
|
||||||
|
|
||||||
|
`body`プロパティの中に、投稿情報が含まれています。
|
@ -31,9 +31,6 @@ if (process.env.NODE_ENV != 'production') {
|
|||||||
process.env.DEBUG = 'misskey:*';
|
process.env.DEBUG = 'misskey:*';
|
||||||
}
|
}
|
||||||
|
|
||||||
// https://github.com/Automattic/kue/issues/822
|
|
||||||
require('events').EventEmitter.prototype._maxListeners = 512;
|
|
||||||
|
|
||||||
// Start app
|
// Start app
|
||||||
main();
|
main();
|
||||||
|
|
||||||
|
@ -1,45 +1,39 @@
|
|||||||
import { createQueue } from 'kue';
|
import * as Queue from 'bee-queue';
|
||||||
|
|
||||||
import config from '../config';
|
import config from '../config';
|
||||||
import http from './processors/http';
|
import http from './processors/http';
|
||||||
import { ILocalUser } from '../models/user';
|
import { ILocalUser } from '../models/user';
|
||||||
|
|
||||||
const queue = createQueue({
|
const queue = new Queue('misskey', {
|
||||||
redis: {
|
redis: {
|
||||||
port: config.redis.port,
|
port: config.redis.port,
|
||||||
host: config.redis.host,
|
host: config.redis.host,
|
||||||
auth: config.redis.pass
|
password: config.redis.pass
|
||||||
}
|
},
|
||||||
|
|
||||||
|
removeOnSuccess: true,
|
||||||
|
removeOnFailure: true,
|
||||||
|
getEvents: false,
|
||||||
|
sendEvents: false,
|
||||||
|
storeJobs: false
|
||||||
});
|
});
|
||||||
|
|
||||||
export function createHttp(data: any) {
|
export function createHttpJob(data: any) {
|
||||||
return queue
|
return queue.createJob(data)
|
||||||
.create('http', data)
|
//.retries(4)
|
||||||
.removeOnComplete(true)
|
//.backoff('exponential', 16384) // 16s
|
||||||
.events(false)
|
.save();
|
||||||
.attempts(8)
|
|
||||||
.backoff({ delay: 16384, type: 'exponential' });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function deliver(user: ILocalUser, content: any, to: any) {
|
export function deliver(user: ILocalUser, content: any, to: any) {
|
||||||
createHttp({
|
createHttpJob({
|
||||||
title: 'deliver',
|
|
||||||
type: 'deliver',
|
type: 'deliver',
|
||||||
user,
|
user,
|
||||||
content,
|
content,
|
||||||
to
|
to
|
||||||
}).save();
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function() {
|
export default function() {
|
||||||
/*
|
queue.process(128, http);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import * as kue from 'kue';
|
import * as bq from 'bee-queue';
|
||||||
|
|
||||||
import request from '../../../remote/activitypub/request';
|
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 {
|
try {
|
||||||
await request(job.data.user, job.data.to, job.data.content);
|
await request(job.data.user, job.data.to, job.data.content);
|
||||||
done();
|
done();
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import * as kue from 'kue';
|
import * as bq from 'bee-queue';
|
||||||
import * as debug from 'debug';
|
import * as debug from 'debug';
|
||||||
|
|
||||||
const httpSignature = require('http-signature');
|
const httpSignature = require('http-signature');
|
||||||
@ -10,7 +10,7 @@ import { resolvePerson } from '../../../remote/activitypub/models/person';
|
|||||||
const log = debug('misskey:queue:inbox');
|
const log = debug('misskey:queue:inbox');
|
||||||
|
|
||||||
// ユーザーの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 signature = job.data.signature;
|
||||||
const activity = job.data.activity;
|
const activity = job.data.activity;
|
||||||
|
|
||||||
|
@ -14,6 +14,34 @@ import htmlToMFM from '../../../mfm/html-to-mfm';
|
|||||||
|
|
||||||
const log = debug('misskey:activitypub');
|
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をフェッチします。
|
* Personをフェッチします。
|
||||||
*
|
*
|
||||||
@ -47,28 +75,10 @@ export async function createPerson(value: any, resolver?: Resolver): Promise<IUs
|
|||||||
|
|
||||||
const object = await resolver.resolve(value) as any;
|
const object = await resolver.resolve(value) as any;
|
||||||
|
|
||||||
if (object == null) {
|
const err = validatePerson(object);
|
||||||
throw new Error('invalid person: object is null');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (object.type != 'Person' && object.type != 'Service') {
|
if (err) {
|
||||||
throw new Error(`invalid person: object is not a person or service '${object.type}'`);
|
throw err;
|
||||||
}
|
|
||||||
|
|
||||||
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');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const person: IPerson = object;
|
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;
|
const object = await resolver.resolve(value) as any;
|
||||||
|
|
||||||
if (
|
const err = validatePerson(object);
|
||||||
object == null ||
|
|
||||||
object.type !== 'Person'
|
if (err) {
|
||||||
) {
|
throw err;
|
||||||
log(`invalid person: ${JSON.stringify(object, null, 2)}`);
|
|
||||||
throw new Error('invalid person');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const person: IPerson = object;
|
const person: IPerson = object;
|
||||||
|
@ -3,7 +3,7 @@ import * as Router from 'koa-router';
|
|||||||
const json = require('koa-json-body');
|
const json = require('koa-json-body');
|
||||||
const httpSignature = require('http-signature');
|
const httpSignature = require('http-signature');
|
||||||
|
|
||||||
import { createHttp } from '../queue';
|
import { createHttpJob } from '../queue';
|
||||||
import pack from '../remote/activitypub/renderer';
|
import pack from '../remote/activitypub/renderer';
|
||||||
import Note from '../models/note';
|
import Note from '../models/note';
|
||||||
import User, { isLocalUser, ILocalUser, IUser } from '../models/user';
|
import User, { isLocalUser, ILocalUser, IUser } from '../models/user';
|
||||||
@ -30,11 +30,11 @@ function inbox(ctx: Router.IRouterContext) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
createHttp({
|
createHttpJob({
|
||||||
type: 'processInbox',
|
type: 'processInbox',
|
||||||
activity: ctx.request.body,
|
activity: ctx.request.body,
|
||||||
signature
|
signature
|
||||||
}).save();
|
});
|
||||||
|
|
||||||
ctx.status = 202;
|
ctx.status = 202;
|
||||||
}
|
}
|
||||||
|
@ -52,7 +52,7 @@ export default (endpoint: string, user: IUser, app: IApp, data: any, file?: any)
|
|||||||
|
|
||||||
const time = after - before;
|
const time = after - before;
|
||||||
|
|
||||||
if (time > 500) {
|
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) {
|
} catch (e) {
|
||||||
|
@ -3,7 +3,6 @@ import ReversiGame, { pack } from '../../../../../models/games/reversi/game';
|
|||||||
import { ILocalUser } from '../../../../../models/user';
|
import { ILocalUser } from '../../../../../models/user';
|
||||||
|
|
||||||
export const meta = {
|
export const meta = {
|
||||||
requireCredential: true
|
|
||||||
};
|
};
|
||||||
|
|
||||||
export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
|
export default (params: any, user: ILocalUser) => new Promise(async (res, rej) => {
|
||||||
|
@ -25,7 +25,14 @@ async function save(readable: stream.Readable, name: string, type: string, hash:
|
|||||||
const minio = new Minio.Client(config.drive.config);
|
const minio = new Minio.Client(config.drive.config);
|
||||||
const id = uuid.v4();
|
const id = uuid.v4();
|
||||||
const obj = `${config.drive.prefix}/${id}`;
|
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, {
|
Object.assign(metadata, {
|
||||||
withoutChunks: true,
|
withoutChunks: true,
|
||||||
@ -33,7 +40,7 @@ async function save(readable: stream.Readable, name: string, type: string, hash:
|
|||||||
storageProps: {
|
storageProps: {
|
||||||
id: id
|
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({
|
const file = await DriveFile.insert({
|
||||||
@ -242,17 +249,19 @@ export default async function(
|
|||||||
const calcAvg = async () => {
|
const calcAvg = async () => {
|
||||||
log('calculate average color...');
|
log('calculate average color...');
|
||||||
|
|
||||||
const info = await (img as any).stats();
|
try {
|
||||||
|
const info = await (img as any).stats();
|
||||||
|
|
||||||
const r = Math.round(info.channels[0].mean);
|
const r = Math.round(info.channels[0].mean);
|
||||||
const g = Math.round(info.channels[1].mean);
|
const g = Math.round(info.channels[1].mean);
|
||||||
const b = Math.round(info.channels[2].mean);
|
const b = Math.round(info.channels[2].mean);
|
||||||
|
|
||||||
log(`average color is calculated: ${r}, ${g}, ${b}`);
|
log(`average color is calculated: ${r}, ${g}, ${b}`);
|
||||||
|
|
||||||
const value = info.isOpaque ? [r, g, b] : [r, g, b, 255];
|
const value = info.isOpaque ? [r, g, b] : [r, g, b, 255];
|
||||||
|
|
||||||
properties['avgColor'] = value;
|
properties['avgColor'] = value;
|
||||||
|
} catch (e) { }
|
||||||
};
|
};
|
||||||
|
|
||||||
propPromises = [calcWh(), calcAvg()];
|
propPromises = [calcWh(), calcAvg()];
|
||||||
|
@ -22,7 +22,8 @@ export default async function(user: IUser, note: INote) {
|
|||||||
text: null,
|
text: null,
|
||||||
tags: [],
|
tags: [],
|
||||||
mediaIds: [],
|
mediaIds: [],
|
||||||
poll: null
|
poll: null,
|
||||||
|
geo: null
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user