Skip to content

Instantly share code, notes, and snippets.

@nobodyplace
Last active November 5, 2016 01:05
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nobodyplace/3905580 to your computer and use it in GitHub Desktop.
Save nobodyplace/3905580 to your computer and use it in GitHub Desktop.
ニコニコ動画プレイヤーのUIをカスタマイズするGreasemonkey
// ==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 = {
"<" : "&lt;",
">" : "&gt;",
"&" : "&amp;",
"'" : "&#39;",
"\"" : "&quot;",
" " : "&nbsp;",
" " : "&#8195;"
};
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();
})();
@nobodyplace
Copy link
Author

nobodyplace commented Nov 5, 2016

既知の問題(3.0.1)

  • 動画遷移時にブログパーツが更新されない
  • チャンネル動画でブログパーツが表示されない → 3.0.2で対応済み

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment