Implement #771
This commit is contained in:
parent
99db09b702
commit
5c37b9cef3
|
@ -2,6 +2,10 @@ ChangeLog (Release Notes)
|
||||||
=========================
|
=========================
|
||||||
主に notable な changes を書いていきます
|
主に notable な changes を書いていきます
|
||||||
|
|
||||||
|
unreleased
|
||||||
|
----------
|
||||||
|
* New: ユーザーページによく使うドメインを表示 (#771)
|
||||||
|
|
||||||
2566 (2017/09/07)
|
2566 (2017/09/07)
|
||||||
-----------------
|
-----------------
|
||||||
* New: 投稿することの多いキーワードをユーザーページに表示する (#768)
|
* New: 投稿することの多いキーワードをユーザーページに表示する (#768)
|
||||||
|
|
|
@ -498,6 +498,7 @@ mobile:
|
||||||
images: "Images"
|
images: "Images"
|
||||||
activity: "Activity"
|
activity: "Activity"
|
||||||
keywords: "Keywords"
|
keywords: "Keywords"
|
||||||
|
domains: "Domains"
|
||||||
followers-you-know: "Followers you know"
|
followers-you-know: "Followers you know"
|
||||||
last-used-at: "Latest used at"
|
last-used-at: "Latest used at"
|
||||||
|
|
||||||
|
@ -512,6 +513,9 @@ mobile:
|
||||||
mk-user-overview-keywords:
|
mk-user-overview-keywords:
|
||||||
no-keywords: "No keywords"
|
no-keywords: "No keywords"
|
||||||
|
|
||||||
|
mk-user-overview-domains:
|
||||||
|
no-domains: "No domains"
|
||||||
|
|
||||||
mk-user-overview-followers-you-know:
|
mk-user-overview-followers-you-know:
|
||||||
loading: "Loading"
|
loading: "Loading"
|
||||||
no-users: "No users"
|
no-users: "No users"
|
||||||
|
|
|
@ -498,6 +498,7 @@ mobile:
|
||||||
images: "画像"
|
images: "画像"
|
||||||
activity: "アクティビティ"
|
activity: "アクティビティ"
|
||||||
keywords: "キーワード"
|
keywords: "キーワード"
|
||||||
|
domains: "頻出ドメイン"
|
||||||
followers-you-know: "知り合いのフォロワー"
|
followers-you-know: "知り合いのフォロワー"
|
||||||
last-used-at: "最終ログイン"
|
last-used-at: "最終ログイン"
|
||||||
|
|
||||||
|
@ -512,6 +513,9 @@ mobile:
|
||||||
mk-user-overview-keywords:
|
mk-user-overview-keywords:
|
||||||
no-keywords: "キーワードはありません(十分な数の投稿をしていない可能性があります)"
|
no-keywords: "キーワードはありません(十分な数の投稿をしていない可能性があります)"
|
||||||
|
|
||||||
|
mk-user-overview-domains:
|
||||||
|
no-domains: "よく表れるドメインは検出されませんでした"
|
||||||
|
|
||||||
mk-user-overview-followers-you-know:
|
mk-user-overview-followers-you-know:
|
||||||
loading: "読み込み中"
|
loading: "読み込み中"
|
||||||
no-users: "知り合いのユーザーはいません"
|
no-users: "知り合いのユーザーはいません"
|
||||||
|
|
120
src/tools/analysis/extract-user-domains.ts
Normal file
120
src/tools/analysis/extract-user-domains.ts
Normal file
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
|
@ -240,6 +240,12 @@
|
||||||
<mk-user-overview-keywords user={ user }/>
|
<mk-user-overview-keywords user={ user }/>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</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 }>
|
<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>
|
<h2><i class="fa fa-users"></i>%i18n:mobile.tags.mk-user-overview.followers-you-know%</h2>
|
||||||
<div>
|
<div>
|
||||||
|
@ -579,6 +585,40 @@
|
||||||
</script>
|
</script>
|
||||||
</mk-user-overview-keywords>
|
</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>
|
<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>
|
<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 }>
|
<div if={ !initializing && users.length > 0 }>
|
||||||
|
|
Loading…
Reference in a new issue