Compare commits

..

29 Commits

Author SHA1 Message Date
syuilo
c178cfabfa 10.42.0 2018-11-06 15:52:28 +09:00
syuilo
260e4c955d 🎨 2018-11-06 15:51:18 +09:00
syuilo
0c46f5ce70 Clean up 2018-11-06 15:51:05 +09:00
syuilo
6d67cd07a0 [Client] Use dynamic import to reduce bundle size 2018-11-06 15:37:41 +09:00
syuilo
fb8af53751 [Client] Improve usability & Refactoring 2018-11-06 15:08:22 +09:00
syuilo
37999f4af7 [API] Implement notes/watching/ 2018-11-06 14:58:20 +09:00
MeiMei
3b6ab327c1 Twemojiで合字に対応 (#3140)
* Twemojiで合字に対応

* split emoji regex
2018-11-06 14:09:40 +09:00
syuilo
d3ff3a7d54 10.41.0 2018-11-06 08:06:08 +09:00
syuilo
cf36106520 🎨 2018-11-06 08:04:34 +09:00
syuilo
1642fbec31 [Client] カスタム絵文字サジェストの結果をアルファベット順にソートするように 2018-11-06 08:02:19 +09:00
syuilo
b195fd8145 🎨 2018-11-06 07:57:16 +09:00
MeiMei
5f59b980a7 Fix: download file (#3138)
* Fix: url download

* not explicitly close on end

* resolve on stream finish

* remove unnecessary code

* reject on file error
2018-11-06 07:53:03 +09:00
syuilo
2a5c19cd01 リモートのファイルをキャッシュするかどうかの設定をDBに保存するように 2018-11-06 07:52:13 +09:00
syuilo
42e007ddb7 🎨 2018-11-06 07:28:49 +09:00
syuilo
756dc397d9 🎨 2018-11-06 07:22:39 +09:00
syuilo
8f714b5b12 ドライブ容量の設定をDBに保存するようにしたりリファクタリングしたり 2018-11-06 07:14:43 +09:00
syuilo
06bb2a1c7c Clean up 2018-11-06 06:25:35 +09:00
syuilo
ac50bb9225 Resolve #3137 2018-11-06 06:24:31 +09:00
syuilo
8fd95de25b 整理 2018-11-06 06:12:51 +09:00
dependabot[bot]
0e14b2eba4 Update file-type requirement from 10.3.0 to 10.4.0 (#3135)
Updates the requirements on [file-type](https://github.com/sindresorhus/file-type) to permit the latest version.
- [Release notes](https://github.com/sindresorhus/file-type/releases)
- [Commits](https://github.com/sindresorhus/file-type/commits/v10.4.0)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2018-11-06 06:08:41 +09:00
dependabot[bot]
08413a7550 Update webpack requirement from 4.23.1 to 4.25.1 (#3136)
Updates the requirements on [webpack](https://github.com/webpack/webpack) to permit the latest version.
- [Release notes](https://github.com/webpack/webpack/releases)
- [Commits](https://github.com/webpack/webpack/commits/v4.25.1)

Signed-off-by: dependabot[bot] <support@dependabot.com>
2018-11-06 05:35:53 +09:00
syuilo
5e0f2a5b06 [Client] Fix bug 2018-11-06 04:10:30 +09:00
syuilo
3b505709c6 10.40.1 2018-11-06 04:02:04 +09:00
syuilo
af32d1f81e [Client] Fix bug 2018-11-06 04:01:22 +09:00
syuilo
67d8773e38 [Client] Fix bug 2018-11-06 03:59:58 +09:00
syuilo
e445d39c2f [Client] Use v-if instaed of v-show 2018-11-06 03:59:02 +09:00
syuilo
961ed969db メッセージでのカスタム絵文字対応 2018-11-06 03:57:02 +09:00
syuilo
e9a3495225 Resolve #3132 2018-11-06 03:48:23 +09:00
Aya Morisawa
6c5a78aeb2 Fix #3133 (#3134) 2018-11-06 03:31:16 +09:00
52 changed files with 462 additions and 262 deletions

View File

@@ -43,8 +43,8 @@ jobs:
- run:
name: Configure
command: |
cp .ci/default.yml .config
cp .ci/test.yml .config
cp .circleci/misskey/default.yml .config
cp .circleci/misskey/test.yml .config
- run:
name: Build
command: |

View File

@@ -57,21 +57,6 @@ mongodb:
user: example-misskey-user
pass: example-misskey-pass
# Drive capacity of a local user (MB)
localDriveCapacityMb: 256
# Drive capacity of a remote user (MB)
remoteDriveCapacityMb: 8
# If enabled:
# Server will not cache remote files (Using direct link instead).
# You can save your storage.
#
# NOTE:
# * Users cannot see remote images when they turn off "Show media from a remote server" setting.
# * Since thumbnails are not provided, traffic increases.
preventCacheRemoteFiles: false
drive:
storage: 'db'
@@ -110,6 +95,10 @@ drive:
# accessKey: XXX
# secretKey: YYY
# If enabled:
# The first account created is automatically marked as Admin.
autoAdmin: true
#
# Below settings are optional
#

View File

@@ -947,6 +947,7 @@ common/views/components/api-settings.vue:
title: 'APIコンソール'
endpoint: 'エンドポイント'
parameter: 'パラメータ'
credential-info: "「i」パラメータは自動で付与されます。"
send: '送信'
sending: '応答待ち'
response: '結果'
@@ -1078,6 +1079,12 @@ admin/views/instance.vue:
instance-name: "インスタンス名"
instance-description: "インスタンスの紹介"
banner-url: "バナー画像URL"
drive-config: "ドライブの設定"
cache-remote-files: "リモートのファイルをキャッシュする"
cache-remote-files-desc: "この設定を無効にすると、リモートファイルをキャッシュせず直リンクするようになります。そのためサーバーのストレージを節約できますが、プライバシー設定で直リンクを無効にしているユーザーにはファイルが見えなくなったり、サムネイルが生成されないので通信量が増加します。通常はこの設定をオンにしておくことをおすすめします。"
local-drive-capacity-mb: "ローカルユーザーひとりあたりのドライブ容量"
remote-drive-capacity-mb: "リモートユーザーひとりあたりのドライブ容量"
mb: "メガバイト単位"
max-note-text-length: "投稿の最大文字数"
disable-registration: "ユーザー登録の受付を停止する"
disable-local-timeline: "ローカルタイムラインを無効にする"

View File

@@ -1,8 +1,8 @@
{
"name": "misskey",
"author": "syuilo <i@syuilo.com>",
"version": "10.40.0",
"clientVersion": "1.0.11572",
"version": "10.42.0",
"clientVersion": "1.0.11601",
"codename": "nighthike",
"main": "./built/index.js",
"private": true,
@@ -113,7 +113,7 @@
"eslint-plugin-vue": "4.7.1",
"eventemitter3": "3.1.0",
"file-loader": "2.0.0",
"file-type": "10.3.0",
"file-type": "10.4.0",
"fuckadblock": "3.2.1",
"gulp": "3.9.1",
"gulp-cssnano": "2.1.3",
@@ -228,7 +228,7 @@
"vuex-persistedstate": "2.5.4",
"web-push": "3.3.3",
"webfinger.js": "2.6.6",
"webpack": "4.23.1",
"webpack": "4.25.1",
"webpack-cli": "3.1.2",
"websocket": "1.0.28",
"ws": "6.1.0",

View File

@@ -10,8 +10,8 @@
<span>%i18n:@text%</span>
</ui-textarea>
<ui-horizon-group>
<ui-button @click="save()">%fa:save R% %i18n:@save%</ui-button>
<ui-button @click="remove(i)">%fa:trash-alt R% %i18n:@remove%</ui-button>
<ui-button @click="save()"><fa :icon="['far', 'save']"/> %i18n:@save%</ui-button>
<ui-button @click="remove(i)"><fa :icon="['far', 'trash-alt']"/> %i18n:@remove%</ui-button>
</ui-horizon-group>
</section>
<section>

View File

@@ -274,12 +274,15 @@ export default Vue.extend({
return {
series: [{
name: 'Combined',
type: 'line',
data: this.format(sum(this.stats.notes.local.total, this.stats.notes.remote.total))
}, {
name: 'Local',
type: 'area',
data: this.format(this.stats.notes.local.total)
}, {
name: 'Remote',
type: 'area',
data: this.format(this.stats.notes.remote.total)
}]
};
@@ -289,18 +292,21 @@ export default Vue.extend({
return {
series: [{
name: 'Combined',
type: 'line',
data: this.format(total
? sum(this.stats.users.local.total, this.stats.users.remote.total)
: sum(this.stats.users.local.inc, negate(this.stats.users.local.dec), this.stats.users.remote.inc, negate(this.stats.users.remote.dec))
)
}, {
name: 'Local',
type: 'area',
data: this.format(total
? this.stats.users.local.total
: sum(this.stats.users.local.inc, negate(this.stats.users.local.dec))
)
}, {
name: 'Remote',
type: 'area',
data: this.format(total
? this.stats.users.remote.total
: sum(this.stats.users.remote.inc, negate(this.stats.users.remote.dec))
@@ -314,6 +320,7 @@ export default Vue.extend({
bytes: true,
series: [{
name: 'All',
type: 'line',
data: this.format(
sum(
this.stats.drive.local.incSize,
@@ -324,15 +331,19 @@ export default Vue.extend({
)
}, {
name: 'Local +',
type: 'area',
data: this.format(this.stats.drive.local.incSize)
}, {
name: 'Local -',
type: 'area',
data: this.format(negate(this.stats.drive.local.decSize))
}, {
name: 'Remote +',
type: 'area',
data: this.format(this.stats.drive.remote.incSize)
}, {
name: 'Remote -',
type: 'area',
data: this.format(negate(this.stats.drive.remote.decSize))
}]
};
@@ -343,12 +354,15 @@ export default Vue.extend({
bytes: true,
series: [{
name: 'Combined',
type: 'line',
data: this.format(sum(this.stats.drive.local.totalSize, this.stats.drive.remote.totalSize))
}, {
name: 'Local',
type: 'area',
data: this.format(this.stats.drive.local.totalSize)
}, {
name: 'Remote',
type: 'area',
data: this.format(this.stats.drive.remote.totalSize)
}]
};
@@ -358,6 +372,7 @@ export default Vue.extend({
return {
series: [{
name: 'All',
type: 'line',
data: this.format(
sum(
this.stats.drive.local.incCount,
@@ -368,15 +383,19 @@ export default Vue.extend({
)
}, {
name: 'Local +',
type: 'area',
data: this.format(this.stats.drive.local.incCount)
}, {
name: 'Local -',
type: 'area',
data: this.format(negate(this.stats.drive.local.decCount))
}, {
name: 'Remote +',
type: 'area',
data: this.format(this.stats.drive.remote.incCount)
}, {
name: 'Remote -',
type: 'area',
data: this.format(negate(this.stats.drive.remote.decCount))
}]
};
@@ -386,12 +405,15 @@ export default Vue.extend({
return {
series: [{
name: 'Combined',
type: 'line',
data: this.format(sum(this.stats.drive.local.totalCount, this.stats.drive.remote.totalCount))
}, {
name: 'Local',
type: 'area',
data: this.format(this.stats.drive.local.totalCount)
}, {
name: 'Remote',
type: 'area',
data: this.format(this.stats.drive.remote.totalCount)
}]
};

View File

@@ -6,14 +6,15 @@
<ui-horizon-group inputs>
<ui-input v-model="name">
<span>%i18n:@add-emoji.name%</span>
<span slot="text">%i18n:@add-emoji.name-desc%</span>
<span slot="desc">%i18n:@add-emoji.name-desc%</span>
</ui-input>
<ui-input v-model="aliases">
<span>%i18n:@add-emoji.aliases%</span>
<span slot="text">%i18n:@add-emoji.aliases-desc%</span>
<span slot="desc">%i18n:@add-emoji.aliases-desc%</span>
</ui-input>
</ui-horizon-group>
<ui-input v-model="url">
<i slot="icon"><fa icon="link"/></i>
<span>%i18n:@add-emoji.url%</span>
</ui-input>
<ui-info>%i18n:@add-emoji.info%</ui-info>
@@ -34,6 +35,7 @@
</ui-input>
</ui-horizon-group>
<ui-input v-model="emoji.url">
<i slot="icon"><fa icon="link"/></i>
<span>%i18n:@add-emoji.url%</span>
</ui-input>
<ui-horizon-group>

View File

@@ -21,7 +21,7 @@
<li @click="nav('dashboard')" :class="{ active: page == 'dashboard' }"><fa icon="home" fixed-width/>%i18n:@dashboard%</li>
<li @click="nav('instance')" :class="{ active: page == 'instance' }"><fa icon="cog" fixed-width/>%i18n:@instance%</li>
<li @click="nav('users')" :class="{ active: page == 'users' }"><fa icon="users" fixed-width/>%i18n:@users%</li>
<li @click="nav('emoji')" :class="{ active: page == 'emoji' }"><fa icon="grin R" fixed-width/>%i18n:@emoji%</li>
<li @click="nav('emoji')" :class="{ active: page == 'emoji' }"><fa :icon="['far', 'grin']" fixed-width/>%i18n:@emoji%</li>
<li @click="nav('announcements')" :class="{ active: page == 'announcements' }"><fa icon="broadcast-tower" fixed-width/>%i18n:@announcements%</li>
<li @click="nav('hashtags')" :class="{ active: page == 'hashtags' }"><fa icon="hashtag" fixed-width/>%i18n:@hashtags%</li>
@@ -36,12 +36,12 @@
</div>
</nav>
<main>
<div v-show="page == 'dashboard'"><x-dashboard/></div>
<div v-show="page == 'instance'"><x-instance/></div>
<div v-if="page == 'dashboard'"><x-dashboard/></div>
<div v-if="page == 'instance'"><x-instance/></div>
<div v-if="page == 'users'"><x-users/></div>
<div v-show="page == 'emoji'"><x-emoji/></div>
<div v-show="page == 'announcements'"><x-announcements/></div>
<div v-show="page == 'hashtags'"><x-hashtags/></div>
<div v-if="page == 'emoji'"><x-emoji/></div>
<div v-if="page == 'announcements'"><x-announcements/></div>
<div v-if="page == 'hashtags'"><x-hashtags/></div>
<div v-if="page == 'drive'"></div>
<div v-if="page == 'update'"></div>
</main>

View File

@@ -1,12 +1,22 @@
<template>
<div class="axbwjelsbymowqjyywpirzhdlszoncqs">
<ui-card>
<div slot="title">%fa:cog% %i18n:@instance%</div>
<section class="fit-top">
<div slot="title"><fa icon="cog"/> %i18n:@instance%</div>
<section class="fit-top fit-bottom">
<ui-input v-model="name">%i18n:@instance-name%</ui-input>
<ui-textarea v-model="description">%i18n:@instance-description%</ui-textarea>
<ui-input v-model="bannerUrl">%i18n:@banner-url%</ui-input>
<ui-input v-model="bannerUrl"><i slot="icon"><fa icon="link"/></i>%i18n:@banner-url%</ui-input>
</section>
<section class="fit-top fit-bottom">
<ui-input v-model="maxNoteTextLength">%i18n:@max-note-text-length%</ui-input>
</section>
<section class="fit-bottom">
<header><fa icon="cloud"/> %i18n:@drive-config%</header>
<ui-switch v-model="cacheRemoteFiles">%i18n:@cache-remote-files%<span slot="desc">%i18n:@cache-remote-files-desc%</span></ui-switch>
<ui-input v-model="localDriveCapacityMb">%i18n:@local-drive-capacity-mb%<span slot="desc">%i18n:@mb%</span><span slot="suffix">MB</span></ui-input>
<ui-input v-model="remoteDriveCapacityMb" :disabled="!cacheRemoteFiles">%i18n:@remote-drive-capacity-mb%<span slot="desc">%i18n:@mb%</span><span slot="suffix">MB</span></ui-input>
</section>
<section>
<ui-button @click="updateMeta">%i18n:@save%</ui-button>
</section>
</ui-card>
@@ -40,6 +50,9 @@ export default Vue.extend({
bannerUrl: null,
name: null,
description: null,
cacheRemoteFiles: false,
localDriveCapacityMb: null,
remoteDriveCapacityMb: null,
maxNoteTextLength: null,
inviteCode: null,
};
@@ -50,6 +63,9 @@ export default Vue.extend({
this.bannerUrl = meta.bannerUrl;
this.name = meta.name;
this.description = meta.description;
this.cacheRemoteFiles = meta.cacheRemoteFiles;
this.localDriveCapacityMb = meta.driveCapacityPerLocalUserMb;
this.remoteDriveCapacityMb = meta.driveCapacityPerRemoteUserMb;
this.maxNoteTextLength = meta.maxNoteTextLength;
});
},
@@ -73,6 +89,9 @@ export default Vue.extend({
bannerUrl: this.bannerUrl,
name: this.name,
description: this.description,
cacheRemoteFiles: this.cacheRemoteFiles,
localDriveCapacityMb: parseInt(this.localDriveCapacityMb, 10),
remoteDriveCapacityMb: parseInt(this.remoteDriveCapacityMb, 10),
maxNoteTextLength: parseInt(this.maxNoteTextLength, 10)
}).then(() => {
this.$swal({

View File

@@ -19,6 +19,7 @@
</ui-input>
<ui-textarea v-model="body">
<span>%i18n:@console.parameter% (JSON or JSON5)</span>
<span slot="desc">%i18n:@console.credential-info%</span>
</ui-textarea>
<ui-button @click="send" :disabled="sending">
<template v-if="sending">%i18n:@console.sending%</template>

View File

@@ -41,11 +41,17 @@ const lib = Object.entries(emojilib.lib).filter((x: any) => {
return x[1].category != 'flags';
});
const char2file = (char: string) => {
let codes = [...char].map(x => x.codePointAt(0).toString(16));
if (!codes.includes('200d')) codes = codes.filter(x => x != 'fe0f');
return codes.join('-');
};
const emjdb: EmojiDef[] = lib.map((x: any) => ({
emoji: x[1].char,
name: x[0],
aliasOf: null,
url: `https://twemoji.maxcdn.com/2/svg/${x[1].char.codePointAt(0).toString(16)}.svg`
url: `https://twemoji.maxcdn.com/2/svg/${char2file(x[1].char)}.svg`
}));
lib.forEach((x: any) => {
@@ -55,7 +61,7 @@ lib.forEach((x: any) => {
emoji: x[1].char,
name: k,
aliasOf: x[0],
url: `https://twemoji.maxcdn.com/2/svg/${x[1].char.codePointAt(0).toString(16)}.svg`
url: `https://twemoji.maxcdn.com/2/svg/${char2file(x[1].char)}.svg`
});
});
}
@@ -216,7 +222,11 @@ export default Vue.extend({
}
} else if (this.type == 'emoji') {
if (this.q == null || this.q == '') {
this.emojis = this.emojiDb.filter(x => x.isCustomEmoji && !x.aliasOf);
this.emojis = this.emojiDb.filter(x => x.isCustomEmoji && !x.aliasOf).sort((a, b) => {
var textA = a.name.toUpperCase();
var textB = b.name.toUpperCase();
return (textA < textB) ? -1 : (textA > textB) ? 1 : 0;
});
return;
}

View File

@@ -60,7 +60,10 @@ export default Vue.extend({
}
if (this.char) {
this.url = `https://twemoji.maxcdn.com/2/svg/${this.char.codePointAt(0).toString(16)}.svg`;
let codes = [...this.char].map(x => x.codePointAt(0).toString(16));
if (!codes.includes('200d')) codes = codes.filter(x => x != 'fe0f');
this.url = `https://twemoji.maxcdn.com/2/svg/${codes.join('-')}.svg`;
}
}
});

View File

@@ -40,7 +40,6 @@ import twitterSetting from './twitter-setting.vue';
import githubSetting from './github-setting.vue';
import fileTypeIcon from './file-type-icon.vue';
import emoji from './emoji.vue';
import Reversi from './games/reversi/reversi.vue';
import welcomeTimeline from './welcome-timeline.vue';
import uiInput from './ui/input.vue';
import uiButton from './ui/button.vue';
@@ -95,7 +94,6 @@ Vue.component('mk-twitter-setting', twitterSetting);
Vue.component('mk-github-setting', githubSetting);
Vue.component('mk-file-type-icon', fileTypeIcon);
Vue.component('mk-emoji', emoji);
Vue.component('mk-reversi', Reversi);
Vue.component('mk-welcome-timeline', welcomeTimeline);
Vue.component('ui-input', uiInput);
Vue.component('ui-button', uiButton);

View File

@@ -187,13 +187,14 @@ export default Vue.component('misskey-flavored-markdown', {
}
case 'emoji': {
const customEmojis = (this.os.getMetaSync() || { emojis: [] }).emojis || [];
return [createElement('mk-emoji', {
attrs: {
emoji: token.emoji,
name: token.name
},
props: {
customEmojis: this.customEmojis
customEmojis: this.customEmojis || customEmojis
}
})];
}

View File

@@ -31,13 +31,13 @@
<ui-input type="file" @change="onAvatarChange">
<span>%i18n:@avatar%</span>
<span slot="icon"><fa icon="image"/></span>
<span slot="text" v-if="avatarUploading">%i18n:@uploading%<mk-ellipsis/></span>
<span slot="desc" v-if="avatarUploading">%i18n:@uploading%<mk-ellipsis/></span>
</ui-input>
<ui-input type="file" @change="onBannerChange">
<span>%i18n:@banner%</span>
<span slot="icon"><fa icon="image"/></span>
<span slot="text" v-if="bannerUploading">%i18n:@uploading%<mk-ellipsis/></span>
<span slot="desc" v-if="bannerUploading">%i18n:@uploading%<mk-ellipsis/></span>
</ui-input>
<ui-button @click="save(true)">%i18n:@save%</ui-button>

View File

@@ -4,35 +4,35 @@
<ui-input v-if="meta.disableRegistration" v-model="invitationCode" type="text" :autocomplete="Math.random()" spellcheck="false" required styl="fill">
<span>%i18n:@invitation-code%</span>
<span slot="prefix"><fa icon="id-card-alt"/></span>
<p slot="text" v-html="'%i18n:@invitation-info%'.replace('{}', meta.maintainer.url)"></p>
<p slot="desc" v-html="'%i18n:@invitation-info%'.replace('{}', meta.maintainer.url)"></p>
</ui-input>
<ui-input v-model="username" type="text" pattern="^[a-zA-Z0-9_]{1,20}$" :autocomplete="Math.random()" spellcheck="false" required @input="onChangeUsername" styl="fill">
<span>%i18n:@username%</span>
<span slot="prefix">@</span>
<span slot="suffix">@{{ host }}</span>
<p slot="text" v-if="usernameState == 'wait'" style="color:#999"><fa icon="spinner .pulse" fixed-width/> %i18n:@checking%</p>
<p slot="text" v-if="usernameState == 'ok'" style="color:#3CB7B5"><fa icon="check" fixed-width/> %i18n:@available%</p>
<p slot="text" v-if="usernameState == 'unavailable'" style="color:#FF1161"><fa icon="exclamation-triangle" fixed-width/> %i18n:@unavailable%</p>
<p slot="text" v-if="usernameState == 'error'" style="color:#FF1161"><fa icon="exclamation-triangle" fixed-width/> %i18n:@error%</p>
<p slot="text" v-if="usernameState == 'invalid-format'" style="color:#FF1161"><fa icon="exclamation-triangle" fixed-width/> %i18n:@invalid-format%</p>
<p slot="text" v-if="usernameState == 'min-range'" style="color:#FF1161"><fa icon="exclamation-triangle" fixed-width/> %i18n:@too-short%</p>
<p slot="text" v-if="usernameState == 'max-range'" style="color:#FF1161"><fa icon="exclamation-triangle" fixed-width/> %i18n:@too-long%</p>
<p slot="desc" v-if="usernameState == 'wait'" style="color:#999"><fa icon="spinner .pulse" fixed-width/> %i18n:@checking%</p>
<p slot="desc" v-if="usernameState == 'ok'" style="color:#3CB7B5"><fa icon="check" fixed-width/> %i18n:@available%</p>
<p slot="desc" v-if="usernameState == 'unavailable'" style="color:#FF1161"><fa icon="exclamation-triangle" fixed-width/> %i18n:@unavailable%</p>
<p slot="desc" v-if="usernameState == 'error'" style="color:#FF1161"><fa icon="exclamation-triangle" fixed-width/> %i18n:@error%</p>
<p slot="desc" v-if="usernameState == 'invalid-format'" style="color:#FF1161"><fa icon="exclamation-triangle" fixed-width/> %i18n:@invalid-format%</p>
<p slot="desc" v-if="usernameState == 'min-range'" style="color:#FF1161"><fa icon="exclamation-triangle" fixed-width/> %i18n:@too-short%</p>
<p slot="desc" v-if="usernameState == 'max-range'" style="color:#FF1161"><fa icon="exclamation-triangle" fixed-width/> %i18n:@too-long%</p>
</ui-input>
<ui-input v-model="password" type="password" :autocomplete="Math.random()" required @input="onChangePassword" :with-password-meter="true" styl="fill">
<span>%i18n:@password%</span>
<span slot="prefix"><fa icon="lock"/></span>
<div slot="text">
<p slot="text" v-if="passwordStrength == 'low'" style="color:#FF1161"><fa icon="exclamation-triangle" fixed-width/> %i18n:@weak-password%</p>
<p slot="text" v-if="passwordStrength == 'medium'" style="color:#3CB7B5"><fa icon="check" fixed-width/> %i18n:@normal-password%</p>
<p slot="text" v-if="passwordStrength == 'high'" style="color:#3CB7B5"><fa icon="check" fixed-width/> %i18n:@strong-password%</p>
<div slot="desc">
<p v-if="passwordStrength == 'low'" style="color:#FF1161"><fa icon="exclamation-triangle" fixed-width/> %i18n:@weak-password%</p>
<p v-if="passwordStrength == 'medium'" style="color:#3CB7B5"><fa icon="check" fixed-width/> %i18n:@normal-password%</p>
<p v-if="passwordStrength == 'high'" style="color:#3CB7B5"><fa icon="check" fixed-width/> %i18n:@strong-password%</p>
</div>
</ui-input>
<ui-input v-model="retypedPassword" type="password" :autocomplete="Math.random()" required @input="onChangePasswordRetype" styl="fill">
<span>%i18n:@password% (%i18n:@retype%)</span>
<span slot="prefix"><fa icon="lock"/></span>
<div slot="text">
<p slot="text" v-if="passwordRetypeState == 'match'" style="color:#3CB7B5"><fa icon="check" fixed-width/> %i18n:@password-matched%</p>
<p slot="text" v-if="passwordRetypeState == 'not-match'" style="color:#FF1161"><fa icon="exclamation-triangle" fixed-width/> %i18n:@password-not-matched%</p>
<div slot="desc">
<p v-if="passwordRetypeState == 'match'" style="color:#3CB7B5"><fa icon="check" fixed-width/> %i18n:@password-matched%</p>
<p v-if="passwordRetypeState == 'not-match'" style="color:#FF1161"><fa icon="exclamation-triangle" fixed-width/> %i18n:@password-not-matched%</p>
</div>
</ui-input>
<div v-if="meta.recaptchaSitekey != null" class="g-recaptcha" :data-sitekey="meta.recaptchaSitekey" style="margin: 16px 0;"></div>

View File

@@ -48,6 +48,9 @@ export default Vue.extend({
&.fit-top
padding-top 0
&.fit-bottom
padding-bottom 0
> header
margin-bottom 16px
font-weight bold

View File

@@ -1,5 +1,5 @@
<template>
<div class="ui-input" :class="[{ focused, filled, inline }, styl]">
<div class="ui-input" :class="[{ focused, filled, inline, disabled }, styl]">
<div class="icon" ref="icon"><slot name="icon"></slot></div>
<div class="input">
<div class="password-meter" v-if="withPasswordMeter" v-show="passwordStrength != ''" :data-strength="passwordStrength">
@@ -11,6 +11,7 @@
<input ref="input"
:type="type"
v-model="v"
:disabled="disabled"
:required="required"
:readonly="readonly"
:pattern="pattern"
@@ -32,7 +33,7 @@
</template>
<div class="suffix" ref="suffix"><slot name="suffix"></slot></div>
</div>
<div class="text"><slot name="text"></slot></div>
<div class="desc"><slot name="desc"></slot></div>
</div>
</template>
@@ -62,6 +63,10 @@ export default Vue.extend({
type: Boolean,
required: false
},
disabled: {
type: Boolean,
required: false
},
pattern: {
type: String,
required: false
@@ -316,7 +321,7 @@ root(fill)
if fill
padding-right 12px
> .text
> .desc
margin 6px 0
font-size 13px
@@ -353,4 +358,10 @@ root(fill)
display inline-block
margin 0
&.disabled
opacity 0.7
&, *
cursor not-allowed !important
</style>

View File

@@ -129,5 +129,6 @@ export default Vue.extend({
> p
margin 0
opacity 0.7
font-size 90%
</style>

View File

@@ -13,7 +13,7 @@
@blur="focused = false"
></textarea>
</div>
<div class="text"><slot name="text"></slot></div>
<div class="desc"><slot name="desc"></slot></div>
</div>
</template>
@@ -139,7 +139,7 @@ root(fill)
outline none
box-shadow none
> .text
> .desc
margin 6px 0
font-size 13px

View File

@@ -1,7 +1,7 @@
<template>
<mk-window ref="window" width="500px" height="560px" :popout-url="popout" @closed="destroyDom">
<span slot="header" :class="$style.header"><fa icon="gamepad"/>%i18n:@game%</span>
<mk-reversi :class="$style.content" @gamed="g => game = g"/>
<x-reversi :class="$style.content" @gamed="g => game = g"/>
</mk-window>
</template>
@@ -10,6 +10,9 @@ import Vue from 'vue';
import { url } from '../../../config';
export default Vue.extend({
components: {
XReversi: () => import('../../../common/views/components/games/reversi/reversi.vue')
},
data() {
return {
game: null

View File

@@ -255,7 +255,7 @@ export default Vue.extend({
p
margin 0
i, .mk-reaction-icon
[data-icon], .mk-reaction-icon
margin-right 4px
.note-preview
@@ -272,19 +272,19 @@ export default Vue.extend({
margin-right 3px
&.renote, &.quote
.text p i
.text p [data-icon]
color #77B255
&.follow
.text p i
.text p [data-icon]
color #53c7ce
&.receiveFollowRequest
.text p i
.text p [data-icon]
color #888
&.reply, &.mention
.text p i
.text p [data-icon]
color #555
> .date

View File

@@ -1,6 +1,6 @@
<template>
<component :is="ui ? 'mk-ui' : 'div'">
<mk-reversi :game-id="$route.params.game" @nav="nav" :self-nav="false"/>
<x-reversi :game-id="$route.params.game" @nav="nav" :self-nav="false"/>
</component>
</template>
@@ -8,6 +8,9 @@
import Vue from 'vue';
export default Vue.extend({
components: {
XReversi: () => import('../../../../common/views/components/games/reversi/reversi.vue')
},
props: {
ui: {
default: false

View File

@@ -105,7 +105,7 @@ export default Vue.extend({
p
margin 0
i, mk-reaction-icon
[data-icon], mk-reaction-icon
margin-right 4px
.note-ref
@@ -118,19 +118,19 @@ export default Vue.extend({
margin-right 3px
&.renote, &.quote
.text p i
.text p [data-icon]
color #77B255
&.follow
.text p i
.text p [data-icon]
color #53c7ce
&.receiveFollowRequest
.text p i
.text p [data-icon]
color #888
&.reply, &.mention
.text p i
.text p [data-icon]
color #fff
</style>

View File

@@ -149,7 +149,7 @@ export default Vue.extend({
align-items baseline
white-space nowrap
i, .mk-reaction-icon
[data-icon], .mk-reaction-icon
margin-right 4px
> .mk-time
@@ -171,15 +171,15 @@ export default Vue.extend({
margin-right 3px
&.renote
> div > header i
> div > header [data-icon]
color #77B255
&.follow
> div > header i
> div > header [data-icon]
color #53c7ce
&.receiveFollowRequest
> div > header i
> div > header [data-icon]
color #888
</style>

View File

@@ -1,7 +1,7 @@
<template>
<mk-ui>
<span slot="header"><span style="margin-right:4px;"><fa icon="gamepad"/></span>%i18n:@reversi%</span>
<mk-reversi :game-id="$route.params.game" @nav="nav" :self-nav="false"/>
<x-reversi :game-id="$route.params.game" @nav="nav" :self-nav="false"/>
</mk-ui>
</template>
@@ -9,6 +9,9 @@
import Vue from 'vue';
export default Vue.extend({
components: {
XReversi: () => import('../../../../common/views/components/games/reversi/reversi.vue')
},
mounted() {
document.title = `${(this as any).os.instanceName} %i18n:@reversi%`;
},

View File

@@ -46,8 +46,7 @@ export default function load() {
mixin.drive_url = `${mixin.scheme}://${mixin.host}/files`;
mixin.user_agent = `Misskey/${pkg.version} (${config.url})`;
if (config.localDriveCapacityMb == null) config.localDriveCapacityMb = 256;
if (config.remoteDriveCapacityMb == null) config.remoteDriveCapacityMb = 8;
if (config.autoAdmin == null) config.autoAdmin = false;
return Object.assign(config, mixin);
}

View File

@@ -19,7 +19,6 @@ export type Source = {
feedback_url?: string;
};
languages?: string[];
welcome_bg_url?: string;
url: string;
port: number;
https?: { [x: string]: string };
@@ -46,10 +45,6 @@ export type Source = {
secret_key: string;
};
localDriveCapacityMb: number;
remoteDriveCapacityMb: number;
preventCacheRemoteFiles: boolean;
drive?: {
storage: string;
bucket?: string;
@@ -58,6 +53,8 @@ export type Source = {
config?: any;
};
autoAdmin?: boolean;
/**
* ゴーストアカウントのID
*/
@@ -80,17 +77,6 @@ export type Source = {
hook_secret: string;
username: string;
};
reversi_ai?: {
id: string;
i: string;
};
line_bot?: {
channel_secret: string;
channel_access_token: string;
};
analysis?: {
mecab_command?: string;
};
/**
* Service Worker

View File

@@ -17,7 +17,6 @@ import * as program from 'commander';
import mongo from './db/mongodb';
import Logger from './misc/logger';
import ProgressBar from './misc/cli/progressbar';
import EnvironmentInfo from './misc/environmentInfo';
import MachineInfo from './misc/machineInfo';
import serverStats from './daemons/server-stats';
@@ -87,10 +86,9 @@ async function masterMain() {
if (!program.disableClustering) {
await spawnWorkers(config.clusterLimit);
Logger.succ('All workers started');
}
Logger.info(`Now listening on port ${config.port} on ${config.url}`);
Logger.succ(`Now listening on port ${config.port} on ${config.url}`);
}
/**
@@ -114,7 +112,7 @@ async function init(): Promise<Config> {
Logger.info(`<<< Misskey v${pkg.version} >>>`);
new Logger('Deps').info(`Node.js ${process.version}`);
MachineInfo.show();
await MachineInfo.show();
EnvironmentInfo.show();
const configLogger = new Logger('Config');
@@ -168,28 +166,30 @@ function checkMongoDb(config: Config) {
}
function spawnWorkers(limit: number) {
Logger.info('Starting workers...');
return new Promise(res => {
// Count the machine's CPUs
const cpuCount = os.cpus().length;
const count = limit || cpuCount;
const progress = new ProgressBar(count, 'Starting workers');
let started = 0;
// Create a worker for each CPU
for (let i = 0; i < count; i++) {
const worker = cluster.fork();
worker.on('message', message => {
if (message === 'ready') {
progress.increment();
if (message !== 'ready') return;
started++;
// When all workers started
if (started == count) {
Logger.succ('All workers started');
res();
}
});
}
// On all workers started
progress.on('complete', () => {
res();
});
});
}

File diff suppressed because one or more lines are too long

View File

@@ -2,6 +2,8 @@
* Emoji
*/
import { emojiRegex } from "./emoji.regex";
export type TextElementEmoji = {
type: 'emoji';
content: string;
@@ -9,8 +11,6 @@ export type TextElementEmoji = {
name?: string;
};
const emojiRegex = /^[\u{1f300}-\u{1f5ff}\u{1f900}-\u{1f9ff}\u{1f600}-\u{1f64f}\u{1f680}-\u{1f6ff}\u{2600}-\u{26ff}\u{2700}-\u{27bf}\u{1f1e6}-\u{1f1ff}\u{1f191}-\u{1f251}\u{1f004}\u{1f0cf}\u{1f170}-\u{1f171}\u{1f17e}-\u{1f17f}\u{1f18e}\u{3030}\u{2b50}\u{2b55}\u{2934}-\u{2935}\u{2b05}-\u{2b07}\u{2b1b}-\u{2b1c}\u{3297}\u{3299}\u{303d}\u{00a9}\u{00ae}\u{2122}\u{23f3}\u{24c2}\u{23e9}-\u{23ef}\u{25b6}\u{23f8}-\u{23fa}]/ug;
export default function(text: string) {
const name = text.match(/^:([a-zA-Z0-9+_-]+):/);
if (name) {

View File

@@ -1,85 +0,0 @@
import { EventEmitter } from 'events';
import * as readline from 'readline';
import chalk from 'chalk';
/**
* Progress bar
*/
export default class extends EventEmitter {
public max: number;
public value: number;
public text: string;
private indicator: number;
constructor(max: number, text: string = null) {
super();
this.max = max;
this.value = 0;
this.text = text;
this.indicator = 0;
this.draw();
const iclock = setInterval(() => {
this.indicator = (this.indicator + 1) % 4;
this.draw();
}, 200);
this.on('complete', () => {
clearInterval(iclock);
});
}
public increment(): void {
this.value++;
this.draw();
// Check if it is fulfilled
if (this.value === this.max) {
this.indicator = null;
cll();
process.stdout.write(`${this.render()} -> ${chalk.bold('Complete')}\n`);
this.emit('complete');
}
}
public draw(): void {
const str = this.render();
cll();
process.stdout.write(str);
}
private render(): string {
const width = 30;
const t = this.text ? `${this.text} ` : '';
const v = Math.floor((this.value / this.max) * width);
const vs = new Array(v + 1).join('*');
const p = width - v;
const ps = new Array(p + 1).join(' ');
const percentage = Math.floor((this.value / this.max) * 100);
const percentages = chalk.gray(`(${percentage} %)`);
let i: string;
switch (this.indicator) {
case 0: i = '-'; break;
case 1: i = '\\'; break;
case 2: i = '|'; break;
case 3: i = '/'; break;
case null: i = '+'; break;
}
return `${i} ${t}[${vs}${ps}] ${this.value} / ${this.max} ${percentages}`;
}
}
/**
* Clear current line
*/
function cll(): void {
readline.clearLine(process.stdout, 0); // Clear current text
readline.cursorTo(process.stdout, 0, null); // Move cursor to the head of line
}

20
src/misc/fetch-meta.ts Normal file
View File

@@ -0,0 +1,20 @@
import Meta, { IMeta } from '../models/meta';
const defaultMeta: any = {
name: 'Misskey',
cacheRemoteFiles: true,
localDriveCapacityMb: 256,
remoteDriveCapacityMb: 8,
hidedTags: [],
stats: {
originalNotesCount: 0,
originalUsersCount: 0
},
maxNoteTextLength: 1000
};
export default async function(): Promise<IMeta> {
const meta = await Meta.findOne({});
return Object.assign({}, defaultMeta, meta);
}

View File

@@ -1,15 +1,17 @@
import * as os from 'os';
import Logger from './logger';
import * as sysUtils from 'systeminformation';
export default class {
public static show(): void {
const totalmem = (os.totalmem() / 1024 / 1024 / 1024).toFixed(1);
const freemem = (os.freemem() / 1024 / 1024 / 1024).toFixed(1);
public static async show() {
const logger = new Logger('Machine');
logger.info(`Hostname: ${os.hostname()}`);
logger.info(`Platform: ${process.platform}`);
logger.info(`Architecture: ${process.arch}`);
logger.info(`CPU: ${os.cpus().length} core`);
logger.info(`MEM: ${totalmem}GB (available: ${freemem}GB)`);
const mem = await sysUtils.mem();
const totalmem = (mem.total / 1024 / 1024 / 1024).toFixed(1);
const availmem = (mem.available / 1024 / 1024 / 1024).toFixed(1);
logger.info(`MEM: ${totalmem}GB (available: ${availmem}GB)`);
}
}

View File

@@ -28,6 +28,39 @@ if ((config as any).description) {
}
});
}
if ((config as any).localDriveCapacityMb) {
Meta.findOne({}).then(m => {
if (m != null && m.localDriveCapacityMb == null) {
Meta.update({}, {
$set: {
localDriveCapacityMb: (config as any).localDriveCapacityMb
}
});
}
});
}
if ((config as any).remoteDriveCapacityMb) {
Meta.findOne({}).then(m => {
if (m != null && m.remoteDriveCapacityMb == null) {
Meta.update({}, {
$set: {
remoteDriveCapacityMb: (config as any).remoteDriveCapacityMb
}
});
}
});
}
if ((config as any).preventCacheRemoteFiles) {
Meta.findOne({}).then(m => {
if (m != null && m.cacheRemoteFiles == null) {
Meta.update({}, {
$set: {
cacheRemoteFiles: !(config as any).preventCacheRemoteFiles
}
});
}
});
}
export type IMeta = {
name?: string;
@@ -44,6 +77,18 @@ export type IMeta = {
hidedTags?: string[];
bannerUrl?: string;
cacheRemoteFiles?: boolean;
/**
* Drive capacity of a local user (MB)
*/
localDriveCapacityMb?: number;
/**
* Drive capacity of a remote user (MB)
*/
remoteDriveCapacityMb?: number;
/**
* Max allowed note text length in charactors
*/

View File

@@ -65,6 +65,29 @@ export const meta = {
desc: {
'ja-JP': '投稿の最大文字数'
}
},
localDriveCapacityMb: {
validator: $.num.optional.min(0),
desc: {
'ja-JP': 'ローカルユーザーひとりあたりのドライブ容量 (メガバイト単位)',
'en-US': 'Drive capacity of a local user (MB)'
}
},
remoteDriveCapacityMb: {
validator: $.num.optional.min(0),
desc: {
'ja-JP': 'リモートユーザーひとりあたりのドライブ容量 (メガバイト単位)',
'en-US': 'Drive capacity of a remote user (MB)'
}
},
cacheRemoteFiles: {
validator: $.bool.optional,
desc: {
'ja-JP': 'リモートのファイルをキャッシュするか否か'
}
}
}
};
@@ -104,6 +127,18 @@ export default define(meta, (ps) => new Promise(async (res, rej) => {
set.maxNoteTextLength = ps.maxNoteTextLength;
}
if (ps.localDriveCapacityMb !== undefined) {
set.localDriveCapacityMb = ps.localDriveCapacityMb;
}
if (ps.remoteDriveCapacityMb !== undefined) {
set.remoteDriveCapacityMb = ps.remoteDriveCapacityMb;
}
if (ps.cacheRemoteFiles !== undefined) {
set.cacheRemoteFiles = ps.cacheRemoteFiles;
}
await Meta.update({}, {
$set: set
}, { upsert: true });

View File

@@ -1,14 +1,14 @@
import Note from '../../../../models/note';
import Meta from '../../../../models/meta';
import define from '../../define';
import fetchMeta from '../../../../misc/fetch-meta';
export const meta = {
requireCredential: false,
};
export default define(meta, (ps) => new Promise(async (res, rej) => {
const meta = await Meta.findOne({});
const hidedTags = meta ? (meta.hidedTags || []).map(t => t.toLowerCase()) : [];
const instance = await fetchMeta();
const hidedTags = instance.hidedTags.map(t => t.toLowerCase());
const span = 1000 * 60 * 60 * 24 * 7; // 1週間

View File

@@ -1,6 +1,6 @@
import DriveFile from '../../../models/drive-file';
import config from '../../../config';
import define from '../define';
import fetchMeta from '../../../misc/fetch-meta';
export const meta = {
desc: {
@@ -14,6 +14,8 @@ export const meta = {
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
const instance = await fetchMeta();
// Calculate drive usage
const usage = await DriveFile
.aggregate([{
@@ -39,7 +41,7 @@ export default define(meta, (ps, user) => new Promise(async (res, rej) => {
});
res({
capacity: 1024 * 1024 * config.localDriveCapacityMb,
capacity: 1024 * 1024 * instance.localDriveCapacityMb,
usage: usage
});
}));

View File

@@ -1,7 +1,7 @@
import Note from '../../../../models/note';
import { erase } from '../../../../prelude/array';
import Meta from '../../../../models/meta';
import define from '../../define';
import fetchMeta from '../../../../misc/fetch-meta';
/*
トレンドに載るためには「『直近a分間のユニーク投稿数が今からa分前今からb分前の間のユニーク投稿数のn倍以上』のハッシュタグの上位5位以内に入る」ことが必要
@@ -20,8 +20,8 @@ export const meta = {
};
export default define(meta, () => new Promise(async (res, rej) => {
const meta = await Meta.findOne({});
const hidedTags = meta ? (meta.hidedTags || []).map(t => t.toLowerCase()) : [];
const instance = await fetchMeta();
const hidedTags = instance.hidedTags.map(t => t.toLowerCase());
//#region 1. 直近Aの内に投稿されたハッシュタグ(とユーザーのペア)を集計
const data = await Note.aggregate([{

View File

@@ -1,9 +1,9 @@
import $ from 'cafy';
import * as os from 'os';
import config from '../../../config';
import Meta from '../../../models/meta';
import Emoji from '../../../models/emoji';
import define from '../define';
import fetchMeta from '../../../misc/fetch-meta';
const pkg = require('../../../../package.json');
const client = require('../../../../built/client/meta.json');
@@ -27,7 +27,7 @@ export const meta = {
};
export default define(meta, (ps, me) => new Promise(async (res, rej) => {
const met: any = (await Meta.findOne()) || {};
const instance = await fetchMeta();
const emojis = await Emoji.find({ host: null }, {
fields: {
@@ -41,8 +41,8 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
version: pkg.version,
clientVersion: client.version,
name: met.name || 'Misskey',
description: met.description,
name: instance.name,
description: instance.description,
secure: config.https != null,
machine: os.hostname(),
@@ -54,21 +54,23 @@ export default define(meta, (ps, me) => new Promise(async (res, rej) => {
cores: os.cpus().length
},
broadcasts: met.broadcasts || [],
disableRegistration: met.disableRegistration,
disableLocalTimeline: met.disableLocalTimeline,
driveCapacityPerLocalUserMb: config.localDriveCapacityMb,
broadcasts: instance.broadcasts || [],
disableRegistration: instance.disableRegistration,
disableLocalTimeline: instance.disableLocalTimeline,
driveCapacityPerLocalUserMb: instance.localDriveCapacityMb,
driveCapacityPerRemoteUserMb: instance.remoteDriveCapacityMb,
cacheRemoteFiles: instance.cacheRemoteFiles,
recaptchaSitekey: config.recaptcha ? config.recaptcha.site_key : null,
swPublickey: config.sw ? config.sw.public_key : null,
hidedTags: (me && me.isAdmin) ? met.hidedTags : undefined,
bannerUrl: met.bannerUrl,
maxNoteTextLength: met.maxNoteTextLength || 1000,
hidedTags: (me && me.isAdmin) ? instance.hidedTags : undefined,
bannerUrl: instance.bannerUrl,
maxNoteTextLength: instance.maxNoteTextLength,
emojis: emojis,
features: ps.detail ? {
registration: !met.disableRegistration,
localTimeLine: !met.disableLocalTimeline,
registration: !instance.disableRegistration,
localTimeLine: !instance.disableLocalTimeline,
elasticsearch: config.elasticsearch ? true : false,
recaptcha: config.recaptcha ? true : false,
objectStorage: config.drive && config.drive.storage === 'minio',

View File

@@ -6,13 +6,13 @@ import User, { IUser } from '../../../../models/user';
import DriveFile, { IDriveFile } from '../../../../models/drive-file';
import create from '../../../../services/note/create';
import define from '../../define';
import Meta from '../../../../models/meta';
import fetchMeta from '../../../../misc/fetch-meta';
let maxNoteTextLength = 1000;
setInterval(() => {
Meta.findOne({}).then(m => {
if (m.maxNoteTextLength) maxNoteTextLength = m.maxNoteTextLength;
fetchMeta().then(m => {
maxNoteTextLength = m.maxNoteTextLength;
});
}, 3000);

View File

@@ -0,0 +1,44 @@
import $ from 'cafy'; import ID, { transform } from '../../../../../misc/cafy-id';
import Note from '../../../../../models/note';
import define from '../../../define';
import watch from '../../../../../services/note/watch';
export const meta = {
stability: 'stable',
desc: {
'ja-JP': '指定した投稿をウォッチします。',
'en-US': 'Watch a note.'
},
requireCredential: true,
kind: 'account-write',
params: {
noteId: {
validator: $.type(ID),
transform: transform,
desc: {
'ja-JP': '対象の投稿のID',
'en-US': 'Target note ID.'
}
}
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
// Get note
const note = await Note.findOne({
_id: ps.noteId
});
if (note === null) {
return rej('note not found');
}
await watch(user._id, note);
// Send response
res();
}));

View File

@@ -0,0 +1,44 @@
import $ from 'cafy'; import ID, { transform } from '../../../../../misc/cafy-id';
import Note from '../../../../../models/note';
import define from '../../../define';
import unwatch from '../../../../../services/note/unwatch';
export const meta = {
stability: 'stable',
desc: {
'ja-JP': '指定した投稿のウォッチを解除します。',
'en-US': 'Unwatch a note.'
},
requireCredential: true,
kind: 'account-write',
params: {
noteId: {
validator: $.type(ID),
transform: transform,
desc: {
'ja-JP': '対象の投稿のID',
'en-US': 'Target note ID.'
}
}
}
};
export default define(meta, (ps, user) => new Promise(async (res, rej) => {
// Get note
const note = await Note.findOne({
_id: ps.noteId
});
if (note === null) {
return rej('note not found');
}
await unwatch(user._id, note);
// Send response
res();
}));

View File

@@ -1,7 +1,7 @@
import Meta from '../../../models/meta';
import define from '../define';
import driveChart from '../../../chart/drive';
import federationChart from '../../../chart/federation';
import fetchMeta from '../../../misc/fetch-meta';
export const meta = {
requireCredential: false,
@@ -15,9 +15,9 @@ export const meta = {
};
export default define(meta, () => new Promise(async (res, rej) => {
const meta = await Meta.findOne();
const instance = await fetchMeta();
const stats: any = meta ? meta.stats : {};
const stats: any = instance.stats;
const driveStats = await driveChart.getChart('hour', 1);
stats.driveUsageLocal = driveStats.local.totalSize[0];

View File

@@ -2,10 +2,10 @@ import * as Router from 'koa-router';
import User from '../../../models/user';
import { toASCII } from 'punycode';
import config from '../../../config';
import Meta from '../../../models/meta';
import { ObjectID } from 'bson';
import Emoji from '../../../models/emoji';
import { toMastodonEmojis } from './emoji';
import fetchMeta from '../../../misc/fetch-meta';
const pkg = require('../../../../package.json');
// Init router
@@ -19,11 +19,8 @@ router.get('/v1/custom_emojis', async ctx => ctx.body =
})).map(x => toMastodonEmojis(x)));
router.get('/v1/instance', async ctx => { // TODO: This is a temporary implementation. Consider creating helper methods!
const meta = await Meta.findOne() || {};
const { originalNotesCount, originalUsersCount } = meta.stats || {
originalNotesCount: 0,
originalUsersCount: 0
};
const meta = await fetchMeta();
const { originalNotesCount, originalUsersCount } = meta.stats;
const domains = await User.distinct('host', { host: { $ne: null } }) as any as [] || [];
const maintainer = await User.findOne({ isAdmin: true }) || {
_id: ObjectID.createFromTime(0),

View File

@@ -8,6 +8,7 @@ import config from '../../../config';
import Meta from '../../../models/meta';
import RegistrationTicket from '../../../models/registration-tickets';
import usersChart from '../../../chart/users';
import fetchMeta from '../../../misc/fetch-meta';
if (config.recaptcha) {
recaptcha.init({
@@ -33,9 +34,9 @@ export default async (ctx: Koa.Context) => {
const password = body['password'];
const invitationCode = body['invitationCode'];
const meta = await Meta.findOne({});
const instance = await fetchMeta();
if (meta && meta.disableRegistration) {
if (instance && instance.disableRegistration) {
if (invitationCode == null || typeof invitationCode != 'string') {
ctx.status = 400;
return;
@@ -67,14 +68,16 @@ export default async (ctx: Koa.Context) => {
return;
}
const usersCount = await User.count({});
// Fetch exist user that same username
const usernameExist = await User
.count({
usernameLower: username.toLowerCase(),
host: null
}, {
limit: 1
});
limit: 1
});
// Check username already used
if (usernameExist !== 0) {
@@ -104,17 +107,12 @@ export default async (ctx: Koa.Context) => {
host: null,
keypair: generateKeypair(),
token: secret,
email: null,
password: hash,
isAdmin: config.autoAdmin && usersCount === 0,
profile: {
bio: null,
birthday: null,
blood: null,
gender: null,
handedness: null,
height: null,
location: null,
weight: null
location: null
},
settings: {
autoWatch: false

View File

@@ -19,6 +19,7 @@ import config from '../../config';
import { getDriveFileThumbnailBucket } from '../../models/drive-file-thumbnail';
import driveChart from '../../chart/drive';
import perUserDriveChart from '../../chart/per-user-drive';
import fetchMeta from '../../misc/fetch-meta';
const log = debug('misskey:drive:add-file');
@@ -255,7 +256,8 @@ export default async function(
log(`drive usage is ${usage}`);
const driveCapacity = 1024 * 1024 * (isLocalUser(user) ? config.localDriveCapacityMb : config.remoteDriveCapacityMb);
const instance = await fetchMeta();
const driveCapacity = 1024 * 1024 * (isLocalUser(user) ? instance.localDriveCapacityMb : instance.remoteDriveCapacityMb);
// If usage limit exceeded
if (usage + size > driveCapacity) {

View File

@@ -10,6 +10,7 @@ import create from './add-file';
import config from '../../config';
import { IUser } from '../../models/user';
import * as mongodb from 'mongodb';
import fetchMeta from '../../misc/fetch-meta';
const log = debug('misskey:drive:upload-from-url');
@@ -34,28 +35,48 @@ export default async (url: string, user: IUser, folderId: mongodb.ObjectID = nul
// write content at URL to temp file
await new Promise((res, rej) => {
const writable = fs.createWriteStream(path);
writable.on('finish', () => {
res();
});
writable.on('error', error => {
rej(error);
});
const requestUrl = URL.parse(url).pathname.match(/[^\u0021-\u00ff]/) ? encodeURI(url) : url;
request({
const req = request({
url: requestUrl,
proxy: config.proxy,
timeout: 10 * 1000,
headers: {
'User-Agent': config.user_agent
}
})
.on('error', rej)
.on('end', () => {
});
req.pipe(writable);
req.on('response', response => {
if (response.statusCode !== 200) {
writable.close();
res();
})
.pipe(writable)
.on('error', rej);
rej(response.statusCode);
}
});
req.on('error', error => {
writable.close();
rej(error);
});
});
const instance = await fetchMeta();
let driveFile: IDriveFile;
let error;
try {
driveFile = await create(user, path, name, null, folderId, false, config.preventCacheRemoteFiles, url, uri, sensitive);
driveFile = await create(user, path, name, null, folderId, false, !instance.cacheRemoteFiles, url, uri, sensitive);
log(`got: ${driveFile._id}`);
} catch (e) {
error = e;

View File

@@ -0,0 +1,9 @@
import * as mongodb from 'mongodb';
import Watching from '../../models/note-watching';
export default async (me: mongodb.ObjectID, note: object) => {
await Watching.remove({
noteId: (note as any)._id,
userId: me
});
};

View File

@@ -1,7 +1,8 @@
import * as mongo from 'mongodb';
import redis from './db/redis';
import Xev from 'xev';
import Meta, { IMeta } from './models/meta';
import { IMeta } from './models/meta';
import fetchMeta from './misc/fetch-meta';
type ID = string | mongo.ObjectID;
@@ -16,14 +17,14 @@ class Publisher {
}
setInterval(async () => {
this.meta = await Meta.findOne({});
this.meta = await fetchMeta();
}, 5000);
}
public getMeta = async () => {
public fetchMeta = async () => {
if (this.meta != null) return this.meta;
this.meta = await Meta.findOne({});
this.meta = await fetchMeta();
return this.meta;
}
@@ -82,13 +83,13 @@ class Publisher {
}
public publishLocalTimelineStream = async (note: any): Promise<void> => {
const meta = await this.getMeta();
const meta = await this.fetchMeta();
if (meta.disableLocalTimeline) return;
this.publish('localTimeline', null, note);
}
public publishHybridTimelineStream = async (userId: ID, note: any): Promise<void> => {
const meta = await this.getMeta();
const meta = await this.fetchMeta();
if (meta.disableLocalTimeline) return;
this.publish(userId ? `hybridTimeline:${userId}` : 'hybridTimeline', null, note);
}