Implement #771
This commit is contained in:
		
							parent
							
								
									99db09b702
								
							
						
					
					
						commit
						5c37b9cef3
					
				
					 5 changed files with 172 additions and 0 deletions
				
			
		|  | @ -2,6 +2,10 @@ ChangeLog (Release Notes) | |||
| ========================= | ||||
| 主に notable な changes を書いていきます | ||||
| 
 | ||||
| unreleased | ||||
| ---------- | ||||
| * New: ユーザーページによく使うドメインを表示 (#771) | ||||
| 
 | ||||
| 2566 (2017/09/07) | ||||
| ----------------- | ||||
| * New: 投稿することの多いキーワードをユーザーページに表示する (#768) | ||||
|  |  | |||
|  | @ -498,6 +498,7 @@ mobile: | |||
|       images: "Images" | ||||
|       activity: "Activity" | ||||
|       keywords: "Keywords" | ||||
|       domains: "Domains" | ||||
|       followers-you-know: "Followers you know" | ||||
|       last-used-at: "Latest used at" | ||||
| 
 | ||||
|  | @ -512,6 +513,9 @@ mobile: | |||
|     mk-user-overview-keywords: | ||||
|       no-keywords: "No keywords" | ||||
| 
 | ||||
|     mk-user-overview-domains: | ||||
|       no-domains: "No domains" | ||||
| 
 | ||||
|     mk-user-overview-followers-you-know: | ||||
|       loading: "Loading" | ||||
|       no-users: "No users" | ||||
|  |  | |||
|  | @ -498,6 +498,7 @@ mobile: | |||
|       images: "画像" | ||||
|       activity: "アクティビティ" | ||||
|       keywords: "キーワード" | ||||
|       domains: "頻出ドメイン" | ||||
|       followers-you-know: "知り合いのフォロワー" | ||||
|       last-used-at: "最終ログイン" | ||||
| 
 | ||||
|  | @ -512,6 +513,9 @@ mobile: | |||
|     mk-user-overview-keywords: | ||||
|       no-keywords: "キーワードはありません(十分な数の投稿をしていない可能性があります)" | ||||
| 
 | ||||
|     mk-user-overview-domains: | ||||
|       no-domains: "よく表れるドメインは検出されませんでした" | ||||
| 
 | ||||
|     mk-user-overview-followers-you-know: | ||||
|       loading: "読み込み中" | ||||
|       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 }/> | ||||
| 		</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 }> | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue