From a1b82e97230eab994c06fc7c902e71539664d3d7 Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 5 Sep 2018 19:32:46 +0900 Subject: #2620 --- src/models/drive-file.ts | 2 +- src/models/note.ts | 26 +++++++++++++++++++++----- 2 files changed, 22 insertions(+), 6 deletions(-) (limited to 'src/models') diff --git a/src/models/drive-file.ts b/src/models/drive-file.ts index 698ef092a6..215b49b305 100644 --- a/src/models/drive-file.ts +++ b/src/models/drive-file.ts @@ -92,7 +92,7 @@ export async function deleteDriveFile(driveFile: string | mongo.ObjectID | IDriv // このDriveFileを添付しているNoteをすべて削除 await Promise.all(( - await Note.find({ mediaIds: d._id }) + await Note.find({ fileIds: d._id }) ).map(x => deleteNote(x))); // このDriveFileを添付しているMessagingMessageをすべて削除 diff --git a/src/models/note.ts b/src/models/note.ts index 9d2e23d901..98d37caf22 100644 --- a/src/models/note.ts +++ b/src/models/note.ts @@ -6,7 +6,7 @@ import { IUser, pack as packUser } from './user'; import { pack as packApp } from './app'; import PollVote, { deletePollVote } from './poll-vote'; import Reaction, { deleteNoteReaction } from './note-reaction'; -import { pack as packFile } from './drive-file'; +import { pack as packFile, IDriveFile } from './drive-file'; import NoteWatching, { deleteNoteWatching } from './note-watching'; import NoteReaction from './note-reaction'; import Favorite, { deleteFavorite } from './favorite'; @@ -17,9 +17,20 @@ const Note = db.get('notes'); Note.createIndex('uri', { sparse: true, unique: true }); Note.createIndex('userId'); Note.createIndex('tagsLower'); +Note.createIndex('_files.contentType'); Note.createIndex({ createdAt: -1 }); + +// 後方互換性のため +Note.update({}, { + $rename: { + mediaIds: 'fileIds' + } +}, { + multi: true +}); + export default Note; export function isValidText(text: string): boolean { @@ -34,7 +45,7 @@ export type INote = { _id: mongo.ObjectID; createdAt: Date; deletedAt: Date; - mediaIds: mongo.ObjectID[]; + fileIds: mongo.ObjectID[]; replyId: mongo.ObjectID; renoteId: mongo.ObjectID; poll: { @@ -92,6 +103,7 @@ export type INote = { inbox?: string; }; _replyIds?: mongo.ObjectID[]; + _files?: IDriveFile[]; }; /** @@ -271,11 +283,15 @@ export const pack = async ( _note.app = packApp(_note.appId); } - // Populate media - _note.media = hide ? [] : Promise.all(_note.mediaIds.map((fileId: mongo.ObjectID) => + // Populate files + _note.files = hide ? [] : Promise.all(_note.fileIds.map((fileId: mongo.ObjectID) => packFile(fileId) )); + // 後方互換性のため + _note.mediaIds = _note.fileIds; + _note.media = _note.files; + // When requested a detailed note data if (opts.detail) { //#region 重いので廃止 @@ -344,7 +360,7 @@ export const pack = async ( } if (hide) { - _note.mediaIds = []; + _note.fileIds = []; _note.text = null; _note.poll = null; _note.cw = null; -- cgit v1.3.1-freya From 7343e6e2e8e94b2a58943c62c047b5a7c4116e3a Mon Sep 17 00:00:00 2001 From: syuilo Date: Fri, 7 Sep 2018 19:20:50 +0900 Subject: 統計で無視するハッシュタグを設定できるように MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../desktop/views/pages/admin/admin.hashtags.vue | 41 ++++++++++++++++++++++ src/client/app/desktop/views/pages/admin/admin.vue | 7 ++++ src/models/meta.ts | 7 ++-- src/server/api/endpoints/admin/update-meta.ts | 12 ++++++- src/server/api/endpoints/hashtags/trend.ts | 7 ++++ src/server/api/endpoints/meta.ts | 6 ++-- 6 files changed, 74 insertions(+), 6 deletions(-) create mode 100644 src/client/app/desktop/views/pages/admin/admin.hashtags.vue (limited to 'src/models') diff --git a/src/client/app/desktop/views/pages/admin/admin.hashtags.vue b/src/client/app/desktop/views/pages/admin/admin.hashtags.vue new file mode 100644 index 0000000000..c6bf20361f --- /dev/null +++ b/src/client/app/desktop/views/pages/admin/admin.hashtags.vue @@ -0,0 +1,41 @@ + + + + + diff --git a/src/client/app/desktop/views/pages/admin/admin.vue b/src/client/app/desktop/views/pages/admin/admin.vue index a71059c378..510252b447 100644 --- a/src/client/app/desktop/views/pages/admin/admin.vue +++ b/src/client/app/desktop/views/pages/admin/admin.vue @@ -5,6 +5,8 @@
  • %fa:chalkboard .fw%%i18n:@dashboard%
  • %fa:users .fw%%i18n:@users%
  • %fa:broadcast-tower .fw%%i18n:@announcements%
  • +
  • %fa:hashtag .fw%%i18n:@hashtags%
  • + @@ -17,6 +19,9 @@
    +
    + +
    @@ -33,6 +38,7 @@ import Vue from "vue"; import XDashboard from "./admin.dashboard.vue"; import XAnnouncements from "./admin.announcements.vue"; +import XHashtags from "./admin.hashtags.vue"; import XSuspendUser from "./admin.suspend-user.vue"; import XUnsuspendUser from "./admin.unsuspend-user.vue"; import XVerifyUser from "./admin.verify-user.vue"; @@ -43,6 +49,7 @@ export default Vue.extend({ components: { XDashboard, XAnnouncements, + XHashtags, XSuspendUser, XUnsuspendUser, XVerifyUser, diff --git a/src/models/meta.ts b/src/models/meta.ts index aef0163dfe..4f1977f3b5 100644 --- a/src/models/meta.ts +++ b/src/models/meta.ts @@ -4,12 +4,13 @@ const Meta = db.get('meta'); export default Meta; export type IMeta = { - broadcasts: any[]; - stats: { + broadcasts?: any[]; + stats?: { notesCount: number; originalNotesCount: number; usersCount: number; originalUsersCount: number; }; - disableRegistration: boolean; + disableRegistration?: boolean; + hidedTags?: string[]; }; diff --git a/src/server/api/endpoints/admin/update-meta.ts b/src/server/api/endpoints/admin/update-meta.ts index 10ca15d329..f903628774 100644 --- a/src/server/api/endpoints/admin/update-meta.ts +++ b/src/server/api/endpoints/admin/update-meta.ts @@ -21,7 +21,13 @@ export const meta = { desc: { 'ja-JP': '招待制か否か' } - }) + }), + + hidedTags: $.arr($.str).optional.nullable.note({ + desc: { + 'ja-JP': '統計などで無視するハッシュタグ' + } + }), } }; @@ -39,6 +45,10 @@ export default (params: any) => new Promise(async (res, rej) => { set.disableRegistration = ps.disableRegistration; } + if (Array.isArray(ps.hidedTags)) { + set.hidedTags = ps.hidedTags; + } + await Meta.update({}, { $set: set }, { upsert: true }); diff --git a/src/server/api/endpoints/hashtags/trend.ts b/src/server/api/endpoints/hashtags/trend.ts index e7c08ca9f0..bfa475619c 100644 --- a/src/server/api/endpoints/hashtags/trend.ts +++ b/src/server/api/endpoints/hashtags/trend.ts @@ -1,5 +1,6 @@ import Note from '../../../../models/note'; import { erase } from '../../../../prelude/array'; +import Meta from '../../../../models/meta'; /* トレンドに載るためには「『直近a分間のユニーク投稿数が今からa分前~今からb分前の間のユニーク投稿数のn倍以上』のハッシュタグの上位5位以内に入る」ことが必要 @@ -17,6 +18,9 @@ const max = 5; * Get trends of hashtags */ export default () => new Promise(async (res, rej) => { + const meta = await Meta.findOne({}); + const hidedTags = (meta.hidedTags || []).map(t => t.toLowerCase()); + //#region 1. 直近Aの内に投稿されたハッシュタグ(とユーザーのペア)を集計 const data = await Note.aggregate([{ $match: { @@ -53,6 +57,9 @@ export default () => new Promise(async (res, rej) => { // カウント data.map(x => x._id).forEach(x => { + // ブラックリストに登録されているタグなら弾く + if (hidedTags.includes(x.tag)) return; + const i = tags.findIndex(tag => tag.name == x.tag); if (i != -1) { tags[i].count++; diff --git a/src/server/api/endpoints/meta.ts b/src/server/api/endpoints/meta.ts index b0876eaafd..4472d8d779 100644 --- a/src/server/api/endpoints/meta.ts +++ b/src/server/api/endpoints/meta.ts @@ -4,6 +4,7 @@ import * as os from 'os'; import config from '../../../config'; import Meta from '../../../models/meta'; +import { ILocalUser } from '../../../models/user'; const pkg = require('../../../../package.json'); const client = require('../../../../built/client/meta.json'); @@ -11,7 +12,7 @@ const client = require('../../../../built/client/meta.json'); /** * Show core info */ -export default () => new Promise(async (res, rej) => { +export default (params: any, me: ILocalUser) => new Promise(async (res, rej) => { const meta: any = (await Meta.findOne()) || {}; res({ @@ -35,6 +36,7 @@ export default () => new Promise(async (res, rej) => { disableRegistration: meta.disableRegistration, driveCapacityPerLocalUserMb: config.localDriveCapacityMb, recaptchaSitekey: config.recaptcha ? config.recaptcha.site_key : null, - swPublickey: config.sw ? config.sw.public_key : null + swPublickey: config.sw ? config.sw.public_key : null, + hidedTags: (me && me.isAdmin) ? meta.hidedTags : undefined }); }); -- cgit v1.3.1-freya From 0790dd7a2c02a94698f50aad15236ec05bf77437 Mon Sep 17 00:00:00 2001 From: syuilo Date: Sun, 9 Sep 2018 02:45:29 +0900 Subject: 8.33.1 --- package.json | 2 +- src/models/note.ts | 10 ---------- 2 files changed, 1 insertion(+), 11 deletions(-) (limited to 'src/models') diff --git a/package.json b/package.json index 0112967b43..9323ab0ea3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "misskey", "author": "syuilo ", - "version": "8.33.0", + "version": "8.33.1", "clientVersion": "1.0.9497", "codename": "nighthike", "main": "./built/index.js", diff --git a/src/models/note.ts b/src/models/note.ts index 98d37caf22..181ebecf24 100644 --- a/src/models/note.ts +++ b/src/models/note.ts @@ -21,16 +21,6 @@ Note.createIndex('_files.contentType'); Note.createIndex({ createdAt: -1 }); - -// 後方互換性のため -Note.update({}, { - $rename: { - mediaIds: 'fileIds' - } -}, { - multi: true -}); - export default Note; export function isValidText(text: string): boolean { -- cgit v1.3.1-freya From 6ac92ac4b86a2e9aeac55b7e1259a9dedcb7e379 Mon Sep 17 00:00:00 2001 From: syuilo Date: Mon, 10 Sep 2018 02:43:16 +0900 Subject: Fix #2321 --- src/models/note.ts | 127 ++++++++++++++++++++++++-------------------- src/services/note/create.ts | 9 +++- 2 files changed, 75 insertions(+), 61 deletions(-) (limited to 'src/models') diff --git a/src/models/note.ts b/src/models/note.ts index 181ebecf24..624bdbdfe8 100644 --- a/src/models/note.ts +++ b/src/models/note.ts @@ -162,6 +162,66 @@ export async function deleteNote(note: string | mongo.ObjectID | INote) { console.log(`Note: deleted ${n._id}`); } +export const hideNote = async (packedNote: any, meId: mongo.ObjectID) => { + let hide = false; + + // visibility が private かつ投稿者のIDが自分のIDではなかったら非表示 + if (packedNote.visibility == 'private' && (meId == null || !meId.equals(packedNote.userId))) { + hide = true; + } + + // visibility が specified かつ自分が指定されていなかったら非表示 + if (packedNote.visibility == 'specified') { + if (meId == null) { + hide = true; + } else if (meId.equals(packedNote.userId)) { + hide = false; + } else { + // 指定されているかどうか + const specified = packedNote.visibleUserIds.some((id: mongo.ObjectID) => id.equals(meId)); + + if (specified) { + hide = false; + } else { + hide = true; + } + } + } + + // visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示 + if (packedNote.visibility == 'followers') { + if (meId == null) { + hide = true; + } else if (meId.equals(packedNote.userId)) { + hide = false; + } else { + // フォロワーかどうか + const following = await Following.findOne({ + followeeId: packedNote.userId, + followerId: meId + }); + + if (following == null) { + hide = true; + } else { + hide = false; + } + } + } + + if (hide) { + packedNote.fileIds = []; + packedNote.files = []; + packedNote.text = null; + packedNote.poll = null; + packedNote.cw = null; + packedNote.tags = []; + packedNote.tagsLower = []; + packedNote.geo = null; + packedNote.isHidden = true; + } +}; + /** * Pack a note for API response * @@ -174,11 +234,13 @@ export const pack = async ( note: string | mongo.ObjectID | INote, me?: string | mongo.ObjectID | IUser, options?: { - detail: boolean + detail?: boolean; + skipHide?: boolean; } ) => { const opts = Object.assign({ - detail: true + detail: true, + skipHide: false }, options); // Me @@ -207,52 +269,6 @@ export const pack = async ( if (!_note) throw `invalid note arg ${note}`; - let hide = false; - - // visibility が private かつ投稿者のIDが自分のIDではなかったら非表示 - if (_note.visibility == 'private' && (meId == null || !meId.equals(_note.userId))) { - hide = true; - } - - // visibility が specified かつ自分が指定されていなかったら非表示 - if (_note.visibility == 'specified') { - if (meId == null) { - hide = true; - } else if (meId.equals(_note.userId)) { - hide = false; - } else { - // 指定されているかどうか - const specified = _note.visibleUserIds.some((id: mongo.ObjectID) => id.equals(meId)); - - if (specified) { - hide = false; - } else { - hide = true; - } - } - } - - // visibility が followers かつ自分が投稿者のフォロワーでなかったら非表示 - if (_note.visibility == 'followers') { - if (meId == null) { - hide = true; - } else if (meId.equals(_note.userId)) { - hide = false; - } else { - // フォロワーかどうか - const following = await Following.findOne({ - followeeId: _note.userId, - followerId: meId - }); - - if (following == null) { - hide = true; - } else { - hide = false; - } - } - } - const id = _note._id; // Rename _id to id @@ -274,7 +290,7 @@ export const pack = async ( } // Populate files - _note.files = hide ? [] : Promise.all(_note.fileIds.map((fileId: mongo.ObjectID) => + _note.files = Promise.all(_note.fileIds.map((fileId: mongo.ObjectID) => packFile(fileId) )); @@ -304,7 +320,7 @@ export const pack = async ( } // Poll - if (meId && _note.poll && !hide) { + if (meId && _note.poll) { _note.poll = (async poll => { const vote = await PollVote .findOne({ @@ -349,15 +365,8 @@ export const pack = async ( _note.text = _note.text.replace(/な/g, 'にゃ').replace(/ナ/g, 'ニャ').replace(/ナ/g, 'ニャ'); } - if (hide) { - _note.fileIds = []; - _note.text = null; - _note.poll = null; - _note.cw = null; - _note.tags = []; - _note.tagsLower = []; - _note.geo = null; - _note.isHidden = true; + if (!opts.skipHide) { + await hideNote(_note, meId); } return _note; diff --git a/src/services/note/create.ts b/src/services/note/create.ts index 7062bc481b..ede3a01014 100644 --- a/src/services/note/create.ts +++ b/src/services/note/create.ts @@ -447,6 +447,11 @@ async function publishToUserLists(note: INote, noteObj: any) { } async function publishToFollowers(note: INote, noteObj: any, user: IUser, noteActivity: any) { + const detailPackedNote = await pack(note, null, { + detail: true, + skipHide: true + }); + const followers = await Following.find({ followeeId: note.userId }); @@ -465,10 +470,10 @@ async function publishToFollowers(note: INote, noteObj: any, user: IUser, noteAc } // Publish event to followers stream - publishUserStream(following.followerId, 'note', noteObj); + publishUserStream(following.followerId, 'note', detailPackedNote); if (isRemoteUser(user) || note.visibility != 'public') { - publishHybridTimelineStream(following.followerId, noteObj); + publishHybridTimelineStream(following.followerId, detailPackedNote); } } else { // フォロワーがリモートユーザーかつ投稿者がローカルユーザーなら投稿を配信 -- cgit v1.3.1-freya From b24f368d3f207554735e4edc5c5a2b7e16a6d281 Mon Sep 17 00:00:00 2001 From: "Acid Chicken (硫酸鶏)" Date: Mon, 10 Sep 2018 18:02:46 +0900 Subject: サロゲートペアを字数にカウントしないようにする (#2661) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Update post-form.vue * Update messaging-message.ts * Update post-form.vue * Update note.ts * Update post-form.vue refs: https://github.com/syuilo/misskey/pull/2661#issuecomment-419579444 * Update post-form.vue refs: https://github.com/syuilo/misskey/pull/2661#issuecomment-419579444 * Update messaging-message.ts refs: https://github.com/syuilo/misskey/pull/2661#issuecomment-419579444 * Update note.ts refs: https://github.com/syuilo/misskey/pull/2661#issuecomment-419579444 * Update post-form.vue refs: https://github.com/syuilo/misskey/pull/2661#discussion_r216175581 * Update post-form.vue * Update post-form.vue refs: https://github.com/syuilo/misskey/pull/2661#discussion_r216242002 * Update post-form.vue refs: https://github.com/syuilo/misskey/pull/2661#discussion_r216242105 --- src/client/app/desktop/views/components/post-form.vue | 9 +++++++-- src/client/app/mobile/views/components/post-form.vue | 7 ++++++- src/models/messaging-message.ts | 3 ++- src/models/note.ts | 5 +++-- 4 files changed, 18 insertions(+), 6 deletions(-) (limited to 'src/models') diff --git a/src/client/app/desktop/views/components/post-form.vue b/src/client/app/desktop/views/components/post-form.vue index fd489283f1..483a51b0ae 100644 --- a/src/client/app/desktop/views/components/post-form.vue +++ b/src/client/app/desktop/views/components/post-form.vue @@ -45,7 +45,7 @@ %fa:envelope% %fa:lock% -

    {{ 1000 - text.length }}

    +

    {{ 1000 - this.trimmedLength(text) }}

    @@ -63,6 +63,7 @@ import MkVisibilityChooser from '../../../common/views/components/visibility-cho import parse from '../../../../../mfm/parse'; import { host } from '../../../config'; import { erase } from '../../../../../prelude/array'; +import { length } from 'stringz'; import parseAcct from '../../../../../misc/acct/parse'; export default Vue.extend({ @@ -147,7 +148,7 @@ export default Vue.extend({ canPost(): boolean { return !this.posting && (1 <= this.text.length || 1 <= this.files.length || this.poll || this.renote) && - (this.text.trim().length <= 1000); + (length(this.text.trim()) <= 1000); } }, @@ -199,6 +200,10 @@ export default Vue.extend({ }, methods: { + trimmedLength(text: string) { + return length(text.trim()); + }, + addTag(tag: string) { insertTextAtCursor(this.$refs.text, ` #${tag} `); }, diff --git a/src/client/app/mobile/views/components/post-form.vue b/src/client/app/mobile/views/components/post-form.vue index 7e6a4c38e1..be20b11f14 100644 --- a/src/client/app/mobile/views/components/post-form.vue +++ b/src/client/app/mobile/views/components/post-form.vue @@ -4,7 +4,7 @@
    - {{ 1000 - text.length }} + {{ 1000 - trimmedLength(text) }} %fa:map-marker-alt%
    @@ -60,6 +60,7 @@ import getFace from '../../../common/scripts/get-face'; import parse from '../../../../../mfm/parse'; import { host } from '../../../config'; import { erase } from '../../../../../prelude/array'; +import { length } from 'stringz'; import parseAcct from '../../../../../misc/acct/parse'; export default Vue.extend({ @@ -180,6 +181,10 @@ export default Vue.extend({ }, methods: { + trimmedLength(text: string) { + return length(text.trim()); + }, + addTag(tag: string) { insertTextAtCursor(this.$refs.text, ` #${tag} `); }, diff --git a/src/models/messaging-message.ts b/src/models/messaging-message.ts index f46abd506d..d778164de0 100644 --- a/src/models/messaging-message.ts +++ b/src/models/messaging-message.ts @@ -4,6 +4,7 @@ import { pack as packUser } from './user'; import { pack as packFile } from './drive-file'; import db from '../db/mongodb'; import MessagingHistory, { deleteMessagingHistory } from './messaging-history'; +import { length } from 'stringz'; const MessagingMessage = db.get('messagingMessages'); export default MessagingMessage; @@ -19,7 +20,7 @@ export interface IMessagingMessage { } export function isValidText(text: string): boolean { - return text.length <= 1000 && text.trim() != ''; + return length(text.trim()) <= 1000 && text.trim() != ''; } /** diff --git a/src/models/note.ts b/src/models/note.ts index 624bdbdfe8..6530d0b324 100644 --- a/src/models/note.ts +++ b/src/models/note.ts @@ -2,6 +2,7 @@ import * as mongo from 'mongodb'; const deepcopy = require('deepcopy'); import rap from '@prezzemolo/rap'; import db from '../db/mongodb'; +import { length } from 'stringz'; import { IUser, pack as packUser } from './user'; import { pack as packApp } from './app'; import PollVote, { deletePollVote } from './poll-vote'; @@ -24,11 +25,11 @@ Note.createIndex({ export default Note; export function isValidText(text: string): boolean { - return text.length <= 1000 && text.trim() != ''; + return length(text.trim()) <= 1000 && text.trim() != ''; } export function isValidCw(text: string): boolean { - return text.length <= 100; + return length(text.trim()) <= 100; } export type INote = { -- cgit v1.3.1-freya From 046976dffc1aa8bc02259ab4a65e74b1216a0ec3 Mon Sep 17 00:00:00 2001 From: syuilo Date: Wed, 12 Sep 2018 02:48:19 +0900 Subject: Resolve #2691 --- .../app/desktop/views/components/timeline.vue | 11 +- .../desktop/views/pages/admin/admin.dashboard.vue | 35 ++++-- src/client/app/mobile/views/pages/home.vue | 11 +- src/models/meta.ts | 1 + src/server/api/endpoints/admin/update-meta.ts | 10 ++ src/server/api/endpoints/meta.ts | 1 + src/stream.ts | 119 ++++++++++++++------- 7 files changed, 134 insertions(+), 54 deletions(-) (limited to 'src/models') diff --git a/src/client/app/desktop/views/components/timeline.vue b/src/client/app/desktop/views/components/timeline.vue index 52a7753438..8d72016f22 100644 --- a/src/client/app/desktop/views/components/timeline.vue +++ b/src/client/app/desktop/views/components/timeline.vue @@ -2,8 +2,8 @@
    %fa:home% %i18n:@home% - %fa:R comments% %i18n:@local% - %fa:share-alt% %i18n:@hybrid% + %fa:R comments% %i18n:@local% + %fa:share-alt% %i18n:@hybrid% %fa:globe% %i18n:@global% %fa:list% {{ list.title }} @@ -29,7 +29,8 @@ export default Vue.extend({ data() { return { src: 'home', - list: null + list: null, + enableLocalTimeline: false }; }, @@ -44,6 +45,10 @@ export default Vue.extend({ }, created() { + (this as any).os.getMeta().then(meta => { + this.enableLocalTimeline = !meta.disableLocalTimeline; + }); + if (this.$store.state.device.tl) { this.src = this.$store.state.device.tl.src; if (this.src == 'list') { diff --git a/src/client/app/desktop/views/pages/admin/admin.dashboard.vue b/src/client/app/desktop/views/pages/admin/admin.dashboard.vue index ebb54d782e..c86c30db17 100644 --- a/src/client/app/desktop/views/pages/admin/admin.dashboard.vue +++ b/src/client/app/desktop/views/pages/admin/admin.dashboard.vue @@ -1,22 +1,34 @@ @@ -33,6 +45,7 @@ export default Vue.extend({ return { stats: null, disableRegistration: false, + disableLocalTimeline: false, inviteCode: null, connection: null, connectionId: null @@ -44,6 +57,7 @@ export default Vue.extend({ (this as any).os.getMeta().then(meta => { this.disableRegistration = meta.disableRegistration; + this.disableLocalTimeline = meta.disableLocalTimeline; }); (this as any).api('stats').then(stats => { @@ -61,7 +75,8 @@ export default Vue.extend({ }, updateMeta() { (this as any).api('admin/update-meta', { - disableRegistration: this.disableRegistration + disableRegistration: this.disableRegistration, + disableLocalTimeline: this.disableLocalTimeline }); } } @@ -97,4 +112,8 @@ export default Vue.extend({ border solid 1px #eee border-radius: 8px + > .form + > div + border-bottom solid 1px #eee + diff --git a/src/client/app/mobile/views/pages/home.vue b/src/client/app/mobile/views/pages/home.vue index 706c9cd28b..333ca1a7a1 100644 --- a/src/client/app/mobile/views/pages/home.vue +++ b/src/client/app/mobile/views/pages/home.vue @@ -24,8 +24,8 @@
    %fa:home% %i18n:@home% - %fa:R comments% %i18n:@local% - %fa:share-alt% %i18n:@hybrid% + %fa:R comments% %i18n:@local% + %fa:share-alt% %i18n:@hybrid% %fa:globe% %i18n:@global% @@ -46,6 +47,12 @@ export default Vue.extend({ birthday: null, }; }, + computed: { + alwaysMarkNsfw: { + get() { return this.$store.state.i.settings.alwaysMarkNsfw; }, + set(value) { (this as any).api('i/update', { alwaysMarkNsfw: value }); } + }, + }, created() { this.name = this.$store.state.i.name || ''; this.location = this.$store.state.i.profile.location; diff --git a/src/client/app/mobile/views/pages/settings/settings.profile.vue b/src/client/app/mobile/views/pages/settings/settings.profile.vue index 6f5ac9ae93..127f531902 100644 --- a/src/client/app/mobile/views/pages/settings/settings.profile.vue +++ b/src/client/app/mobile/views/pages/settings/settings.profile.vue @@ -49,6 +49,7 @@
    %i18n:@is-cat% + %i18n:common.always-mark-nsfw%
    @@ -85,6 +86,13 @@ export default Vue.extend({ }; }, + computed: { + alwaysMarkNsfw: { + get() { return this.$store.state.i.settings.alwaysMarkNsfw; }, + set(value) { (this as any).api('i/update', { alwaysMarkNsfw: value }); } + }, + }, + created() { this.name = this.$store.state.i.name || ''; this.username = this.$store.state.i.username; diff --git a/src/models/user.ts b/src/models/user.ts index 8f3fbbdc8f..64197c91c2 100644 --- a/src/models/user.ts +++ b/src/models/user.ts @@ -102,7 +102,10 @@ export interface ILocalUser extends IUserBase { twoFactorEnabled: boolean; twoFactorTempSecret?: string; clientSettings: any; - settings: any; + settings: { + autoWatch: boolean; + alwaysMarkNsfw?: boolean; + }; hasUnreadNotification: boolean; hasUnreadMessagingMessage: boolean; } diff --git a/src/server/api/endpoints/drive/files/create.ts b/src/server/api/endpoints/drive/files/create.ts index dfbd11d0c2..4b5ffa90e0 100644 --- a/src/server/api/endpoints/drive/files/create.ts +++ b/src/server/api/endpoints/drive/files/create.ts @@ -31,8 +31,8 @@ export const meta = { } }), - isSensitive: $.bool.optional.note({ - default: false, + isSensitive: $.bool.optional.nullable.note({ + default: null, desc: { 'ja-JP': 'このメディアが「閲覧注意」(NSFW)かどうか', 'en-US': 'Whether this media is NSFW' diff --git a/src/server/api/endpoints/i/update.ts b/src/server/api/endpoints/i/update.ts index 6aa4cc1148..c1be0b6ebc 100644 --- a/src/server/api/endpoints/i/update.ts +++ b/src/server/api/endpoints/i/update.ts @@ -84,6 +84,12 @@ export const meta = { 'ja-JP': '投稿の自動ウォッチをするか否か' } }), + + alwaysMarkNsfw: $.bool.optional.note({ + desc: { + 'ja-JP': 'アップロードするメディアをデフォルトで「閲覧注意」として設定するか' + } + }), } }; @@ -106,6 +112,7 @@ export default async (params: any, user: ILocalUser, app: IApp) => new Promise(a if (typeof ps.isBot == 'boolean') updates.isBot = ps.isBot; if (typeof ps.isCat == 'boolean') updates.isCat = ps.isCat; if (typeof ps.autoWatch == 'boolean') updates['settings.autoWatch'] = ps.autoWatch; + if (typeof ps.alwaysMarkNsfw == 'boolean') updates['settings.alwaysMarkNsfw'] = ps.alwaysMarkNsfw; if (ps.avatarId) { const avatar = await DriveFile.findOne({ diff --git a/src/services/drive/add-file.ts b/src/services/drive/add-file.ts index 828ebcbb94..666a6ca742 100644 --- a/src/services/drive/add-file.ts +++ b/src/services/drive/add-file.ts @@ -153,7 +153,7 @@ export default async function( isLink: boolean = false, url: string = null, uri: string = null, - sensitive = false + sensitive: boolean = null ): Promise { // Calc md5 hash const calcHash = new Promise((res, rej) => { @@ -329,7 +329,13 @@ export default async function( properties: properties, withoutChunks: isLink, isRemote: isLink, - isSensitive: sensitive + isSensitive: (sensitive !== null && sensitive !== undefined) + ? sensitive + : isLocalUser(user) + ? user.settings.alwaysMarkNsfw + ? true + : false + : false } as IMetadata; if (url !== null) { -- cgit v1.3.1-freya From c985fed3e43bae05f9e6e854f651f49f2bc3e83a Mon Sep 17 00:00:00 2001 From: syuilo Date: Sat, 15 Sep 2018 05:40:58 +0900 Subject: Resolve #2328 --- locales/ja-JP.yml | 4 + package.json | 1 + src/client/app/desktop/views/components/charts.vue | 103 ++++++++++++++++++++- src/models/stats.ts | 26 ++++++ src/server/api/endpoints/chart.ts | 21 +++++ src/server/index.ts | 25 +++++ src/services/update-chart.ts | 22 +++++ 7 files changed, 200 insertions(+), 2 deletions(-) (limited to 'src/models') diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index 5257d616c4..72dc88b7b3 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -518,6 +518,7 @@ desktop/views/components/charts.vue: notes: "投稿" users: "ユーザー" drive: "ドライブ" + network: "ネットワーク" charts: notes: "投稿の増減 (統合)" local-notes: "投稿の増減 (ローカル)" @@ -529,6 +530,9 @@ desktop/views/components/charts.vue: drive-total: "ドライブ使用量の累計" drive-files: "ドライブのファイル数の増減" drive-files-total: "ドライブのファイル数の累計" + network-requests: "リクエスト" + network-time: "応答時間" + network-usage: "通信量" desktop/views/components/choose-file-from-drive-window.vue: choose-file: "ファイル選択中" diff --git a/package.json b/package.json index 4d2a4b285b..4b9eaf9e22 100644 --- a/package.json +++ b/package.json @@ -179,6 +179,7 @@ "redis": "2.8.0", "request": "2.88.0", "request-promise-native": "1.0.5", + "request-stats": "3.0.0", "rimraf": "2.6.2", "rndstr": "1.0.0", "s-age": "1.1.2", diff --git a/src/client/app/desktop/views/components/charts.vue b/src/client/app/desktop/views/components/charts.vue index c4e92e429f..6514cdf788 100644 --- a/src/client/app/desktop/views/components/charts.vue +++ b/src/client/app/desktop/views/components/charts.vue @@ -19,6 +19,11 @@ + + + + +
    %i18n:@per-day% | %i18n:@per-hour% @@ -41,7 +46,10 @@ const colors = { localPlus: 'rgb(52, 178, 118)', remotePlus: 'rgb(158, 255, 209)', localMinus: 'rgb(255, 97, 74)', - remoteMinus: 'rgb(255, 149, 134)' + remoteMinus: 'rgb(255, 149, 134)', + + incoming: 'rgb(52, 178, 118)', + outgoing: 'rgb(255, 97, 74)', }; const rgba = (color: string): string => { @@ -75,6 +83,9 @@ export default Vue.extend({ case 'drive-total': return this.driveTotalChart(); case 'drive-files': return this.driveFilesChart(); case 'drive-files-total': return this.driveFilesTotalChart(); + case 'network-requests': return this.networkRequestsChart(); + case 'network-time': return this.networkTimeChart(); + case 'network-usage': return this.networkUsageChart(); } }, @@ -544,7 +555,95 @@ export default Vue.extend({ } } }]; - } + }, + + networkRequestsChart(): any { + const data = this.stats.slice().reverse().map(x => ({ + date: new Date(x.date), + requests: x.network.requests + })); + + return [{ + datasets: [{ + label: 'Requests', + fill: true, + backgroundColor: rgba(colors.localPlus), + borderColor: colors.localPlus, + borderWidth: 2, + pointBackgroundColor: '#fff', + lineTension: 0, + data: data.map(x => ({ t: x.date, y: x.requests })) + }] + }]; + }, + + networkTimeChart(): any { + const data = this.stats.slice().reverse().map(x => ({ + date: new Date(x.date), + time: x.network.requests != 0 ? (x.network.totalTime / x.network.requests) : 0, + })); + + return [{ + datasets: [{ + label: 'Avg time (ms)', + fill: true, + backgroundColor: rgba(colors.localPlus), + borderColor: colors.localPlus, + borderWidth: 2, + pointBackgroundColor: '#fff', + lineTension: 0, + data: data.map(x => ({ t: x.date, y: x.time })) + }] + }]; + }, + + networkUsageChart(): any { + const data = this.stats.slice().reverse().map(x => ({ + date: new Date(x.date), + incoming: x.network.incomingBytes, + outgoing: x.network.outgoingBytes + })); + + return [{ + datasets: [{ + label: 'Incoming', + fill: true, + backgroundColor: rgba(colors.incoming), + borderColor: colors.incoming, + borderWidth: 2, + pointBackgroundColor: '#fff', + lineTension: 0, + data: data.map(x => ({ t: x.date, y: x.incoming })) + }, { + label: 'Outgoing', + fill: true, + backgroundColor: rgba(colors.outgoing), + borderColor: colors.outgoing, + borderWidth: 2, + pointBackgroundColor: '#fff', + lineTension: 0, + data: data.map(x => ({ t: x.date, y: x.outgoing })) + }] + }, { + scales: { + yAxes: [{ + ticks: { + callback: value => { + return Vue.filter('bytes')(value, 1); + } + } + }] + }, + tooltips: { + callbacks: { + label: (tooltipItem, data) => { + const label = data.datasets[tooltipItem.datasetIndex].label || ''; + return `${label}: ${Vue.filter('bytes')(tooltipItem.yLabel, 1)}`; + } + } + } + }]; + }, } }); diff --git a/src/models/stats.ts b/src/models/stats.ts index d496f2c480..c4c838caeb 100644 --- a/src/models/stats.ts +++ b/src/models/stats.ts @@ -204,4 +204,30 @@ export interface IStats { decSize: number; }; }; + + /** + * ネットワークに関する統計 + */ + network: { + /** + * サーバーへのリクエスト数 + */ + requests: number; + + /** + * 応答時間の合計 + * TIP: (totalTime / requests) でひとつのリクエストに平均でどれくらいの時間がかかったか知れる + */ + totalTime: number; + + /** + * 合計受信データ量 + */ + incomingBytes: number; + + /** + * 合計送信データ量 + */ + outgoingBytes: number; + }; } diff --git a/src/server/api/endpoints/chart.ts b/src/server/api/endpoints/chart.ts index 7da970131e..3b1a3b56fc 100644 --- a/src/server/api/endpoints/chart.ts +++ b/src/server/api/endpoints/chart.ts @@ -6,6 +6,15 @@ type Omit = Pick>; function migrateStats(stats: IStats[]) { stats.forEach(stat => { + if (stat.network == null) { + stat.network = { + requests: 0, + totalTime: 0, + incomingBytes: 0, + outgoingBytes: 0 + }; + } + const isOldData = stat.users.local.inc == null || stat.users.local.dec == null || @@ -180,6 +189,12 @@ export default (params: any) => new Promise(async (res, rej) => { decCount: 0, decSize: 0 } + }, + network: { + requests: 0, + totalTime: 0, + incomingBytes: 0, + outgoingBytes: 0 } }); } else { @@ -236,6 +251,12 @@ export default (params: any) => new Promise(async (res, rej) => { decCount: 0, decSize: 0 } + }, + network: { + requests: 0, + totalTime: 0, + incomingBytes: 0, + outgoingBytes: 0 } }); } diff --git a/src/server/index.ts b/src/server/index.ts index f1fcf58c8d..dc60b0d9ec 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -11,11 +11,13 @@ import * as Router from 'koa-router'; import * as mount from 'koa-mount'; import * as compress from 'koa-compress'; import * as logger from 'koa-logger'; +const requestStats = require('request-stats'); //const slow = require('koa-slow'); import activityPub from './activitypub'; import webFinger from './webfinger'; import config from '../config'; +import { updateNetworkStats } from '../services/update-chart'; // Init app const app = new Koa(); @@ -81,4 +83,27 @@ export default () => new Promise(resolve => { // Listen server.listen(config.port, resolve); + + //#region Network stats + let queue: any[] = []; + + requestStats(server, (stats: any) => { + if (stats.ok) { + queue.push(stats); + } + }); + + // Bulk write + setInterval(() => { + if (queue.length == 0) return; + + const requests = queue.length; + const time = queue.reduce((a, b) => a + b.time, 0); + const incomingBytes = queue.reduce((a, b) => a + b.req.bytes, 0); + const outgoingBytes = queue.reduce((a, b) => a + b.res.bytes, 0); + queue = []; + + updateNetworkStats(requests, time, incomingBytes, outgoingBytes); + }, 5000); + //#endregion }); diff --git a/src/services/update-chart.ts b/src/services/update-chart.ts index 1f8da6be9f..78834ba601 100644 --- a/src/services/update-chart.ts +++ b/src/services/update-chart.ts @@ -96,6 +96,12 @@ async function getCurrentStats(span: 'day' | 'hour'): Promise { decCount: 0, decSize: 0 } + }, + network: { + requests: 0, + totalTime: 0, + incomingBytes: 0, + outgoingBytes: 0 } }; @@ -161,6 +167,12 @@ async function getCurrentStats(span: 'day' | 'hour'): Promise { decCount: 0, decSize: 0 } + }, + network: { + requests: 0, + totalTime: 0, + incomingBytes: 0, + outgoingBytes: 0 } }; @@ -243,3 +255,13 @@ export async function updateDriveStats(file: IDriveFile, isAdditional: boolean) await update(inc); } + +export async function updateNetworkStats(requests: number, time: number, incomingBytes: number, outgoingBytes: number) { + const inc = {} as any; + inc['network.requests'] = requests; + inc['network.totalTime'] = time; + inc['network.incomingBytes'] = incomingBytes; + inc['network.outgoingBytes'] = outgoingBytes; + + await update(inc); +} -- cgit v1.3.1-freya