summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorsyuilo <syuilotan@yahoo.co.jp>2017-09-08 22:10:25 +0900
committersyuilo <syuilotan@yahoo.co.jp>2017-09-08 22:10:25 +0900
commit5c37b9cef307bf795c4bc49dc7ab21de0c8ba97b (patch)
tree2b322eaebbb0ff5e3eafdf572014f49cd8cfa7a8 /src
parentMerge branch 'master' of https://github.com/syuilo/misskey (diff)
downloadmisskey-5c37b9cef307bf795c4bc49dc7ab21de0c8ba97b.tar.gz
misskey-5c37b9cef307bf795c4bc49dc7ab21de0c8ba97b.tar.bz2
misskey-5c37b9cef307bf795c4bc49dc7ab21de0c8ba97b.zip
Implement #771
Diffstat (limited to 'src')
-rw-r--r--src/tools/analysis/extract-user-domains.ts120
-rw-r--r--src/web/app/mobile/tags/user.tag40
2 files changed, 160 insertions, 0 deletions
diff --git a/src/tools/analysis/extract-user-domains.ts b/src/tools/analysis/extract-user-domains.ts
new file mode 100644
index 0000000000..bc120f5c17
--- /dev/null
+++ b/src/tools/analysis/extract-user-domains.ts
@@ -0,0 +1,120 @@
+import * as URL from 'url';
+
+import Post from '../../api/models/post';
+import User from '../../api/models/user';
+import parse from '../../api/common/text';
+
+process.on('unhandledRejection', console.dir);
+
+function tokenize(text: string) {
+ if (text == null) return [];
+
+ // パース
+ const ast = parse(text);
+
+ const domains = ast
+ // URLを抽出
+ .filter(t => t.type == 'url' || t.type == 'link')
+ .map(t => URL.parse(t.url).hostname);
+
+ return domains;
+}
+
+// Fetch all users
+User.find({}, {
+ fields: {
+ _id: true
+ }
+}).then(users => {
+ let i = -1;
+
+ const x = cb => {
+ if (++i == users.length) return cb();
+ extractDomainsOne(users[i]._id).then(() => x(cb), err => {
+ console.error(err);
+ setTimeout(() => {
+ i--;
+ x(cb);
+ }, 1000);
+ });
+ };
+
+ x(() => {
+ console.log('complete');
+ });
+});
+
+function extractDomainsOne(id) {
+ return new Promise(async (resolve, reject) => {
+ process.stdout.write(`extracting domains of ${id} ...`);
+
+ // Fetch recent posts
+ const recentPosts = await Post.find({
+ user_id: id,
+ text: {
+ $exists: true
+ }
+ }, {
+ sort: {
+ _id: -1
+ },
+ limit: 10000,
+ fields: {
+ _id: false,
+ text: true
+ }
+ });
+
+ // 投稿が少なかったら中断
+ if (recentPosts.length < 100) {
+ process.stdout.write(' >>> -\n');
+ return resolve();
+ }
+
+ const domains = {};
+
+ // Extract domains from recent posts
+ recentPosts.forEach(post => {
+ const domainsOfPost = tokenize(post.text);
+
+ domainsOfPost.forEach(domain => {
+ if (domains[domain]) {
+ domains[domain]++;
+ } else {
+ domains[domain] = 1;
+ }
+ });
+ });
+
+ // Calc peak
+ let peak = 0;
+ Object.keys(domains).forEach(domain => {
+ if (domains[domain] > peak) peak = domains[domain];
+ });
+
+ // Sort domains by frequency
+ const domainsSorted = Object.keys(domains).sort((a, b) => domains[b] - domains[a]);
+
+ // Lookup top 10 domains
+ const topDomains = domainsSorted.slice(0, 10);
+
+ process.stdout.write(' >>> ' + topDomains.join(', ') + '\n');
+
+ // Make domains object (includes weights)
+ const domainsObj = topDomains.map(domain => ({
+ domain: domain,
+ weight: domains[domain] / peak
+ }));
+
+ // Save
+ User.update({ _id: id }, {
+ $set: {
+ domains: domainsObj
+ }
+ }).then(() => {
+ resolve();
+ }, err => {
+ reject(err);
+ });
+ });
+}
diff --git a/src/web/app/mobile/tags/user.tag b/src/web/app/mobile/tags/user.tag
index ca777b8fd1..a323559218 100644
--- a/src/web/app/mobile/tags/user.tag
+++ b/src/web/app/mobile/tags/user.tag
@@ -240,6 +240,12 @@
<mk-user-overview-keywords user={ user }/>
</div>
</section>
+ <section class="domains">
+ <h2><i class="fa fa-globe"></i>%i18n:mobile.tags.mk-user-overview.domains%</h2>
+ <div>
+ <mk-user-overview-domains user={ user }/>
+ </div>
+ </section>
<section class="followers-you-know" if={ SIGNIN && I.id !== user.id }>
<h2><i class="fa fa-users"></i>%i18n:mobile.tags.mk-user-overview.followers-you-know%</h2>
<div>
@@ -579,6 +585,40 @@
</script>
</mk-user-overview-keywords>
+<mk-user-overview-domains>
+ <div if={ user.domains != null && user.domains.length > 1 }>
+ <virtual each={ domain in user.domains }>
+ <a style="opacity: { 0.5 + (domain.weight / 2) }">{ domain.domain }</a>
+ </virtual>
+ </div>
+ <p class="empty" if={ user.domains == null || user.domains.length == 0 }>%i18n:mobile.tags.mk-user-overview-domains.no-domains%</p>
+ <style>
+ :scope
+ display block
+
+ > div
+ padding 4px
+
+ > a
+ display inline-block
+ margin 4px
+ color #555
+
+ > .empty
+ margin 0
+ padding 16px
+ text-align center
+ color #aaa
+
+ > i
+ margin-right 4px
+
+ </style>
+ <script>
+ this.user = this.opts.user;
+ </script>
+</mk-user-overview-domains>
+
<mk-user-overview-followers-you-know>
<p class="initializing" if={ initializing }><i class="fa fa-spinner fa-pulse fa-fw"></i>%i18n:mobile.tags.mk-user-overview-followers-you-know.loading%<mk-ellipsis/></p>
<div if={ !initializing && users.length > 0 }>