futaba_catalog_NG

カタログのスレをNGで非表示

目前为 2018-09-11 提交的版本。查看 最新版本

// ==UserScript==
// @name        futaba_catalog_NG
// @namespace   https://github.com/akoya-tomo
// @description カタログのスレをNGで非表示
// @author      akoya_tomo
// @include     http://*.2chan.net/*/futaba.php?mode=cat*
// @include     https://*.2chan.net/*/futaba.php?mode=cat*
// @include     http://*.2chan.net/*/futaba.htm
// @include     https://*.2chan.net/*/futaba.htm
// @version     1.5.1
// @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==

/* globals jQuery, md5 */
this.$ = this.jQuery = jQuery.noConflict(true);

(function ($) {
	/**
	 *	設定
	 */
	var USE_NG_IMAGES = true;				// スレ画像のNGを有効にする
	var MAX_NG_THREADS = 500;				// NGスレの最大保持数(板毎)
	var MAX_OK_IMAGES = 500;				// 非NG画像名の最大保持数(板毎)
	var HIDE_CATALOG_BEFORE_LOAD = false;	// ページの読み込みが完了するまでカタログを隠す

	var serverName = document.domain.match(/^[^.]+/);
	var pathName = location.pathname.match(/[^/]+/);
	var serverFullPath = serverName + "_" + pathName;
	var selectIndex = -1;
	var imageList, commentList, dateList, images, ngDate, okImages;

	if (HIDE_CATALOG_BEFORE_LOAD) {
		hideCatalog();
	} else {
		$(init);
	}

	function init(){
		clearNgNumber();
		if (!isCatalog()) return;
		console.log("futaba_catalog_NG commmon: " +	// eslint-disable-line no-console
			GM_getValue("_futaba_catalog_NG_words", ""));
		console.log("futaba_catalog_NG indivisual: " +	// eslint-disable-line no-console
			getCurrentIndivValue("NG_words_indiv", ""));
		GM_registerMenuCommand("NGワード編集", editNgWords);
		if (USE_NG_IMAGES) GM_registerMenuCommand("NGリスト編集", editNgList);
		setStyle();
		makeNgMenubar();
		makeConfigUI();
		makeNgListUI();
		makeNgButton();
		hideNgThreads();
		checkAkahukuReload();
		listenKoshianDelEvent();
		listenFthPickupEvent();
	}

	/**
	 * NG番号クリア
	 */
	function clearNgNumber(){
		if (window.name && isCatalog()) return;
		window.name = location.href;
		setIndivValue("NG_numbers_indiv", []);
	}

	/**
	 * カタログ確認
	 * @return {boolean} 開いているページがカタログか 
	 */
	function isCatalog(){
		return location.search.match(/mode=cat/) !== null;
	}

	/**
	 * 各板個別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() < 1) {
			$("html, body").scrollTop(1);
		}
		$("html, body").css("overflow", "hidden");

		refreshNgList();
		var $ngListContainer = $("#GM_fcn_ng_list_container");
		$ngListContainer.fadeIn(100);
	}

	/**
	 * NGリスト更新
	 */
	function refreshNgList() {
		imageList = GM_getValue("_futaba_catalog_NG_images", []);
		commentList = GM_getValue("_futaba_catalog_NG_comment", []);
		dateList = GM_getValue("_futaba_catalog_NG_date", []);
		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_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"
			}
		});
		$("body > table[border]").before($ngMenubarArea);
		$ngMenubarArea.append($ngWordsHeader);
		// 設定ボタン
		var $ngWordsButton = $("<span>", {
			id: "GM_fcn_config_ng_words",
			text: "[設定]",
			css: {
				cursor: "pointer",
			},
			click: function() {
				editNgWords();
			}
		});
		$ngWordsButton.hover(function () {
			$(this).css({ backgroundColor:"#EEAA88" });
		}, function () {
			$(this).css({ backgroundColor:"#F0E0D6" });
		});
		$ngWordsHeader.append($ngWordsButton);
		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: function() {
					editNgList();
				}
			});
			$ngListButton.hover(function () {
				$(this).css({ backgroundColor:"#EEAA88" });
			}, function () {
				$(this).css({ backgroundColor:"#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",
				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>").attr({
						"id": "GM_fcn_ng_words_common",
						"class": "GM_fcn_input"
					}).css("width", "54em"),
					$("<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>").attr({
						"id": "GM_fcn_ng_words_individual",
						"class": "GM_fcn_input"
					}).css("width", "54em"),
					$("<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: function(){
							setNgWords();
						},
					})
				),
				$("<span>").css("margin", "0 1em").append(
					$("<input>", {
						class: "GM_fcn_config_button",
						type: "button",
						val: "キャンセル",
						click: function(){
							$configContainer.fadeOut(100);
						},
					})
				)
			)
		);
		$(".GM_fcn_config_button").css({
			"cursor": "pointer",
			"background-color": "#FFECFD",
			"border": "2px outset #96ABFF",
			"border-radius": "5px",
		}).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);
		$("#GM_fcn_config_container").fadeOut(100);
		hideNgThreads(true);
	}

	/**
	 * 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", "");

		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",
				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-weight": "bold"
				}),
				$("<div>").css("margin-top", "1em").append(
					$("<div>").append(
						$("<label>").text("md5:").attr("for", "GM_fcn_md5"),
						$("<input>").attr({
							"id": "GM_fcn_md5",
							"class": "GM_fcn_ng_list_input",
							"readonly": "readonly"
						}).css("width", "360px"),
						$("<label>").text("コメント:").attr("for", "GM_fcn_comment"),
						$("<input>").attr({
							"id": "GM_fcn_comment",
							"class": "GM_fcn_ng_list_input"
						}).css("width", "360px")
					)
				),
				$("<div>").css("margin-top", "1em").append(
					$("<div>").append(
						$("<span>").append(
							$("<input>", {
								class: "GM_fcn_ng_list_button",
								type: "button",
								val: "修正",
								width: "70px",
								click: function(){
									editSelectedRow();
								},
							})
						),
						$("<span>").append(
							$("<input>", {
								class: "GM_fcn_ng_list_button",
								type: "button",
								val: "削除",
								width: "70px",
								click: function(){
									deleteSelectedRow();
								},
							})
						)
					)
				)
			),
			$("<div>").css("margin-top", "1em").append(
				$("<div>", {
					id: "GM_fcn_ng_list_pane",
				}).append(
					$("<div>", {
						id: "GM_fcn_ng_list_item"
					}).append(
						$("<div>", {
							class: "GM_fcn_ng_list_item",
							text: "md5",
							width: "358px",
						}),
						$("<div>", {
							class: "GM_fcn_ng_list_item",
							text: "コメント",
							width: "258px",
						}),
						$("<div>", {
							class: "GM_fcn_ng_list_item",
							text: "最終検出日",
							width: "98px",
						}),
						$("<div>", {
							class: "GM_fcn_ng_list_item",
							text: " ",
							width: "16px",
						})
					),
					$("<div>", {
						id: "GM_fcn_ng_list_content"
					})
				),
				$("<div>").css({
					"margin-top": "1em",
				}).append(
					$("<span>").css("margin", "0 1em").append(
						$("<input>", {
							class: "GM_fcn_close_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", "");
								$ngListContainer.fadeOut(100);
								$(".GM_fcn_ng_images").css("display","");
								$(".GM_fcn_ng_images").removeClass("GM_fcn_ng_images");
								hideNgThreads();
							},
						})
					)
				)
			)
		);
		$(".GM_fcn_close_button").css({
			"cursor": "pointer",
			"background-color": "#FFECFD",
			"border": "2px outset #96ABFF",
			"border-radius": "5px",
		}).hover(function() {
			$(this).css("background-color", "#CCE9FF");
		}, function() {
			$(this).css("background-color", "#FFECFD");
		});

		/**
		 * NGリスト選択行コメント修正
		 */
		function editSelectedRow() {
			if (selectIndex > -1) {
				commentList[selectIndex] = $("#GM_fcn_comment").val();
				GM_setValue("_futaba_catalog_NG_comment", commentList);
				$(".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);
			}
			$(".GM_fcn_ng_list_row").css("background-color", "#ffffff");
			$("#GM_fcn_md5").val("");
			$("#GM_fcn_comment").val("");
			selectIndex = -1;
			GM_setValue("_futaba_catalog_NG_images", imageList);
			GM_setValue("_futaba_catalog_NG_comment", commentList);
			GM_setValue("_futaba_catalog_NG_date", dateList);
			refreshNgList();
		}
	}

	/**
	 * 赤福の動的リロードの状態を取得
	 */
	function checkAkahukuReload() {
		var target = $("#akahuku_catalog_reload_status").get(0);
		if (target) {
			checkAkahukuReloadStatus();
		} else {
			document.addEventListener("AkahukuContentApplied", () => {
				target = $("#akahuku_catalog_reload_status").get(0);
				if (target) checkAkahukuReloadStatus();
			});
		}

		var status = "";
		function checkAkahukuReloadStatus() {
			var config = { childList: true };
			var observer = new MutationObserver(function(mutations) {
				mutations.forEach(function(mutation) {	// eslint-disable-line no-unused-vars
					if (target.textContent == status) return;
					status = target.textContent;
					if (status == "完了しました" || status == "アンドゥしました" || status == "リドゥしました") {
						makeNgButton();
						hideNgThreads();
						$("body").attr("__fcn_catalog_visibility", "visible");
						$("body > table[border] > tbody").css("opacity", "1");
						$("#GM_fth_highlighted_threads").css("visibility", "visible");
					} else if (HIDE_CATALOG_BEFORE_LOAD && status !== "") {
						$("body").attr("__fcn_catalog_visibility", "hidden");
						$("body > table[border] > tbody").css("opacity", "0");
						$("#GM_fth_highlighted_threads").css("visibility", "hidden");
					}
				});
			});
			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",
				cursor: "pointer",
				display: "none",
				position: "relative",
			},
		});
		// NGボタンメニュー
		var $ngButtonMenu = $("<div>", {
			class: "GM_fcn_ng_menu",
			css: {
				"background-color": "rgba(240, 192, 214, 0.95)",
				display: "none",
				"z-index": "100",
				position: "absolute",
				top: "16px",
				left: "0px",
				"min-width": "140px",
				width: "100%",
				"border": "1px outset",
				"border-radius": "5px",
				"padding": "5px",
			}
		});

		$("body > table[border] td").each(function(){
			if (!$(this).children(".GM_fcn_ng_button").length) {
				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");
				}, function () {
					$cloneNgButton.css("display", "none");
					$cloneNgButtonMenu.css("display", "none");
					$cloneNgButton.siblings(".KOSHIAN_response_increase").css("display", "inline");
				});

				$cloneNgButton.append($cloneNgButtonMenu);
				$(this).append($cloneNgButton);
			}
		});
	}

	/**
	 * NGボタンメニュー作成
	 * @param {jQuery} $button メニューを作成するNGボタンのjQueryオブジェクト
	 */
	function makeNgButtonMenu($button) {
		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.children("a:first").length ? $td.children("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")) {
					hideNgThreads();
				}
			});

			if (threadNumber) {
				$button.children(".GM_fcn_ng_menu").append($cloneNgNumber);
			}
			if (threadComment) {
				$button.children(".GM_fcn_ng_menu").append($cloneNgWordCommon);
				$button.children(".GM_fcn_ng_menu").append($cloneNgWordIndiv);
			}
			if (threadImgObj && USE_NG_IMAGES) {
				$button.children(".GM_fcn_ng_menu").append($cloneNgImage);
			}
		}
		$button.children(".GM_fcn_ng_menu").css("display", "block");

		/**
		 * NGワード追加
		 * @param {string} ngWords 追加前のNGワード
		 * @param {string} newNgWord 追加するNGワード
		 * @return {string} 追加後のNGワード
		 */
		function addNgWord(ngWords, newNgWord) {
			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);
			//console.log("futaba_catalog_NG - hexHash: " + hexHash);
			addNgListObj("_futaba_catalog_NG_images", hexHash);
			addNgListObj("_futaba_catalog_NG_comment", comment);
			addNgListObj("_futaba_catalog_NG_date", getDate());
			$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]);
			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_NG_THREADS) {
					ngListObj.pop();
				}
				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] = [];
		}
		ngNumberObj[serverFullPath].push(number);
		if (ngNumberObj[serverFullPath].length > MAX_NG_THREADS) {
			ngNumberObj[serverFullPath].shift();
		}
		var jsonString = JSON.stringify(ngNumberObj);
		GM_setValue("NG_numbers_indiv", jsonString);
	}

	/**
	 * dataURI変換
	 * @param {HTMLImageElement} imgObj dataURIに変換する画像のimg要素
	 * @return {string} 変換したdataURI文字列
	 */
	function convertDataURI(imgObj){
		if (!imgObj || !imgObj.complete || !imgObj.width || !imgObj.height) return;
		// canvasを生成してimg要素を反映
		var cvs = document.createElement("canvas");
		cvs.width = imgObj.width;
		cvs.height = imgObj.height;
		var ctx = cvs.getContext("2d");
		ctx.drawImage(imgObj, 0, 0);
		// canvasをdataURI化
		var data;
		try {
			data = cvs.toDataURL("image/jpeg");
		} catch (e) {
			console.error("futaba_catalog_NG - dataURI convert error: src=" + imgObj.src + ", error=" + e);	// eslint-disable-line no-console
			return;
		}
		if (data.substr(0,23) !== "data:image/jpeg;base64,"){
			console.error("futaba_catalog_NG - dataURI abnormal: src=" + imgObj.src + ", dataURI=" + data);	// eslint-disable-line no-console
			return;
		}
		return data;
	}

	/**
	 * 日付取得
	 * @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) {
		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", []);

		// 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 !== "") {
			$("body > table[border] 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().parent("td").css("display","none");
					}
				}
			});
		}
		if (isWordsChanged) {
			console.log("futaba_catalog_NG - Parsing@" + serverFullPath + ": "+((new Date()).getTime()-Start) +"msec");	// eslint-disable-line no-console
			return;
		}

		// NG番号
		if (numbers.length) {
			$("body > table[border] td > a:first-of-type").each(function(){
				var hrefNum = $(this).attr("href").slice(4,-4);
				if (numbers.indexOf(hrefNum) > -1){
					$(this).parent("td").addClass("GM_fcn_ng_numbers");
					$(this).parent("td").css("display","none");
				}
			});
		}

		// NG画像
		if (images.length) {
			$("body > table[border] td > a:first-of-type > img").each(function(){
				var imgSrc = this.src.match(/(\d+)s\.jpg$/);
				if (imgSrc) {
					var imgNumber = parseInt(imgSrc[1]);
					if (okImages.indexOf(imgNumber) == -1) {
						var data = convertDataURI(this);
						if (data) {
							var hexHash = md5(data);
							var imagesIndex = images.indexOf(hexHash);
							if (imagesIndex > -1){
								$(this).parent().parent("td").addClass("GM_fcn_ng_images");
								$(this).parent().parent("td").css("display","none");
								ngDate[imagesIndex] = getDate();
							} else if (hexHash.length == 32) {
								okImages.unshift(imgNumber);
							} else {
								console.error("futaba_catalog_NG - hexHash abnormal: image No." + imgNumber + ", hexHash: " + hexHash);	// eslint-disable-line no-console
							}
						} else {
							// スレ画像読込完了確認
							this.onload = () => {
								this.onload = null;
								imgNumber = parseInt(this.src.match(/(\d+)s\.jpg$/)[1]);
								data = convertDataURI(this);
								if (data) {
									var hexHash = md5(data);
									var imagesIndex = images.indexOf(hexHash);
									if (imagesIndex > -1){
										$(this).parent().parent("td").addClass("GM_fcn_ng_images");
										$(this).parent().parent("td").css("display","none");
										ngDate[imagesIndex] = getDate();
										GM_setValue("_futaba_catalog_NG_date", ngDate);
									} else if (hexHash.length == 32) {
										okImages.unshift(imgNumber);
									} else {
										console.error("futaba_catalog_NG - hexHash abnormal: image No." + imgNumber + ", hexHash: " + hexHash);	// eslint-disable-line no-console
									}
								} else {
									console.error("futaba_catalog_NG - image data abnormal: image No." + imgNumber);	// eslint-disable-line no-console
								}
							};
							if (this.complete && this.width && this.height && this.onload) {
								$(this).trigger("load");
							}
						}
					}
				}
			});
			GM_setValue("_futaba_catalog_NG_date", ngDate);
			if (okImages.length > MAX_OK_IMAGES) {
				okImages.splice(MAX_OK_IMAGES);
			}
			setIndivValue("OK_images_indiv", okImages);
		} else if (USE_NG_IMAGES) {
			$("body > table[border] td a img").each(function(){
				var imgSrc = this.src.match(/(\d+)s\.jpg$/);
				if (imgSrc) {
					var imgNumber = parseInt(imgSrc[1]);
					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
	}

	/**
	 * KOSHIAN delイベント監視
	 */
	function listenKoshianDelEvent() {
		document.addEventListener("KOSHIAN_del", () => {
			// delされたスレをNG登録して非表示
			$(".KOSHIAN_del").each(function(){
				var threadNumber = $(this).children("a:first").length ? $(this).children("a:first").attr("href").slice(4,-4) : "";
				if (threadNumber) {
					addNgNumber(threadNumber);
				}
				$(this).addClass("GM_fcn_ng_numbers");
				$(this).css("display","none");
				$(this).removeClass("KOSHIAN_del");
			});
			hideNgThreads();
		});
	}

	/**
	 * futaba thread highlighter Kピックアップイベント監視
	 */
	function listenFthPickupEvent() {
		document.addEventListener("FutabaTH_pickup", () => {
			// ピックアップされたスレにNGボタンをセット
			$(".GM_fth_pickuped, .GM_fth_opened").each(function() {
				var $ngButton = $(this).children(".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");
					}, function () {
						$ngButton.css("display", "none");
						$ngButtonMenu.css("display", "none");
						$ngButton.siblings(".KOSHIAN_response_increase").css("display", "inline");
					});
				}
			});
		});
	}

	/**
	 * カタログ非表示
	 */
	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 =
			"body > table[border] {" +
			"  opacity: 0;" +
			"}";
		GM_addStyle(css);
	}

	/**
	 * カタログ表示スタイル設定
	 */
	function setCatalogShownStyle() {
		var css =
			"body > table[border] {" +
			"  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_button {" +
			"  font-size: small;" +
			"}" +
			// NGメニュー
			".GM_fcn_ng_menu {" +
			"  font-size: medium;" +
			"}" +
			// NGメニュー項目
			".GM_fcn_ng_menu_item {" +
			"  padding: 5px;" +
			"  z-index: 1;" +
			"  cursor: pointer;" +
			"}" +
			// NGリストラベル
			".GM_fcn_ng_list_label {" +
			"  display: inline-block;" +
			"  width: 100px;" +
			"}" +
			// NGリスト入力
			".GM_fcn_ng_list_input {" +
			"  margin-right: 16px;" +
			"}" +
			// NGリストボタン
			".GM_fcn_ng_list_button {" +
			"  margin-right: 16px;" +
			"}" +
			// NGリスト枠
			"#GM_fcn_ng_list_pane {" +
			"  width: 738px;" +
			"  height: 308px;" +
			"  margin-left: 105px;" +
			"  border-width: 1px;" +
			"  border-style: solid;" +
			"  background-color: #eee;" +
			"}" +
			// NGリスト項目
			".GM_fcn_ng_list_item {" +
			"  display: inline-block;" +
			"  height: 20px;" +
			"  border-width: 1px;" +
			"  border-style: solid;" +
			"}" +
			// NGリストコンテンツ
			"#GM_fcn_ng_list_content {" +
			"  width: 738px;" +
			"  height: 286px;" +
			"  overflow-x: hidden;" +
			"  overflow-y: auto;" +
			"}" +
			// NGリスト行
			".GM_fcn_ng_list_row {" +
			"  width: 738px;" +
			"  height: 22px;" +
			"  cursor: pointer;" +
			"}" +
			// NGリスト画像
			".GM_fcn_ng_list_image {" +
			"  display: inline-block;" +
			"  width: 358px;" +
			"  height: 20px;" +
			"  border-width: 1px;" +
			"  border-style: solid;" +
			"  overflow: hidden;" +
			"  white-space: nowrap;" +
			"  text-overflow: ellipsis;" +
			"}" +
			// NGリストコメント
			".GM_fcn_ng_list_comment {" +
			"  display: inline-block;" +
			"  width: 253px;" +
			"  height: 20px;" +
			"  padding-left: 5px;" +
			"  border-width: 1px;" +
			"  border-style: solid;" +
			"  overflow: hidden;" +
			"  white-space: nowrap;" +
			"  text-overflow: ellipsis;" +
			"}" +
			// NGリスト日時
			".GM_fcn_ng_list_date {" +
			"  display: inline-block;" +
			"  width: 98px;" +
			"  height: 20px;" +
			"  border-width: 1px;" +
			"  border-style: solid;" +
			"  overflow: hidden;" +
			"  white-space: nowrap;" +
			"  text-overflow: ellipsis;" +
			"}" +
			// NGリストスクロールバー
			".GM_fcn_ng_list_scrl {" +
			"  display: inline-block;" +
			"  min-width: 16px;" +
			"  min-height: 22px;" +
			"  border-width: 0px 1px;" +
			"  border-style: solid;" +
			"}" +
			// カタログ下スペース
			"#GM_fcn_catalog_space {" +
			"  min-height: 2000px;" +
			"}";
		GM_addStyle(css);
	}

})(jQuery);

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址