// ==UserScript==
// @name NicoNicoFavlist
// @namespace http://www.nicovideo.jp/
// @description Get your favorite mylists checked twenty-four-seven!
// @include http://www.nicovideo.jp/
// @include http://www.nicovideo.jp/mylist/*
// @include http://www.nicovideo.jp/user/*
// @include http://www.nicovideo.jp/myvideo/*
// ==/UserScript==
//NicoNicoFavlist 1.18改 2009/12/15
//改変部分は「★」を検索してください
(function () {
var w = (this.unsafeWindow || window), document = w.document;
var isMonkey = (typeof GM_xmlhttpRequest == "function");
var NicoNicoFavlist = {
version: "1.18",
scriptUrl: "http://svn.coderepos.org/share/lang/javascript/userscripts/niconicofavlist.user.js",
historyUrl: "http://kotas.jp/to/favlist",
getUserAgent: function () {
return "NicoNicoFavlist/" + NicoNicoFavlist.version + " Greasemonkey";
},
checkVersionUp: function (onUpdated) {
if (!isMonkey) return;
var lastUpdate = parseInt(Storage.getItem("lastCheckVersionUp") || "0");
var currentTime = Math.floor((new Date()).getTime() / 1000);
if (currentTime < (lastUpdate + 24*60*60)) {
var newver = Storage.getItem("newVersion");
if (newver && newver != NicoNicoFavlist.version) {
onUpdated(newver);
}
return;
}
Storage.setItem("lastCheckVersionUp", currentTime.toString());
Storage.setItem("newVersion", "");
Util.request({
method: "GET",
url: NicoNicoFavlist.scriptUrl,
headers: { "User-Agent": NicoNicoFavlist.getUserAgent() },
onload: function (r) {
var m;
if (m = r.responseText.match(/version:\s*"([^"]+)"/)) {
if (m[1] != NicoNicoFavlist.version) {
Storage.setItem("newVersion", m[1]);
onUpdated(m[1]);
}
}
}
});
}
};
var Storage = isMonkey ?
{
getItem: function (key) { return GM_getValue(key); },
setItem: function (key, value) { GM_setValue(key, value); }
}
: (function (store) {
return {
getItem: function (key) { return store[key]; },
setItem: function (key, value) { store[key] = value; }
};
})(w.localStorage || w.globalStorage[w.location.hostname]);
var Util = {
observe: function (elem, event, func, capture) {
capture = !!capture;
if (elem.attachEvent) {
elem.attachEvent("on" + event, func);
} else if (elem.addEventListener) {
elem.addEventListener(event, func, capture);
} else {
elem["on" + event] = func;
}
},
request: isMonkey ? GM_xmlhttpRequest : function (options) {
var req = new XMLHttpRequest();
req.open(options.method || "GET", options.url, true);
if (options.onload) {
req.onload = function () {
options.onload(req);
}
}
if (options.onerror) {
req.onerror = function () {
options.onerror(req);
}
}
if (options.onreadystatechange) {
req.onreadystatechange = function () {
options.onreadystatechange(req);
}
}
if (options.overrideMimeType) {
req.overrideMimeType(options.overrideMimeType);
}
if (options.headers) {
for (var key in options.headers) {
req.setRequestHeader(key, options.headers[key]);
}
}
req.send(options.data || null);
return req;
}
};
var Config = {
checkInterval: 30 * 60,
maxNewVideos: 10,
hideCheckedList: false,
showInAllTabs: false,
checkVersionUp: true,
show: function (elem) {
var ul = document.createElement("ul");
ul.style.margin = "0px";
ul.style.padding = "8px";
ul.style.listStyleType = "none";
this.addTextInput(ul, "更新チェック間隔", "favlistCheckInterval", Config.checkInterval, "10", "秒");
this.addTextInput(ul, "新着動画数の上限", "favlistMaxNewVideos", Config.maxNewVideos, "10", "件まで");
this.addCheckBox(ul, "新着がないマイリストを隠す", "favlistHideCheckedList", Config.hideCheckedList);
this.addCheckBox(ul, "カテゴリをまたいで表示", "favlistShowInAllTabs", Config.showInAllTabs);
if (isMonkey) {
this.addCheckBox(ul, "NicoNicoFavlist の更新をチェック", "favlistCheckVersionUp", Config.checkVersionUp);
}
elem.appendChild(ul);
},
addTextInput: function (elem, label, name, value, size, unit) {
var li = document.createElement("li");
li.className = "TXT12";
li.style.fontWeight = "bold";
li.style.marginBottom = "8px";
li.innerHTML = '
';
var input = document.createElement("input");
input.type = "text";
input.id = name;
input.name = name;
input.value = value;
input.size = size;
li.appendChild(input);
if (unit) {
var span = document.createElement("span");
span.innerHTML = unit;
span.style.marginLeft = "4px";
li.appendChild(span);
}
elem.appendChild(li);
},
addCheckBox: function (elem, label, name, value) {
var li = document.createElement("li");
li.className = "TXT12";
li.style.fontWeight = "bold";
li.style.marginBottom = "4px";
li.innerHTML = '';
var check = document.createElement("input");
check.type = "checkbox";
check.id = name;
check.name = name;
check.value = "1";
check.checked = !!value;
li.insertBefore(check, li.firstChild);
elem.appendChild(li);
},
load: function () {
var v;
v = Storage.getItem("checkInterval");
if (v !== undefined && !isNaN(v = parseInt(v))) Config.checkInterval = v;
v = Storage.getItem("maxNewVideos");
if (v !== undefined && !isNaN(v = parseInt(v))) Config.maxNewVideos = v;
v = Storage.getItem("hideCheckedList");
if (v !== undefined) Config.hideCheckedList = (v != "0");
v = Storage.getItem("showInAllTabs");
if (v !== undefined) Config.showInAllTabs = (v != "0");
v = Storage.getItem("checkVersionUp");
if (v !== undefined) Config.checkVersionUp = (v != "0");
},
save: function () {
var interval = document.getElementById("favlistCheckInterval").value;
try {
interval = w.parseInt(interval);
if (interval < 0) interval = 0;
Config.checkInterval = interval;
Storage.setItem("checkInterval", interval.toString());
} catch (e) {
w.alert("更新チェック間隔の値がおかしいです");
return;
}
var maxnewvideos = document.getElementById("favlistMaxNewVideos").value;
try {
maxnewvideos = w.parseInt(maxnewvideos);
if (maxnewvideos < 0) maxnewvideos = 0;
Config.maxNewVideos = maxnewvideos;
Storage.setItem("maxNewVideos", maxnewvideos.toString());
} catch (e) {
w.alert("新着動画数の上限の値がおかしいです");
return;
}
var hidechedkedlist = document.getElementById("favlistHideCheckedList").checked;
Config.hideCheckedList = hidechedkedlist;
Storage.setItem("hideCheckedList", hidechedkedlist ? "1" : "0");
var showInAllTabs = document.getElementById("favlistShowInAllTabs").checked;
Config.showInAllTabs = showInAllTabs;
Storage.setItem("showInAllTabs", showInAllTabs ? "1" : "0");
var checkVersionUp = document.getElementById("favlistCheckVersionUp").checked;
Config.checkVersionUp = checkVersionUp;
Storage.setItem("checkVersionUp", checkVersionUp ? "1" : "0");
}
};
var Video = function () { this.initialize.apply(this, arguments); };
Video.prototype = {
initialize: function (mylist, id, title, uri, thumbnail, memo, timestamp) {
this.mylist = mylist;
this.id = id || false;
this.title = title || false;
this.uri = uri || false;
this.thumbnail = thumbnail || false;
this.memo = memo || false;
this.timestamp = timestamp || false;
this.container = false;
},
serialize: function () {
return [
this.id ? w.escape(this.id) : "",
this.title ? w.escape(this.title) : "",
this.uri ? w.escape(this.uri) : "",
this.thumbnail ? w.escape(this.thumbnail) : "",
this.memo ? w.escape(this.memo) : "",
this.timestamp ? w.escape(this.timestamp) : ""
].join("&");
},
unserialize: function (data) {
var r = [];
if (data) r = data.split(/&/);
this.id = r[0] ? w.unescape(r[0]) : false;
this.title = r[1] ? w.unescape(r[1]) : false;
this.uri = r[2] ? w.unescape(r[2]) : false;
this.thumbnail = r[3] ? w.unescape(r[3]) : false;
this.memo = r[4] ? w.unescape(r[4]) : false;
this.timestamp = r[5] ? w.unescape(r[5]) : false;
},
updateByAtomEntry: function (entry) {
var m;
if (m = entry.match(/
(.*?)<\/title>/)) this.title = m[1];
if (m = entry.match(//)) this.uri = m[1];
if (this.uri && (m = this.uri.match(/watch\/(.+)/))) this.id = m[1];
if (m = entry.match(/(.*?)<\/p>/)) this.memo = m[1];
if (m = entry.match(/(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}).*?<\/published>/)) {
this.timestamp = m[1]+"年"+m[2]+"月"+m[3]+"日 "+m[4]+":"+m[5]+":"+m[6];
}
},
getCaption: function () {
var caption = this.title || "(無題)";
if (caption.length > 26) {
caption = caption.substr(0, 13) + "..." + caption.substr(-13);
}
return caption;
},
show: function (elem) {
var li = document.createElement("li");
li.style.clear = "both";
li.style.padding = "4px 0px";
li.style.borderTop = "1px dotted #CCC";
var self = this;
var clearance = function () { self.mylist.clear(self); };
var thumbLink = document.createElement("a");
thumbLink.href = this.uri;
thumbLink.title = this.title;
try { thumbLink.style.cssFloat = "left"; }
catch (e) { thumbLink.style.styleFloat = "left"; }
thumbLink.style.marginRight = "4px";
Util.observe(thumbLink, "click", clearance);
var thumb = document.createElement("img");
thumb.className = "thumb_img";
thumb.src = this.thumbnail;
thumb.width = "46";
thumb.height = "34";
thumbLink.appendChild(thumb);
li.appendChild(thumbLink);
if (this.timestamp) {
var time = document.createElement("p");
var m = this.timestamp.split(/\D+/);
time.innerHTML = "" +
m[0].substr(-2) + "/" + m[1] + "/" + m[2] + " " +
m[3] + ":" + m[4] + ":" + m[5] + " 追加";
time.className = "TXT10";
li.appendChild(time);
}
var title = document.createElement("p");
title.className = "TXT12";
var titleLink = document.createElement("a");
titleLink.href = this.uri;
titleLink.title = this.title;
titleLink.className = "video";
titleLink.innerHTML = this.getCaption();
Util.observe(titleLink, "click", clearance);
title.appendChild(titleLink);
li.appendChild(title);
var breaker = document.createElement("p");
breaker.style.clear = "both";
breaker.innerHTML = '';
li.appendChild(breaker);
if (this.memo) {
var memo = document.createElement("p");
memo.innerHTML = this.memo;
memo.className = "TXT12";
memo.style.backgroundColor = "#F7F7F7";
memo.style.border = "1px solid #CCC";
memo.style.padding = "4px";
memo.style.marginTop = "4px";
li.appendChild(memo);
}
elem.appendChild(li);
this.container = li;
},
remove: function () {
if (this.container) {
this.container.parentNode.removeChild(this.container);
this.container = false;
}
}
};
var Mylist = function () { this.initialize.apply(this, arguments); };
Mylist.prototype = {
baseUri: "http://www.nicovideo.jp",
initialize: function (favlist, listId, title, params) {
this.favlist = favlist;
this.listId = listId || false;
this.title = title || false;
this.params = params || false;
this.checked = { };
this.newVideos = [ ];
this.caption = false;
this.container = false;
this.videoList = false;
this.titleBar = false;
this.counter = false;
this.statusBar = false;
this.buttons = false;
this.toRemove = false;
},
serialize: function () {
var checked = [ ];
for (var k in this.checked) { checked.push(k); }
var newVideos = [ ];
for (var i = 0, len = this.newVideos.length; i < len; i++) {
newVideos.push(this.newVideos[i].serialize());
}
return [
"0", /* for backward compatibility */
this.listId ? w.escape(this.listId) : "",
this.title ? w.escape(this.title) : "",
checked.join(":"),
newVideos.join(":"),
this.caption ? w.escape(this.caption) : "",
this.params ? w.escape(this.params) : ""
].join(";");
},
unserialize: function (data) {
var r = [];
if (data) r = data.split(/;/);
// r[0] is userId, but not in use
this.listId = r[1] ? w.unescape(r[1]) : false;
if (/^\d+$/.test(this.listId)) this.listId = "mylist/" + this.listId;
this.title = r[2] ? w.unescape(r[2]) : false;
this.checked = { };
if (r[3]) {
var checked = r[3].split(/:/);
for (var i = 0; i < checked.length; i++) {
if (checked[i]) {
this.checked[ checked[i] ] = true;
}
}
}
this.newVideos = [ ];
if (r[4]) {
var vids = r[4].split(/:/);
for (var i = 0; i < vids.length; i++) {
if (vids[i]) {
var v = new Video(this);
v.unserialize(vids[i]);
this.newVideos.push(v);
}
}
}
this.caption = r[5] ? w.unescape(r[5]) : false;
this.params = r[6] ? w.unescape(r[6]) : false;
},
getUri: function () {
return this.baseUri + "/" + this.listId;
},
getCaption: function () {
var caption = this.caption || this.title || "(無題)";
if (caption.length > 26) {
caption = caption.substr(0, 13) + "..." + caption.substr(-13);
}
return caption;
},
update: function () {
this.setStatus("更新中", "#333");
this.newVideos = [ ];
this.videoList.innerHTML = "";
var url = this.getUri() + "?rss=atom&nodescription=1&noinfo=1";
if (this.params) {
url += "&" + this.params;
}
var self = this;
Util.request({
method: "GET",
url: url,
headers: { "User-Agent": NicoNicoFavlist.getUserAgent() },
onload: function (r) {
self.setStatus(false);
if (200 <= r.status && r.status < 300) {
self.updateByAtom(r.responseText);
} else if (r.status == 403) {
self.setStatus("非公開", "#C00", 3000);
}
},
onerror: function (r) {
self.setStatus("更新失敗", "#C00", 3000);
},
onreadystatechange: function (r) {
if (r.readyState == 4) {
self.favlist.updateCallback(this);
}
}
});
},
updateByAtom: function (xml) {
var m;
if (m = xml.match(/(?:マイリスト )?(.+?)‐ニコニコ動画.*?<\/title>/)) {
this.title = m[1];
}
var oldChecked = this.checked;
this.checked = { };
var re_entry = /([\S\s]*?)<\/entry>/g;
while (m = re_entry.exec(xml)) {
var v = new Video(this);
v.updateByAtomEntry(m[1]);
if (v.id in oldChecked) {
this.checked[v.id] = true;
} else {
this.newVideos.push(v);
}
}
this.updateTitleBar();
this.updateVideoList();
this.favlist.save();
},
updateTitleBar: function () {
if (this.titleBar && this.titleBar.tagName.toUpperCase() == "A") {
this.titleBar.title = this.title;
this.titleBar.innerHTML = this.getCaption();
if (this.newVideos.length > 0) {
this.titleBar.style.fontWeight = "bold";
this.counter.innerHTML = "(" + this.newVideos.length + ")";
this.counter.style.display = "";
this.container.style.display = "";
} else {
this.titleBar.style.fontWeight = "";
this.counter.style.display = "none";
if (Config.hideCheckedList && this.statusBar.style.display == "none") {
this.container.style.display = "none";
}
}
}
},
updateVideoList: function () {
if (!this.videoList) return;
this.videoList.innerHTML = "";
var len = this.newVideos.length;
if (0 < Config.maxNewVideos && Config.maxNewVideos < len) {
len = Config.maxNewVideos;
}
for (var i = 0; i < len; i++) {
this.newVideos[i].show(this.videoList);
}
},
show: function (elem, editting) {
var div = document.createElement("div");
div.style.clear = "both";
div.style.padding = "4px 0px";
div.style.borderBottom = "1px solid #999";
this.container = div;
var buttons = document.createElement("p");
try { buttons.style.cssFloat = "right"; }
catch (e) { buttons.style.styleFloat = "right"; }
div.appendChild(buttons);
this.buttons = buttons;
var self = this;
if (editting) {
this.toRemove = false;
this.addButton(buttons, "削除", function () {
self.toRemove = true;
self.container.parentNode.removeChild(self.container);
self.container = false;
});
} else {
this.addButton(buttons, "クリア", function () { self.clearAll(); });
}
var status = document.createElement("p");
status.className = "TXT12";
try { status.style.cssFloat = "right"; }
catch (e) { status.style.styleFloat = "right"; }
status.style.color = "#FFF";
status.style.backgroundColor = "#666";
status.style.fontWeight = "bold";
status.style.padding = "0px 4px";
status.style.lineHeight = "22px";
status.style.display = "none";
div.appendChild(status);
this.statusBar = status;
var h = document.createElement("p");
h.className = "TXT12";
h.style.padding = "2px 0px";
div.appendChild(h);
if (editting) {
var input = document.createElement("input");
input.type = "text";
input.value = this.caption || this.title;
input.style.width = "170px";
h.appendChild(input);
this.titleBar = input;
this.counter = null;
} else {
var a = document.createElement("a");
a.href = this.getUri();
a.title = this.title;
a.innerHTML = this.getCaption();
h.appendChild(a);
this.titleBar = a;
var counter = document.createElement("span");
counter.style.color = "#F33";
counter.style.fontWeight = "bold";
counter.style.marginLeft = "4px";
counter.style.display = "none";
h.appendChild(counter);
this.counter = counter;
}
var breaker = document.createElement("p");
breaker.style.clear = "both";
breaker.innerHTML = '';
div.appendChild(breaker);
if (editting) {
this.videoList = null;
} else {
var ul = document.createElement("ul");
ul.style.clear = "both";
ul.style.listStyleType = "none";
ul.style.margin = "0px";
ul.style.padding = "0px";
div.appendChild(ul);
this.videoList = ul;
}
this.updateTitleBar();
this.updateVideoList();
elem.appendChild(div);
},
showConfig: function (elem) {
this.show(elem, true);
},
addButton: function (elem, caption, func) {
var btn = document.createElement("input");
btn.type = "button";
btn.value = caption;
btn.className = "submit";
btn.style.marginLeft = "4px";
Util.observe(btn, "click", func);
elem.appendChild(btn);
return btn;
},
saveConfig: function () {
if (this.toRemove) {
this.remove();
return;
}
if (!this.titleBar.value || this.titleBar.value == this.title) {
this.caption = false;
} else {
this.caption = this.titleBar.value;
}
if (this.caption != this.titleBar.value) {
this.caption = this.titleBar.value;
}
},
remove: function () {
this.favlist.remove(this.listId);
if (this.container) {
this.container.parentNode.removeChild(this.container);
this.container = false;
}
},
clear: function (video) {
if (!video) return;
for (var i = 0, len = this.newVideos.length; i < len; i++) {
if (this.newVideos[i] == video) {
this.newVideos.splice(i, 1);
}
}
video.remove();
this.checked[video.id] = true;
this.updateTitleBar();
this.favlist.save();
},
clearAll: function () {
for (var i = 0, len = this.newVideos.length; i < len; i++) {
var v = this.newVideos[i];
v.remove();
this.checked[v.id] = true;
}
this.newVideos = [ ];
this.updateTitleBar();
this.favlist.save();
},
setStatus: function (status, color, timeout) {
if (status) {
this.statusBar.innerHTML = status;
this.statusBar.style.backgroundColor = color;
this.statusBar.style.display = "";
this.buttons.style.display = "none";
this.container.style.display = "";
if (timeout) {
var self = this;
w.setTimeout(function () { self.setStatus(false); }, timeout);
}
} else {
this.statusBar.style.display = "none";
this.buttons.style.display = "";
if (Config.hideCheckedList && this.newVideos.length == 0) {
this.container.style.display = "none";
}
}
}
};
var Favlist = function () { this.initialize.apply(this, arguments); };
Favlist.prototype = {
initialize: function () {
this.id = arguments[0] || "favlist";
this.list = { };
this.tabs = [ ];
this.container = false;
this.updateAllButton = false;
this.updateQueue = [ ];
this.newVersion = false;
},
save: function () {
Storage.setItem(this.id, this.serialize());
},
load: function () {
this.unserialize(Storage.getItem(this.id));
},
serialize: function () {
var data = [];
for (var k in this.list) {
data.push(this.list[k].serialize());
}
return data.join("#");
},
unserialize: function (data) {
this.list = { };
if (data) {
data = data.split(/#/);
for (var i = 0; i < data.length; i ++) {
var ml = new Mylist(this);
ml.unserialize(data[i]);
this.list[ml.listId] = ml;
}
}
},
get: function (listId) {
return this.list[listId];
},
add: function (listId, title, params) {
var ml = new Mylist(this, listId, title, params);
this.list[listId] = ml;
this.save();
},
remove: function (listId) {
var ml = this.list[listId];
if (ml) {
delete this.list[listId];
this.save();
}
},
updateAll: function () {
var first = false;
for (var k in this.list) {
if (!first) {
first = this.list[k];
} else {
this.updateQueue.push(this.list[k]);
this.list[k].setStatus("待機中", "#CCC");
}
}
if (first) {
if (this.updateAllButton) {
this.updateAllButton.disabled = true;
}
first.update();
}
},
updateCallback: function (ml) {
if (this.updateQueue.length > 0) {
var ml = this.updateQueue.shift();
ml.update();
} else {
if (this.updateAllButton) {
this.updateAllButton.disabled = false;
}
}
},
clearByVideoId: function (videoId) {
var toRemove;
if (videoId instanceof Array) {
var ids = { };
for (var i = 0, len = videoId.length; i < len; i++) {
ids[videoId[i]] = true;
}
toRemove = function (id) {
return ids[id];
}
} else {
toRemove = function (id) {
return (videoId == id);
}
}
var changed = false;
for (var k in this.list) {
var mylist = this.list[k], videos = mylist.newVideos;
var mylist_changed = false;
for (var i = videos.length - 1; i >= 0; i--) {
var video = videos[i];
if (toRemove(video.id)) {
video.remove();
mylist.checked[video.id] = true;
videos.splice(i, 1);
changed = true;
mylist_changed = true;
}
}
if (mylist_changed) mylist.updateTitleBar();
}
if (changed) this.save();
},
addToPlaylist: function () {
if (typeof w.gm_playlistController == "undefined") return;
var addVideos = [];
for (var k in this.list) {
var mylist = this.list[k], videos = mylist.newVideos;
for (var i = 0, len = videos.length; i < len; i++) {
addVideos.push(videos[i]);
}
mylist.clearAll();
}
if (addVideos.length > 0) {
w.gm_playlistController.pushVideos(addVideos);
}
},
show: function (elem) {
var div = document.createElement("div");
div.className = "mb16p4";
div.style.position = "relative";
var h = document.createElement("h2");
div.appendChild(h);
var self = this;
this.tabs = [];
this.addTab(h, "一覧", function () { self.showList(); });
this.addTab(h, "設定", function () { self.showConfig(); });
var span = document.createElement("span");
span.innerHTML = "favlist";
span.style.display = "block";
span.style.borderWidth = "2px";
span.style.borderColor = "#FFF #FFF #333 #FFF";
span.style.borderStyle = "solid";
h.appendChild(span);
var container = document.createElement("div");
container.style.clear = "both";
container.style.position = "relative";
container.style.paddingTop = "4px";
//★
container.style.fontSize = "85%";
div.appendChild(container);
this.container = container;
if (this.newVersion) {
this.showVersionUp(this.newVersion);
}
elem.insertBefore(div, elem.firstChild);
this.switchTab(0);
},
addTab: function (elem, caption, func) {
var tab = document.createElement("a");
tab.href = "javascript:void(0);";
tab.innerHTML = caption;
tab.style.display = "block";
tab.style.textAlign = "center";
tab.style.textDecoration = "none";
try {
tab.style.cssFloat = "right";
} catch (e) {
tab.style.styleFloat = "right";
}
tab.style.width = "3em";
tab.style.color = "#333";
tab.style.backgroundColor = "#FFF";
tab.style.borderWidth = "2px";
tab.style.borderColor = "#FFF #FFF #333 #FFF";
tab.style.borderStyle = "solid";
elem.insertBefore(tab, elem.firstChild);
this.tabs.push({
tab: tab,
func: func
});
var self = this;
Util.observe(tab, "click", function () { self.switchTab(tab); });
return tab;
},
switchTab: function (selectTab) {
if (this.updateQueue.length > 0) return;
if (typeof selectTab == "number") selectTab = this.tabs[selectTab].tab;
var func = false;
for (var i = 0, len = this.tabs.length; i < len; i++) {
var tab = this.tabs[i].tab;
if (tab == selectTab) {
tab.style.borderColor = "#333 #333 #FFF #333";
func = this.tabs[i].func;
} else {
tab.style.borderColor = "#FFF #FFF #333 #FFF";
}
}
if (func) func();
},
showList: function () {
this.container.innerHTML = "";
for (var k in this.list) {
this.list[k].show(this.container);
}
var buttons = document.createElement("p");
buttons.style.clear = "both";
buttons.style.paddingTop = "4px";
var self = this;
this.updateAllButton = this.addButton(buttons, "いますぐ更新", function () { self.updateAll(); });
var checkPlaylist = function () {
if (typeof w.gm_playlistController != "undefined") {
var b = self.addButton(buttons, "プレイリストに移動", function () { self.addToPlaylist(); });
b.style.marginLeft = "5px";
return true;
}
}
checkPlaylist() || setTimeout(checkPlaylist, 1);
this.container.appendChild(buttons);
},
addButton: function (elem, caption, func) {
var btn = document.createElement("input");
btn.type = "button";
btn.value = caption;
btn.className = "submit";
Util.observe(btn, "click", func);
elem.appendChild(btn);
return btn;
},
showConfig: function () {
this.container.innerHTML = "";
for (var k in this.list) {
this.list[k].showConfig(this.container);
}
Config.show(this.container);
var p = document.createElement("p");
p.style.borderTop = "1px solid #999";
p.style.padding = "8px";
var saveButton = document.createElement("input");
saveButton.className = "submit";
saveButton.type = "button";
saveButton.value = "保存";
saveButton.style.width = "5em";
saveButton.style.fontWeight = "bold";
var self = this;
Util.observe(saveButton, "click", function () {
for (var k in self.list) {
self.list[k].saveConfig();
}
self.save();
Config.save();
self.switchTab(0);
});
p.appendChild(saveButton);
this.container.appendChild(p);
},
showVersionUp: function (newVersion) {
this.newVersion = newVersion;
if (!this.container) return;
var p = document.createElement("p");
p.style.position = "relative";
p.style.border = "1px solid #996";
p.style.backgroundColor = "#FFC";
p.style.marginBottom = "8px";
p.style.padding = "4px";
p.style.color = "#000";
p.className = "TXT12";
p.innerHTML =
'新しいバージョン ' + newVersion + ' がリリースされています。
'
+ 'インストール'
+ ' (更新履歴)';
var el = this.container.parentNode;
el.insertBefore(p, el.firstChild);
}
};
var NicoHistory = {
videoIds: [ ],
loadFromCookie: function () {
NicoHistory.videoIds = [ ];
if (w.document.cookie && /\bnicohistory\s*=\s*([^;]+)/.test(w.document.cookie)) {
var hist = RegExp.$1;
var m, re = /(?:^%2C|%2C)(.+?)(?=%3A|$)/ig;
while (m = re.exec(hist)) {
this.videoIds.push(w.unescape(m[1]));
}
}
}
};
var RegisterButton = function () { this.initialize.apply(this, arguments); };
RegisterButton.prototype = {
initialize: function (favlist, listId, title, params) {
this.favlist = favlist;
this.listId = listId;
this.title = title;
this.params = params;
this.button = false;
this.onExecute = false;
},
show: function (elem) {
if (this.button) return;
var div = document.createElement("div");
div.style.position = "absolute";
div.style.right = "0";
div.style.top = "0";
div.style.textAlign = "left";
var a = document.createElement("a");
a.id = this.buttonId;
a.href = "javascript:void(0);";
a.className = "TXT12";
a.style.display = "block";
a.style.textDecoration = "none";
a.style.padding = "4px 8px";
a.style.border = "1px solid #CCC";
a.style.backgroundColor = "#EEE";
div.appendChild(a);
var self = this;
Util.observe(a, "click", function () { self.execute(); });
elem.style.position = "relative";
elem.appendChild(div);
this.button = a;
this.update();
},
update: function () {
if (!this.button) return;
if (this.favlist.get(this.listId)) {
this.button.innerHTML = '× お気に入りリストから削除';
} else {
this.button.innerHTML = '★ お気に入りリストに登録';
}
},
execute: function () {
if (this.onExecute) {
this.onExecute(this);
}
if (this.favlist.get(this.listId)) {
this.favlist.remove(this.listId);
} else {
this.favlist.add(this.listId, this.title, this.params);
Storage.setItem("lastUpdate", "0");
}
this.update();
}
};
var MylistSortValueToNumber = {
"at_d": 1, "at_a": 0, "mm_a": 2, "mm_d": 3, "tt_a": 4, "tt_d": 5,
"pt_d": 6, "pt_a": 7, "vc_d": 8, "vc_a": 9, "ct_d": 10, "ct_a": 11,
"cc_d": 12, "cc_a": 13, "mc_d": 14, "mc_a": 15, "ls_d": 16, "ls_a": 17
};
var FavlistApp = {
start: function () {
Config.load();
this.favlist = new Favlist();
this.favlist.load();
if (this.showRegisterButton())
return;
this.clearFavlistByHistory();
if (this.showFavlist()) {
this.hookCategoryTabSwitch();
this.updateExpiredFavlist();
if (Config.checkVersionUp) {
var self = this;
NicoNicoFavlist.checkVersionUp(function () {
self.favlist.showVersionUp.apply(self.favlist, arguments);
});
}
}
},
showRegisterButton: function () {
var m;
if (m = w.location.href.match(/nicovideo\.jp\/mylist\/(?:\d+\/)?(\d+)(?!.*rss=)/)) {
var h1 = document.getElementsByTagName("h1");
if (h1 && h1.length > 0) {
var button = new RegisterButton(this.favlist, "mylist/" + m[1], h1[0].innerHTML);
button.show(h1[0].parentNode);
button.onExecute = function () {
var sortselect = document.getElementById("sortselect");
if (sortselect && sortselect.value in MylistSortValueToNumber) {
button.params = "sort=" + MylistSortValueToNumber[sortselect.value];
}
}
}
return true;
} else if (m = w.location.href.match(/nicovideo\.jp\/(myvideo|user)\/(\d+)(?!.*rss=)/)) {
var pageBody = document.getElementById("PAGEBODY");
if (pageBody) {
var title = document.title.match(/^(.+)さんの/) ? RegExp.$1 + "さんの投稿動画" : "投稿動画(" + m[2] + ")";
var params = (m[1] == "myvideo" && w.location.search) ? w.location.search.substr(1) : false;
var button = new RegisterButton(this.favlist, "myvideo/" + m[2], title, params);
button.show(pageBody);
}
return true;
}
return false;
},
clearFavlistByHistory: function () {
NicoHistory.loadFromCookie();
if (NicoHistory.videoIds.length > 0) {
this.favlist.clearByVideoId(NicoHistory.videoIds);
}
},
showFavlist: function () {
if (!w.location.href.match(/nicovideo\.jp\/(?![a-z])/)) return false;
var parentContainer;
if (document.evaluate) {
var xp = "//div[@class='content_360']";
var r = document.evaluate(xp, document, null,
XPathResult.FIRST_ORDERED_NODE_TYPE, null);
if (r) parentContainer = r.singleNodeValue;
} else {
var divs = document.getElementsByClassName("content_360");
if (divs.length > 0) {
parentContainer = divs[0];
}
}
if (!parentContainer) {
parentContainer = this.createRescueContainer();
}
if (parentContainer) {
this.favlist.show(parentContainer);
return true;
} else {
return false;
}
},
hookCategoryTabSwitch: function () {
if (w.Category) {
var self = this;
var oldCategoryUpdate = w.Category.update;
if (oldCategoryUpdate) {
w.Category.update = function () {
oldCategoryUpdate.apply(w.Category, arguments);
if (Config.showInAllTabs) {
self.showFavlist();
} else {
w.Category.update = oldCategoryUpdate;
}
};
}
}
},
updateExpiredFavlist: function () {
if (Config.checkInterval > 0) {
var now = w.Math.floor((new Date()).getTime() / 1000);
var last = parseInt(Storage.getItem("lastUpdate") || "0");
if (last + Config.checkInterval < now) {
Storage.setItem("lastUpdate", now.toString());
this.favlist.updateAll();
}
}
},
rescueContainer: null,
createRescueContainer: function () {
if (this.rescueContainer) {
this.rescueContainer.innerHTML = "";
return this.rescueContainer;
}
var div = document.createElement("div");
div.style.position = "fixed";
div.style.width = "360px";
div.style.height = "300px";
div.style.right = "10px";
div.style.bottom = "10px";
div.style.overflow = "auto";
div.style.backgroundColor = "#FFF";
div.style.border = "1px solid #CCC";
document.body.appendChild(div);
var p = document.createElement("p");
p.className = "TXT12";
p.style.color = "#C00";
p.innerHTML = 'レスキューモードで実行中 (Favlist を閉じる)';
div.appendChild(p);
var container = document.createElement("div");
div.appendChild(container);
var cb = p.getElementsByTagName("a")[0];
Util.observe(cb, "click", function () {
if (div.style.height == "300px") {
div.style.height = "1em";
cb.innerHTML = "Favlist を開く";
} else {
div.style.height = "300px";
cb.innerHTML = "Favlist を閉じる";
}
});
return this.rescueContainer = container;
}
};
FavlistApp.start();
})();