// ==UserScript==
// @name ニコニコ静画NGスクリプト
// @match *://seiga.nicovideo.jp/*
// @match *://seiga.nicovideo.jp/search/*
// @match *://seiga.nicovideo.jp/tag/*
// @match *://seiga.nicovideo.jp/illust/*
// @match *://seiga.nicovideo.jp/my/personalize*
// @description ニコニコ静画でNGユーザの投稿を非表示にする
// @license MIT License Copyright (c) 2020 Tennosuke Tokoro
// @license このスクリプトはkengo312氏のNico Nico Ranking NG (MIT License)を流用しています。
// @version 202006200
// @namespace https://greasyfork.org/users/585074
// @downloadURL none
// ==/UserScript==
var i;
var parent_element, mode;
if(location.href.match(/seiga.nicovideo.jp\/search/)){
mode = "キーワード検索";
parent_element = document.getElementById("usearch_form");
}else if(location.href.match(/seiga.nicovideo.jp\/my\/personalize/)){
mode = "定点観測";
parent_element = document.getElementsByClassName("sg_pankuzu")[0];
}else if(location.href.match(/seiga.nicovideo.jp\/tag/)){
mode = "タグ検索";
parent_element = document.getElementsByClassName("sg_pankuzu")[0];
}else if(location.href.match(/seiga.nicovideo.jp\/illust\/ranking\/point\/.*\/g_.*/)){
mode = "ランキング・個別";
parent_element = document.getElementsByClassName("sg_pankuzu")[0];
}else if(location.href.match(/seiga.nicovideo.jp\/illust\/ranking/)){
mode = "ランキング・まとめ";
parent_element = document.getElementsByClassName("sg_pankuzu")[0];
}else if(location.href.match(/seiga.nicovideo.jp\/illust\/list/)){
mode = "すべてのイラスト";
parent_element = document.getElementsByClassName("sg_pankuzu")[0];
}else{
mode = "トップページ";
parent_element = document.getElementsByClassName("sg_pankuzu")[0];
}
const ng_config_button = document.createElement("input");
ng_config_button.id = "ng_config_button";
ng_config_button.type = "button";
ng_config_button.value = "静画NG設定 --作動中--";
ng_config_button.style.cssText = "background-color:red; padding-left:0.5em; padding-right:0.5em; padding-top:1px; padding-bottom:1px; font-size:12px; margin-left:1em;";
parent_element.appendChild(ng_config_button);
//NG設定画面
const ng_dialog = document.createElement("div");
ng_dialog.id = "ng_dialog";
ng_dialog.style.cssText = "margin-bottom:1em; position:fixed; width:auto; height:40%; line-height:1.5em; display:none; flex-direction:column; justify-content:center; background-color:gray; z-index:10";
const ng_dialog_top = document.createElement("div");
ng_dialog_top.style.cssText = "display:flex; flex-direction:row;";
const ng_types = document.createElement("select");
ng_types.style.cssText = "font-size:14px; margin-top:1em; margin-bottom:1em; margin-left:1em;";
ng_types.innerHTML = `
`;
const ng_durations = document.createElement("select");
ng_durations.style.cssText = "font-size:14px; margin-top:1em; margin-bottom:1em; margin-left:1em;";
ng_durations.innerHTML = `
`;
const ng_close_button = document.createElement("input");
ng_close_button.type = "button";
ng_close_button.style.cssText = "font-size:18px; margin-top:0.5em; margin-bottom:0.5em; margin-left:1em; padding-left:1em; padding-right:1em;";
ng_close_button.value = "閉じる";
ng_close_button.onclick = function () {
ng_dialog.style.display = "none";
}
const ng_onoff_button = document.createElement("input");
ng_onoff_button.type = "button";
ng_onoff_button.style.cssText = "font-size:18px; background-color:steelblue; width:6em; margin-top:0.5em; margin-bottom:0.5em; margin-left:1em; margin-right:1em; padding-left:1em; padding-right:1em;";
ng_onoff_button.value = "一時停止";
const ng_dialog_middle = document.createElement("div");
ng_dialog_middle.style.cssText = "width:100%; height:100%; margin-bottom:1em; display:flex; flex:auto; flex-direction:row;";
const ng_list = document.createElement("select");
ng_list.style.cssText = "width:auto; margin-left:1em; margin-right:1em;";
ng_list.multiple = "multiple";
ng_list.size = 14;
const ng_operation_area = document.createElement("p");
ng_operation_area.style.cssText = "display:flex; flex:auto; flex-direction:column;";
const view_config = document.createElement("select");
view_config.style.cssText = "font-size: 14px; margin-top:1em; margin-right:1em;";
view_config.innerHTML = ``;
const enable_keyword_search = document.createElement("select");
enable_keyword_search.style.cssText = "font-size: 14px; margin-top:1em; margin-bottom:1em; margin-right:1em;";
enable_keyword_search.innerHTML = ``;
const ng_add_delete_buttons = document.createElement("div");
ng_add_delete_buttons.style.cssText = "display:flex; flex-direction:row;";
const ng_add_button = document.createElement("input");
ng_add_button.style.cssText = "font-size:18px; width:4em; margin-top:1em; margin-bottom:2em;";
ng_add_button.type = "button";
ng_add_button.value = "追加";
const ng_delete_button = document.createElement("input");
ng_delete_button.style.cssText = "font-size:18px; width:4em; margin-left:2em; margin-top:1em; margin-bottom:2em;";
ng_delete_button.type = "button";
ng_delete_button.value = "削除";
const ng_import_export_buttons = document.createElement("div");
ng_import_export_buttons.style.cssText = "display:flex; flex-direction:row;";
const ng_import_button = document.createElement("input");
ng_import_button.style.cssText = "font-size:14px; padding:0.5em; margin-bottom:2em;";
ng_import_button.type = "button";
ng_import_button.value = "インポート";
const ng_export_button = document.createElement("input");
ng_export_button.style.cssText = "font-size:14px; padding:0.5em; margin-left:2em; margin-bottom:2em;";
ng_export_button.type = "button";
ng_export_button.value = "エクスポート";
ng_dialog_top.appendChild(ng_types);
ng_dialog_top.appendChild(ng_durations);
ng_dialog_top.appendChild(ng_close_button);
ng_dialog_top.appendChild(ng_onoff_button);
ng_dialog.appendChild(ng_dialog_top);
ng_dialog_middle.appendChild(ng_list);
ng_operation_area.appendChild(view_config);
ng_operation_area.appendChild(enable_keyword_search);
ng_operation_area.appendChild(ng_add_delete_buttons);
ng_add_delete_buttons.appendChild(ng_add_button);
ng_add_delete_buttons.appendChild(ng_delete_button);
ng_operation_area.appendChild(ng_import_export_buttons);
ng_import_export_buttons.appendChild(ng_import_button);
ng_import_export_buttons.appendChild(ng_export_button);
ng_dialog_middle.appendChild(ng_operation_area);
ng_dialog.appendChild(ng_dialog_middle);
parent_element.appendChild(ng_dialog);
var ng_list_db = [];
var id_name_list = [];
var id_query_flag = 0;
var view_config_db = {};
loadNgData();
loadViewConfig();
updateNgList();
ng_config_button.onclick = function(){
ng_dialog.style.display = "flex";
};
ng_add_button.onclick = function(){
var ng_target = window.prompt(ng_types.value + "を入力してください", "");
var ng_duration = ng_durations.value;
var end_date = new Date();
if (ng_duration == "設定期間:永久"){
end_date.setFullYear(4000);
}else if (ng_duration == "設定期間:1週間"){
end_date.setDate(end_date.getDate() + 7);
}else if (ng_duration == "設定期間:1ヶ月間"){
end_date.setDate(end_date.getDate() + 30);
}else if (ng_duration == "設定期間:3ヶ月間"){
end_date.setMonth(end_date.getMonth() + 3);
}else if (ng_duration == "設定期間:6ヶ月間"){
end_date.setMonth(end_date.getMonth() + 6);
}else if (ng_duration == "設定期間:1年間"){
end_date.setFullYear(end_date.getFullYear() + 1);
}else if (ng_duration == "設定期間:3年間"){
end_date.setFullYear(end_date.getFullYear() + 3);
}
var opt = document.createElement("option");
opt.text = formatNgInfo(ng_types.value, dateString(end_date), ng_target);
var ng_info = { user : ng_target, type : ng_types.value, date : dateString(end_date) };
ng_list_db.push(ng_info);
if (ng_types.value == "NGユーザーID"){
var ng_user_name = getUserName(ng_target);
if (ng_user_name == null) {
alert("指定されたIDに該当するユーザ名を取得できず");
return;
}
var id_name_obj = { id : ng_target, name : ng_user_name };
id_name_list.push(id_name_obj);
}
updateNgList();
saveNgData();
};
ng_delete_button.onclick = function(){
var ng_info = parseNgInfo(ng_list.value);
ng_list_db.splice(ngListIndexOf(ng_info.user), 1);
updateNgList();
saveNgData();
};
ng_import_button.onclick = function(){
var open_dlg = document.createElement("input");
open_dlg.type = "file";
open_dlg.onchange = (e) => {
var reader = new FileReader();
reader.onload = () => {
var obj = JSON.parse(reader.result);
ng_list_db = obj.ng_list_db;
view_config_db = obj.view_config_db;
saveNgData();
saveViewConfig();
location.reload();
}
reader.readAsText(e.target.files[0]);
};
open_dlg.click();
};
ng_export_button.onclick = function(){
var a = document.createElement("a");
var blob = new Blob([JSON.stringify({ng_list_db, view_config_db})], {type: "text/plain"});
a.href = URL.createObjectURL(blob);
a.download = "nicovideo_seiga_ng.config";
a.style.display = "none";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
};
ng_onoff_button.onclick = function () {
if (ng_onoff_button.value == "一時停止"){
ng_onoff_button.value = "再開";
ng_config_button.value = "静画NG設定 --一時停止中--";
ng_config_button.style.backgroundColor = "slategray";
}else{
ng_onoff_button.value = "一時停止";
ng_config_button.value = "静画NG設定 --作動中--";
ng_config_button.style.backgroundColor = "red";
}
view_config_db.state = ng_config_button.value;
saveViewConfig();
location.reload();
}
view_config.onchange = function(){
view_config_db.ng_item_filling =view_config.value;
saveViewConfig();
}
enable_keyword_search.onchange = function(){
view_config_db.at_keyword_search = enable_keyword_search.value;
saveViewConfig();
}
function dateString(date){
var year = date.getFullYear();
var month = date.getMonth() + 1;
var day = date.getDate();
var toofar = new Date(3000, 11, 31, 23, 59, 59, 999);
if (date > toofar) return "9999/12/31";
return year + "/" + month + "/" + day;
}
function formatNgInfo(type, date, user){
return "[" + type + "]" + date + "まで" + user;
}
function parseNgInfo(nginfo_text){
var type, date, user;
type = nginfo_text.substr(1, nginfo_text.indexOf("]") - 1);
date = nginfo_text.substr(nginfo_text.indexOf("]") + 1, nginfo_text.indexOf("まで") - 1 - nginfo_text.indexOf("]"));
user = nginfo_text.substr(nginfo_text.indexOf("まで") + "まで".length);
return { user : user, type : type, date : date };
}
function saveNgData(){
localStorage.setItem("nicovideo_seiga_ng", JSON.stringify(ng_list_db));
}
function loadNgData(){
//ブラウザに保存したNGリストデータをロード
var db = localStorage.getItem("nicovideo_seiga_ng");
if (db == null){
ng_list_db = [];
}else{
ng_list_db = JSON.parse(localStorage.getItem("nicovideo_seiga_ng"));
}
//期限切れのNG指定があったら削除
var today = new Date();
for (var i = ng_list_db.length - 1; i >= 0; i--){
var yy_mm_dd = ng_list_db[i].date.split("/");
var end_date = new Date(yy_mm_dd[0], yy_mm_dd[1], yy_mm_dd[2]);
if (today > end_date){
ng_list_db.splice(i, 1);
}
}
//ユーザID形式の項目があったら対応するユーザ名を取得
if (id_query_flag == 0){
for (var j = 0; j < ng_list_db.length; j++){
if (ng_list_db[j].type == "NGユーザーID"){
var name = getUserName(ng_list_db[j].user);
var obj = { id : ng_list_db[j].user, name : name };
id_name_list.push(obj);
}
}
id_query_flag = 1;
}
}
function updateNgList(){
ng_list.innerHTML = "";
for (var i = 0; i < ng_list_db.length; i++) {
var opt = document.createElement("option");
opt.text = formatNgInfo(ng_list_db[i].type, ng_list_db[i].date, ng_list_db[i].user);
ng_list.appendChild(opt);
}
}
function loadViewConfig(){
//ブラウザに保存した表示設定をロード
var db = localStorage.getItem("nicovideo_seiga_ng_view_config");
if (db == null){
view_config_db.ng_item_filling = "削除した枠を:詰める";
view_config_db.at_keyword_search = "キーワード・定点観測時:無効";
view_config_db.state = "静画NG設定 --作動中--";
}else{
view_config_db = JSON.parse(db);
if (view_config_db.ng_item_filling == null) view_config_db.ng_item_filling = "削除した枠を:詰める";
if (view_config_db.at_keyword_search == null) view_config_db.at_keyword_search = "キーワード・定点観測時:無効";
if (view_config_db.state == null) view_config_db.state = "静画NG設定 --作動中--";
}
view_config.value = view_config_db.ng_item_filling;
enable_keyword_search.value = view_config_db.at_keyword_search;
ng_config_button.value = view_config_db.state;
if (ng_config_button.value == "静画NG設定 --作動中--") {
ng_config_button.style.backgroundColor = "red";
ng_onoff_button.value = "一時停止";
}else{
ng_config_button.style.backgroundColor = "slategray";
ng_onoff_button.value = "再開";
}
}
function saveViewConfig(){
//表示設定をブラウザに保存
localStorage.setItem("nicovideo_seiga_ng_view_config", JSON.stringify(view_config_db));
}
function ngListIndexOf(user){
for(var i = 0; i < ng_list_db.length; i++){
if (ng_list_db[i].user == user) return i;
}
return -1;
}
function requestXml(url){
let xhr = new XMLHttpRequest();
xhr.onreadystatechange = function (){
if(xhr.readyState == 4){
if(xhr.status == 0) alert("XHR 通信失敗");
else{
if((200 <= xhr.status && xhr.status < 300) || (xhr.status == 304));
else alert("その他の応答:" + xhr.status);
}
}
};
xhr.open("GET", url, false);
xhr.send(null);
var resp = xhr.responseXML;
xhr.abort();
return resp;
}
function getUserName(id){
var username_query_url = "https://seiga.nicovideo.jp/api/user/info?id=" + id;
var xml = requestXml(username_query_url);
if (xml == null) return null;
var nickname = xml.documentElement.getElementsByTagName("nickname")[0];
if (nickname == null) return null;
return nickname.textContent;
}
function getIllustInfo(id){
var illustinfo_query_url = "https://seiga.nicovideo.jp/api/illust/info?id=" + id;
var xml = requestXml(illustinfo_query_url);
var title = xml.documentElement.getElementsByTagName("title")[0];
var user_id = xml.documentElement.getElementsByTagName("user_id")[0];
return {title: (title != null) ? title.textContent : null, user_id: (user_id != null) ? user_id.textContent : null};
}
function checkNgItem(user_names, user_ids, illust_ids, illust_tags){
var matched = [];
for (var idx = 0; idx < user_names.length; idx++){
//NGリストから1件ずつ該非をチェック
for (var i = 0; i < ng_list_db.length; i++){
if (ng_list_db[i].type == "NGユーザー名") {
//NGユーザ名での判定
if (user_names[idx] != null){
//投稿のユーザ名が与えられている場合
if (user_names[idx] == ng_list_db[i].user) matched.push(illust_tags[idx]);
}else{
//投稿のユーザ名が与えられていない場合
if (user_ids[idx] == null) user_ids[idx] = getIllustInfo(illust_ids[idx]).user_id;
if (getUserName(user_ids[idx]) == ng_list_db[i].user) matched.push(illust_tags[idx]);
}
}else if(ng_list_db[i].type == "NGユーザーID"){
//NGユーザIDでの判定
if (user_ids[idx] != null){
//投稿のユーザIDが与えられている場合
if (user_ids[idx] == ng_list_db[i].user) matched.push(illust_tags[idx]);
}else{
//投稿のユーザIDの代わりにユーザ名が与えられている場合
for (var j = 0; j < id_name_list.length; j++){
//NGユーザID→ユーザ名変換リストを検索
if (user_names[idx] == id_name_list[j].name){
//ユーザ名は一致したが同名の別人の可能性があるので、
//静画IDがある場合は、努力範囲でユーザIDを問い合わせる。
if (illust_ids[idx] != null){
var info = getIllustInfo(illust_ids[idx]);
if ((info == null) || (info.user_id == null)) matched.push(illust_tags[idx]);
if (info.user_id == ng_list_db[i].user) matched.push(illust_tags[idx]);
}
else matched.push(illust_tags[idx]);
}
}
}
}else if(ng_list_db[i].type == "NG静画ID"){
//NG静画IDでの判定
if (illust_ids[idx] != null) {
if (illust_ids[idx].replace("im", "") == ng_list_db[i].user.replace("im", "")) matched.push(illust_tags[idx]);
}
}
}
}
return matched;
}
function parseAndCheckNgItems(mode){
var ngElement, illust_tags, illust_ids, user_names, user_ids;
if((enable_keyword_search.value == "キーワード・定点観測時:無効") && ((mode == "キーワード検索") || (mode == "定点観測"))) return [];
var info = getTagsIdsNames(mode);
illust_tags = info.tags;
illust_ids = info.iids;
user_names = info.names;
user_ids = info.uids;
return checkNgItem(user_names, user_ids, illust_ids, illust_tags);
}
function getTagsIdsNames(mode){
var inner_tags, illust_tags, illust_ids, user_names, user_ids;
var getCenterImgInnerTags = () => Array.prototype.slice.call(document.getElementsByClassName("center_img_inner "));
var getUserTags = () => Array.prototype.slice.call(document.getElementsByClassName('user'));
var getIdsFromMiddle = (tags) => tags.map(elem => elem.href.substring("https://seiga.nicovideo.jp/seiga/im".length, elem.href.indexOf("?")));
var getIdsFromLast = (tags) => tags.map(elem => elem.href.substr(elem.href.indexOf("/seiga/im") + ("/seiga/im").length));
var get2ndAncestor = (tags) => tags.map(elem => elem.parentNode.parentNode);
var get3rdAncestor = (tags) => tags.map(elem => elem.parentNode.parentNode.parentNode);
var getUserNamesEmpty = (tags) => tags.map(elem => null);
var getUserNames = (tags) => tags.map(elem => elem.innerHTML);
var getUserNamesRanking = (tags) => tags.map(elem => {
var elem2 = elem.getElementsByClassName("rank_txt_user");
return (elem2.length > 0) ? elem2[0].children[0].textContent : null;
});
var getUserIdsEmpty = (tags) => tags.map(elem => null);
var getUserIdsRanking = (tags) => tags.map(elem => {
var elem2 = elem.getElementsByClassName("rank_txt_user");
return (elem2.length > 0) ? elem2[0].children[0].href.substr(elem2[0].children[0].href.lastIndexOf("/") + 1) : null;
});
if (mode == "キーワード検索"){
inner_tags = getCenterImgInnerTags();
illust_ids = getIdsFromMiddle(inner_tags);
illust_tags = get2ndAncestor(inner_tags);
user_names = getUserNamesEmpty(illust_tags);
user_ids = getUserIdsEmpty(illust_tags);
}
else if (mode == "ランキング・まとめ"){
inner_tags = getCenterImgInnerTags();
illust_ids = getIdsFromMiddle(inner_tags);
illust_tags = get3rdAncestor(inner_tags);
user_names = getUserNamesRanking(illust_tags);
user_ids = getUserIdsRanking(illust_tags);
}
else if (mode == "ランキング・個別"){
inner_tags = getCenterImgInnerTags();
illust_ids = getIdsFromMiddle(inner_tags);
illust_tags = get3rdAncestor(inner_tags);
user_names = getUserNamesRanking(illust_tags);
user_ids = getUserIdsRanking(illust_tags);
}
else if (mode == "定点観測") {
inner_tags = getCenterImgInnerTags();
illust_ids = getIdsFromLast(inner_tags);
illust_tags = get2ndAncestor(inner_tags);
user_names = getUserNamesEmpty(illust_tags);
user_ids = getUserIdsEmpty(illust_tags);
}
else if ((mode == "タグ検索") || (mode == "すべてのイラスト")) {
inner_tags = getUserTags();
illust_ids = getIdsFromLast(get2ndAncestor(inner_tags));
illust_tags = get3rdAncestor(inner_tags);
user_names = getUserNames(inner_tags);
user_ids = getUserIdsEmpty(illust_tags);
}
else if (mode == "トップページ") {
inner_tags = getUserTags();
illust_ids = getIdsFromLast(get2ndAncestor(inner_tags));
illust_tags = get3rdAncestor(inner_tags);
user_names = getUserNames(inner_tags);
user_ids = getUserIdsEmpty(illust_tags);
}
return { tags:illust_tags, iids:illust_ids, names:user_names, uids:user_ids };
}
if (ng_config_button.value == "静画NG設定 --一時停止中--") return;
var matched_ng_items = parseAndCheckNgItems(mode);
for (i = matched_ng_items.length-1; i >= 0; i--){
if (mode.match(/ランキング/)){
//ランキングで詰める機能は未実装
matched_ng_items[i].style.visibility = 'hidden';
}
else if (view_config.value == "削除した枠を:詰める"){
matched_ng_items[i].remove();
}else{
matched_ng_items[i].style.visibility = 'hidden';
}
}