diff --git a/locales/ja.yml b/locales/ja.yml index 64ee00dba..da751c9e3 100644 --- a/locales/ja.yml +++ b/locales/ja.yml @@ -254,6 +254,9 @@ common/views/widgets/posts-monitor.vue: title: "投稿チャート" toggle: "表示を切り替え" +common/views/widgets/hashtags.vue: + title: "ハッシュタグ" + common/views/widgets/server.vue: title: "サーバー情報" toggle: "表示を切り替え" diff --git a/src/client/app/common/views/widgets/hashtags.chart.vue b/src/client/app/common/views/widgets/hashtags.chart.vue new file mode 100644 index 000000000..19b56ef28 --- /dev/null +++ b/src/client/app/common/views/widgets/hashtags.chart.vue @@ -0,0 +1,78 @@ + + + diff --git a/src/client/app/common/views/widgets/hashtags.vue b/src/client/app/common/views/widgets/hashtags.vue new file mode 100644 index 000000000..c4647ee0f --- /dev/null +++ b/src/client/app/common/views/widgets/hashtags.vue @@ -0,0 +1,100 @@ + + + + + diff --git a/src/client/app/common/views/widgets/index.ts b/src/client/app/common/views/widgets/index.ts index 0190393ba..7d548ef35 100644 --- a/src/client/app/common/views/widgets/index.ts +++ b/src/client/app/common/views/widgets/index.ts @@ -13,6 +13,7 @@ import wSlideshow from './slideshow.vue'; import wTips from './tips.vue'; import wDonation from './donation.vue'; import wNav from './nav.vue'; +import wHashtags from './hashtags.vue'; Vue.component('mkw-analog-clock', wAnalogClock); Vue.component('mkw-nav', wNav); @@ -27,3 +28,4 @@ Vue.component('mkw-posts-monitor', wPostsMonitor); Vue.component('mkw-memo', wMemo); Vue.component('mkw-rss', wRss); Vue.component('mkw-version', wVersion); +Vue.component('mkw-hashtags', wHashtags); diff --git a/src/client/app/desktop/views/components/home.vue b/src/client/app/desktop/views/components/home.vue index cac1fd935..8774aada6 100644 --- a/src/client/app/desktop/views/components/home.vue +++ b/src/client/app/desktop/views/components/home.vue @@ -23,6 +23,7 @@ + diff --git a/src/client/app/desktop/views/pages/deck/deck.widgets-column.vue b/src/client/app/desktop/views/pages/deck/deck.widgets-column.vue index 2a3a2472d..a41bf8c3c 100644 --- a/src/client/app/desktop/views/pages/deck/deck.widgets-column.vue +++ b/src/client/app/desktop/views/pages/deck/deck.widgets-column.vue @@ -23,6 +23,7 @@ + diff --git a/src/client/app/mobile/views/pages/widgets.vue b/src/client/app/mobile/views/pages/widgets.vue index ea8580b4d..294c80e7a 100644 --- a/src/client/app/mobile/views/pages/widgets.vue +++ b/src/client/app/mobile/views/pages/widgets.vue @@ -15,6 +15,7 @@ + diff --git a/src/daemons/hashtags-stats-child.ts b/src/daemons/hashtags-stats-child.ts new file mode 100644 index 000000000..3f7f4d6e9 --- /dev/null +++ b/src/daemons/hashtags-stats-child.ts @@ -0,0 +1,60 @@ +import Note from '../models/note'; + +// 10分 +const interval = 1000 * 60 * 10; + +async function tick() { + const res = await Note.aggregate([{ + $match: { + createdAt: { + $gt: new Date(Date.now() - interval) + }, + tags: { + $exists: true, + $ne: [] + } + } + }, { + $unwind: '$tags' + }, { + $group: { + _id: '$tags', + count: { + $sum: 1 + } + } + }, { + $group: { + _id: null, + tags: { + $push: { + tag: '$_id', + count: '$count' + } + } + } + }, { + $project: { + _id: false, + tags: true + } + }]) as { + tags: Array<{ + tag: string; + count: number; + }> + }; + + const stats = res.tags + .sort((a, b) => a.count - b.count) + .map(tag => [tag.tag, tag.count]) + .slice(0, 10); + + console.log(stats); + + process.send(stats); +} + +tick(); + +setInterval(tick, interval); diff --git a/src/daemons/hashtags-stats.ts b/src/daemons/hashtags-stats.ts new file mode 100644 index 000000000..5ed028ac3 --- /dev/null +++ b/src/daemons/hashtags-stats.ts @@ -0,0 +1,20 @@ +import * as childProcess from 'child_process'; +import Xev from 'xev'; + +const ev = new Xev(); + +export default function() { + const log = []; + + const p = childProcess.fork(__dirname + '/hashtags-stats-child.js'); + + p.on('message', stats => { + ev.emit('hashtagsStats', stats); + log.push(stats); + if (log.length > 30) log.shift(); + }); + + ev.on('requestHashTagsStatsLog', id => { + ev.emit('hashtagsStatsLog:' + id, log); + }); +} diff --git a/src/notes-stats-child.ts b/src/daemons/notes-stats-child.ts similarity index 75% rename from src/notes-stats-child.ts rename to src/daemons/notes-stats-child.ts index 5f85a2a3c..7f54a36bf 100644 --- a/src/notes-stats-child.ts +++ b/src/daemons/notes-stats-child.ts @@ -1,8 +1,8 @@ -import Note from './models/note'; +import Note from '../models/note'; const interval = 5000; -setInterval(async () => { +async function tick() { const [all, local] = await Promise.all([Note.count({ createdAt: { $gte: new Date(Date.now() - interval) @@ -19,4 +19,8 @@ setInterval(async () => { }; process.send(stats); -}, interval); +} + +tick(); + +setInterval(tick, interval); diff --git a/src/notes-stats.ts b/src/daemons/notes-stats.ts similarity index 100% rename from src/notes-stats.ts rename to src/daemons/notes-stats.ts diff --git a/src/server-stats.ts b/src/daemons/server-stats.ts similarity index 88% rename from src/server-stats.ts rename to src/daemons/server-stats.ts index 7b0d4a857..140340250 100644 --- a/src/server-stats.ts +++ b/src/daemons/server-stats.ts @@ -5,6 +5,8 @@ import Xev from 'xev'; const ev = new Xev(); +const interval = 1000; + /** * Report server stats regularly */ @@ -15,7 +17,7 @@ export default function() { ev.emit('serverStatsLog:' + id, log); }); - setInterval(() => { + async function tick() { osUtils.cpuUsage(cpuUsage => { const disk = diskusage.checkSync(os.platform() == 'win32' ? 'c:' : '/'); const stats = { @@ -32,5 +34,9 @@ export default function() { log.push(stats); if (log.length > 50) log.shift(); }); - }, 1000); + } + + tick(); + + setInterval(tick, interval); } diff --git a/src/index.ts b/src/index.ts index 4a98b7564..35cf5a243 100644 --- a/src/index.ts +++ b/src/index.ts @@ -17,8 +17,8 @@ import ProgressBar from './utils/cli/progressbar'; import EnvironmentInfo from './utils/environmentInfo'; import MachineInfo from './utils/machineInfo'; import DependencyInfo from './utils/dependencyInfo'; -import serverStats from './server-stats'; -import notesStats from './notes-stats'; +import serverStats from './daemons/server-stats'; +import notesStats from './daemons/notes-stats'; import loadConfig from './config/load'; import { Config } from './config/types'; diff --git a/src/server/api/endpoints.ts b/src/server/api/endpoints.ts index 91e5298e7..5f0a020d6 100644 --- a/src/server/api/endpoints.ts +++ b/src/server/api/endpoints.ts @@ -628,6 +628,11 @@ const endpoints: Endpoint[] = [ withCredential: true }, + { + name: 'hashtags/trend', + withCredential: true + }, + { name: 'messaging/history', withCredential: true, diff --git a/src/server/api/endpoints/hashtags/trend.ts b/src/server/api/endpoints/hashtags/trend.ts new file mode 100644 index 000000000..c888a6cbb --- /dev/null +++ b/src/server/api/endpoints/hashtags/trend.ts @@ -0,0 +1,80 @@ +import Note from '../../../../models/note'; + +/** + * Get trends of hashtags + */ +module.exports = (params, user) => new Promise(async (res, rej) => { + const data = await Note.aggregate([{ + $match: { + createdAt: { + $gt: new Date(Date.now() - 1000 * 60 * 60) + }, + tags: { + $exists: true, + $ne: [] + } + } + }, { + $unwind: '$tags' + }, { + $group: { + _id: '$tags', + count: { + $sum: 1 + } + } + }, { + $group: { + _id: null, + tags: { + $push: { + tag: '$_id', + count: '$count' + } + } + } + }, { + $project: { + _id: false, + tags: true + } + }]) as Array<{ + tags: Array<{ + tag: string; + count: number; + }> + }>; + + if (data.length == 0) { + return res([]); + } + + const hots = data[0].tags + .sort((a, b) => b.count - a.count) + .map(tag => tag.tag) + .slice(0, 10); + + const countPromises: Array> = []; + + for (let i = 0; i < 10; i++) { + // 10分 + const interval = 1000 * 60 * 10; + + countPromises.push(Promise.all(hots.map(tag => Note.count({ + tags: tag, + createdAt: { + $lt: new Date(Date.now() - (interval * i)), + $gt: new Date(Date.now() - (interval * (i + 1))) + } + })))); + } + + const countsLog = await Promise.all(countPromises); + + const stats = hots.map((tag, i) => ({ + tag, + chart: countsLog.map(counts => counts[i]) + })); + + res(stats); +});