// ==UserScript==
// @name futaba_catalog_NG
// @namespace https://github.com/akoya-tomo
// @description カタログのスレをNGで非表示
// @author akoya_tomo
// @match http://*.2chan.net/*/futaba.php?mode=cat*
// @match https://*.2chan.net/*/futaba.php?mode=cat*
// @version 1.10.0
// @require http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js
// @require https://cdn.jsdelivr.net/npm/[email protected]/src/md5.min.js
// @grant GM_registerMenuCommand
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_addStyle
// @license MIT
// @run-at document-start
// @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMjHxIGmVAAABXElEQVQ4T8VSTUvDQBRcEcGL0H+QQ3c3KSVtmhRMe5aC4rX+Fb15F/PVUqFSqwcV8SxSPYknUcSDKCqICNZ/YBEv62xcS0CQ5iBOeMm+92beDrsh/w8WMIuFrEdD2kdc8ZAvacvapGoPIWs84IsxJ+J96I6yftYmKLywiInCekFUdirCbJsCzfNiUMwoLdECLQPe2TfH6lhCarDxK8HrKU5UlLfKwu7agvnsUOkJDehBaaMknE1nyIsjYM8ExBkMGSQbkiid0FU6D9uzcv1DHLEB9Wkt3kH3dBNDTpIEd9uVFvdA2o3XiR4cnfIVXojFScDSNAghnnu9qUuLD1jf5Vo5gTN4RETUo66i/w7msTmI3yH64D5fQGnsq5MCcHCNa71VaXpgwAXiUqXpgd1vEOkH5Fv5mt7Qj+WJG01jTZVHh9Ewuvhd3+S32qlOqfLoqO/Xx522M6HSvwQhn8ChorvE+0t7AAAAAElFTkSuQmCC
// ==/UserScript==
this.$ = this.jQuery = jQuery.noConflict(true);
(function ($) {
/**
* 設定
*/
var USE_NG_IMAGES = true; // スレ画像のNGを有効にする
var MAX_NG_THREADS = 512; // NGスレの最大保持数(板毎)
var MAX_REGISTERED_NG_IMAGES = 2048; // NG画像の最大登録数
var MAX_OK_IMAGES = 1024; // 非NG画像名の最大保持数(板毎)
var HIDE_CATALOG_BEFORE_LOAD = false; // ページの読み込みが完了するまでカタログを隠す
var USE_NG_THREAD_CLEAR_BUTTON = true; // スレNGのクリアボタンを使用する
var USE_NG_DISABLE_BUTTON = true; // NG機能一時無効ボタンを使用する
var USE_DHASH = false; // 近似画像NGを使用する
var DISTANCE_THRESHOLD = 3; // 近似画像判定閾値(デフォルト:3)
var ENABLE_DHASH_TEST = false; // 近似画像NGのテストモードを有効にする
var CHECK_NG_LIST = true; // NGリストが正常かチェックする
var serverName = document.domain.match(/^[^.]+/);
var pathName = location.pathname.match(/[^/]+/);
var serverFullPath = serverName + "_" + pathName;
var selectIndex = -1;
var imageList = GM_getValue("_futaba_catalog_NG_images", []);
var commentList = GM_getValue("_futaba_catalog_NG_comment", []);
var dateList = GM_getValue("_futaba_catalog_NG_date", []);
var dHashList = GM_getValue("_futaba_catalog_NG_dHashes", []);
var images, ngDate, okImages, dHashes;
var item_md5_text = " md5";
var item_comment_text = " コメント";
var item_date_text = " 最終検出日";
var item_dHash_text = " dHash";
var isNgEnable = true;
// dHashリスト初期化
if (dHashList.length == 0 && imageList.length > 0) {
dHashList = new Array(imageList.length).fill(null);
GM_setValue("_futaba_catalog_NG_dHashes", dHashList);
}
if (CHECK_NG_LIST && USE_NG_IMAGES && !isNgListNormal()) {
alert("NGリストが壊れています");
}
if (HIDE_CATALOG_BEFORE_LOAD) {
hideCatalog();
} else {
$(init);
}
function isNgListNormal() {
var imageNum = imageList.length;
var commentNum = commentList.length;
var dateNum = dateList.length;
var dHashNum = dHashList.length;
//console.log("futaba catalog NG - imageNum: " + imageNum);
//console.log("futaba catalog NG - commentNum: " + commentNum);
//console.log("futaba catalog NG - dateNum: " + dateNum);
//console.log("futaba catalog NG - dHashNum: " + dHashNum);
return (imageNum == commentNum) && (imageNum == dateNum) && (imageNum == dHashNum);
}
function init() {
//clearNgNumber();
//console.log("futaba_catalog_NG - commmon: " +
// GM_getValue("_futaba_catalog_NG_words", ""));
//console.log("futaba_catalog_NG - indivisual: " +
// getCurrentIndivValue("NG_words_indiv", ""));
GM_registerMenuCommand("NGワード編集", editNgWords);
if (USE_NG_IMAGES) {
GM_registerMenuCommand("NGリスト編集", editNgList);
}
GM_registerMenuCommand("スレNGクリア", confirmClearNgNumber);
setStyle();
makeNgMenubar();
makeConfigUI();
makeNgListUI();
makeNgButton();
hideNgThreads();
checkAkahukuReload();
listenKoshianDelEvent();
listenFthPickupEvent();
}
/**
* NG番号クリア
* @param {boolean} forced 強制的にクリアするか
*/
function clearNgNumber(forced) {
if (!forced && window.name) {
return;
}
window.name = location.href;
var ngNumberObj = getIndivObj("NG_numbers_indiv");
if (ngNumberObj === "") {
ngNumberObj = {};
}
ngNumberObj[serverFullPath] = [];
var jsonString = JSON.stringify(ngNumberObj);
GM_setValue("NG_numbers_indiv", jsonString);
}
/**
* 各板個別NGデータを保存
* @param {string} target 保存するNG名
* @param {string|Array} val 保存するNGデータ
*/
function setIndivValue(target, val) {
var indivObj = getIndivObj(target);
if (indivObj === "") {
indivObj = {};
}
indivObj[serverFullPath] = val;
var jsonString = JSON.stringify(indivObj);
GM_setValue(target, jsonString);
//console.log("futaba_catalog_NG: " + target + " updated@" + serverFullPath + " - " + val);
}
/**
* 各板個別NGデータのオブジェクトを取得
* @param {string} target 取得するNG名
* @return {object} 取得したNGデータ
*/
function getIndivObj(target) {
var indivVal = GM_getValue(target, "");
var indivObj = "";
if (indivVal !== "") {
indivObj = JSON.parse(indivVal);
}
return indivObj;
}
/**
* NGワード編集メニュー表示
*/
function editNgWords(){
var wordsCommon = GM_getValue("_futaba_catalog_NG_words", "");
var wordsIndiv = getCurrentIndivValue("NG_words_indiv", "");
$("#GM_fcn_ng_words_common").val(wordsCommon);
$("#GM_fcn_ng_words_individual").val(wordsIndiv);
var $configContainer = $("#GM_fcn_config_container");
$configContainer.fadeIn(100);
}
/**
* 現在の板の個別NGデータを取得
* @param {string} target NGデータ名
* @param {string|Array} defaultVal NGデータが未定義のときの既定値
* @return {string|Array} 現在の板の個別NGデータ
*/
function getCurrentIndivValue(target, defaultVal) {
var indivObj = getIndivObj(target);
var currentIndivVal;
if (indivObj !== "") {
currentIndivVal = indivObj[serverFullPath];
}
if (!currentIndivVal) {
currentIndivVal = defaultVal;
}
return currentIndivVal;
}
/**
* NGリスト編集メニュー表示
*/
function editNgList() {
// マウスホイールリロード対策
$("<div>", {
id: "GM_fcn_catalog_space"
}).appendTo("body");
if ($(window).scrollTop() < 2) {
$("html, body").scrollTop(2);
}
$("html, body").css("overflow", "hidden");
refreshNgList(true);
resetNgListItemText();
var $ngListContainer = $("#GM_fcn_ng_list_container");
$ngListContainer.fadeIn(100);
}
/**
* NGリスト項目名リセット
*/
function resetNgListItemText() {
$("#GM_fcn_ng_list_item_md5").text(item_md5_text + " ");
$("#GM_fcn_ng_list_item_comment").text(item_comment_text + " ");
$("#GM_fcn_ng_list_item_date").text(item_date_text + " ");
$("#GM_fcn_ng_list_item_dHash").text(item_dHash_text + " ");
}
/**
* スレNGクリア確認
*/
function confirmClearNgNumber() {
if (confirm("この板のスレNGを全てクリアします。\nよろしいですか?")) {
$(".GM_fcn_ng_numbers").each(function() {
$(this).removeClass("GM_fcn_ng_numbers");
$(this).css("display", "");
});
clearNgNumber(true);
}
}
/**
* NG機能無効切り替え
*/
function toggleNgDisable() {
var $ngDisableButton = $("#GM_fcn_ng_disable_button");
if (isNgEnable) {
$ngDisableButton.css("background", "#a9d8ff");
isNgEnable = false;
$(".GM_fcn_ng_words").css("display", "");
$(".GM_fcn_ng_words").removeClass("GM_fcn_ng_words");
$(".GM_fcn_ng_numbers").css("display", "");
$(".GM_fcn_ng_numbers").removeClass("GM_fcn_ng_numbers");
$(".GM_fcn_ng_images").css("display", "");
$(".GM_fcn_ng_images").removeClass("GM_fcn_ng_images");
$(".GM_fcn_ng_dhash_td").removeClass("GM_fcn_ng_dhash_td");
$(".GM_fcn_ng_dhash_img").removeClass("GM_fcn_ng_dhash_img");
} else {
$ngDisableButton.css("background", "none");
isNgEnable = true;
hideNgThreads();
}
}
/**
* NGリスト表示更新
* @param {boolean} loadNgList NGリストデータを読み込みするか
*/
function refreshNgList(loadNgList) {
if (loadNgList) {
imageList = GM_getValue("_futaba_catalog_NG_images", []);
commentList = GM_getValue("_futaba_catalog_NG_comment", []);
dateList = GM_getValue("_futaba_catalog_NG_date", []);
dHashList = GM_getValue("_futaba_catalog_NG_dHashes", []);
}
var listCount = imageList.length;
$(".GM_fcn_ng_list_row").remove();
for (var i = 0; i < listCount; ++i) {
var row = $("<div>", {
class: "GM_fcn_ng_list_row",
click: function() { // eslint-disable-line no-loop-func
selectIndex = $(this).index();
selectNgList();
}
}).appendTo("#GM_fcn_ng_list_content");
row.append(
$("<div>", {
class: "GM_fcn_ng_list_image",
text: imageList[i],
}),
$("<div>", {
class: "GM_fcn_ng_list_dHash",
text: dHashList[i] ? ("000000000000" + dHashList[i].toString(16)).slice(-13) : "-",
}),
$("<div>", {
class: "GM_fcn_ng_list_comment",
text: commentList[i],
}),
$("<div>", {
class: "GM_fcn_ng_list_date",
text: dateList[i],
}),
$("<div>", {
class: "GM_fcn_ng_list_scrl",
})
);
}
$(".GM_fcn_ng_list_row").css("background-color", "#ffffff");
}
/**
* NGリスト選択
*/
function selectNgList() {
$(".GM_fcn_ng_list_row").css("background-color", "#ffffff")
.eq(selectIndex).css("background-color", "#ffecfd");
$("#GM_fcn_md5").val(imageList[selectIndex]);
$("#GM_fcn_comment").val(commentList[selectIndex]);
}
/**
* NGメニューバー作成
*/
function makeNgMenubar() {
var $ngMenubarArea = $("<div>", {
id: "GM_fcn_ng_menubar",
css: {
"background-color": "#F0E0D6"
}
});
var $ngWordsHeader = $("<span>", {
id: "GM_fcn_ng_words_header",
text: "NGワード",
css: {
"background-color": "#F0E0D6",
fontWeight: "bolder",
"padding-right": "16px"
}
});
$("#cattable").before($ngMenubarArea);
$ngMenubarArea.append($ngWordsHeader);
// 設定ボタン
var $ngWordsButton = $("<span>", {
id: "GM_fcn_config_ng_words",
text: "[設定]",
css: {
cursor: "pointer",
},
click: editNgWords
});
$ngWordsButton.hover(function() {
$(this).css("background-color", "#EEAA88");
}, function() {
$(this).css("background-color", "#F0E0D6");
});
$ngWordsHeader.append($ngWordsButton);
if (USE_NG_DISABLE_BUTTON) {
// NG機能一時無効
var $ngDisableHeader = $("<span>", {
id: "GM_fcn_ng_disalbe_header",
text: "NG機能",
css: {
"background-color": "#F0E0D6",
fontWeight: "bolder",
"padding-right": "16px"
}
});
$ngWordsHeader.after($ngDisableHeader);
// NG機能一時無効ボタン
var $ngDisableButton = $("<a>", {
id: "GM_fcn_ng_disable_button",
text: "[一時無効]",
css: {
cursor: "pointer",
},
click: toggleNgDisable
});
$ngDisableHeader.append($ngDisableButton);
}
if (USE_NG_THREAD_CLEAR_BUTTON) {
// スレNG
var $ngThreadHeader = $("<span>", {
id: "GM_fcn_ng_thread_header",
text: "スレNG",
css: {
"background-color": "#F0E0D6",
fontWeight: "bolder",
"padding-right": "16px"
}
});
$ngWordsHeader.after($ngThreadHeader);
// スレNGクリアボタン
var $ngThreadClearButton = $("<span>", {
id: "GM_fcn_ng_thread_clear_button",
text: "[クリア]",
css: {
cursor: "pointer",
},
click: confirmClearNgNumber
});
$ngThreadClearButton.hover(function() {
$(this).css("background-color", "#EEAA88");
}, function() {
$(this).css("background-color", "#F0E0D6");
});
$ngThreadHeader.append($ngThreadClearButton);
}
if (USE_NG_IMAGES) {
// NGリスト
var $ngListHeader = $("<span>", {
id: "GM_fcn_ng_list_header",
text: "NGリスト",
css: {
"background-color": "#F0E0D6",
fontWeight: "bolder",
"padding-right": "16px"
}
});
$ngWordsHeader.after($ngListHeader);
// NGリスト編集ボタン
var $ngListButton = $("<span>", {
id: "GM_fcn_edit_ng_list",
text: "[編集]",
css: {
cursor: "pointer",
},
click: editNgList
});
$ngListButton.hover(function () {
$(this).css("background-color", "#EEAA88");
}, function () {
$(this).css("background-color", "#F0E0D6" );
});
$ngListHeader.append($ngListButton);
}
}
/**
* NGワード編集メニュー作成
*/
function makeConfigUI() {
var $configContainer = $("<div>", {
id: "GM_fcn_config_container",
css: {
position: "fixed",
"z-index": "1001",
left: "50%",
top: "50%",
"text-align": "center",
"margin-left": "-475px",
"margin-top": "-50px",
"background-color": "rgba(240, 192, 214, 0.95)",
width: "950px",
//height: "100px",
display: "none",
fontSize: "16px",
fontWeight: "normal",
"box-shadow": "3px 3px 5px #853e52",
"border": "1px outset",
"border-radius": "10px",
"padding": "5px",
}
});
$("#GM_fcn_ng_words_header").append($configContainer);
$configContainer.append(
$("<div>").append(
$("<div>").text("NGワード編集").css({
"background-color": "#ffeeee",
"padding": "2px",
"font-weight": "bold"
}),
$("<div>").text("スレ本文に含まれる語句を入力してください。 | を挟むと複数指定できます。正規表現使用可。")
),
$("<div>").css("margin-top", "1em").append(
$("<div>").append(
$("<label>").text("全板共通").attr("for", "GM_fcn_ng_words_common"),
$("<input>", {
id: "GM_fcn_ng_words_common",
class: "GM_fcn_input",
css: {
width: "54em",
fontSize: "13.33px"
}
}),
$("<span>").append(
$("<input>", {
class: "GM_fcn_config_button",
type: "button",
val: "区切り文字挿入",
click: function() {
insertDelimiter("GM_fcn_ng_words_common");
},
})
)
),
$("<div>").append(
$("<label>").text("各板個別").attr("for", "GM_fcn_ng_words_individual"),
$("<input>", {
"id": "GM_fcn_ng_words_individual",
"class": "GM_fcn_input",
css: {
width: "54em",
fontSize: "13.33px"
}
}),
$("<span>").append(
$("<input>", {
class: "GM_fcn_config_button",
type: "button",
val: "区切り文字挿入",
click: function() {
insertDelimiter("GM_fcn_ng_words_individual");
},
})
)
)
),
$("<div>").css({
"margin-top": "1em",
}).append(
$("<span>").css("margin", "0 1em").append(
$("<input>", {
class: "GM_fcn_config_button",
type: "button",
val: "更新",
click: setNgWords
})
),
$("<span>").css("margin", "0 1em").append(
$("<input>", {
class: "GM_fcn_config_button",
type: "button",
val: "キャンセル",
click: function() {
setCursor();
$configContainer.fadeOut(100);
},
})
)
)
);
$(".GM_fcn_config_button").css({
"cursor": "pointer",
"background-color": "#FFECFD",
"border": "2px outset #96ABFF",
"border-radius": "5px",
"font-size": "13.33px",
}).hover(function() {
$(this).css("background-color", "#CCE9FF");
}, function() {
$(this).css("background-color", "#FFECFD");
});
/**
* カーソル位置にデリミタ挿入
* @param {string} id デリミタを挿入する入力欄のID名
*/
function insertDelimiter(id){
var $input = $("#" + id);
var val = $input.val();
var position = $input[0].selectionStart;
var newval = val.substr(0, position) + "|" + val.substr(position);
$input.val(newval);
$input[0].setSelectionRange(position + 1 ,position + 1);
}
}
/**
* NGワードをセット
*/
function setNgWords() {
var inputCommon = $("#GM_fcn_ng_words_common").val();
var inputIndiv = $("#GM_fcn_ng_words_individual").val();
GM_setValue("_futaba_catalog_NG_words", inputCommon);
console.log("futaba_catalog_NG: common NGword updated - " + inputCommon); // eslint-disable-line no-console
setIndivValue("NG_words_indiv", inputIndiv);
setCursor();
$("#GM_fcn_config_container").fadeOut(100);
hideNgThreads(true);
}
/**
* NGワードのカーソルを先頭に移動
*/
function setCursor() {
var inputCommonElm = $("#GM_fcn_ng_words_common").get(0);
if (inputCommonElm) {
inputCommonElm.focus();
inputCommonElm.setSelectionRange(0, 0); // カーソルを先頭に移動
inputCommonElm.blur();
}
var inputIndivElm = $("#GM_fcn_ng_words_individual").get(0);
if (inputIndivElm) {
inputIndivElm.focus();
inputIndivElm.setSelectionRange(0, 0); // カーソルを先頭に移動
inputIndivElm.blur();
}
}
/**
* NGリスト編集メニュー作成
*/
function makeNgListUI() {
if (!USE_NG_IMAGES) {
GM_setValue("OK_images_indiv", "");
return;
}
imageList = GM_getValue("_futaba_catalog_NG_images", []);
commentList = GM_getValue("_futaba_catalog_NG_comment", []);
dateList = GM_getValue("_futaba_catalog_NG_date", []);
dHashList = GM_getValue("_futaba_catalog_NG_dHashes", []);
var $ngListContainer = $("<div>", {
id: "GM_fcn_ng_list_container",
css: {
position: "fixed",
"z-index": "1001",
left: "50%",
top: "50%",
"text-align": "center",
"margin-left": "-475px",
"margin-top": "-250px",
"background-color": "rgba(240, 192, 214, 0.95)",
width: "950px",
//height: "500px",
display: "none",
fontSize: "16px",
fontWeight: "normal",
"box-shadow": "3px 3px 5px #853e52",
"border": "1px outset",
"border-radius": "10px",
"padding": "5px",
}
});
$("#GM_fcn_ng_list_header").append($ngListContainer);
$ngListContainer.append(
$("<div>").append(
$("<div>").text("NGリスト編集").css({
"background-color": "#ffeeee",
"padding": "2px",
"font-size": "16px",
"font-weight": "bold"
}),
$("<div>").css("margin-top", "1em").append(
$("<div>").append(
$("<label>").text("md5:").attr("for", "GM_fcn_md5"),
$("<input>", {
id: "GM_fcn_md5",
class: "GM_fcn_ng_list_input",
readonly: "readonly",
}),
$("<label>").text("コメント:").attr("for", "GM_fcn_comment"),
$("<input>", {
id: "GM_fcn_comment",
class: "GM_fcn_ng_list_input",
keypress: function(e) {
if (e.key == "Enter") {
editSelectedRow();
}
}
})
)
),
$("<div>").css("margin-top", "1em").append(
$("<div>").css("margin-left", "475px").append(
$("<span>").append(
$("<input>", {
class: "GM_fcn_ng_list_button GM_fcn_ng_list_edit_button",
type: "button",
val: "修正",
click: editSelectedRow
})
),
$("<span>").append(
$("<input>", {
class: "GM_fcn_ng_list_button GM_fcn_ng_list_edit_button",
type: "button",
val: "削除",
click: deleteSelectedRow
})
),
$("<span>").css("margin", "0 0 0 1em").append(
$("<input>", {
class: "GM_fcn_ng_list_button GM_fcn_ng_list_move_button",
type: "button",
val: "上",
click: function() {
swapRow(selectIndex - 1);
},
})
),
$("<span>").append(
$("<input>", {
class: "GM_fcn_ng_list_button GM_fcn_ng_list_move_button",
type: "button",
val: "下",
click: function() {
swapRow(selectIndex);
},
})
)
)
)
),
$("<div>").css("margin-top", "1em").append(
$("<div>", {
id: "GM_fcn_ng_list_pane",
}).append(
$("<div>", {
id: "GM_fcn_ng_list_item_row"
}).append(
$("<div>", {
id: "GM_fcn_ng_list_item_md5",
class: "GM_fcn_ng_list_item",
text: item_md5_text + " ",
click: function() {
if ($(this).text() == item_md5_text + "▲") {
resetNgListItemText();
$(this).text(item_md5_text + "▼");
sortNgList(0, -1);
} else {
resetNgListItemText();
$(this).text(item_md5_text + "▲");
sortNgList(0);
}
},
}),
$("<div>", {
id: "GM_fcn_ng_list_item_dHash",
class: "GM_fcn_ng_list_item",
text: item_dHash_text + " ",
click: function() {
if ($(this).text() == item_dHash_text + "▲") {
resetNgListItemText();
$(this).text(item_dHash_text + "▼");
sortNgList(3, -1);
} else {
resetNgListItemText();
$(this).text(item_dHash_text + "▲");
sortNgList(3);
}
},
}),
$("<div>", {
id: "GM_fcn_ng_list_item_comment",
class: "GM_fcn_ng_list_item",
text: item_comment_text + " ",
click: function() {
if ($(this).text() == item_comment_text + "▲") {
resetNgListItemText();
$(this).text(item_comment_text + "▼");
sortNgList(1, -1);
} else {
resetNgListItemText();
$(this).text(item_comment_text + "▲");
sortNgList(1);
}
},
}),
$("<div>", {
id: "GM_fcn_ng_list_item_date",
class: "GM_fcn_ng_list_item",
text: item_date_text + " ",
click: function() {
if ($(this).text() == item_date_text + "▼") {
resetNgListItemText();
$(this).text(item_date_text + "▲");
sortNgList(2);
} else {
resetNgListItemText();
$(this).text(item_date_text + "▼");
sortNgList(2, -1);
}
},
}),
$("<div>", {
id: "GM_fcn_ng_list_item_scrl",
class: "GM_fcn_ng_list_item",
})
),
$("<div>", {
id: "GM_fcn_ng_list_content"
})
),
$("<div>").css("margin-top", "1em").append(
$("<span>").css("margin", "0 1em").append(
$("<input>", {
class: "GM_fcn_ng_list_button",
type: "button",
val: "更新",
click: function() {
if (!CHECK_NG_LIST || isNgListNormal()) {
GM_setValue("_futaba_catalog_NG_images", imageList);
GM_setValue("_futaba_catalog_NG_comment", commentList);
GM_setValue("_futaba_catalog_NG_date", dateList);
GM_setValue("_futaba_catalog_NG_dHashes", dHashList);
$(".GM_fcn_ng_list_row").css("background-color", "#ffffff");
$("#GM_fcn_md5").val("");
$("#GM_fcn_comment").val("");
$("#GM_fcn_ng_list_content").scrollTop(0);
$("#GM_fcn_catalog_space").remove();
$("html, body").css("overflow", "");
selectIndex = -1;
$ngListContainer.fadeOut(100);
$(".GM_fcn_ng_images").css("display", "");
$(".GM_fcn_ng_images").removeClass("GM_fcn_ng_images");
$(".GM_fcn_ng_dhash_td").removeClass("GM_fcn_ng_dhash_td");
$(".GM_fcn_ng_dhash_img").removeClass("GM_fcn_ng_dhash_img");
hideNgThreads();
} else {
alert("NGリストが壊れています\n一度キャンセルしてからやり直してください");
}
},
})
),
$("<span>").css("margin", "0 1em").append(
$("<input>", {
class: "GM_fcn_ng_list_button",
type: "button",
val: "キャンセル",
click: function(){
$(".GM_fcn_ng_list_row").css("background-color", "#ffffff");
$("#GM_fcn_md5").val("");
$("#GM_fcn_comment").val("");
$("#GM_fcn_ng_list_content").scrollTop(0);
$("#GM_fcn_catalog_space").remove();
$("html, body").css("overflow", "");
selectIndex = -1;
$ngListContainer.fadeOut(100);
},
})
)
)
)
);
$(".GM_fcn_ng_list_button").css({
"cursor": "pointer",
"background-color": "#FFECFD",
"border": "2px outset #96ABFF",
"border-radius": "5px",
"font-size": "13.33px"
}).hover(function() {
$(this).css("background-color", "#CCE9FF");
}, function() {
$(this).css("background-color", "#FFECFD");
});
$(document).keydown((e) => {
if (e.key == "ArrowUp" && selectIndex > 0) {
$(".GM_fcn_ng_list_comment").eq(selectIndex - 1).click();
e.preventDefault();
scrollToSelectedRow();
$("#GM_fcn_comment").blur();
} else if (e.key == "ArrowDown" && selectIndex > -1 && selectIndex < imageList.length) {
$(".GM_fcn_ng_list_comment").eq(selectIndex + 1).click();
e.preventDefault();
scrollToSelectedRow();
$("#GM_fcn_comment").blur();
} else if (e.key == "F2" && selectIndex > -1) {
$("#GM_fcn_comment").focus();
e.preventDefault();
}
});
/**
* NGリスト選択行コメント修正
*/
function editSelectedRow() {
if (selectIndex > -1) {
commentList[selectIndex] = $("#GM_fcn_comment").val();
$(".GM_fcn_ng_list_comment").eq(selectIndex).text(commentList[selectIndex]);
}
}
/**
* NGリスト選択行削除
*/
function deleteSelectedRow() {
if (selectIndex > -1) {
imageList.splice(selectIndex, 1);
commentList.splice(selectIndex, 1);
dateList.splice(selectIndex, 1);
dHashList.splice(selectIndex, 1);
}
$(".GM_fcn_ng_list_row").css("background-color", "#ffffff");
$("#GM_fcn_md5").val("");
$("#GM_fcn_comment").val("");
selectIndex = -1;
refreshNgList();
}
/**
* NGリスト行入替
* indexの行と一つ後の行を入れ替える。
* @param {number} index 入替する行番号(先頭行が0)
*/
function swapRow(index) {
if (index <= -1 || index + 1 >= imageList.length) {
return;
}
imageList.splice(index, 2, imageList[index + 1], imageList[index]);
commentList.splice(index, 2, commentList[index + 1], commentList[index]);
dateList.splice(index, 2, dateList[index + 1], dateList[index]);
dHashList.splice(index, 2, dHashList[index + 1], dHashList[index]);
selectIndex = selectIndex == index ? index + 1 : index;
scrollToSelectedRow();
refreshNgList();
selectNgList();
resetNgListItemText();
}
/**
* 選択行までスクロール
*/
function scrollToSelectedRow() {
var rowHeight = 22; // NGリストの1行当たりの高さ(px)
var listLines = 13; // NGリストの表示行数
var selectPos = selectIndex * rowHeight;
var scrollTop = $("#GM_fcn_ng_list_content").scrollTop();
var scrollBottom = scrollTop + (rowHeight * (listLines - 2));
if (selectPos < scrollTop) {
$("#GM_fcn_ng_list_content").scrollTop(selectPos);
}
if (selectPos > scrollBottom) {
$("#GM_fcn_ng_list_content").scrollTop(selectPos - (rowHeight * (listLines - 2)));
}
}
/**
* NGリストソート
* @param {number} index ソート対象の項目(md5:0, コメント:1, 最終検出日:2, dHash:3)
* @param {number} order ソート方向(昇順:1, 降順:-1)未指定は昇順
*/
function sortNgList(index, order = 1) {
var array = new Array();
for (var i = 0; i < imageList.length; ++i) {
array[i] = new Array();
array[i][0] = imageList[i];
array[i][1] = commentList[i];
array[i][2] = dateList[i];
array[i][3] = dHashList[i];
}
array.sort(function(a, b){
if (a[index] > b[index]) {
return order;
}
if (a[index] < b[index]) {
return -order;
}
return 0;
});
for (var j = 0; j < imageList.length; ++j) {
imageList[j] = array[j][0];
commentList[j] = array[j][1];
dateList[j] = array[j][2];
dHashList[j] = array[j][3];
}
$(".GM_fcn_ng_list_row").css("background-color", "#ffffff");
$("#GM_fcn_md5").val("");
$("#GM_fcn_comment").val("");
selectIndex = -1;
refreshNgList();
}
}
/**
* 動的リロードの状態を取得
*/
function checkAkahukuReload() {
// 赤福
var target = $("#akahuku_catalog_reload_status").get(0);
if (target) {
checkAkahukuReloadStatus(target);
} else {
$(document).on("AkahukuContentApplied", () => {
target = $("#akahuku_catalog_reload_status").get(0);
if (target) {
checkAkahukuReloadStatus(target);
} else {
console.error ("futaba_catalog_NG - #akahuku_catalog_reload_status not found");
}
});
}
function checkAkahukuReloadStatus(target) {
var status = "";
var config = { childList: true };
var observer = new MutationObserver(function() {
if (target.textContent == status) {
return;
}
status = target.textContent;
if (status == "完了しました" || status == "アンドゥしました" || status == "リドゥしました") {
if (!isNgEnable) {
$("#GM_fcn_ng_disable_button").css("background", "none");
isNgEnable = true;
}
makeNgButton();
hideNgThreads();
$("body").attr("__fcn_catalog_visibility", "visible");
$("#cattable > tbody").css("opacity", "1");
$("#GM_fth_highlighted_threads").css("visibility", "visible");
} else if (HIDE_CATALOG_BEFORE_LOAD && status !== "") {
$("body").attr("__fcn_catalog_visibility", "hidden");
$("#cattable > tbody").css("opacity", "0");
$("#GM_fth_highlighted_threads").css("visibility", "hidden");
}
});
observer.observe(target, config);
}
// KOSHIAN
$(document).on("KOSHIAN_cat_reload", () => {
if (HIDE_CATALOG_BEFORE_LOAD) {
$("body").attr("__fcn_catalog_visibility", "hidden");
$("#cattable > tbody").css("opacity", "0");
$("#GM_fth_highlighted_threads").css("visibility", "hidden");
}
if (!isNgEnable) {
$("#GM_fcn_ng_disable_button").css("background", "none");
isNgEnable = true;
}
makeNgButton();
hideNgThreads();
$("body").attr("__fcn_catalog_visibility", "visible");
$("#cattable > tbody").css("opacity", "1");
$("#GM_fth_highlighted_threads").css("visibility", "visible");
});
// ふたクロ
checkFutakuroReloadStatus();
function checkFutakuroReloadStatus() {
var opacityZero = false;
var target = $("#cattable").get(0);
var config = { attributes: true , attributeFilter: ["style"] };
var observer = new MutationObserver(function(mutations) {
if ($("#cat_search").length) {
mutations.forEach(function(mutation) {
if (mutation.target.attributes.style.nodeValue == "opacity: 0;") {
opacityZero = true;
if (HIDE_CATALOG_BEFORE_LOAD) {
$("body").attr("__fcn_catalog_visibility", "hidden");
$("#cattable > tbody").css("opacity", "0");
$("#GM_fth_highlighted_threads").css("visibility", "hidden");
}
} else if (opacityZero && mutation.target.attributes.style.nodeValue != "opacity: 0;") {
opacityZero = false;
if (!isNgEnable) {
$("#GM_fcn_ng_disable_button").css("background", "none");
isNgEnable = true;
}
makeNgButton();
hideNgThreads();
$("body").attr("__fcn_catalog_visibility", "visible");
$("#cattable > tbody").css("opacity", "1");
$("#GM_fth_highlighted_threads").css("visibility", "visible");
}
});
}
});
observer.observe(target, config);
}
}
/**
* カタログのスレにNGボタン作成
*/
function makeNgButton() {
// カタログソートが「設定」なら作成しない
if (location.search.match(/mode=catset/)) {
return;
}
// NGボタン
var $ngButton = $("<span>", {
class: "GM_fcn_ng_button",
text: "[NG]",
css: {
color: "blue",
display: "none",
},
});
// NGボタンメニュー
var $ngButtonMenu = $("<div>", {
class: "GM_fcn_ng_menu",
css: {
display: "none",
}
});
var $catTd = $("#cattable td");
$catTd.each(function() {
var $oldNgButtons = $(this).children(".GM_fcn_ng_button");
if ($oldNgButtons.length) {
$oldNgButtons.remove();
}
var $cloneNgButton = $ngButton.clone();
var $cloneNgButtonMenu = $ngButtonMenu.clone();
$cloneNgButton.hover(function () {
$(this).css("color", "red");
}, function () {
$(this).css("color", "blue");
});
$cloneNgButton.on("click",function(){
makeNgButtonMenu($(this));
});
$(this).hover(function () {
$cloneNgButton.css("display", "inline");
$cloneNgButton.siblings(".KOSHIAN_response_increase").css("display", "none");
$cloneNgButton.siblings(".fvw_num").css("display", "none");
}, function () {
$cloneNgButton.css("display", "none");
$cloneNgButtonMenu.css("display", "none");
$cloneNgButton.siblings(".KOSHIAN_response_increase").css("display", "inline");
$cloneNgButton.siblings(".fvw_num").css("display", "");
});
$cloneNgButton.append($cloneNgButtonMenu);
$(this).append($cloneNgButton);
});
}
/**
* NGボタンメニュー作成
* @param {jQuery} $button メニューを作成するNGボタンのjQueryオブジェクト
*/
function makeNgButtonMenu($button) {
if (!isNgEnable) {
return;
}
var $menu = $button.children(".GM_fcn_ng_menu");
if (!$button.find(".GM_fcn_ng_menu_item").length) {
// スレNG
var $ngNumber = $("<div>", {
class: "GM_fcn_ng_menu_item",
text: "スレNG",
css: {
color: "blue",
"background-color": "rgba(240, 224, 214, 0.95)",
}
});
// 本文NG
var $ngWordCommon = $("<div>", {
class: "GM_fcn_ng_menu_item",
text: "本文NG(共通)",
css: {
color: "blue",
"background-color": "rgba(240, 224, 214, 0.95)",
}
});
var $ngWordIndiv = $("<div>", {
class: "GM_fcn_ng_menu_item",
text: "本文NG(板別)",
css: {
color: "blue",
"background-color": "rgba(240, 224, 214, 0.95)",
}
});
// 画像NG
var $ngImage = $("<div>", {
class: "GM_fcn_ng_menu_item",
text: "画像NG",
css: {
color: "blue",
"background-color": "rgba(240, 224, 214, 0.95)",
}
});
var $td = $button.parent();
var threadNumber = $td.find("a:first").length ? $td.find("a:first").attr("href").slice(4,-4) : "";
var threadImgObj = $td.find("img:first").length ? $td.find("img:first")[0] : "";
var threadComment = $td.find("small:first, .GM_fth_pickuped_caption, .GM_fth_opened_caption").length ? $td.find("small:first, .GM_fth_pickuped_caption, .GM_fth_opened_caption").text() : "";
var $cloneNgNumber = $ngNumber.clone();
var $cloneNgWordCommon = $ngWordCommon.clone();
var $cloneNgWordIndiv = $ngWordIndiv.clone();
var $cloneNgImage = $ngImage.clone();
$cloneNgNumber.hover(function () {
$(this).css("color", "red");
$(this).css("background-color", "rgba(204, 233, 255, 0.95)");
}, function () {
$(this).css("color", "blue");
$(this).css("background-color", "rgba(240, 224, 214, 0.95)");
});
$cloneNgNumber.click(function () {
addNgNumber(threadNumber);
$td.addClass("GM_fcn_ng_numbers");
$td.css("display","none");
if ($td.hasClass("GM_fth_pickuped") || $td.hasClass("GM_fth_opened")) {
hideNgThreads();
}
});
$cloneNgWordCommon.hover(function () {
$(this).css("color", "red");
$(this).css("background-color", "rgba(204, 233, 255, 0.95)");
}, function () {
$(this).css("color", "blue");
$(this).css("background-color", "rgba(240, 224, 214, 0.95)");
});
$cloneNgWordCommon.click(function () {
var words = GM_getValue("_futaba_catalog_NG_words", "");
words = addNgWord(words, threadComment);
GM_setValue("_futaba_catalog_NG_words", words);
$td.addClass("GM_fcn_ng_words");
$td.css("display","none");
if ($td.hasClass("GM_fth_pickuped") || $td.hasClass("GM_fth_opened")) {
hideNgThreads();
}
});
$cloneNgWordIndiv.hover(function () {
$(this).css("color", "red");
$(this).css("background-color", "rgba(204, 233, 255, 0.95)");
}, function () {
$(this).css("color", "blue");
$(this).css("background-color", "rgba(240, 224, 214, 0.95)");
});
$cloneNgWordIndiv.click(function () {
var words = getCurrentIndivValue("NG_words_indiv", "");
words = addNgWord(words, threadComment);
setIndivValue("NG_words_indiv", words);
$td.addClass("GM_fcn_ng_words");
$td.css("display","none");
if ($td.hasClass("GM_fth_pickuped") || $td.hasClass("GM_fth_opened")) {
hideNgThreads();
}
});
$cloneNgImage.hover(function () {
$(this).css("color", "red");
$(this).css("background-color", "rgba(204, 233, 255, 0.95)");
}, function () {
$(this).css("color", "blue");
$(this).css("background-color", "rgba(240, 224, 214, 0.95)");
});
$cloneNgImage.click(function () {
hideNgImageThread(threadImgObj, threadComment, $td);
if ($td.hasClass("GM_fth_pickuped") || $td.hasClass("GM_fth_opened") || USE_DHASH) {
hideNgThreads();
}
});
if (threadNumber) {
$menu.append($cloneNgNumber);
}
if (threadComment) {
$menu.append($cloneNgWordCommon);
$menu.append($cloneNgWordIndiv);
}
if (threadImgObj && USE_NG_IMAGES) {
$menu.append($cloneNgImage);
}
}
var menuLeft = 0;
var menuTop = $button.height();
$menu.css("left", `${menuLeft}px`);
$menu.css("top", `${menuTop}px`);
$menu.css("display", "block");
/**
* NGワード追加
* @param {string} ngWords 追加前のNGワード
* @param {string} newNgWord 追加するNGワード
* @return {string} 追加後のNGワード
*/
function addNgWord(ngWords, newNgWord) {
newNgWord = newNgWord.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
if (newNgWord && ngWords) {
ngWords = newNgWord + "|" + ngWords;
} else {
ngWords += newNgWord;
}
return ngWords;
}
/**
* スレ画像NG
* @param {HTMLImageElement} imgObj NGにするスレ画像のimg要素
* @param {string} comment NG画像のコメント
* @param {jQuery} $td NGにするスレ画像の親td要素のjQueryオブジェクト
*/
function hideNgImageThread(imgObj, comment, $td) {
var data = convertDataURI(imgObj);
//console.log("futaba_catalog_NG - data: " + data);
if (!data) {
alert("スレ画像の取得に失敗しました");
return;
}
var hexHash = md5(data);
var dHash = convertDHash(imgObj, true);
//console.log("futaba_catalog_NG - hexHash: " + hexHash);
//console.log("futaba_catalog_NG - dHash: " + dHash.toString(16));
addNgListObj("_futaba_catalog_NG_images", hexHash);
addNgListObj("_futaba_catalog_NG_comment", comment);
addNgListObj("_futaba_catalog_NG_date", getDate());
addNgListObj("_futaba_catalog_NG_dHashes", dHash);
if (isNgEnable) {
$td.addClass("GM_fcn_ng_images");
$td.css("display","none");
}
// 非NG画像リストからNG画像を削除
var okImages = getCurrentIndivValue("OK_images_indiv", []);
var imgNumber = parseInt($td.find("img").attr("src").match(/(\d+)s\.jpg$/)[1], 10);
if (USE_DHASH) {
// 近似画像NG使用時は非NG画像リストを全削除
GM_setValue("OK_images_indiv", "{}");
} else {
var index = okImages.indexOf(imgNumber);
if (index > -1) {
okImages.splice(index, 1);
setIndivValue("OK_images_indiv", okImages);
}
}
/**
* NGリストにNGデータを追加
* @param {string} target NGデータを追加するNGリスト名
* @param {string} val NGリストへ追加するNGデータ
*/
function addNgListObj(target, val) {
var ngListObj = GM_getValue(target, "");
if (ngListObj === ""){
ngListObj = [];
}
ngListObj.unshift(val);
if (ngListObj.length > MAX_REGISTERED_NG_IMAGES) {
ngListObj.splice(MAX_REGISTERED_NG_IMAGES);
}
GM_setValue(target, ngListObj);
}
}
}
/**
* NG番号追加
* @param {string} number 追加するNG番号
*/
function addNgNumber(number) {
var ngNumberObj = getIndivObj("NG_numbers_indiv");
if (ngNumberObj === ""){
ngNumberObj = {};
}
if (!ngNumberObj[serverFullPath]) {
ngNumberObj[serverFullPath] = [];
}
if (ngNumberObj[serverFullPath].indexOf(number) > -1) {
return;
}
ngNumberObj[serverFullPath].push(number);
var deleteCount = ngNumberObj[serverFullPath].length - MAX_NG_THREADS;
if (deleteCount > 0) {
ngNumberObj[serverFullPath].splice(0, deleteCount);
}
var jsonString = JSON.stringify(ngNumberObj);
GM_setValue("NG_numbers_indiv", jsonString);
}
/**
* dataURI変換
* @param {HTMLImageElement} imgObj dataURIに変換する画像のimg要素
* @param {number} width 変換する画像の幅 未指定時は画像の本来の幅
* @param {number} height 変換する画像の高さ 未指定時は画像の本来の高さ
* @return {string} 変換したdataURI文字列
*/
function convertDataURI(imgObj, width = imgObj.naturalWidth, height = imgObj.naturalHeight){
if (!imgObj || !imgObj.complete || !width || !height) {
return;
}
// canvasを生成してimg要素を反映
var cvs = document.createElement("canvas");
cvs.width = width;
cvs.height = height;
var ctx = cvs.getContext("2d", {
alpha: false // 背景不透明で高速化
});
try {
ctx.drawImage(imgObj, 0, 0);
} catch (e) {
console.error("futaba_catalog_NG - drawImage error: src= " + imgObj.src + ", " + e.name + ": " + e.message);
console.dir(e);
return;
}
// canvasをdataURI化
var data;
try {
data = cvs.toDataURL("image/jpeg");
} catch (e) {
console.error("futaba_catalog_NG - dataURI convert error: src= " + imgObj.src + ", " + e.name + ": " + e.message);
console.dir(e);
return;
}
if (data.substr(0,23) !== "data:image/jpeg;base64,") {
console.error("futaba_catalog_NG - dataURI abnormal: src= " + imgObj.src + ", dataURI= " + data);
return;
}
return data;
}
/**
* dHash変換
* @param {HTMLImageElement} imgObj dHashに変換する画像のimg要素
* @param {boolean} force 近似画像NGが無効でも強制的にdHash変換する
* @return {number} 変換したdHash 0 ~ 2^49 - 1
* 変換できないときはnullを返す
* オリジナルは64bitのHash値だがJavaScriptの最大整数値2^53に収める為49bitのHash値を返す
*/
function convertDHash(imgObj, force = false) {
if ((!USE_DHASH && !force) || !imgObj || !imgObj.complete || !imgObj.naturalWidth || !imgObj.naturalHeight) {
return null;
}
// 8x7のcanvasを生成してimg要素を反映
var cvs = document.createElement("canvas");
cvs.width = 8;
cvs.height = 7;
var ctx = cvs.getContext("2d", {
alpha: false // 背景不透明で高速化
});
try {
ctx.drawImage(imgObj, 0, 0, imgObj.naturalWidth, imgObj.naturalHeight, 0, 0, cvs.width, cvs.height);
} catch (e) {
console.error("futaba_catalog_NG - drawImage error: src= " + imgObj.src + ", " + e.name + ": " + e.message);
console.dir(e);
return null;
}
var pixels = ctx.getImageData(0, 0, cvs.width, cvs.height);
var grayScale = [];
for (var y = 0; y < pixels.height; ++y) {
for (var x = 0; x < pixels.width; ++x) {
var i = (y * 4) * pixels.width + x * 4;
var rgb = pixels.data[i] + pixels.data[i + 1] + pixels.data[i + 2]; // 画素をグレイスケール化
grayScale.push(rgb);
}
}
var dHash = 0;
var exponent = 0; // べき指数
grayScale.forEach((v, i) => {
if ((i + 1) % cvs.width !== 0) { // 右端のピクセルは右隣が無いのでスキップ
// 右隣の輝度と比較した結果を1bitとしてセット
dHash += (v < grayScale[i + 1] ? 1 : 0) * 2 ** exponent;
++exponent;
}
});
//console.log("futaba_catalog_NG - imgObj.src: " + imgObj.src + ", dHash: 0x" + dHash.toString(16).toUpperCase());
return dHash;
}
/**
* 日付取得
* @return {string} 現在の日付の文字列 yy/mm/dd
*/
function getDate() {
var now = new Date();
var date = ("" + (now.getFullYear())).slice(-2) + "/" +
("0" + (now.getMonth() + 1)).slice(-2) + "/" +
("0" + now.getDate()).slice(-2);
return date;
}
/**
* カタログを検索してNGスレを非表示
* @param {boolean} isWordsChanged NGワードを変更したか
*/
function hideNgThreads(isWordsChanged) {
if (!isNgEnable) {
return;
}
var Start = new Date().getTime();//count parsing time
var words = "";
var wordsCommon = GM_getValue("_futaba_catalog_NG_words", "");
var wordsIndiv = getCurrentIndivValue("NG_words_indiv", "");
var numbers = getCurrentIndivValue("NG_numbers_indiv", []);
images = GM_getValue("_futaba_catalog_NG_images", []);
ngDate = GM_getValue("_futaba_catalog_NG_date", []);
okImages = getCurrentIndivValue("OK_images_indiv", []);
dHashes = GM_getValue("_futaba_catalog_NG_dHashes", []);
// NGワード
if( wordsCommon !== "" ) {
words += wordsCommon;
if( wordsIndiv !== "" ) {
words += "|" + wordsIndiv;
}
}
else {
words += wordsIndiv;
}
//console.log(words);
//console.dir(numbers);
//console.dir("futaba_catalog_NG - images.length: " + images.length);
try {
var re = new RegExp(words, "i");
}
catch (e) {
alert("NGワードのパターンが無効です\n\n" + e);
editNgWords();
return;
}
if (isWordsChanged) {
$(".GM_fcn_ng_words").css("display", "");
$(".GM_fcn_ng_words").removeClass("GM_fcn_ng_words");
}
if (words !== "") {
$("#cattable td small").each(function() {
if (re.test($(this).text())) {
if ($(this).parent("a").length) { //文字スレ
$(this).parent().parent("td").addClass("GM_fcn_ng_words");
$(this).parent().parent("td").css("display", "none");
} else {
$(this).parent("td").addClass("GM_fcn_ng_words");
$(this).parent("td").css("display", "none");
}
//console.log("futaba catalog NG - caption: " + $(this).text() + " NG word: " + $(this).text().match(re)[0]);
}
});
}
if (isWordsChanged) {
console.log("futaba_catalog_NG - Parsing@" + serverFullPath + ": "+((new Date()).getTime()-Start) +"msec"); // eslint-disable-line no-console
return;
}
// NG番号
if (numbers.length) {
var $catAnchors = $("#cattable td[class!='GM_fcn_ng_words'] > a:first-of-type");
$catAnchors.each(function() {
var hrefNum = $(this).attr("href").slice(4,-4);
if (numbers.indexOf(hrefNum) > -1){
$(this).closest("td, .cs").addClass("GM_fcn_ng_numbers");
$(this).closest("td, .cs").css("display", "none");
}
});
}
// NG画像
if (USE_NG_IMAGES) {
if (images.length) {
var $catImages = $("#cattable td:not([class^='GM_fcn_ng_']) > a:first-of-type > img");
$catImages.each(function() {
var $td = $(this).closest("td");
var imgSrc = this.src.match(/(\d+)s\.jpg$/);
if (imgSrc) {
var imgNumber = parseInt(imgSrc[1], 10);
if (okImages.indexOf(imgNumber) == -1) {
var data = convertDataURI(this);
if (data) {
var hexHash = md5(data);
var index = images.indexOf(hexHash);
if (index > -1){
$td.addClass("GM_fcn_ng_images");
$td.css("display", "none");
ngDate[index] = getDate();
if (dHashes[index] == null) {
dHashes[index] = convertDHash(this, true);
}
} else {
var isError = false; // md5変換エラーフラグ
if (hexHash.length !== 32) {
isError = true;
console.error("futaba_catalog_NG - hexHash abnormal: image No." + imgNumber + ", hexHash: " + hexHash); // eslint-disable-line no-console
}
// dHash判定
var distance, dHash = convertDHash(this);
[index, distance] = findIndexOfDHashes(dHash);
if (index > -1) {
if (ENABLE_DHASH_TEST) {
$(this).addClass("GM_fcn_ng_dhash_img");
$td.children(".GM_fcn_ng_button").attr("title", "distance: " + distance + ", dHash: " + ("000000000000" + dHash.toString(16)).slice(-13));
$td.addClass("GM_fcn_ng_dhash_td");
} else {
$td.addClass("GM_fcn_ng_images");
$td.css("display", "none");
}
ngDate[index] = getDate();
} else if (!isError) {
okImages.unshift(imgNumber);
}
}
} else {
// スレ画像読込完了確認
this.onload = () => {
this.onload = null;
var imgNumber = parseInt(this.src.match(/(\d+)s\.jpg$/)[1], 10);
var data = convertDataURI(this);
if (data) {
var hexHash = md5(data);
var index = images.indexOf(hexHash);
if (index > -1){
$td.addClass("GM_fcn_ng_images");
$td.css("display", "none");
ngDate[index] = getDate();
GM_setValue("_futaba_catalog_NG_date", ngDate);
if (dHashes[index] == null) {
dHashes[index] = convertDHash(this, true);
GM_setValue("_futaba_catalog_NG_dHashes", dHashes);
}
} else {
var isError = false; // md5変換エラーフラグ
if (hexHash.length !== 32) {
isError = true;
console.error("futaba_catalog_NG - hexHash abnormal: image No." + imgNumber + ", hexHash: " + hexHash);
}
// dHash判定
var distance, dHash = convertDHash(this);
[index, distance] = findIndexOfDHashes(dHash);
if (index > -1) {
if (ENABLE_DHASH_TEST) {
$(this).addClass("GM_fcn_ng_dhash_img");
$td.children(".GM_fcn_ng_button").attr("title", "distance: " + distance + ", dHash: " + ("000000000000" + dHash.toString(16)).slice(-13));
$td.addClass("GM_fcn_ng_dhash_td");
} else {
$td.addClass("GM_fcn_ng_images");
$td.css("display", "none");
}
ngDate[index] = getDate();
GM_setValue("_futaba_catalog_NG_date", ngDate);
} else if (!isError) {
okImages.unshift(imgNumber);
}
}
} else {
console.error("futaba_catalog_NG - image data abnormal: image No." + imgNumber);
}
};
if (this.complete && this.width && this.height && this.onload) {
// onloadセット中に画像読込完了していたらloadをトリガーする
$(this).trigger("load");
}
}
}
}
});
GM_setValue("_futaba_catalog_NG_date", ngDate);
GM_setValue("_futaba_catalog_NG_dHashes", dHashes);
if (okImages.length > MAX_OK_IMAGES) {
okImages.splice(MAX_OK_IMAGES);
}
setIndivValue("OK_images_indiv", okImages);
} else {
var $catImg = $("#cattable td a img");
$catImg.each(function() {
var imgSrc = this.src.match(/(\d+)s\.jpg$/);
if (imgSrc) {
var imgNumber = parseInt(imgSrc[1], 10);
if (okImages.indexOf(imgNumber) == -1) {
okImages.unshift(imgNumber);
}
}
});
if (okImages.length > MAX_OK_IMAGES) {
okImages.splice(MAX_OK_IMAGES);
}
setIndivValue("OK_images_indiv", okImages);
}
}
//console.log("futaba_catalog_NG - okImages.length: " + okImages.length);
console.log("futaba_catalog_NG - Parsing@" + serverFullPath + ": " + ((new Date()).getTime() - Start) + "msec"); // eslint-disable-line no-console
/**
* dHashリストのインデックス探索
* @param {number} dHash 近似画像NG判定する画像のdHash値
* @return {Array.<number>} [i, distance]
* {number} i 近似度が閾値以下のdHashリスト(dHashes)配列内のインデックス
* {number} distance ハミング距離
* dHashリストに該当が無ければi, distance共に-1を返す
*/
function findIndexOfDHashes(dHash) {
if (dHash != null) {
for (var i = 0, num = dHashes.length; i < num; ++i) {
if (dHashes[i]) {
var distance = getHammingDistance(dHash, dHashes[i]);
if (distance <= DISTANCE_THRESHOLD) {
if (ENABLE_DHASH_TEST) {
console.debug("futaba_catalog_NG - catalog dHash: " + ("000000000000" + dHash.toString(16)).slice(-13) + ", NG list dHash: " + ("000000000000" + dHashes[i].toString(16)).slice(-13));
console.debug("futaba_catalog_NG - hamming distance: " + distance);
}
return [i, distance];
}
}
}
}
return [-1, -1];
}
/**
* ハミング距離取得
* @param {number} hash1 測定するHash(49bit)
* @param {number} hash2 測定するHash(49bit)
* @return {number} ハミング距離(0~49)
*/
function getHammingDistance(hash1, hash2) {
// 2つのHashを上位17bitと下位32bitに分割して32ビット演算する
var hash1L = hash1 >>> 0;
var hash1H = (hash1 - hash1L) / 0x100000000;
var hash2L = hash2 >>> 0;
var hash2H = (hash2 - hash2L) / 0x100000000;
// 下位32bitのビットの異なる位置を抽出
var xorL = hash1L ^ hash2L;
var count = 0;
// 立っている最下位ビットを消してカウント
while (xorL) {
xorL &= xorL - 1;
++count;
}
// 上位17bitのビットの異なる位置を抽出
var xorH = hash1H ^ hash2H;
// 立っている最下位ビットを消してカウント
while (xorH) {
xorH &= xorH - 1;
++count;
}
return count;
}
}
/**
* KOSHIAN del イベント監視
*/
function listenKoshianDelEvent() {
$(document).on("KOSHIAN_del", () => {
// delされたスレをNG登録して非表示
$(".KOSHIAN_del").each(function() {
var threadNumber = $(this).find("a:first").length ? $(this).find("a:first").attr("href").slice(4,-4) : "";
if (threadNumber) {
addNgNumber(threadNumber);
}
if (isNgEnable) {
$(this).addClass("GM_fcn_ng_numbers");
$(this).css("display", "none");
}
$(this).removeClass("KOSHIAN_del");
});
hideNgThreads();
});
}
/**
* futaba thread highlighter K ピックアップイベント監視
*/
function listenFthPickupEvent() {
$(document).on("FutabaTH_pickup", () => {
// ピックアップされたスレにNGボタンをセット
$(".GM_fth_pickuped, .GM_fth_opened").each(function() {
var $ngButton = $(this).find(".GM_fcn_ng_button");
if ($ngButton.length) {
var $ngButtonMenu = $ngButton.children(".GM_fcn_ng_menu");
$ngButton.hover(function() {
$(this).css("color", "red");
}, function () {
$(this).css("color", "blue");
});
$ngButton.on("click",function() {
makeNgButtonMenu($ngButton);
});
$(this).hover(function () {
$ngButton.css("display", "inline");
$ngButton.siblings(".KOSHIAN_response_increase").css("display", "none");
$ngButton.siblings(".fvw_num").css("display", "none");
}, function () {
$ngButton.css("display", "none");
$ngButtonMenu.css("display", "none");
$ngButton.siblings(".KOSHIAN_response_increase").css("display", "inline");
$ngButton.siblings(".fvw_num").css("display", "");
});
}
});
});
}
/**
* カタログ非表示
*/
function hideCatalog() {
setCatalogHiddenStyle();
$(function() {
$("body").attr("__fcn_catalog_visibility", "hidden");
$("#GM_fth_highlighted_threads").css("visibility", "hidden");
init();
});
$(window).on("load", function() {
$("body").attr("__fcn_catalog_visibility", "visible");
setCatalogShownStyle();
$("#GM_fth_highlighted_threads").css("visibility", "visible");
});
}
/**
* カタログ非表示スタイル設定
*/
function setCatalogHiddenStyle() {
var css =
"#cattable {" +
" opacity: 0;" +
"}";
GM_addStyle(css);
}
/**
* カタログ表示スタイル設定
*/
function setCatalogShownStyle() {
var css =
"#cattable {" +
" opacity: 1;" +
"}";
GM_addStyle(css);
}
/**
* スタイル設定
*/
function setStyle() {
var css =
// NGワード
".GM_fcn_ng_words {" +
" display: none;" +
"}" +
// NG番号
".GM_fcn_ng_numbers {" +
" display: none;" +
"}" +
// NG画像
".GM_fcn_ng_images {" +
" display: none;" +
"}" +
// 近似画像NGスレ
".GM_fcn_ng_dhash_td {" +
" background-color: #e1b2ec;" +
" opacity: 0.2;" +
"}" +
".GM_fcn_ng_dhash_td:hover {" +
" opacity: 1;" +
"}" +
// 近似NG画像
".GM_fcn_ng_dhash_img {" +
" opacity: 0.2;" +
"}" +
".GM_fcn_ng_dhash_img:hover {" +
" opacity: 1;" +
"}" +
// NGボタン
".GM_fcn_ng_button {" +
" position: relative;" +
" font-size: 12px;" +
" cursor: pointer;" +
"}" +
// NGメニュー
".GM_fcn_ng_menu {" +
" font-size: medium;" +
" background-color: rgba(240, 192, 214, 0.95);" +
" z-index: 203;" +
" position: absolute;" +
" min-width: 140px;" +
" width: auto;" +
" border: 1px outset;" +
" border-radius: 5px;" +
" padding: 5px;" +
"}" +
// NGメニュー項目
".GM_fcn_ng_menu_item {" +
" padding: 5px;" +
" z-index: 203;" +
" cursor: pointer;" +
"}" +
// NGリストラベル
".GM_fcn_ng_list_label {" +
" display: inline-block;" +
" width: 100px;" +
"}" +
// NGリスト入力
".GM_fcn_ng_list_input {" +
" width: 360px;" +
" margin-right: 16px;" +
" font-size: 16px;" +
"}" +
// NGリスト編集ボタン
".GM_fcn_ng_list_edit_button {" +
" width: 70px;" +
" margin-right: 16px;" +
"}" +
// NGリスト移動ボタン
".GM_fcn_ng_list_move_button {" +
" margin-right: 16px;" +
"}" +
// NGリスト枠
"#GM_fcn_ng_list_pane {" +
" width: 928px;" +
" height: 308px;" +
" margin-left: 11px;" +
" border-width: 1px;" +
" border-style: solid;" +
" background-color: #eee;" +
"}" +
// NGリスト項目行
"#GM_fcn_ng_list_item_row {" +
" display: inline-block;" +
" height: 22px;" +
" overflow: hidden;" +
" white-space: nowrap;" +
"}" +
// NGリスト項目md5
"#GM_fcn_ng_list_item_md5 {" +
" width: 360px;" +
"}" +
// NGリスト項目dHash
"#GM_fcn_ng_list_item_dHash {" +
" width: 170px;" +
"}" +
// NGリスト項目コメント
"#GM_fcn_ng_list_item_comment {" +
" width: 250px;" +
"}" +
// NGリスト項目最終検出日
"#GM_fcn_ng_list_item_date {" +
" width: 130px;" +
"}" +
// NGリスト項目スクロールバースペース
"#GM_fcn_ng_list_item_scrl {" +
" width: 18px;" +
"}" +
// NGリスト項目
".GM_fcn_ng_list_item {" +
" display: inline-block;" +
" height: 22px;" +
" border-width: 1px;" +
" border-style: solid;" +
" box-sizing: border-box;" +
" overflow: hidden;" +
" white-space: nowrap;" +
" text-overflow: clip;" +
" cursor: pointer;" +
"}" +
// NGリストコンテンツ
"#GM_fcn_ng_list_content {" +
" width: 928px;" +
" height: 286px;" +
" overflow-x: hidden;" +
" overflow-y: auto;" +
"}" +
// NGリスト行
".GM_fcn_ng_list_row {" +
" width: 928px;" +
" height: 22px;" +
" cursor: pointer;" +
" overflow: hidden;" +
" white-space: nowrap;" +
"}" +
// NGリスト画像
".GM_fcn_ng_list_image {" +
" display: inline-block;" +
" width: 360px;" +
" height: 22px;" +
" border-width: 1px;" +
" border-style: solid;" +
" box-sizing: border-box;" +
" overflow: hidden;" +
" white-space: nowrap;" +
" text-overflow: ellipsis;" +
" font-family: Consolas, 'Courier New', monospace;" +
"}" +
// NGリストdHash
".GM_fcn_ng_list_dHash {" +
" display: inline-block;" +
" width: 170px;" +
" height: 22px;" +
" border-width: 1px;" +
" border-style: solid;" +
" box-sizing: border-box;" +
" overflow: hidden;" +
" white-space: nowrap;" +
" text-overflow: ellipsis;" +
" font-family: Consolas, 'Courier New', monospace;" +
"}" +
// NGリストコメント
".GM_fcn_ng_list_comment {" +
" display: inline-block;" +
" width: 250px;" +
" height: 22px;" +
" padding-left: 10px;" +
" border-width: 1px;" +
" border-style: solid;" +
" box-sizing: border-box;" +
" text-align: left;" +
" overflow: hidden;" +
" white-space: nowrap;" +
" text-overflow: ellipsis;" +
"}" +
// NGリスト日時
".GM_fcn_ng_list_date {" +
" display: inline-block;" +
" width: 130px;" +
" height: 22px;" +
" border-width: 1px;" +
" border-style: solid;" +
" box-sizing: border-box;" +
" overflow: hidden;" +
" white-space: nowrap;" +
" text-overflow: ellipsis;" +
" font-family: Consolas, 'Courier New', monospace;" +
"}" +
// NGリストスクロールバー
".GM_fcn_ng_list_scrl {" +
" display: inline-block;" +
" width: 18px;" +
" height: 22px;" +
" border-width: 0px 1px;" +
" border-style: solid;" +
" box-sizing: border-box;" +
" overflow: hidden;" +
" white-space: nowrap;" +
"}" +
// カタログ下スペース
"#GM_fcn_catalog_space {" +
" min-height: 2000px;" +
"}" +
// futaba thread highlighter ピックアップ
".GM_fth_pickuped {" +
" overflow: visible !important;" +
"}" +
// futaba thread highlighter 既読
".GM_fth_opened {" +
" overflow: visible !important;" +
"}" +
// ふたクロNGボタン
".fvw_ng {" +
" display: none !important;" +
"}" +
// スレのプルダウンメニューボタン用スペース
"#cattable > tbody > tr > td {" +
" padding-bottom: 12px !important;" +
"}";
GM_addStyle(css);
}
})(jQuery);