Last active
September 26, 2017 08:41
-
-
Save khsk/0e8aaa498211a7ab2b1f6e37b93932b9 to your computer and use it in GitHub Desktop.
Qiitaの記事でフォロイーのいいねを表示するユーザースクリプト ref: http://qiita.com/khsk/items/4ac21bd483b9b2be653a
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name Qiita show followees iine | |
// @namespace khsk | |
// @description 記事に自分(ユーザー)がフォローandいいねしているユーザーを表示 | |
// @include http://qiita.com/*/items/* | |
// @include https://qiita.com/*/items/* | |
// @include http://qiita.com/*/private/* | |
// @include https://qiita.com/*/private/* | |
// @version 1 | |
// @grant none | |
// ==/UserScript== | |
console.time('Qiita show followees iine') | |
const userId = ''; // 主に自分 | |
const token = ''; // 各自で取得してください | |
const displayFolloweesNum = 5; // 表示数 -1 で無制限 | |
(async () => { | |
// 表示場所作成 | |
const iine = document.getElementsByClassName('list-inline ArticleMainHeader__users')[0] | |
const ciine = iine.cloneNode() | |
iine.parentElement.insertBefore(ciine, null) | |
// TODO いいね取得が長いから進捗を適当に書いているんだけれど、事前にheightを確保していないので、フォロイいねが無かったときなどにガタッとレイアウトが変わるかも。オプションめ。 | |
ciine.innerText = 'フォロイー取得中' | |
let followees = sessionStorage.getItem('followees') | |
if (!followees) { | |
followees = await fetchAll('https://qiita.com/api/v2/users/' + userId + '/followees?per_page=100',user => { | |
// 無いとは思うが、sessionStorage容量上限対策として削れるものは削る | |
delete user.description | |
delete user.facebook_id | |
delete user.followees_count | |
delete user.followers_count | |
delete user.github_login_name | |
delete user.items_count | |
delete user.linkedin_id | |
delete user.location | |
delete user.prganization | |
delete user.permanent_id | |
delete user.twitter_screen_name | |
delete user.website_url | |
delete user.organization | |
return user | |
}) | |
// 保存 | |
followees = JSON.stringify(followees) | |
sessionStorage.setItem('followees', followees) | |
} | |
followees = JSON.parse(followees) | |
ciine.innerText = 'いいね取得中' | |
// 記事のいいね取得 | |
const itemId = location.pathname.split('/').pop() | |
// 1ページ取得→即アイコン表示の方が止まっている疑惑が出にくいが… | |
let likes = await fetchAll('https://qiita.com/api/v2/items/' + itemId + '/likes?per_page=100', o => { return o.user.id }) | |
ciine.innerText = '' | |
// 要素作成ループ | |
// ここ100*1000程度はありそうでヤバめ | |
// いいねしたフォロイーだけ抽出するとまたループが必要なのでDOMまで作るか… | |
// breakしたいしsomeは嫌だからforにするか… | |
// フォロイーといいねどちらを削除していけば性能良いかわからん… | |
for (let i = 0, likesLength = likes.length, displayedNum = 0; i < likesLength; i++ ) { | |
if (!followees.length || (displayFolloweesNum > 0 && displayFolloweesNum == displayedNum) ) { | |
break | |
} | |
for (let j = 0, followeesLength = followees.length; j < followeesLength; j++) { | |
if (likes[i] == followees[j].id) { | |
let followee = followees[j] | |
let id = followee.id | |
let userTemplate = iine.firstChild.cloneNode(true) | |
userTemplate.dataHovercardTargetName = id | |
userTemplate.querySelector('a').href = '/' + id | |
let img = userTemplate.querySelector('img') | |
img.alt = followee.name || id | |
img.src = followee.profile_image_url | |
// ツールチップ追加 フォロイーはアイコンでわかるだろうからオマケ程度 | |
setPopover(userTemplate, followee) | |
ciine.insertBefore(userTemplate, null) | |
displayedNum++ | |
// 表示し終わったフォロイーは配列から削除しようと思っていたが、影響が出るような数百単位のいいねの場合はいいね取得がボトルネックに見えるので保留にする | |
} | |
} | |
} | |
})() | |
/////////// functions | |
// 今回は無名関数でなくfuntionを使ってみる。ライブラリコピペを下部に持ってくるので、自作関数も下にまとめたい→変数なら上に書かなければ行けないので関数で | |
// レスポンスがないとnextがわからないのでオーバーヘッドがだいぶある。 | |
// いいねやフォロイー総数から事前にページ数を計算すれば非同期並列で取得して最後にPromise.allで合算、で高速化できそうなんだけれども、手間がかかる。 | |
// 計算せずとも最初のlinkにラストページが表示されるのでその数までループ、なら総数取得は無しに出来るかも。初回1本+以降並列。 | |
// TODO 進捗表示のためにいちループごとに何か出来るコールバック作るか | |
async function fetchAll(url, callback = (v) =>{return v}) { | |
let next = url | |
let result = [] | |
while (next) { | |
let response = await fetch(next, { | |
headers: new Headers({ Authorization: 'Bearer ' + token,}), | |
}) | |
if (response.ok == false || response.status != 200) { | |
return result | |
} | |
next = li().parse(response.headers.get('Link')).next | |
let responseData = await response.json() | |
// ここデフォルト引数付けずに無いならmapしない方が性能いいけど | |
Array.prototype.push.apply(result, responseData.map(callback)) | |
} | |
return result | |
} | |
// フィードのポップアップスクリプトの関数を持ってきて調整 | |
function setPopover(elm, data) { | |
// Qiitaのhovercardはhovercard.jsではないのかもで諦め | |
// 最後に$(elm).tooltip() or popover()を実行するとタイトルがタグになる。謎。なので最初に設定 | |
// datasetでは効かないものがある | |
$(elm).popover({ | |
trigger : 'hover', | |
placement : 'bottom', // いいねは下付き | |
container : 'body', | |
html : true, | |
// 画像サイズ固定化や左配置のために、本家で使っている.hovervardクラスをデフォルトテンプレートに追加 | |
template : '<div class="popover" role="tooltip"><div class="popover-arrow"></div><h3 class="popover-title"></h3><div class="popover-content hovercard"></div></div>' | |
}) | |
elm.dataset.toggle = 'popover' | |
elm.dataset.originalTitle = 'Followee Info' | |
// hovercardっぽさのためにアイコン画像は今回は載せる | |
elm.dataset.content = '<img src="' + data.profile_image_url + '">' + '<br>\n' + (data.name || data.id) | |
// 改行対策にデフォルト値 + nowrap | |
elm.dataset.viewport = "{ selector: 'body', padding: 0 , white-space: nowrap,}" | |
} | |
/////////// 外部ライブラリ(コピペ) | |
// MITライセンス表示はこれで大丈夫かしら | |
/** | |
* Author : José F. Romaniello <jfromaniello@gmail.com> (http://joseoncode.com) | |
* License : MIT 2014 - JOSE F. ROMANIELLO https://opensource.org/licenses/mit-license.php | |
* URL : https://github.com/jfromaniello/li | |
**/ | |
function li () { | |
// compile regular expressions ahead of time for efficiency | |
var relsRegExp = /^;\s*([^"=]+)=(?:"([^"]+)"|([^";,]+)(?:[;,]|$))/; | |
var keysRegExp = /([^\s]+)/g; | |
var sourceRegExp = /^<([^>]*)>/; | |
var delimiterRegExp = /^\s*,\s*/; | |
return { | |
parse: function (linksHeader, options) { | |
var match; | |
var source; | |
var rels; | |
var extended = options && options.extended || false; | |
var links = []; | |
while (linksHeader) { | |
linksHeader = linksHeader.trim(); | |
// Parse `<link>` | |
source = sourceRegExp.exec(linksHeader); | |
if (!source) break; | |
var current = { | |
link: source[1] | |
}; | |
// Move cursor | |
linksHeader = linksHeader.slice(source[0].length); | |
// Parse `; attr=relation` and `; attr="relation"` | |
var nextDelimiter = linksHeader.match(delimiterRegExp); | |
while(linksHeader && (!nextDelimiter || nextDelimiter.index > 0)) { | |
match = relsRegExp.exec(linksHeader); | |
if (!match) break; | |
// Move cursor | |
linksHeader = linksHeader.slice(match[0].length); | |
nextDelimiter = linksHeader.match(delimiterRegExp); | |
if (match[1] === 'rel' || match[1] === 'rev') { | |
// Add either quoted rel or unquoted rel | |
rels = (match[2] || match[3]).split(/\s+/); | |
current[match[1]] = rels; | |
} else { | |
current[match[1]] = match[2] || match[3]; | |
} | |
} | |
links.push(current); | |
// Move cursor | |
linksHeader = linksHeader.replace(delimiterRegExp, ''); | |
} | |
if (!extended) { | |
return links.reduce(function(result, currentLink) { | |
if (currentLink.rel) { | |
currentLink.rel.forEach(function(rel) { | |
result[rel] = currentLink.link; | |
}); | |
} | |
return result; | |
}, {}); | |
} | |
return links; | |
}, | |
stringify: function (headerObject, callback) { | |
var result = ""; | |
for (var x in headerObject) { | |
result += '<' + headerObject[x] + '>; rel="' + x + '", '; | |
} | |
result = result.substring(0, result.length - 2); | |
return result; | |
} | |
}; | |
} | |
/////////////// | |
console.timeEnd('Qiita show followees iine') |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment