Last active
November 5, 2016 01:05
-
-
Save nobodyplace/3905580 to your computer and use it in GitHub Desktop.
ニコニコ動画プレイヤーのUIをカスタマイズするGreasemonkey
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 NicoPlayer UI Customize | |
// @namespace http://1ni.co/greasemonkeys | |
// @description ニコニコ動画プレイヤーのUIをカスタマイズするGreasemonkey | |
// @version 3.0.2 | |
// @downloadURL https://gist.github.com/raw/3905580/nicoplayer_ui_customize.user.js | |
// @updateURL https://gist.github.com/raw/3905580/nicoplayer_ui_customize.user.js | |
// @include http://www.nicovideo.jp/watch/* | |
// @grant GM_getValue | |
// @grant GM_setValue | |
// @author Ippei "is" Suzuki | |
// @license MIT License; http://en.wikipedia.org/wiki/MIT_License | |
// ==/UserScript== | |
// Version History: | |
// 0.0.1 - 2012/05/04 リリース | |
// 0.1.0 - 2012/05/04 再生数/コメント数/マイリスト数を投稿者情報の下に | |
// 0.1.1 - 2012/05/04 投稿者名の見栄えを改善 | |
// 0.2.0 - 2012/05/05 投稿日時をファーストビューに表示 | |
// 0.2.1 - 2012/05/05 ページ遷移した場合に投稿者情報が更新されない問題を修正 | |
// 0.2.2 - 2012/05/05 ページ遷移した場合の様々な問題を修正 | |
// 0.2.3 - 2012/06/09 [69の日] 文字サイズの微調整など | |
// 0.2.4 - 2012/06/18 プレイヤー下のHTML変更などに対応 | |
// 0.2.5 - 2012/06/19 投稿者情報のCSSを微調整 | |
// 0.2.6 - 2012/08/22 コメント欄下の広告を削除 | |
// 0.3.0 - 2012/10/17 「Q」に対応 | |
// 1.0.0 - 2012/10/17 表示方法を変更、バージョンを1.0.0に変更 | |
// 1.0.1 - 2012/10/18 コード整形とMetadataの変更 | |
// 1.0.2 - 2012/10/18 ブログパーツとプロフィール情報の位置を変更、動画IDを変更 | |
// 1.0.3 - 2012/12/20 デザイン変更で実装された機能を削除/ブログパーツ表示場所の変更 | |
// 2.0.0 - 2012/12/26 コントロールチップの採用 | |
// 2.0.1 - 2012/12/27 ブログパーツ表示場所の変更 | |
// 2.0.2 - 2012/12/29 未ログイン時にもブログパーツを表示するように | |
// 2.0.3 - 2012/12/29 表示個所、マウスオーバー時の振る舞いを微調整 | |
// 2.0.4 - 2013/02/16 動画情報のURLがthumb_watchからthumbに変わっていたのに対応 | |
// 2.0.5 - 2013/05/22 動画情報のURLがthumbからthumb_watchに戻っていたのに対応 | |
// 2.0.6 - 2013/07/25 Qwatchの更新に対応 / 動画説明見出しの表示可否削除 | |
// 3.0.0 - 2016/11/04 動画視聴ページ HTML5版(β)に対応 | |
// 3.0.1 - 2016/11/05 動画読み込み時の挙動を調整 | |
// 3.0.2 - 2016/11/05 チャンネル動画に対応 | |
// Copyrights: | |
// コードの一部を@toriimiyukkiさんのZeroFixからいただいています。 | |
// @toriimiyukkiさん記述以外の部分については@nobdoyplaceに所属します。 | |
(function() { | |
// version | |
var isHtml5 = (document.getElementsByTagName('html')[0].lang == 'ja') ? true | |
: false; | |
// settings | |
var bp = GM_getValue('showBlogParts', 0); // 0,1 | |
var vt = GM_getValue('showVideoTitleOnBlogParts', 0); // 0,1 | |
var ok = 'data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%08%00%00%00%08%08%03%00%00%00%F3%D1N%B9%00%00%00%04gAMA%00%00%AF%C87%05%8A%E9%00%00%00%19tEXtSoftware%00Adobe%20ImageReadyq%C9e%3C%00%00%00%0CPLTE%C2%E1g%EE%F7%D4t%98%3B%FF%FF%FFRq%08%BF%00%00%00%04tRNS%FF%FF%FF%00%40*%A9%F4%00%00%00%2CIDATx%DA%2C%CAQ%12%00%00%04%02%D1%AC%FB%DFY%C1%CF%AB%91%FAO%07%1F(%94F%C9%C1%CD%FA%85b6%C4%1D%C7%1E%01%06%00%16%B3%00%9A2%07c%95%00%00%00%00IEND%AEB%60%82'; | |
var ng = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAICAMAAADz0U65AAAABGdBTUEAAK%2FINwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAAJUExURfpgApsAAP%2F%2F%2F7DQ0cQAAAADdFJOU%2F%2F%2FANfKDUEAAAAhSURBVHjaYmCCAgZkBiMQghiMDEAIEWHAEIGqgQCAAAMAD54AayU%2BhVYAAAAASUVORK5CYII%3D'; | |
var blogPartsId = 'blogPartsArea'; | |
var tipId = 'CustomizeControlTip'; | |
// utility | |
var d = document; | |
var $ = function(name) { | |
return document.querySelector(name); | |
} | |
var ins = function(e, p) { | |
p.insertBefore(e, p.firstChild); | |
} | |
var isLogined = ($('#suggest_login')) ? false : true; | |
var htmlEscape = function(str) { | |
var map = { | |
"<" : "<", | |
">" : ">", | |
"&" : "&", | |
"'" : "'", | |
"\"" : """, | |
" " : " ", | |
" " : " " | |
}; | |
var replaceStr = function(s) { | |
return map[s]; | |
}; | |
return str.replace(/<|>|&|'|"|\s| /g, replaceStr); | |
} | |
var videoHeader = (isHtml5) ? $('.TagContainer') : $('#videoHeader'); | |
// video data | |
var videoId, profile, icon, expand, titleArea, title, author, videoStats, videoDate; | |
var initVideoData = function() { | |
// HTML5バージョン | |
if (isHtml5) { | |
var apiData = JSON.parse($('#js-initial-watch-data').dataset.apiData); | |
videoId = apiData.video.id; // コミュニティ/チャンネル対策 | |
// profile = $('#userProfile').cloneNode(true); | |
icon = (apiData.owner) ? apiData.owner.iconURL : ''; | |
// expand = $('.videoDetailExpand'); | |
// titleArea = expand.querySelector('.videoHeaderTitle'); | |
title = apiData.video.title; | |
author = (apiData.owner) ? apiData.owner.nickname : ''; | |
// videoStats = $('#videoStats').cloneNode(true); | |
videoDate = apiData.video.postedDateTime; | |
// Flashバージョン | |
} else { | |
videoId = $('.dicIcon a').href.match(/\/v\/(.+)$/)[1]; // コミュニティ/チャンネル対策 | |
profile = $('#userProfile').cloneNode(true); | |
icon = profile.querySelector('.usericon'); | |
expand = $('.videoDetailExpand'); | |
titleArea = expand.querySelector('.videoHeaderTitle'); | |
title = titleArea.innerHTML; | |
author = profile.querySelector('.userName').innerHTML; | |
videoStats = $('#videoStats').cloneNode(true); | |
videoDate = $('#videoInfoHead').querySelector('.videoPostedAt').innerHTML; | |
} | |
} | |
var initVideoDataNotLogined = function() { | |
videoId = location.href.match(/\/watch\/(.+)$/)[1]; // コミュニティ/チャンネル対策 | |
title = d.getElementsByTagName('h1')[0].innerHTML; | |
// この処理に改善の余地あり | |
author = d.getElementsByTagName('strong')[3].innerHTML + ' さん'; | |
videoHeader = $('.content_312') | |
} | |
// タグを作成する | |
var el = function(e) { | |
return d.createElement(e); | |
} | |
// アイコンを変える | |
var changeIcon = function(obj, flag) { | |
obj.src = (flag) ? ok : ng; | |
obj.style.borderTop = (flag) ? 'solid 1px #666' : 'solid 1px white'; | |
obj.style.borderLeft = (flag) ? 'solid 1px #666' : 'solid 1px white'; | |
obj.style.borderRight = (flag) ? 'solid 2px #ccc' : 'solid 2px white'; | |
obj.style.borderBottom = (flag) ? 'solid 2px #ccc' : 'solid 2px white'; | |
obj.style.backgroundColor = (flag) ? 'white' : 'white'; | |
}; | |
// 処理 | |
// ブログパーツを削除する | |
var deleteBlogParts = function() { | |
var t = $('#' + blogPartsId); | |
// 既にある場合に削除 | |
if (t) { | |
t.parentNode.removeChild(t); | |
} | |
} | |
// ブログパーツを表示する | |
var showBlogParts = function() { | |
if (!videoId || !title) | |
return; | |
// 既にある場合には削除 | |
deleteBlogParts(); | |
if (bp) { | |
// 外部プレイヤー | |
var extPlayer = '<script type="text/javascript" src="http://ext.nicovideo.jp/thumb_watch/' | |
+ videoId | |
+ '"></script>' | |
+ '<noscript><a href="http://www.nicovideo.jp/watch/' | |
+ videoId | |
+ '">' + title + '</a></noscript>'; | |
// 動画情報 | |
var movieInfo = '<iframe width="312" height="176" src="http://ext.nicovideo.jp/thumb/' | |
+ videoId | |
+ '" scrolling="no" style="border:solid 1px #CCC;" frameborder="0"><a href="http://www.nicovideo.jp/watch/' | |
+ videoId + '">' + title + '</a></iframe>'; | |
// 表示するテキストを作成 | |
var asterisk = (vt) ? '**' + htmlEscape(title) + '(' + htmlEscape(author) | |
+ ')' + "\n" : ''; | |
var str = '<table cellspacing="0">' | |
+ '<tr><th class="font10">外部プレイヤー</th></tr>' | |
+ '<tr><td><form name="form_script"><input type="text" style="width: 278px;" readonly="true" name="script_code" onclick="javascript:document.form_script.script_code.focus(); document.form_script.script_code.select();" value="' | |
+ htmlEscape(extPlayer) | |
+ '"></form></td></tr>' | |
+ '<tr><th class="font10">動画情報</th></tr>' | |
+ '<tr><td><form name="form_iframe"><textarea style="width: 278px;" readonly="true" name="iframe_code" onclick="javascript:document.form_iframe.iframe_code.focus(); document.form_iframe.iframe_code.select();">' | |
+ asterisk + htmlEscape(movieInfo) + '</textarea></form></td></tr>' | |
+ '</table>'; | |
// 表示 | |
var d = el('div'); | |
d.id = blogPartsId; | |
d.style.padding = "10px"; | |
d.style.color = "#000"; | |
d.style.backgroundColor = "#ccc"; | |
d.style.fontSize = "x-small"; | |
d.style.width = "288px"; | |
if (isLogined) { | |
d.style.position = "absolute"; | |
if (isHtml5) { | |
d.style.right = "-250px"; | |
d.style.bottom = "44px"; | |
} else { | |
d.style.right = "-318px"; | |
d.style.bottom = "60px"; | |
} | |
} else { | |
d.style.marginBottom = "20px"; | |
} | |
d.style.opacity = "0.5"; | |
d.style.zIndex = "802"; | |
d.innerHTML = str; | |
d.addEventListener("mouseover", function() { | |
if (isLogined) { | |
if (isHtml5) { | |
d.style.right = "-200px"; | |
} else { | |
d.style.right = "-268px"; | |
} | |
} | |
d.style.opacity = "1"; | |
}, false); | |
d.addEventListener("mouseout", function() { | |
if (isHtml5) { | |
d.style.right = "-250px"; | |
} else { | |
d.style.right = "-318px"; | |
} | |
d.style.opacity = "0.5"; | |
}, false); | |
ins(d, videoHeader); | |
} | |
} | |
// コントロールチップを削除する | |
var deleteControlTip = function() { | |
var t = $('#' + tipId); | |
// 既にある場合に削除 | |
if (t) { | |
t.parentNode.removeChild(t); | |
} | |
} | |
// コントロールチップを表示する | |
var createControlTip = function() { | |
deleteControlTip(); | |
var div = el('div'); | |
div.id = tipId; | |
div.style.color = "#000"; | |
div.style.backgroundColor = "#ccc"; | |
div.style.fontSize = "x-small"; | |
div.style.position = "absolute"; | |
div.style.opacity = "0.5"; | |
div.style.width = "260px"; | |
div.style.padding = "10px"; | |
if (isHtml5) { | |
div.style.right = "-222px"; | |
div.style.bottom = "0px"; | |
} else { | |
div.style.right = "-290px"; | |
div.style.bottom = "12px"; | |
} | |
div.style.zIndex = "802"; | |
div.addEventListener("mouseover", function() { | |
if (isHtml5) { | |
div.style.right = "-172px"; | |
} else { | |
div.style.right = "-240px"; | |
} | |
div.style.opacity = "1"; | |
}, false); | |
d.addEventListener("mouseout", function() { | |
if (isHtml5) { | |
div.style.right = "-222px"; | |
} else { | |
div.style.right = "-290px"; | |
} | |
div.style.opacity = "0.5"; | |
}, false); | |
// button setting | |
var btn = el('img'); | |
btn.style.cursor = 'pointer'; | |
btn.style.padding = "5px"; | |
btn.style.margin = "0 2px 0 0"; | |
// button1: showBlogParts | |
var btn1 = btn.cloneNode(true); | |
btn1.title = 'ブログパーツを表示する'; | |
changeIcon(btn1, bp); | |
btn1.addEventListener('click', function(e) { | |
if (bp) { | |
bp = 0; | |
} else { | |
bp = 1; | |
} | |
showBlogParts(); | |
changeIcon(btn1, bp); | |
GM_setValue('showBlogParts', bp); | |
}, false); | |
div.appendChild(btn1); | |
// button2: showVideoTitleOnBlogParts | |
var btn2 = btn.cloneNode(true); | |
btn2.title = 'ブログパーツに動画タイトルを表示する'; | |
changeIcon(btn2, vt); | |
btn2.addEventListener('click', function(e) { | |
if (vt) { | |
vt = 0; | |
} else { | |
vt = 1; | |
} | |
showBlogParts(); | |
changeIcon(btn2, vt); | |
GM_setValue('showVideoTitleOnBlogParts', vt); | |
}, false); | |
div.appendChild(btn2); | |
// 表示 | |
videoHeader.appendChild(div); | |
} | |
// fire | |
var fire = function() { | |
if (isLogined) { | |
initVideoData(); | |
createControlTip(); | |
} else { | |
initVideoDataNotLogined(); | |
} | |
showBlogParts(); | |
} | |
// コントロール | |
// Greasemonkeyで取得出来るページ情報は初回ページ読み込み時しか更新されないので | |
// タイトルの更新をListenして把握する | |
// 問題はDOMNodeInsertedが同期なこととdeprecatedなこと | |
// ちなみにそれでもwatchAPIDataContainerの取得は捕捉出来ないので利用を断念 | |
// ログイン済みはh2未ログインはh1 | |
var timer = 0; | |
var titleArea = (isLogined && !isHtml5) ? d.getElementsByTagName('h2')[0] : d | |
.getElementsByTagName('h1')[0]; | |
titleArea.addEventListener('DOMNodeInserted', function() { | |
if (timer) | |
return; | |
timer = setTimeout(function() { | |
if (titleArea.innerHTML) { | |
// 動画読み込み時の処理 | |
fire(); | |
timer = 0; | |
return; | |
} | |
timer = 0; | |
}, 30); | |
}, false); | |
// 動画視聴ページを直接開いたときの処理 | |
fire(); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
既知の問題(3.0.1)