Created
March 5, 2009 05:11
-
-
Save azu/74201 to your computer and use it in GitHub Desktop.
This file contains hidden or 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 nicovideo Thumbinfo popup | |
// @namespace http://d.hatena.ne.jp/gifnksm/ | |
// @description Get information about nicovideo movies before going to watch page. | |
// @include * | |
// @exclude http://ext.nicovideo.jp/thumb/* | |
// @exclude http://ext.nicovideo.jp/thumb_mylist/* | |
// @exclude http://ichiba.nicovideo.jp/parts/* | |
// ==/UserScript== | |
// 設定とか | |
// ポップアップ表示されるまでの遅延時間(ミリ秒) | |
const show_delay = 800; | |
// ポップアップが消えるまでの遅延時間(ミリ秒) | |
const hide_delay = 600; | |
// 選択範囲内に動画IDが含まれる時にポップアップを行うかどうか | |
const enable_selection_popup = true; | |
// iframeでもポップアップを行うかどうか | |
const enable_iframe_popup = true; | |
// 動画再生ページのsmilevideoへのリンクでポップアップを行うかどうか | |
const enable_smilevideo_popup = false; | |
// はてなブックマーク数を表示するかどうか | |
const show_hatena_bookmark = false; | |
// 投稿者名を表示するかどうか | |
const show_uploader_name = true; | |
// 動画のIDのプレフィックス | |
const video_id_prefix = 'sm|nm|fz|ca|za|zb|zc|zd|ze|ax|nl|yk|om|yo|ig|na|fx|cw|sk'; | |
// 以下,スクリプト本体 | |
const DEBUG = false; | |
const is_watchpage = location.href.indexOf('http://www.nicovideo.jp/watch/') == 0; | |
var id_func = function(e) { return e; } | |
var $A = function(array) { return Array.map(array, id_func); }; | |
Function.prototype.bind = function() { | |
var self = this; | |
var obj = Array.shift(arguments); | |
var args = $A(arguments); | |
return function() { | |
return self.apply(obj, args.concat($A(arguments))); | |
} | |
}; | |
Number.prototype.fill = function(order) { | |
var s = this.toString(); | |
while(s.length < order) | |
s = '0' + s; | |
return s; | |
}; | |
String.prototype.insertComma = new function() { | |
var regexp = /(\d{1,3})(?=(?:\d\d\d)+$)/g; | |
return function() { | |
return this.toString().replace(regexp, "$1,"); | |
}; | |
}; | |
// innerHTML -> range | |
String.prototype.encodeEntityReference = new function() { | |
var div = document.createElement('div'); | |
return function() { | |
div.textContent = this; | |
return div.innerHTML; | |
} | |
} | |
String.prototype.decodeEntityReference = new function() { | |
var span = document.createElement('span'); | |
return function() { | |
span.innerHTML = this; | |
if(span.firstChild == null) | |
return ''; | |
return span.firstChild.nodeValue; | |
}; | |
}; | |
// 戻り値を innerHTML -> documentFragmentにする | |
String.prototype.createLink = new function() { | |
const domain_strs = '[-_.!~*\'()a-zA-Z0-9;?:@&=+$,%#]'; | |
const url_strs = '[-_.!~*\'()a-zA-Z0-9;/?:@&=+$,%#]'; | |
const url_regexp = new RegExp( | |
'(^|[^a-z])' + | |
'(?:'+ | |
'((?:'+video_id_prefix+')\\d+)|' + | |
'((?:co)\\d+)|' + | |
'((?:nc)\\d+)|' + | |
'((?:mylist|myvideo|user|watch)/\\d+(?:/\\d+)?)|' + | |
'((?:h?t?t?ps?|ftp)(?:://(' + domain_strs + '+)/?' + url_strs + '*))' + | |
')', 'g'); | |
const func = function(text, pre, video_id, comu_id, commons_id, mylist, url, domain) { | |
if(video_id) | |
return pre + '<a href="http://www.nicovideo.jp/watch/' + video_id + '">' + video_id + '</a>'; | |
if(comu_id) | |
return pre + '<a href="http://com.nicovideo.jp/community/' + comu_id + '">' + comu_id + '</a>'; | |
if(commons_id) | |
return pre + '<a href="http://www.niconicommons.jp/material/' + commons_id + '">' + commons_id + '</a>'; | |
if(mylist) | |
return pre + '<a href="http://www.nicovideo.jp/' + mylist + '">' + mylist + '</a>'; | |
if(url) { | |
if(url.indexOf('p') == 0) | |
url = 'htt' + url; | |
else if(url.indexOf('tp') == 0) | |
url = 'ht' + url; | |
else if(url.indexOf('ttp') == 0) | |
url = 'h' + url; | |
return pre + '<a href="' + url + '" title="' + url + '">[' + domain + ']</a>'; | |
} | |
}; | |
return function() { | |
return this.replace(url_regexp, func); | |
} | |
} | |
Date.prototype.toJpString = function() { | |
return this.getFullYear() + '年' + (this.getMonth()+1).fill(2) + '月' + this.getDate().fill(2) + '日 ' + | |
[this.getHours().fill(2), this.getMinutes().fill(2), this.getSeconds().fill(2)].join(':'); | |
}; | |
Date.fromISO8601 = function(str) { | |
var date = new Date(); | |
date.setISO8601(str); | |
return date; | |
}; | |
Date.prototype.setISO8601 = new function() { | |
var regstr = "^([0-9]{4})(?:-([0-9]{2})(?:-([0-9]{2})" + | |
"(?:T([0-9]{2}):([0-9]{2})(?::([0-9]{2})(?:\.([0-9]+))?)?" + | |
"(?:Z|(?:([-+])([0-9]{2}):([0-9]{2})))?)?)?)?$"; | |
var regexp = new RegExp(regstr); | |
return function (string) { | |
var d = string.match(regexp); | |
if(d == null) | |
return; | |
var offset = 0; | |
var date = new Date(d[1], 0, 1); | |
if (d[2]) { date.setMonth(d[2] - 1); } | |
if (d[3]) { date.setDate(d[3]); } | |
if (d[4]) { date.setHours(d[4]); } | |
if (d[5]) { date.setMinutes(d[5]); } | |
if (d[6]) { date.setSeconds(d[6]); } | |
if (d[7]) { date.setMilliseconds(Number("0." + d[7]) * 1000); } | |
if (d[8]) { | |
offset = (Number(d[9]) * 60) + Number(d[10]); | |
offset *= ((d[8] == '-') ? 1 : -1); | |
} | |
offset -= date.getTimezoneOffset(); | |
time = (Number(date) + (offset * 60 * 1000)); | |
this.setTime(Number(time)); | |
} | |
}; | |
function hasClassName(elem, className) { | |
return (' ' + elem.className + ' ').indexOf(' ' + className + ' ') != -1; | |
} | |
function addClassName(elem, className) { | |
if(hasClassName(elem, className)) | |
return; | |
elem.className += ' ' + className; | |
} | |
function removeClassName(elem, className) { | |
if(!hasClassName(elem, className)) | |
return; | |
elem.className = (' ' + elem.className + ' ').replace(' ' + className + ' ', ' '); | |
} | |
function toggleClassName(elem, className, flag) { | |
if(typeof flag == 'undefined') | |
flag = !hasClassName(elem, className); | |
if(flag) | |
addClassName(elem, className); | |
else | |
removeClassName(elem, className); | |
} | |
function getPosition(elem) { | |
var top = 0; | |
var left = 0; | |
var current = elem; | |
while(current && current != document.body) { | |
top += current.offsetTop - current.scrollTop; | |
left += current.offsetLeft - current.scrollLeft; | |
current = current.offsetParent; | |
} | |
var bottom = top + elem.clientHeight; | |
var right = left + elem.clientWidth; | |
if(elem != null) { | |
Array.forEach(elem.childNodes, function(current) { | |
var current_offset = getPosition(current); | |
if(current_offset.top < top) | |
top = current_offset.top; | |
if(current_offset.left < left) | |
left = current_offset.left; | |
if(current_offset.bottom > bottom) | |
bottom = current_offset.bottom; | |
if(current_offset.right > right) | |
right = current_offset.right; | |
}); | |
} | |
return {top: top, left: left, bottom: bottom, right: right, | |
height: elem.clientHeight, width: elem.clientWidth}; | |
} | |
function $N(elem, attr, children) { | |
if(!elem) return null; | |
if(typeof elem == 'string' || elem instanceof String) | |
elem = document.createElement(elem); | |
else | |
elem = elem.cloneNode(!children); | |
for (key in attr) { | |
if (!attr.hasOwnProperty(key)) continue; | |
elem.setAttribute(key, attr[key]); | |
} | |
function recAppend(child) { | |
if(typeof child == 'string' || child instanceof String) | |
elem.appendChild(document.createTextNode(child)); | |
else if(child instanceof Array) | |
child.forEach(recAppend); | |
else if(child) | |
elem.appendChild(child); | |
} | |
recAppend(children); | |
return elem; | |
} | |
const classname_prefix = '_GM_nicovideo_thumbinfo_popup_' ; | |
const shown_link_classname = classname_prefix + 'shown'; | |
const base_classname = classname_prefix + 'base'; | |
const filled_popup_classname = classname_prefix + 'filled'; | |
const fixed_popup_classname = classname_prefix + 'fixed'; | |
const thumbnail_classname = classname_prefix + 'thumbnail'; | |
const hatena_classname = classname_prefix + 'hatena'; | |
const uploader_classname = classname_prefix + 'uploader'; | |
const tags_classname = classname_prefix + 'tags'; | |
const description_classname = classname_prefix + 'description'; | |
const buttons_container_classname = classname_prefix + 'buttons_container'; | |
const closebutton_classname = classname_prefix + 'closebutton'; | |
const movebutton_classname = classname_prefix + 'movebutton'; | |
const resizebutton_classname = classname_prefix + 'resizebutton'; | |
const disable_popup_link_classname = classname_prefix + 'disable_popup_link'; | |
GM_addStyle(<><![CDATA[ | |
.${created} { | |
cursor: progress; | |
} | |
.${created}:focus { | |
outline: 2px solid red; | |
-moz-outline-radius: 5px; | |
} | |
.${created}.${shown} { | |
cursor: pointer; | |
} | |
.${created}.${shown}:focus { | |
outline-color: blue; | |
} | |
${base} { | |
-moz-border-radius: 5px; | |
position: absolute; | |
overflow: auto; | |
border: 1px solid gray; | |
background-color: white; | |
color: black; | |
text-align: left; | |
font-size: 12px; | |
padding: 8px; | |
margin: 0; | |
font-family: none; | |
z-index: 100000; | |
} | |
${base} * { | |
font-family: none; | |
margin: 0; | |
padding: 0; | |
border: none; | |
text-indent: 0; | |
text-align: left; | |
background: none; | |
background-color: transparent; | |
color: black; | |
width: auto; | |
height: auto; | |
max-width: auto; | |
max-height: auto; | |
min-width: auto; | |
min-height: auto; | |
line-height: 1.5; | |
float: none; | |
clear: none; | |
-moz-box-sizing: content-box; | |
position: static; | |
} | |
${base} strong { | |
font-weight: bold; | |
font-size: inherit; | |
} | |
${base} a { | |
text-decoration: underline; | |
font-size: inherit; | |
} | |
${base} a:link { color: blue; } | |
${base} a:visited { color: #135; } | |
${base} a:hover, | |
${base} a:active { | |
color: red; | |
text-decoration: none; | |
} | |
${base}.${filled} { | |
width: 600px; | |
} | |
${base}.${fixed} { | |
border: 2px solid black; | |
margin: -1px; | |
background-color: white; | |
color: black; | |
text-align: left; | |
} | |
${base} img.${thumbnail} { | |
float: left; | |
width: 130px; | |
height: 100px; | |
margin: 0 5px 5px 0; | |
} | |
${base} h1 { | |
font-size: 14px; | |
line-height: 22px; | |
font-weight: bold; | |
} | |
${base} img.${hatena} { | |
vertical-align: middle; | |
} | |
${base} > p.${tags} { | |
border: 1px solid silver; | |
border-width: 1px 0; | |
margin: 3px 0 3px 0; | |
padding: 3px; | |
word-spacing: 3px; | |
line-height: 1.7; | |
background-color: #eee; | |
} | |
${base} a.${uploader} { | |
font-weight: bold; | |
} | |
${base} > p.${tags} a { | |
white-space: nowrap; | |
} | |
${base} > p.${tags} a:link, | |
${base} a.${uploader}:link { | |
color: #222222; | |
} | |
${base} > p.${tags} a:visited, | |
${base} a.${uploader}:visited { | |
color: #444444; | |
} | |
${base} > p.${tags} a:hover, ${base} > p.${tags} a:active, | |
${base} a.${uploader}:hover, ${base} a.${uploader}:active { | |
color: #666666; | |
} | |
${base} > p.${description} { | |
clear: left; | |
padding: 0 5px; | |
line-height: 1.7; | |
max-height: 150px; | |
overflow-y: auto; | |
} | |
${base} > p.${description} a { font-weight: bolder; } | |
${base} > p.${buttons_container} { | |
position: absolute; | |
top: 0; | |
right: 0; | |
margin: 0; | |
padding: 0; | |
color: white; | |
} | |
${base} > p.${buttons_container} > span { | |
display: block; | |
width: 16px; | |
height: 16px; | |
font-size: 15px; | |
font-weight: bold; | |
line-height: 16px; | |
margin: 0; | |
padding: 0; | |
text-align: center; | |
border: 1px solid gray; | |
border-width: 0 0 1px 1px; | |
background-color: white; | |
color: bkack; | |
} | |
${base}.${fixed} > p.${buttons_container} >span { | |
border: 2px solid black; | |
border-width: 0 0 2px 2px; | |
} | |
${base} span.${closebutton} { | |
-moz-border-radius-topright: 2px; | |
-moz-border-radius-bottomleft: 5px; | |
cursor: pointer; | |
} | |
${base} span.${closebutton}:hover { | |
background-color: red; | |
color: white; | |
} | |
]]></>.toString().replace(/\${([^}]+)}/g, function(_, name) { | |
if(name == 'base') | |
return 'body > div.' + base_classname; | |
else | |
return classname_prefix + name; | |
})); | |
function log() { | |
if(!DEBUG) | |
return; | |
if(unsafeWindow.console) | |
unsafeWindow.console.log.apply(unsafeWindow.console, arguments); | |
else | |
Array.forEach(arguments, GM_log); | |
} | |
var InfoGetter = function(url, converter) { | |
this.callbacks = []; | |
this.url = url; | |
this.converter = converter || function(x, callback) { callback(x); }; | |
}; | |
InfoGetter.prototype = { | |
loaded: false, | |
loading: false, | |
response: null, | |
data: null, | |
reset: function() { | |
this.callbacks = []; | |
this.loaded = false; | |
this.loading = false; | |
this.response = null; | |
this.data = null; | |
}, | |
get: function(callback) { | |
if(typeof callback == 'function') | |
this.callbacks.push(callback); | |
if(this.loading) | |
return; | |
if(this.loaded) { | |
this.call_callbacks(); | |
return; | |
} | |
this.loading = true; | |
GM_xmlhttpRequest({ | |
method: 'GET', | |
url: this.url, | |
headers: { 'User-Agent': 'Mozilla/5.0 Greasemonkey; nicovideo Thumbinfo popup' }, | |
onload: (function(response) { | |
if(!this.loading) | |
return; | |
log('loaded: ', this.url, response); | |
this.response = response; | |
this.converter(response, (function(converted) { | |
log('converted: ', converted); | |
this.data = converted; | |
this.call_callbacks(); | |
this.loading = false; | |
this.loaded = true; | |
}).bind(this)); | |
}).bind(this) | |
}); | |
}, | |
call_callbacks: function() { | |
var callback; | |
while(callback = this.callbacks.shift()) { | |
callback(this.data); | |
} | |
} | |
}; | |
var ThumbInfo = function(video_id) { | |
this.video_id = video_id; | |
this.watch_url = 'http://www.nicovideo.jp/watch/' + video_id; | |
this.thumbnail_url = 'http://tn-skr.smilevideo.jp/smile?i=' + video_id.slice(2, this.video_id.length); | |
this.thumbinfo_getter = new InfoGetter( | |
'http://ext.nicovideo.jp/api/getthumbinfo/' + video_id, | |
ThumbInfo.responseConverter.bind(this)); | |
this.uploader_getter = new InfoGetter( | |
'http://www.smilevideo.jp/allegation/allegation/' + video_id.replace(/^[a-z]{2}/, ''), | |
function(response, callback) { | |
log('convert name: ', response, callback); | |
if(/<strong>([^<]+?)<\/strong> が投稿した動画を/.test(response.responseText)) | |
return callback(RegExp.$1.decodeEntityReference()); | |
else | |
return callback(undefined); | |
} | |
); | |
}; | |
ThumbInfo.data = {}; | |
ThumbInfo.get = function(video_id) { | |
if(typeof this.data[video_id] == 'undefined') | |
this.data[video_id] = new ThumbInfo(video_id); | |
return this.data[video_id]; | |
} | |
ThumbInfo.parser = new DOMParser(); | |
ThumbInfo.createErrorMessage = function(fragment, message, thumbnail_url) { | |
var fragment = document.createDocumentFragment(); | |
fragment.appendChild($N('img', { src: thumbnail_url, 'class': thumbnail_classname, alt:''})); | |
fragment.appendChild($N('h1', {}, message)); | |
return fragment; | |
}; | |
ThumbInfo.responseConverter = function(response, callback) { | |
if(response.responseText == '') { | |
callback(ThumbInfo.createErrorMessage(fragment, 'メンテナンス中かサーバが落ちています。', this.thumbnail_url)); | |
return; | |
} | |
var doc = ThumbInfo.parser.parseFromString(response.responseText, 'text/xml'); | |
this.status = doc.documentElement.getAttribute('status'); | |
if(this.status != 'ok') { | |
log('response(error):', response.responseText); | |
var code = doc.getElementsByTagName('code')[0].textContent; | |
this.code = code; | |
var fragment = ThumbInfo.createErrorMessage(fragment, 'Error! (' + code + ')', this.thumbnail_url); | |
fragment.appendChild($N('p', {}, [ | |
$N('strong', {}, 'description'), ': ', doc.getElementsByTagName('description')[0].textContent, | |
' (', $N('a', {href: this.watch_url}, this.video_id), ').' | |
])); | |
if(code == 'NOT_FOUND' && this.video_id.length > 3) { | |
var new_ids = []; | |
var i = -1; | |
do { | |
new_ids.push(this.video_id.slice(0, i)); | |
i--; | |
} while(this.video_id.length + i > 2); | |
fragment.appendChild( | |
$N('p', {}, ['もしかして:', new_ids.map(function(video_id) { | |
return [' ', $N('a', {href: 'http://www.nicovideo.jp/watch/' + video_id}, video_id)]; | |
})]) | |
); | |
} | |
if(code != 'COMMUNITY') { | |
callback(fragment); | |
return; | |
} | |
var flv_getter = new InfoGetter('http://www.nicovideo.jp/api/getflv/' + this.video_id); | |
var self = this; | |
flv_getter.get(function(response) { | |
var data = {}; | |
response.responseText.split('&').forEach(function(pair) { | |
var [key, val] = pair.split('='); | |
key = decodeURIComponent(key); | |
val = decodeURIComponent(val); | |
data[key] = val; | |
}); | |
if(data.hasOwnProperty('error')) { | |
var message; | |
switch(data.error) { | |
case 'invalid_v1': | |
message = '削除済み、または観覧する権限がありません。'; | |
break; | |
case 'invalid_v2': | |
message = '非表示にされています。'; | |
break; | |
case 'invalid_v3': | |
message = '権利者削除されています。'; | |
break; | |
case 'cant_get_detail': | |
message = '削除されています。(詳細不明)'; | |
break; | |
default: | |
message = '詳細不明なエラーです。'; | |
} | |
fragment.appendChild($N('p', {}, message)); | |
callback(fragment); | |
return; | |
} | |
if(/smile\?(.)=(\d+)/.test(data.url)) { | |
var type = RegExp.$1; | |
var postfix = RegExp.$2; | |
switch(type) { | |
case 'm': | |
case 'v': | |
type = 'sm'; | |
break; | |
case 's': | |
type = 'nm'; | |
break; | |
default: | |
callback(fragment); | |
return; | |
} | |
var video_id = type + postfix; | |
self.real_id = video_id; | |
var thumbinfo = ThumbInfo.get(video_id); | |
thumbinfo.getData(function(orig_fragment) { | |
var fragment = orig_fragment.cloneNode(true); | |
self.status = thumbinfo.status; | |
if(thumbinfo.status == 'ok') { | |
if(fragment.firstChild.nodeName == 'A') { | |
fragment.firstChild.href = 'http://www.nicovideo.jp/watch/' + self.video_id; | |
} | |
var p, h1; | |
Array.some(fragment.childNodes, function(x) { | |
if(x.nodeName == 'P') | |
p = x; | |
if(x.nodeName == 'H1') { | |
h1 = x; | |
return true; | |
} | |
}); | |
if(p) { | |
var comumark = document.createDocumentFragment(); | |
comumark.appendChild(document.createTextNode(' ')); | |
comumark.appendChild($N('strong', {}, 'コミュニティ')); | |
comumark.appendChild(document.createTextNode(' ')); | |
comumark.appendChild($N('a', {href: 'http://www.nicovideo.jp/watch/' + video_id}, '\u00bb元動画')); | |
comumark.appendChild(document.createTextNode(' ')); | |
p.insertBefore(comumark, p.firstChild.nextSibling); | |
} | |
if(h1.getElementsByTagName('a').length) | |
h1.getElementsByTagName('a')[0].href = 'http://www.nicovideo.jp/watch/' + self.video_id; | |
} | |
else if(thumbinfo.code == 'NOT_FOUND') { | |
var new_ids = video_id_prefix.split('|').filter(function(prefix) { | |
return prefix != type; | |
}); | |
fragment.appendChild( | |
$N('p', {}, ['もしかして:', new_ids.map(function(prefix) { | |
return [' ', $N('a', {href: 'http://www.nicovideo.jp/watch/' + prefix + postfix}, prefix + postfix)]; | |
})]) | |
); | |
} | |
callback(fragment); | |
}); | |
return; | |
} | |
callback(fragment); | |
}); | |
return; | |
} | |
var fragment = document.createDocumentFragment(); | |
var data = {}; | |
var tags_data = {jp: [], tw: []};// es: [], de: []}; | |
Array.forEach(doc.getElementsByTagName('thumb')[0].childNodes, function(node) { | |
if(node.nodeType != 1) | |
return; | |
var name = node.nodeName; | |
if(name != 'tags') | |
data[name] = node.textContent; | |
else { | |
var domain = node.getAttribute('domain'); | |
tags_data[domain] = Array.map(node.getElementsByTagName('tag'), function(tag) { | |
return {name: tag.textContent, locked: tag.hasAttribute('lock')}; | |
}); | |
} | |
}); | |
log('data: ', data); | |
this.watch_url = data.watch_url; | |
this.thumbnail_url = data.thumbnail_url; | |
var additional = []; | |
if(data.thumb_type == 'mymemory') { | |
this.real_id = data.video_id; | |
additional.push([' ', $N('strong', {}, 'マイメモリー'), | |
' ', $N('a', {href: 'http://www.nicovideo.jp/watch/' + data.video_id}, '\u00bb元動画')]); | |
} | |
if(show_uploader_name) | |
additional.push([' [up: ', $N('span', {'class': uploader_classname}, 'Loading...'), ']']); | |
if(show_hatena_bookmark) | |
additional.push([' ', $N('a', {href: 'http://b.hatena.ne.jp/entry/' + data.watch_url}, | |
$N('img', {src: 'http://b.hatena.ne.jp/entry/image/' + data.watch_url, 'class': hatena_classname}) | |
)]); | |
fragment.appendChild( | |
$N('a', {href: data.watch_url, 'class': disable_popup_link_classname}, | |
$N('img', {src: data.thumbnail_url, 'class': thumbnail_classname, alt:''}) | |
) | |
); | |
fragment.appendChild($N('p', {}, [Date.fromISO8601(data.first_retrieve).toJpString(), '投稿', additional])); | |
fragment.appendChild($N('h1', {}, | |
$N('a', {href: data.watch_url, 'class': disable_popup_link_classname}, data.title) | |
)); | |
fragment.appendChild($N('p', {}, [ | |
'再生時間: ', $N('strong', {}, data.length.split(':').join('分') + '秒'), | |
' 再生: ', $N('strong', {}, data.view_counter.insertComma()), | |
' コメント: ', $N('strong', {}, data.comment_num.insertComma()), | |
' マイリスト: ', $N('strong', {}, data.mylist_counter.insertComma()) | |
])); | |
var global_length = tags_data['tw'].length;// + tags_data['es'].length + tags_data['de'].length; | |
var make_tag = function(code) { | |
if(tags_data[code].length == '0') | |
return ''; | |
return $N('span', {}, [ | |
(code != 'jp') ? [' ', $N('strong', {}, '[' + code + ']:')] : '', | |
tags_data[code].map(function(tag) { | |
return [' ', | |
tag.locked? $N('span', {style: 'color:#F90;'}, '★') : '' , | |
$N('a', {href: 'http://www.nicovideo.jp/tag/'+encodeURI(tag.name), rel: 'tag'}, | |
tag.name.decodeEntityReference())]; | |
}) | |
]); | |
}; | |
fragment.appendChild($N('p', {'class': tags_classname}, [ | |
$N('strong', {}, 'タグ(' + tags_data['jp'].length + (global_length > 0 ? ' + ' + global_length : '') + '):'), | |
make_tag('jp'), make_tag('tw')//, make_tag('de'), make_tag('es') | |
])); | |
var p = $N('p', {'class': description_classname}); | |
p.innerHTML = data.description.createLink().replace(/\s{3,}/g, '<br/>'); | |
fragment.appendChild(p); | |
callback(fragment); | |
return; | |
}; | |
ThumbInfo.prototype = { | |
loading: false, | |
uploader_name: null, | |
get data_loaded() { | |
return this.thumbinfo_getter.loaded; | |
}, | |
getData: function(callback) { | |
if(show_uploader_name) | |
this.uploader_getter.get(); | |
this.thumbinfo_getter.get(callback); | |
}, | |
getUploaderName: function(callback) { | |
this.uploader_getter.get(callback); | |
} | |
} | |
const popup_parent_classname = '_GM_nicovideo_thumbinfo_popup_parent_'; | |
var popups = []; | |
var Popup = function(elem, video_id) { | |
this.link_elem = elem; | |
this.video_id = video_id; | |
this.uniqueID = popups.length; | |
this.thumbinfo = ThumbInfo.get(video_id); | |
this.parent = null; | |
if(elem != null) { | |
var self = this; | |
// elem.addEventListener('focus', this.showDelay.bind(this, false), false); | |
elem.addEventListener('mouseover', function() { self.showDelay(); }, false); | |
// elem.addEventListener('blur', this.hideDelay.bind(this, true), false); | |
elem.addEventListener('mouseout', function() { self.hideDelay(); }, false); | |
// なぜか動作しない | |
// elem.addEventListener('DOMNodeRemovedFromDocument', this.hideDelay.bind(this, true), false); | |
document.addEventListener('DOMNodeRemoved', function(e) { | |
if(e.target.compareDocumentPosition(elem) & 16) | |
self.hideDelay(hide_delay * 2); | |
}, false); | |
if((new RegExp(' ' + popup_parent_classname + '(\\d+) ').test(' ' + elem.className + ' '))) | |
this.parent = popups[parseInt(RegExp.$1, 10)]; | |
} | |
popups.push(this); | |
} | |
Popup.prototype = { | |
mouseover: false, | |
visible: false, | |
_popup_div: null, | |
disableAutohide: false, | |
_addMouseEvents: function() { | |
var div = this._popup_div; | |
var self = this; | |
div.addEventListener('dblclick', function() { | |
self.fixed = !self.fixed; | |
self.disableAutohide = self.fixed; | |
toggleClassName(div, fixed_popup_classname, self.fixed); | |
}, false); | |
var forEachParent = function(f) { return function(e) { | |
var x = self; do { f(x); } while(x = x.parent); | |
}}; | |
var over_time; | |
div.addEventListener('mouseover', | |
forEachParent(function(popup) { | |
popup.disableAutohide = true; | |
over_time = new Date(); | |
}), false); | |
div.addEventListener('mouseout', forEachParent(function(popup) { | |
popup.disableAutohide = false; | |
// 意図せぬマウスの通過は無視。 | |
if(new Date() - over_time > 50) | |
popup.hideDelay(); | |
}), false); | |
div.addEventListener('click', function(e) { | |
if(!hasClassName(e.target, closebutton_classname)) | |
document.getElementsByTagName('body')[0].appendChild(div); | |
}, false); | |
}, | |
_addMoveEvents: function() { | |
var div = this._popup_div, orig_top, orig_left; | |
var self = this; | |
var mousemove = function(e) { | |
e.preventDefault(); | |
div.style.top = (e.pageY + orig_top) + 'px'; | |
div.style.left = (e.pageX + orig_left) + 'px'; | |
}; | |
div.addEventListener('mousedown', function(e) { | |
if(!e.ctrlKey) return; | |
self.moved = true; | |
orig_top = parseInt(div.style.top, 10) - e.pageY; | |
orig_left = parseInt(div.style.left, 10) - e.pageX; | |
e.preventDefault(); | |
document.addEventListener('mousemove', mousemove, false); | |
}, false); | |
document.addEventListener('mouseup', function(e) { | |
e.preventDefault(); | |
document.removeEventListener('mousemove', mousemove, false); | |
}, false); | |
}, | |
_createButton: function() { | |
var buttons_container = document.createElement('p'); | |
addClassName(buttons_container, buttons_container_classname); | |
var closebutton = document.createElement('span'); | |
addClassName(closebutton, closebutton_classname); | |
closebutton.textContent = '×'; | |
closebutton.addEventListener('click', this.hide.bind(this, false), false); | |
buttons_container.appendChild(closebutton); | |
this._popup_div.appendChild(buttons_container); | |
}, | |
_createPopup: function() { | |
var div = document.createElement('div'); | |
this._popup_div = div; | |
addClassName(div, base_classname); | |
if(is_watchpage) | |
div.style.position = 'fixed'; | |
var message = document.createElement('div'); | |
message.appendChild($N('a', {href: this.thumbinfo.watch_url, 'class': disable_popup_link_classname}, | |
$N('img', {src: this.thumbinfo.thumbnail_url, 'class': thumbnail_classname, alt:''}) | |
)); | |
message.appendChild($N('h1', {}, 'loading...')); | |
div.appendChild(message); | |
this._message_div = message; | |
this._addMouseEvents(); | |
this._addMoveEvents(); | |
this._createButton(); | |
}, | |
_fillData: function(fragment) { | |
var div = this.popup_div; | |
div.replaceChild(fragment.cloneNode(true), this._message_div); | |
if(show_uploader_name) { | |
var name_elem; | |
Array.some(div.getElementsByTagName('span'), function(span) { | |
if(hasClassName(span, uploader_classname)) { | |
name_elem = span; | |
return true; | |
} | |
}); | |
if(typeof name_elem != 'undefined') { | |
var parent = name_elem.parentNode; | |
if(/^\d/.test(this.video_id) && this.thumbinfo.real_id) { | |
this.thumbinfo.uploader_getter = new InfoGetter('http://www.smilevideo.jp/view/' + | |
this.thumbinfo.real_id.slice(2, this.thumbinfo.real_id.length), | |
this.thumbinfo.uploader_getter.converter); | |
} | |
log(this.thumbinfo.uploader_getter); | |
this.thumbinfo.getUploaderName(function(name) { | |
if(typeof name == 'undefined') | |
name_elem.textContent = 'Not Found.' | |
else | |
parent.replaceChild( | |
$N('a', {href: 'http://www.nicochart.jp/name/' + encodeURI(name), | |
'class': uploader_classname}, name), | |
name_elem); | |
}); | |
} | |
} | |
var self = this; | |
var imgs = div.getElementsByTagName('img'); | |
if(imgs.length) { | |
imgs[0].addEventListener('error', function(e) { | |
imgs[0].style.width = '0'; | |
imgs[0].style.height = '0'; | |
self.appendPopup(); | |
}, false); | |
} | |
Array.forEach(div.getElementsByTagName('a'), function(a) { | |
addClassName(a, popup_parent_classname + self.uniqueID); | |
}); | |
if(this.thumbinfo.status == 'ok') | |
addClassName(div, filled_popup_classname); | |
document.getElementsByTagName('body')[0].appendChild(div); | |
}, | |
get popup_div() { | |
if(this._popup_div == null) | |
this._createPopup(); | |
return this._popup_div; | |
}, | |
fillData: function(fragment) { | |
if(!this.data_filled) | |
this._fillData(fragment); | |
this.data_filled = true; | |
this.appendPopup(); | |
}, | |
appendPopup: function() { | |
var div = this.popup_div; | |
document.body.appendChild(div); | |
if(this.moved) | |
return; | |
if(this.link_elem != null) | |
this.move(getPosition(this.link_elem)); | |
else | |
this.move(); | |
}, | |
move: function(position) { | |
if(typeof position == 'undefined') | |
position = this.position; | |
this.position = position; | |
var div = this.popup_div; | |
var top = position.top - div.offsetHeight - 20; | |
if(top < document.documentElement.scrollTop + document.body.scrollTop) { | |
top = position.bottom + 20; | |
} | |
var left = position.left; | |
if(left < 50) | |
left = 50; | |
else if(left + div.offsetWidth > window.innerWidth - 50) { | |
left = position.right - div.offsetWidth / 2; | |
if(left + div.offsetWidth > window.innerWidth - 50) { | |
left = position.right - div.offsetWidth; | |
} | |
} | |
if(is_watchpage && this.parent == null) { | |
top -= document.documentElement.scrollTop; | |
left -= document.documentElement.scrollLeft; | |
} | |
div.style.top = top + 'px'; | |
div.style.left = left + 'px'; | |
}, | |
showDelay: function(delay) { | |
log('showDelay: ', this.video_id); | |
if(typeof delay == 'undefined') { | |
delay = show_delay; | |
} | |
this.mouseover = true; | |
if(this.hide_timer) | |
clearTimeout(this.hide_timer); | |
this.show_timer = setTimeout(this.show.bind(this, false), delay); | |
}, | |
hideDelay: function(delay) { | |
log('hideDelay: ', this.video_id); | |
if(typeof delay == 'undefined') | |
delay = hide_delay; | |
this.mouseover = false; | |
if(this.show_timer) | |
clearTimeout(this.hide_timer); | |
this.hide_timer = setTimeout(this.hide.bind(this, true), delay); | |
}, | |
show: function(force_show) { | |
if(this.visible || (!this.mouseover && !force_show)) | |
return; | |
this.visible = true; | |
if(this.link_elem != null) | |
addClassName(this.link_elem, shown_link_classname); | |
log('show: ', this.video_id, this.uniqueID); | |
if(!this.thumbinfo.data_loaded) | |
this.appendPopup(); | |
this.thumbinfo.getData(this.fillData.bind(this)); | |
}, | |
hide: function(autohide) { | |
if(autohide && (this.disableAutohide || this.fixed)) | |
return; | |
if(this.link_elem != null) | |
removeClassName(this.link_elem, shown_link_classname); | |
this.visible = false; | |
this.mouseover = false; | |
this.disableAutohide = false; | |
this.fixed = false; | |
this.moved = false; | |
removeClassName(this.popup_div, fixed_popup_classname); | |
log('hide: ', this.video_id, this.uniqueID); | |
document.getElementsByTagName('body')[0].removeChild(this.popup_div); | |
} | |
} | |
const url_regex = new RegExp('^http://www.nicovideo.jp/watch/((?:\\w\\w)?\\d+)'); | |
const url_regex_tag = new RegExp('^http://www.nicovideo.jp/tag/.*?((?:'+video_id_prefix+')\\d+)'); | |
const thumb_regex = new RegExp('^http://(?:www|ext).nicovideo.jp/thumb/((?:\\w\\w)?\\d+)') | |
const created_propname = classname_prefix + 'created'; | |
var regist_link = function self(e, elem) { | |
if(typeof elem == 'undefined') | |
elem = e.target; | |
if(hasClassName(elem, disable_popup_link_classname)) | |
return; | |
var video_id; | |
if(enable_iframe_popup && elem.nodeName == 'IFRAME' && (thumb_regex.test(elem.src))) { | |
video_id = RegExp.$1; | |
} | |
else if((elem.nodeName == 'A' || elem.nodeName == 'AREA') && (url_regex.test(elem.href) || url_regex_tag.test(elem.href))) { | |
video_id = RegExp.$1; | |
} | |
else { | |
if(elem.parentNode) | |
self(e, elem.parentNode); | |
return; | |
} | |
if(!elem[created_propname]) { | |
elem[created_propname] = true; | |
var popup = new Popup(elem, video_id); | |
log('create: ', video_id, popup.uniqueID); | |
popup.showDelay(); | |
} | |
} | |
if(is_watchpage && enable_smilevideo_popup) { | |
var h1 = document.getElementsByTagName('h1'); | |
if(!h1.length) | |
return; | |
var links = h1[0].getElementsByTagName('a'); | |
if(!links.length) | |
return; | |
var link = links[0]; | |
var video_id = unsafeWindow.Video.id; | |
link[created_propname] = true; | |
var popup = new Popup(link, video_id); | |
log('create: ', video_id, popup.uniqueID); | |
} | |
document.addEventListener('mouseover', regist_link, false); | |
// document.addEventListener('keyup', function(e) { regist_link(e, document.activeElement);}, false); | |
if(enable_selection_popup) { | |
document.addEventListener('mouseup', new function() { | |
const url_regexp = new RegExp('(^|[^a-z])((?:'+video_id_prefix+')\\d+)', 'g'); | |
var old_str; | |
return function(e) { | |
var selection = getSelection(); | |
if(!selection) | |
return; | |
var str = selection.toString(); | |
if(str.length > 80) | |
return; | |
if(old_str == str) | |
return; | |
old_str = str; | |
var p, i = 0; | |
while(p = url_regexp.exec(str)) { | |
var video_id = p[2]; | |
var popup = new Popup(null, video_id); | |
log('create_from_selection: ', video_id, popup.uniqueID); | |
var top = e.pageY + 10 * i; | |
var left = e.pageX + 50 * i; | |
popup.move({top: top, left: left, bottom: top, right: left, height: 0, width: 0}); | |
popup.showDelay(); | |
i++; | |
} | |
} | |
}, false); | |
} | |
if(is_watchpage) { | |
var sTop = document.documentElement.scrollTop; | |
var sLeft = document.documentElement.scrollLeft; | |
window.addEventListener('scroll', function(e) { | |
var dx = document.documentElement.scrollLeft - sLeft; | |
var dy = document.documentElement.scrollTop - sTop; | |
popups.forEach(function(popup) { | |
popup.popup_div.style.top = (parseInt(popup.popup_div.style.top, 10) - dy) + 'px'; | |
popup.popup_div.style.left = (parseInt(popup.popup_div.style.left, 10) - dx) + 'px'; | |
}); | |
sTop = document.documentElement.scrollTop; | |
sLeft = document.documentElement.scrollLeft; | |
}, false); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment