futaba_catalog_NG

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

  1. // ==UserScript==
  2. // @name futaba_catalog_NG
  3. // @namespace https://github.com/akoya-tomo
  4. // @description カタログのスレをNGで非表示
  5. // @author akoya_tomo
  6. // @match http://*.2chan.net/*/futaba.php?mode=cat*
  7. // @match https://*.2chan.net/*/futaba.php?mode=cat*
  8. // @version 1.10.0
  9. // @require http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js
  10. // @require https://cdn.jsdelivr.net/npm/js-md5@0.7.3/src/md5.min.js
  11. // @grant GM_registerMenuCommand
  12. // @grant GM_getValue
  13. // @grant GM_setValue
  14. // @grant GM_addStyle
  15. // @license MIT
  16. // @run-at document-start
  17. // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAEnQAABJ0Ad5mH3gAAAAZdEVYdFNvZnR3YXJlAHBhaW50Lm5ldCA0LjAuMjHxIGmVAAABXElEQVQ4T8VSTUvDQBRcEcGL0H+QQ3c3KSVtmhRMe5aC4rX+Fb15F/PVUqFSqwcV8SxSPYknUcSDKCqICNZ/YBEv62xcS0CQ5iBOeMm+92beDrsh/w8WMIuFrEdD2kdc8ZAvacvapGoPIWs84IsxJ+J96I6yftYmKLywiInCekFUdirCbJsCzfNiUMwoLdECLQPe2TfH6lhCarDxK8HrKU5UlLfKwu7agvnsUOkJDehBaaMknE1nyIsjYM8ExBkMGSQbkiid0FU6D9uzcv1DHLEB9Wkt3kH3dBNDTpIEd9uVFvdA2o3XiR4cnfIVXojFScDSNAghnnu9qUuLD1jf5Vo5gTN4RETUo66i/w7msTmI3yH64D5fQGnsq5MCcHCNa71VaXpgwAXiUqXpgd1vEOkH5Fv5mt7Qj+WJG01jTZVHh9Ewuvhd3+S32qlOqfLoqO/Xx522M6HSvwQhn8ChorvE+0t7AAAAAElFTkSuQmCC
  18. // ==/UserScript==
  19. this.$ = this.jQuery = jQuery.noConflict(true);
  20.  
  21. (function ($) {
  22. /**
  23. * 設定
  24. */
  25. var USE_NG_IMAGES = true; // スレ画像のNGを有効にする
  26. var MAX_NG_THREADS = 512; // NGスレの最大保持数(板毎)
  27. var MAX_REGISTERED_NG_IMAGES = 2048; // NG画像の最大登録数
  28. var MAX_OK_IMAGES = 1024; // 非NG画像名の最大保持数(板毎)
  29. var HIDE_CATALOG_BEFORE_LOAD = false; // ページの読み込みが完了するまでカタログを隠す
  30. var USE_NG_THREAD_CLEAR_BUTTON = true; // スレNGのクリアボタンを使用する
  31. var USE_NG_DISABLE_BUTTON = true; // NG機能一時無効ボタンを使用する
  32. var USE_DHASH = false; // 近似画像NGを使用する
  33. var DISTANCE_THRESHOLD = 3; // 近似画像判定閾値(デフォルト:3)
  34. var ENABLE_DHASH_TEST = false; // 近似画像NGのテストモードを有効にする
  35. var CHECK_NG_LIST = true; // NGリストが正常かチェックする
  36.  
  37. var serverName = document.domain.match(/^[^.]+/);
  38. var pathName = location.pathname.match(/[^/]+/);
  39. var serverFullPath = serverName + "_" + pathName;
  40. var selectIndex = -1;
  41. var imageList = GM_getValue("_futaba_catalog_NG_images", []);
  42. var commentList = GM_getValue("_futaba_catalog_NG_comment", []);
  43. var dateList = GM_getValue("_futaba_catalog_NG_date", []);
  44. var dHashList = GM_getValue("_futaba_catalog_NG_dHashes", []);
  45. var images, ngDate, okImages, dHashes;
  46. var item_md5_text = " md5";
  47. var item_comment_text = " コメント";
  48. var item_date_text = " 最終検出日";
  49. var item_dHash_text = " dHash";
  50. var isNgEnable = true;
  51.  
  52. // dHashリスト初期化
  53. if (dHashList.length == 0 && imageList.length > 0) {
  54. dHashList = new Array(imageList.length).fill(null);
  55. GM_setValue("_futaba_catalog_NG_dHashes", dHashList);
  56. }
  57.  
  58. if (CHECK_NG_LIST && USE_NG_IMAGES && !isNgListNormal()) {
  59. alert("NGリストが壊れています");
  60. }
  61.  
  62. if (HIDE_CATALOG_BEFORE_LOAD) {
  63. hideCatalog();
  64. } else {
  65. $(init);
  66. }
  67.  
  68. function isNgListNormal() {
  69. var imageNum = imageList.length;
  70. var commentNum = commentList.length;
  71. var dateNum = dateList.length;
  72. var dHashNum = dHashList.length;
  73. //console.log("futaba catalog NG - imageNum: " + imageNum);
  74. //console.log("futaba catalog NG - commentNum: " + commentNum);
  75. //console.log("futaba catalog NG - dateNum: " + dateNum);
  76. //console.log("futaba catalog NG - dHashNum: " + dHashNum);
  77. return (imageNum == commentNum) && (imageNum == dateNum) && (imageNum == dHashNum);
  78. }
  79.  
  80. function init() {
  81. //clearNgNumber();
  82. //console.log("futaba_catalog_NG - commmon: " +
  83. // GM_getValue("_futaba_catalog_NG_words", ""));
  84. //console.log("futaba_catalog_NG - indivisual: " +
  85. // getCurrentIndivValue("NG_words_indiv", ""));
  86. GM_registerMenuCommand("NGワード編集", editNgWords);
  87. if (USE_NG_IMAGES) {
  88. GM_registerMenuCommand("NGリスト編集", editNgList);
  89. }
  90. GM_registerMenuCommand("スレNGクリア", confirmClearNgNumber);
  91. setStyle();
  92. makeNgMenubar();
  93. makeConfigUI();
  94. makeNgListUI();
  95. makeNgButton();
  96. hideNgThreads();
  97. checkAkahukuReload();
  98. listenKoshianDelEvent();
  99. listenFthPickupEvent();
  100. }
  101.  
  102. /**
  103. * NG番号クリア
  104. * @param {boolean} forced 強制的にクリアするか
  105. */
  106. function clearNgNumber(forced) {
  107. if (!forced && window.name) {
  108. return;
  109. }
  110. window.name = location.href;
  111.  
  112. var ngNumberObj = getIndivObj("NG_numbers_indiv");
  113. if (ngNumberObj === "") {
  114. ngNumberObj = {};
  115. }
  116. ngNumberObj[serverFullPath] = [];
  117. var jsonString = JSON.stringify(ngNumberObj);
  118. GM_setValue("NG_numbers_indiv", jsonString);
  119. }
  120.  
  121. /**
  122. * 各板個別NGデータを保存
  123. * @param {string} target 保存するNG名
  124. * @param {string|Array} val 保存するNGデータ
  125. */
  126. function setIndivValue(target, val) {
  127. var indivObj = getIndivObj(target);
  128. if (indivObj === "") {
  129. indivObj = {};
  130. }
  131. indivObj[serverFullPath] = val;
  132. var jsonString = JSON.stringify(indivObj);
  133. GM_setValue(target, jsonString);
  134. //console.log("futaba_catalog_NG: " + target + " updated@" + serverFullPath + " - " + val);
  135. }
  136.  
  137. /**
  138. * 各板個別NGデータのオブジェクトを取得
  139. * @param {string} target 取得するNG名
  140. * @return {object} 取得したNGデータ
  141. */
  142. function getIndivObj(target) {
  143. var indivVal = GM_getValue(target, "");
  144. var indivObj = "";
  145. if (indivVal !== "") {
  146. indivObj = JSON.parse(indivVal);
  147. }
  148. return indivObj;
  149. }
  150.  
  151. /**
  152. * NGワード編集メニュー表示
  153. */
  154. function editNgWords(){
  155. var wordsCommon = GM_getValue("_futaba_catalog_NG_words", "");
  156. var wordsIndiv = getCurrentIndivValue("NG_words_indiv", "");
  157. $("#GM_fcn_ng_words_common").val(wordsCommon);
  158. $("#GM_fcn_ng_words_individual").val(wordsIndiv);
  159. var $configContainer = $("#GM_fcn_config_container");
  160. $configContainer.fadeIn(100);
  161. }
  162.  
  163. /**
  164. * 現在の板の個別NGデータを取得
  165. * @param {string} target NGデータ名
  166. * @param {string|Array} defaultVal NGデータが未定義のときの既定値
  167. * @return {string|Array} 現在の板の個別NGデータ
  168. */
  169. function getCurrentIndivValue(target, defaultVal) {
  170. var indivObj = getIndivObj(target);
  171. var currentIndivVal;
  172. if (indivObj !== "") {
  173. currentIndivVal = indivObj[serverFullPath];
  174. }
  175. if (!currentIndivVal) {
  176. currentIndivVal = defaultVal;
  177. }
  178. return currentIndivVal;
  179. }
  180.  
  181. /**
  182. * NGリスト編集メニュー表示
  183. */
  184. function editNgList() {
  185. // マウスホイールリロード対策
  186. $("<div>", {
  187. id: "GM_fcn_catalog_space"
  188. }).appendTo("body");
  189. if ($(window).scrollTop() < 2) {
  190. $("html, body").scrollTop(2);
  191. }
  192. $("html, body").css("overflow", "hidden");
  193.  
  194. refreshNgList(true);
  195. resetNgListItemText();
  196. var $ngListContainer = $("#GM_fcn_ng_list_container");
  197. $ngListContainer.fadeIn(100);
  198. }
  199.  
  200. /**
  201. * NGリスト項目名リセット
  202. */
  203. function resetNgListItemText() {
  204. $("#GM_fcn_ng_list_item_md5").text(item_md5_text + " ");
  205. $("#GM_fcn_ng_list_item_comment").text(item_comment_text + " ");
  206. $("#GM_fcn_ng_list_item_date").text(item_date_text + " ");
  207. $("#GM_fcn_ng_list_item_dHash").text(item_dHash_text + " ");
  208. }
  209.  
  210. /**
  211. * スレNGクリア確認
  212. */
  213. function confirmClearNgNumber() {
  214. if (confirm("この板のスレNGを全てクリアします。\nよろしいですか?")) {
  215. $(".GM_fcn_ng_numbers").each(function() {
  216. $(this).removeClass("GM_fcn_ng_numbers");
  217. $(this).css("display", "");
  218. });
  219. clearNgNumber(true);
  220. }
  221. }
  222.  
  223. /**
  224. * NG機能無効切り替え
  225. */
  226. function toggleNgDisable() {
  227. var $ngDisableButton = $("#GM_fcn_ng_disable_button");
  228. if (isNgEnable) {
  229. $ngDisableButton.css("background", "#a9d8ff");
  230. isNgEnable = false;
  231. $(".GM_fcn_ng_words").css("display", "");
  232. $(".GM_fcn_ng_words").removeClass("GM_fcn_ng_words");
  233. $(".GM_fcn_ng_numbers").css("display", "");
  234. $(".GM_fcn_ng_numbers").removeClass("GM_fcn_ng_numbers");
  235. $(".GM_fcn_ng_images").css("display", "");
  236. $(".GM_fcn_ng_images").removeClass("GM_fcn_ng_images");
  237. $(".GM_fcn_ng_dhash_td").removeClass("GM_fcn_ng_dhash_td");
  238. $(".GM_fcn_ng_dhash_img").removeClass("GM_fcn_ng_dhash_img");
  239. } else {
  240. $ngDisableButton.css("background", "none");
  241. isNgEnable = true;
  242. hideNgThreads();
  243. }
  244. }
  245.  
  246. /**
  247. * NGリスト表示更新
  248. * @param {boolean} loadNgList NGリストデータを読み込みするか
  249. */
  250. function refreshNgList(loadNgList) {
  251. if (loadNgList) {
  252. imageList = GM_getValue("_futaba_catalog_NG_images", []);
  253. commentList = GM_getValue("_futaba_catalog_NG_comment", []);
  254. dateList = GM_getValue("_futaba_catalog_NG_date", []);
  255. dHashList = GM_getValue("_futaba_catalog_NG_dHashes", []);
  256. }
  257. var listCount = imageList.length;
  258. $(".GM_fcn_ng_list_row").remove();
  259.  
  260. for (var i = 0; i < listCount; ++i) {
  261. var row = $("<div>", {
  262. class: "GM_fcn_ng_list_row",
  263. click: function() { // eslint-disable-line no-loop-func
  264. selectIndex = $(this).index();
  265. selectNgList();
  266. }
  267. }).appendTo("#GM_fcn_ng_list_content");
  268. row.append(
  269. $("<div>", {
  270. class: "GM_fcn_ng_list_image",
  271. text: imageList[i],
  272. }),
  273. $("<div>", {
  274. class: "GM_fcn_ng_list_dHash",
  275. text: dHashList[i] ? ("000000000000" + dHashList[i].toString(16)).slice(-13) : "-",
  276. }),
  277. $("<div>", {
  278. class: "GM_fcn_ng_list_comment",
  279. text: commentList[i],
  280. }),
  281. $("<div>", {
  282. class: "GM_fcn_ng_list_date",
  283. text: dateList[i],
  284. }),
  285. $("<div>", {
  286. class: "GM_fcn_ng_list_scrl",
  287. })
  288. );
  289. }
  290. $(".GM_fcn_ng_list_row").css("background-color", "#ffffff");
  291. }
  292.  
  293. /**
  294. * NGリスト選択
  295. */
  296. function selectNgList() {
  297. $(".GM_fcn_ng_list_row").css("background-color", "#ffffff")
  298. .eq(selectIndex).css("background-color", "#ffecfd");
  299. $("#GM_fcn_md5").val(imageList[selectIndex]);
  300. $("#GM_fcn_comment").val(commentList[selectIndex]);
  301. }
  302.  
  303. /**
  304. * NGメニューバー作成
  305. */
  306. function makeNgMenubar() {
  307. var $ngMenubarArea = $("<div>", {
  308. id: "GM_fcn_ng_menubar",
  309. css: {
  310. "background-color": "#F0E0D6"
  311. }
  312. });
  313. var $ngWordsHeader = $("<span>", {
  314. id: "GM_fcn_ng_words_header",
  315. text: "NGワード",
  316. css: {
  317. "background-color": "#F0E0D6",
  318. fontWeight: "bolder",
  319. "padding-right": "16px"
  320. }
  321. });
  322. $("#cattable").before($ngMenubarArea);
  323. $ngMenubarArea.append($ngWordsHeader);
  324. // 設定ボタン
  325. var $ngWordsButton = $("<span>", {
  326. id: "GM_fcn_config_ng_words",
  327. text: "[設定]",
  328. css: {
  329. cursor: "pointer",
  330. },
  331. click: editNgWords
  332. });
  333. $ngWordsButton.hover(function() {
  334. $(this).css("background-color", "#EEAA88");
  335. }, function() {
  336. $(this).css("background-color", "#F0E0D6");
  337. });
  338. $ngWordsHeader.append($ngWordsButton);
  339.  
  340. if (USE_NG_DISABLE_BUTTON) {
  341. // NG機能一時無効
  342. var $ngDisableHeader = $("<span>", {
  343. id: "GM_fcn_ng_disalbe_header",
  344. text: "NG機能",
  345. css: {
  346. "background-color": "#F0E0D6",
  347. fontWeight: "bolder",
  348. "padding-right": "16px"
  349. }
  350. });
  351. $ngWordsHeader.after($ngDisableHeader);
  352. // NG機能一時無効ボタン
  353. var $ngDisableButton = $("<a>", {
  354. id: "GM_fcn_ng_disable_button",
  355. text: "[一時無効]",
  356. css: {
  357. cursor: "pointer",
  358. },
  359. click: toggleNgDisable
  360. });
  361. $ngDisableHeader.append($ngDisableButton);
  362. }
  363.  
  364. if (USE_NG_THREAD_CLEAR_BUTTON) {
  365. // スレNG
  366. var $ngThreadHeader = $("<span>", {
  367. id: "GM_fcn_ng_thread_header",
  368. text: "スレNG",
  369. css: {
  370. "background-color": "#F0E0D6",
  371. fontWeight: "bolder",
  372. "padding-right": "16px"
  373. }
  374. });
  375. $ngWordsHeader.after($ngThreadHeader);
  376. // スレNGクリアボタン
  377. var $ngThreadClearButton = $("<span>", {
  378. id: "GM_fcn_ng_thread_clear_button",
  379. text: "[クリア]",
  380. css: {
  381. cursor: "pointer",
  382. },
  383. click: confirmClearNgNumber
  384. });
  385. $ngThreadClearButton.hover(function() {
  386. $(this).css("background-color", "#EEAA88");
  387. }, function() {
  388. $(this).css("background-color", "#F0E0D6");
  389. });
  390. $ngThreadHeader.append($ngThreadClearButton);
  391. }
  392.  
  393. if (USE_NG_IMAGES) {
  394. // NGリスト
  395. var $ngListHeader = $("<span>", {
  396. id: "GM_fcn_ng_list_header",
  397. text: "NGリスト",
  398. css: {
  399. "background-color": "#F0E0D6",
  400. fontWeight: "bolder",
  401. "padding-right": "16px"
  402. }
  403. });
  404. $ngWordsHeader.after($ngListHeader);
  405. // NGリスト編集ボタン
  406. var $ngListButton = $("<span>", {
  407. id: "GM_fcn_edit_ng_list",
  408. text: "[編集]",
  409. css: {
  410. cursor: "pointer",
  411. },
  412. click: editNgList
  413. });
  414. $ngListButton.hover(function () {
  415. $(this).css("background-color", "#EEAA88");
  416. }, function () {
  417. $(this).css("background-color", "#F0E0D6" );
  418. });
  419. $ngListHeader.append($ngListButton);
  420. }
  421. }
  422.  
  423. /**
  424. * NGワード編集メニュー作成
  425. */
  426. function makeConfigUI() {
  427. var $configContainer = $("<div>", {
  428. id: "GM_fcn_config_container",
  429. css: {
  430. position: "fixed",
  431. "z-index": "1001",
  432. left: "50%",
  433. top: "50%",
  434. "text-align": "center",
  435. "margin-left": "-475px",
  436. "margin-top": "-50px",
  437. "background-color": "rgba(240, 192, 214, 0.95)",
  438. width: "950px",
  439. //height: "100px",
  440. display: "none",
  441. fontSize: "16px",
  442. fontWeight: "normal",
  443. "box-shadow": "3px 3px 5px #853e52",
  444. "border": "1px outset",
  445. "border-radius": "10px",
  446. "padding": "5px",
  447. }
  448. });
  449. $("#GM_fcn_ng_words_header").append($configContainer);
  450. $configContainer.append(
  451. $("<div>").append(
  452. $("<div>").text("NGワード編集").css({
  453. "background-color": "#ffeeee",
  454. "padding": "2px",
  455. "font-weight": "bold"
  456. }),
  457. $("<div>").text("スレ本文に含まれる語句を入力してください。 | を挟むと複数指定できます。正規表現使用可。")
  458. ),
  459. $("<div>").css("margin-top", "1em").append(
  460. $("<div>").append(
  461. $("<label>").text("全板共通").attr("for", "GM_fcn_ng_words_common"),
  462. $("<input>", {
  463. id: "GM_fcn_ng_words_common",
  464. class: "GM_fcn_input",
  465. css: {
  466. width: "54em",
  467. fontSize: "13.33px"
  468. }
  469. }),
  470. $("<span>").append(
  471. $("<input>", {
  472. class: "GM_fcn_config_button",
  473. type: "button",
  474. val: "区切り文字挿入",
  475. click: function() {
  476. insertDelimiter("GM_fcn_ng_words_common");
  477. },
  478. })
  479. )
  480. ),
  481. $("<div>").append(
  482. $("<label>").text("各板個別").attr("for", "GM_fcn_ng_words_individual"),
  483. $("<input>", {
  484. "id": "GM_fcn_ng_words_individual",
  485. "class": "GM_fcn_input",
  486. css: {
  487. width: "54em",
  488. fontSize: "13.33px"
  489. }
  490. }),
  491. $("<span>").append(
  492. $("<input>", {
  493. class: "GM_fcn_config_button",
  494. type: "button",
  495. val: "区切り文字挿入",
  496. click: function() {
  497. insertDelimiter("GM_fcn_ng_words_individual");
  498. },
  499. })
  500. )
  501. )
  502. ),
  503. $("<div>").css({
  504. "margin-top": "1em",
  505. }).append(
  506. $("<span>").css("margin", "0 1em").append(
  507. $("<input>", {
  508. class: "GM_fcn_config_button",
  509. type: "button",
  510. val: "更新",
  511. click: setNgWords
  512. })
  513. ),
  514. $("<span>").css("margin", "0 1em").append(
  515. $("<input>", {
  516. class: "GM_fcn_config_button",
  517. type: "button",
  518. val: "キャンセル",
  519. click: function() {
  520. setCursor();
  521. $configContainer.fadeOut(100);
  522. },
  523. })
  524. )
  525. )
  526. );
  527. $(".GM_fcn_config_button").css({
  528. "cursor": "pointer",
  529. "background-color": "#FFECFD",
  530. "border": "2px outset #96ABFF",
  531. "border-radius": "5px",
  532. "font-size": "13.33px",
  533. }).hover(function() {
  534. $(this).css("background-color", "#CCE9FF");
  535. }, function() {
  536. $(this).css("background-color", "#FFECFD");
  537. });
  538.  
  539. /**
  540. * カーソル位置にデリミタ挿入
  541. * @param {string} id デリミタを挿入する入力欄のID名
  542. */
  543. function insertDelimiter(id){
  544. var $input = $("#" + id);
  545. var val = $input.val();
  546. var position = $input[0].selectionStart;
  547. var newval = val.substr(0, position) + "|" + val.substr(position);
  548. $input.val(newval);
  549. $input[0].setSelectionRange(position + 1 ,position + 1);
  550. }
  551. }
  552.  
  553. /**
  554. * NGワードをセット
  555. */
  556. function setNgWords() {
  557. var inputCommon = $("#GM_fcn_ng_words_common").val();
  558. var inputIndiv = $("#GM_fcn_ng_words_individual").val();
  559. GM_setValue("_futaba_catalog_NG_words", inputCommon);
  560. console.log("futaba_catalog_NG: common NGword updated - " + inputCommon); // eslint-disable-line no-console
  561. setIndivValue("NG_words_indiv", inputIndiv);
  562. setCursor();
  563. $("#GM_fcn_config_container").fadeOut(100);
  564. hideNgThreads(true);
  565. }
  566.  
  567. /**
  568. * NGワードのカーソルを先頭に移動
  569. */
  570. function setCursor() {
  571. var inputCommonElm = $("#GM_fcn_ng_words_common").get(0);
  572. if (inputCommonElm) {
  573. inputCommonElm.focus();
  574. inputCommonElm.setSelectionRange(0, 0); // カーソルを先頭に移動
  575. inputCommonElm.blur();
  576. }
  577. var inputIndivElm = $("#GM_fcn_ng_words_individual").get(0);
  578. if (inputIndivElm) {
  579. inputIndivElm.focus();
  580. inputIndivElm.setSelectionRange(0, 0); // カーソルを先頭に移動
  581. inputIndivElm.blur();
  582. }
  583. }
  584.  
  585. /**
  586. * NGリスト編集メニュー作成
  587. */
  588. function makeNgListUI() {
  589. if (!USE_NG_IMAGES) {
  590. GM_setValue("OK_images_indiv", "");
  591. return;
  592. }
  593. imageList = GM_getValue("_futaba_catalog_NG_images", []);
  594. commentList = GM_getValue("_futaba_catalog_NG_comment", []);
  595. dateList = GM_getValue("_futaba_catalog_NG_date", []);
  596. dHashList = GM_getValue("_futaba_catalog_NG_dHashes", []);
  597.  
  598. var $ngListContainer = $("<div>", {
  599. id: "GM_fcn_ng_list_container",
  600. css: {
  601. position: "fixed",
  602. "z-index": "1001",
  603. left: "50%",
  604. top: "50%",
  605. "text-align": "center",
  606. "margin-left": "-475px",
  607. "margin-top": "-250px",
  608. "background-color": "rgba(240, 192, 214, 0.95)",
  609. width: "950px",
  610. //height: "500px",
  611. display: "none",
  612. fontSize: "16px",
  613. fontWeight: "normal",
  614. "box-shadow": "3px 3px 5px #853e52",
  615. "border": "1px outset",
  616. "border-radius": "10px",
  617. "padding": "5px",
  618. }
  619. });
  620. $("#GM_fcn_ng_list_header").append($ngListContainer);
  621. $ngListContainer.append(
  622. $("<div>").append(
  623. $("<div>").text("NGリスト編集").css({
  624. "background-color": "#ffeeee",
  625. "padding": "2px",
  626. "font-size": "16px",
  627. "font-weight": "bold"
  628. }),
  629. $("<div>").css("margin-top", "1em").append(
  630. $("<div>").append(
  631. $("<label>").text("md5:").attr("for", "GM_fcn_md5"),
  632. $("<input>", {
  633. id: "GM_fcn_md5",
  634. class: "GM_fcn_ng_list_input",
  635. readonly: "readonly",
  636. }),
  637. $("<label>").text("コメント:").attr("for", "GM_fcn_comment"),
  638. $("<input>", {
  639. id: "GM_fcn_comment",
  640. class: "GM_fcn_ng_list_input",
  641. keypress: function(e) {
  642. if (e.key == "Enter") {
  643. editSelectedRow();
  644. }
  645. }
  646. })
  647. )
  648. ),
  649. $("<div>").css("margin-top", "1em").append(
  650. $("<div>").css("margin-left", "475px").append(
  651. $("<span>").append(
  652. $("<input>", {
  653. class: "GM_fcn_ng_list_button GM_fcn_ng_list_edit_button",
  654. type: "button",
  655. val: "修正",
  656. click: editSelectedRow
  657. })
  658. ),
  659. $("<span>").append(
  660. $("<input>", {
  661. class: "GM_fcn_ng_list_button GM_fcn_ng_list_edit_button",
  662. type: "button",
  663. val: "削除",
  664. click: deleteSelectedRow
  665. })
  666. ),
  667. $("<span>").css("margin", "0 0 0 1em").append(
  668. $("<input>", {
  669. class: "GM_fcn_ng_list_button GM_fcn_ng_list_move_button",
  670. type: "button",
  671. val: "上",
  672. click: function() {
  673. swapRow(selectIndex - 1);
  674. },
  675. })
  676. ),
  677. $("<span>").append(
  678. $("<input>", {
  679. class: "GM_fcn_ng_list_button GM_fcn_ng_list_move_button",
  680. type: "button",
  681. val: "下",
  682. click: function() {
  683. swapRow(selectIndex);
  684. },
  685. })
  686. )
  687. )
  688. )
  689. ),
  690. $("<div>").css("margin-top", "1em").append(
  691. $("<div>", {
  692. id: "GM_fcn_ng_list_pane",
  693. }).append(
  694. $("<div>", {
  695. id: "GM_fcn_ng_list_item_row"
  696. }).append(
  697. $("<div>", {
  698. id: "GM_fcn_ng_list_item_md5",
  699. class: "GM_fcn_ng_list_item",
  700. text: item_md5_text + " ",
  701. click: function() {
  702. if ($(this).text() == item_md5_text + "▲") {
  703. resetNgListItemText();
  704. $(this).text(item_md5_text + "▼");
  705. sortNgList(0, -1);
  706. } else {
  707. resetNgListItemText();
  708. $(this).text(item_md5_text + "▲");
  709. sortNgList(0);
  710. }
  711. },
  712. }),
  713. $("<div>", {
  714. id: "GM_fcn_ng_list_item_dHash",
  715. class: "GM_fcn_ng_list_item",
  716. text: item_dHash_text + " ",
  717. click: function() {
  718. if ($(this).text() == item_dHash_text + "▲") {
  719. resetNgListItemText();
  720. $(this).text(item_dHash_text + "▼");
  721. sortNgList(3, -1);
  722. } else {
  723. resetNgListItemText();
  724. $(this).text(item_dHash_text + "▲");
  725. sortNgList(3);
  726. }
  727. },
  728. }),
  729. $("<div>", {
  730. id: "GM_fcn_ng_list_item_comment",
  731. class: "GM_fcn_ng_list_item",
  732. text: item_comment_text + " ",
  733. click: function() {
  734. if ($(this).text() == item_comment_text + "▲") {
  735. resetNgListItemText();
  736. $(this).text(item_comment_text + "▼");
  737. sortNgList(1, -1);
  738. } else {
  739. resetNgListItemText();
  740. $(this).text(item_comment_text + "▲");
  741. sortNgList(1);
  742. }
  743. },
  744. }),
  745. $("<div>", {
  746. id: "GM_fcn_ng_list_item_date",
  747. class: "GM_fcn_ng_list_item",
  748. text: item_date_text + " ",
  749. click: function() {
  750. if ($(this).text() == item_date_text + "▼") {
  751. resetNgListItemText();
  752. $(this).text(item_date_text + "▲");
  753. sortNgList(2);
  754. } else {
  755. resetNgListItemText();
  756. $(this).text(item_date_text + "▼");
  757. sortNgList(2, -1);
  758. }
  759. },
  760. }),
  761. $("<div>", {
  762. id: "GM_fcn_ng_list_item_scrl",
  763. class: "GM_fcn_ng_list_item",
  764. })
  765. ),
  766. $("<div>", {
  767. id: "GM_fcn_ng_list_content"
  768. })
  769. ),
  770. $("<div>").css("margin-top", "1em").append(
  771. $("<span>").css("margin", "0 1em").append(
  772. $("<input>", {
  773. class: "GM_fcn_ng_list_button",
  774. type: "button",
  775. val: "更新",
  776. click: function() {
  777. if (!CHECK_NG_LIST || isNgListNormal()) {
  778. GM_setValue("_futaba_catalog_NG_images", imageList);
  779. GM_setValue("_futaba_catalog_NG_comment", commentList);
  780. GM_setValue("_futaba_catalog_NG_date", dateList);
  781. GM_setValue("_futaba_catalog_NG_dHashes", dHashList);
  782. $(".GM_fcn_ng_list_row").css("background-color", "#ffffff");
  783. $("#GM_fcn_md5").val("");
  784. $("#GM_fcn_comment").val("");
  785. $("#GM_fcn_ng_list_content").scrollTop(0);
  786. $("#GM_fcn_catalog_space").remove();
  787. $("html, body").css("overflow", "");
  788. selectIndex = -1;
  789. $ngListContainer.fadeOut(100);
  790. $(".GM_fcn_ng_images").css("display", "");
  791. $(".GM_fcn_ng_images").removeClass("GM_fcn_ng_images");
  792. $(".GM_fcn_ng_dhash_td").removeClass("GM_fcn_ng_dhash_td");
  793. $(".GM_fcn_ng_dhash_img").removeClass("GM_fcn_ng_dhash_img");
  794. hideNgThreads();
  795. } else {
  796. alert("NGリストが壊れています\n一度キャンセルしてからやり直してください");
  797. }
  798. },
  799. })
  800. ),
  801. $("<span>").css("margin", "0 1em").append(
  802. $("<input>", {
  803. class: "GM_fcn_ng_list_button",
  804. type: "button",
  805. val: "キャンセル",
  806. click: function(){
  807. $(".GM_fcn_ng_list_row").css("background-color", "#ffffff");
  808. $("#GM_fcn_md5").val("");
  809. $("#GM_fcn_comment").val("");
  810. $("#GM_fcn_ng_list_content").scrollTop(0);
  811. $("#GM_fcn_catalog_space").remove();
  812. $("html, body").css("overflow", "");
  813. selectIndex = -1;
  814. $ngListContainer.fadeOut(100);
  815. },
  816. })
  817. )
  818. )
  819. )
  820. );
  821. $(".GM_fcn_ng_list_button").css({
  822. "cursor": "pointer",
  823. "background-color": "#FFECFD",
  824. "border": "2px outset #96ABFF",
  825. "border-radius": "5px",
  826. "font-size": "13.33px"
  827. }).hover(function() {
  828. $(this).css("background-color", "#CCE9FF");
  829. }, function() {
  830. $(this).css("background-color", "#FFECFD");
  831. });
  832.  
  833. $(document).keydown((e) => {
  834. if (e.key == "ArrowUp" && selectIndex > 0) {
  835. $(".GM_fcn_ng_list_comment").eq(selectIndex - 1).click();
  836. e.preventDefault();
  837. scrollToSelectedRow();
  838. $("#GM_fcn_comment").blur();
  839. } else if (e.key == "ArrowDown" && selectIndex > -1 && selectIndex < imageList.length) {
  840. $(".GM_fcn_ng_list_comment").eq(selectIndex + 1).click();
  841. e.preventDefault();
  842. scrollToSelectedRow();
  843. $("#GM_fcn_comment").blur();
  844. } else if (e.key == "F2" && selectIndex > -1) {
  845. $("#GM_fcn_comment").focus();
  846. e.preventDefault();
  847. }
  848. });
  849.  
  850. /**
  851. * NGリスト選択行コメント修正
  852. */
  853. function editSelectedRow() {
  854. if (selectIndex > -1) {
  855. commentList[selectIndex] = $("#GM_fcn_comment").val();
  856. $(".GM_fcn_ng_list_comment").eq(selectIndex).text(commentList[selectIndex]);
  857. }
  858. }
  859.  
  860. /**
  861. * NGリスト選択行削除
  862. */
  863. function deleteSelectedRow() {
  864. if (selectIndex > -1) {
  865. imageList.splice(selectIndex, 1);
  866. commentList.splice(selectIndex, 1);
  867. dateList.splice(selectIndex, 1);
  868. dHashList.splice(selectIndex, 1);
  869. }
  870. $(".GM_fcn_ng_list_row").css("background-color", "#ffffff");
  871. $("#GM_fcn_md5").val("");
  872. $("#GM_fcn_comment").val("");
  873. selectIndex = -1;
  874. refreshNgList();
  875. }
  876.  
  877. /**
  878. * NGリスト行入替
  879. * indexの行と一つ後の行を入れ替える。
  880. * @param {number} index 入替する行番号(先頭行が0)
  881. */
  882. function swapRow(index) {
  883. if (index <= -1 || index + 1 >= imageList.length) {
  884. return;
  885. }
  886.  
  887. imageList.splice(index, 2, imageList[index + 1], imageList[index]);
  888. commentList.splice(index, 2, commentList[index + 1], commentList[index]);
  889. dateList.splice(index, 2, dateList[index + 1], dateList[index]);
  890. dHashList.splice(index, 2, dHashList[index + 1], dHashList[index]);
  891. selectIndex = selectIndex == index ? index + 1 : index;
  892.  
  893. scrollToSelectedRow();
  894. refreshNgList();
  895. selectNgList();
  896. resetNgListItemText();
  897. }
  898.  
  899. /**
  900. * 選択行までスクロール
  901. */
  902. function scrollToSelectedRow() {
  903. var rowHeight = 22; // NGリストの1行当たりの高さ(px)
  904. var listLines = 13; // NGリストの表示行数
  905. var selectPos = selectIndex * rowHeight;
  906. var scrollTop = $("#GM_fcn_ng_list_content").scrollTop();
  907. var scrollBottom = scrollTop + (rowHeight * (listLines - 2));
  908. if (selectPos < scrollTop) {
  909. $("#GM_fcn_ng_list_content").scrollTop(selectPos);
  910. }
  911. if (selectPos > scrollBottom) {
  912. $("#GM_fcn_ng_list_content").scrollTop(selectPos - (rowHeight * (listLines - 2)));
  913. }
  914. }
  915.  
  916. /**
  917. * NGリストソート
  918. * @param {number} index ソート対象の項目(md5:0, コメント:1, 最終検出日:2, dHash:3)
  919. * @param {number} order ソート方向(昇順:1, 降順:-1)未指定は昇順
  920. */
  921. function sortNgList(index, order = 1) {
  922. var array = new Array();
  923. for (var i = 0; i < imageList.length; ++i) {
  924. array[i] = new Array();
  925. array[i][0] = imageList[i];
  926. array[i][1] = commentList[i];
  927. array[i][2] = dateList[i];
  928. array[i][3] = dHashList[i];
  929. }
  930. array.sort(function(a, b){
  931. if (a[index] > b[index]) {
  932. return order;
  933. }
  934. if (a[index] < b[index]) {
  935. return -order;
  936. }
  937. return 0;
  938. });
  939. for (var j = 0; j < imageList.length; ++j) {
  940. imageList[j] = array[j][0];
  941. commentList[j] = array[j][1];
  942. dateList[j] = array[j][2];
  943. dHashList[j] = array[j][3];
  944. }
  945.  
  946. $(".GM_fcn_ng_list_row").css("background-color", "#ffffff");
  947. $("#GM_fcn_md5").val("");
  948. $("#GM_fcn_comment").val("");
  949. selectIndex = -1;
  950. refreshNgList();
  951. }
  952. }
  953.  
  954. /**
  955. * 動的リロードの状態を取得
  956. */
  957. function checkAkahukuReload() {
  958. // 赤福
  959. var target = $("#akahuku_catalog_reload_status").get(0);
  960. if (target) {
  961. checkAkahukuReloadStatus(target);
  962. } else {
  963. $(document).on("AkahukuContentApplied", () => {
  964. target = $("#akahuku_catalog_reload_status").get(0);
  965. if (target) {
  966. checkAkahukuReloadStatus(target);
  967. } else {
  968. console.error ("futaba_catalog_NG - #akahuku_catalog_reload_status not found");
  969. }
  970. });
  971. }
  972.  
  973. function checkAkahukuReloadStatus(target) {
  974. var status = "";
  975. var config = { childList: true };
  976. var observer = new MutationObserver(function() {
  977. if (target.textContent == status) {
  978. return;
  979. }
  980. status = target.textContent;
  981. if (status == "完了しました" || status == "アンドゥしました" || status == "リドゥしました") {
  982. if (!isNgEnable) {
  983. $("#GM_fcn_ng_disable_button").css("background", "none");
  984. isNgEnable = true;
  985. }
  986. makeNgButton();
  987. hideNgThreads();
  988. $("body").attr("__fcn_catalog_visibility", "visible");
  989. $("#cattable > tbody").css("opacity", "1");
  990. $("#GM_fth_highlighted_threads").css("visibility", "visible");
  991. } else if (HIDE_CATALOG_BEFORE_LOAD && status !== "") {
  992. $("body").attr("__fcn_catalog_visibility", "hidden");
  993. $("#cattable > tbody").css("opacity", "0");
  994. $("#GM_fth_highlighted_threads").css("visibility", "hidden");
  995. }
  996. });
  997. observer.observe(target, config);
  998. }
  999.  
  1000. // KOSHIAN
  1001. $(document).on("KOSHIAN_cat_reload", () => {
  1002. if (HIDE_CATALOG_BEFORE_LOAD) {
  1003. $("body").attr("__fcn_catalog_visibility", "hidden");
  1004. $("#cattable > tbody").css("opacity", "0");
  1005. $("#GM_fth_highlighted_threads").css("visibility", "hidden");
  1006. }
  1007. if (!isNgEnable) {
  1008. $("#GM_fcn_ng_disable_button").css("background", "none");
  1009. isNgEnable = true;
  1010. }
  1011. makeNgButton();
  1012. hideNgThreads();
  1013. $("body").attr("__fcn_catalog_visibility", "visible");
  1014. $("#cattable > tbody").css("opacity", "1");
  1015. $("#GM_fth_highlighted_threads").css("visibility", "visible");
  1016. });
  1017.  
  1018. // ふたクロ
  1019. checkFutakuroReloadStatus();
  1020.  
  1021. function checkFutakuroReloadStatus() {
  1022. var opacityZero = false;
  1023. var target = $("#cattable").get(0);
  1024. var config = { attributes: true , attributeFilter: ["style"] };
  1025. var observer = new MutationObserver(function(mutations) {
  1026. if ($("#cat_search").length) {
  1027. mutations.forEach(function(mutation) {
  1028. if (mutation.target.attributes.style.nodeValue == "opacity: 0;") {
  1029. opacityZero = true;
  1030. if (HIDE_CATALOG_BEFORE_LOAD) {
  1031. $("body").attr("__fcn_catalog_visibility", "hidden");
  1032. $("#cattable > tbody").css("opacity", "0");
  1033. $("#GM_fth_highlighted_threads").css("visibility", "hidden");
  1034. }
  1035. } else if (opacityZero && mutation.target.attributes.style.nodeValue != "opacity: 0;") {
  1036. opacityZero = false;
  1037. if (!isNgEnable) {
  1038. $("#GM_fcn_ng_disable_button").css("background", "none");
  1039. isNgEnable = true;
  1040. }
  1041. makeNgButton();
  1042. hideNgThreads();
  1043. $("body").attr("__fcn_catalog_visibility", "visible");
  1044. $("#cattable > tbody").css("opacity", "1");
  1045. $("#GM_fth_highlighted_threads").css("visibility", "visible");
  1046. }
  1047. });
  1048. }
  1049. });
  1050. observer.observe(target, config);
  1051. }
  1052. }
  1053.  
  1054. /**
  1055. * カタログのスレにNGボタン作成
  1056. */
  1057. function makeNgButton() {
  1058. // カタログソートが「設定」なら作成しない
  1059. if (location.search.match(/mode=catset/)) {
  1060. return;
  1061. }
  1062. // NGボタン
  1063. var $ngButton = $("<span>", {
  1064. class: "GM_fcn_ng_button",
  1065. text: "[NG]",
  1066. css: {
  1067. color: "blue",
  1068. display: "none",
  1069. },
  1070. });
  1071. // NGボタンメニュー
  1072. var $ngButtonMenu = $("<div>", {
  1073. class: "GM_fcn_ng_menu",
  1074. css: {
  1075. display: "none",
  1076. }
  1077. });
  1078.  
  1079. var $catTd = $("#cattable td");
  1080. $catTd.each(function() {
  1081. var $oldNgButtons = $(this).children(".GM_fcn_ng_button");
  1082. if ($oldNgButtons.length) {
  1083. $oldNgButtons.remove();
  1084. }
  1085.  
  1086. var $cloneNgButton = $ngButton.clone();
  1087. var $cloneNgButtonMenu = $ngButtonMenu.clone();
  1088.  
  1089. $cloneNgButton.hover(function () {
  1090. $(this).css("color", "red");
  1091. }, function () {
  1092. $(this).css("color", "blue");
  1093. });
  1094. $cloneNgButton.on("click",function(){
  1095. makeNgButtonMenu($(this));
  1096. });
  1097. $(this).hover(function () {
  1098. $cloneNgButton.css("display", "inline");
  1099. $cloneNgButton.siblings(".KOSHIAN_response_increase").css("display", "none");
  1100. $cloneNgButton.siblings(".fvw_num").css("display", "none");
  1101. }, function () {
  1102. $cloneNgButton.css("display", "none");
  1103. $cloneNgButtonMenu.css("display", "none");
  1104. $cloneNgButton.siblings(".KOSHIAN_response_increase").css("display", "inline");
  1105. $cloneNgButton.siblings(".fvw_num").css("display", "");
  1106. });
  1107.  
  1108. $cloneNgButton.append($cloneNgButtonMenu);
  1109. $(this).append($cloneNgButton);
  1110. });
  1111. }
  1112.  
  1113. /**
  1114. * NGボタンメニュー作成
  1115. * @param {jQuery} $button メニューを作成するNGボタンのjQueryオブジェクト
  1116. */
  1117. function makeNgButtonMenu($button) {
  1118. if (!isNgEnable) {
  1119. return;
  1120. }
  1121. var $menu = $button.children(".GM_fcn_ng_menu");
  1122. if (!$button.find(".GM_fcn_ng_menu_item").length) {
  1123. // スレNG
  1124. var $ngNumber = $("<div>", {
  1125. class: "GM_fcn_ng_menu_item",
  1126. text: "スレNG",
  1127. css: {
  1128. color: "blue",
  1129. "background-color": "rgba(240, 224, 214, 0.95)",
  1130. }
  1131. });
  1132. // 本文NG
  1133. var $ngWordCommon = $("<div>", {
  1134. class: "GM_fcn_ng_menu_item",
  1135. text: "本文NG(共通)",
  1136. css: {
  1137. color: "blue",
  1138. "background-color": "rgba(240, 224, 214, 0.95)",
  1139. }
  1140. });
  1141. var $ngWordIndiv = $("<div>", {
  1142. class: "GM_fcn_ng_menu_item",
  1143. text: "本文NG(板別)",
  1144. css: {
  1145. color: "blue",
  1146. "background-color": "rgba(240, 224, 214, 0.95)",
  1147. }
  1148. });
  1149. // 画像NG
  1150. var $ngImage = $("<div>", {
  1151. class: "GM_fcn_ng_menu_item",
  1152. text: "画像NG",
  1153. css: {
  1154. color: "blue",
  1155. "background-color": "rgba(240, 224, 214, 0.95)",
  1156. }
  1157. });
  1158.  
  1159. var $td = $button.parent();
  1160. var threadNumber = $td.find("a:first").length ? $td.find("a:first").attr("href").slice(4,-4) : "";
  1161. var threadImgObj = $td.find("img:first").length ? $td.find("img:first")[0] : "";
  1162. 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() : "";
  1163.  
  1164. var $cloneNgNumber = $ngNumber.clone();
  1165. var $cloneNgWordCommon = $ngWordCommon.clone();
  1166. var $cloneNgWordIndiv = $ngWordIndiv.clone();
  1167. var $cloneNgImage = $ngImage.clone();
  1168.  
  1169. $cloneNgNumber.hover(function () {
  1170. $(this).css("color", "red");
  1171. $(this).css("background-color", "rgba(204, 233, 255, 0.95)");
  1172. }, function () {
  1173. $(this).css("color", "blue");
  1174. $(this).css("background-color", "rgba(240, 224, 214, 0.95)");
  1175. });
  1176. $cloneNgNumber.click(function () {
  1177. addNgNumber(threadNumber);
  1178. $td.addClass("GM_fcn_ng_numbers");
  1179. $td.css("display","none");
  1180. if ($td.hasClass("GM_fth_pickuped") || $td.hasClass("GM_fth_opened")) {
  1181. hideNgThreads();
  1182. }
  1183. });
  1184.  
  1185. $cloneNgWordCommon.hover(function () {
  1186. $(this).css("color", "red");
  1187. $(this).css("background-color", "rgba(204, 233, 255, 0.95)");
  1188. }, function () {
  1189. $(this).css("color", "blue");
  1190. $(this).css("background-color", "rgba(240, 224, 214, 0.95)");
  1191. });
  1192. $cloneNgWordCommon.click(function () {
  1193. var words = GM_getValue("_futaba_catalog_NG_words", "");
  1194. words = addNgWord(words, threadComment);
  1195. GM_setValue("_futaba_catalog_NG_words", words);
  1196. $td.addClass("GM_fcn_ng_words");
  1197. $td.css("display","none");
  1198. if ($td.hasClass("GM_fth_pickuped") || $td.hasClass("GM_fth_opened")) {
  1199. hideNgThreads();
  1200. }
  1201. });
  1202.  
  1203. $cloneNgWordIndiv.hover(function () {
  1204. $(this).css("color", "red");
  1205. $(this).css("background-color", "rgba(204, 233, 255, 0.95)");
  1206. }, function () {
  1207. $(this).css("color", "blue");
  1208. $(this).css("background-color", "rgba(240, 224, 214, 0.95)");
  1209. });
  1210. $cloneNgWordIndiv.click(function () {
  1211. var words = getCurrentIndivValue("NG_words_indiv", "");
  1212. words = addNgWord(words, threadComment);
  1213. setIndivValue("NG_words_indiv", words);
  1214. $td.addClass("GM_fcn_ng_words");
  1215. $td.css("display","none");
  1216. if ($td.hasClass("GM_fth_pickuped") || $td.hasClass("GM_fth_opened")) {
  1217. hideNgThreads();
  1218. }
  1219. });
  1220.  
  1221. $cloneNgImage.hover(function () {
  1222. $(this).css("color", "red");
  1223. $(this).css("background-color", "rgba(204, 233, 255, 0.95)");
  1224. }, function () {
  1225. $(this).css("color", "blue");
  1226. $(this).css("background-color", "rgba(240, 224, 214, 0.95)");
  1227. });
  1228. $cloneNgImage.click(function () {
  1229. hideNgImageThread(threadImgObj, threadComment, $td);
  1230. if ($td.hasClass("GM_fth_pickuped") || $td.hasClass("GM_fth_opened") || USE_DHASH) {
  1231. hideNgThreads();
  1232. }
  1233. });
  1234.  
  1235. if (threadNumber) {
  1236. $menu.append($cloneNgNumber);
  1237. }
  1238. if (threadComment) {
  1239. $menu.append($cloneNgWordCommon);
  1240. $menu.append($cloneNgWordIndiv);
  1241. }
  1242. if (threadImgObj && USE_NG_IMAGES) {
  1243. $menu.append($cloneNgImage);
  1244. }
  1245. }
  1246.  
  1247. var menuLeft = 0;
  1248. var menuTop = $button.height();
  1249. $menu.css("left", `${menuLeft}px`);
  1250. $menu.css("top", `${menuTop}px`);
  1251. $menu.css("display", "block");
  1252.  
  1253. /**
  1254. * NGワード追加
  1255. * @param {string} ngWords 追加前のNGワード
  1256. * @param {string} newNgWord 追加するNGワード
  1257. * @return {string} 追加後のNGワード
  1258. */
  1259. function addNgWord(ngWords, newNgWord) {
  1260. newNgWord = newNgWord.replace(/[\\^$.*+?()[\]{}|]/g, "\\$&");
  1261. if (newNgWord && ngWords) {
  1262. ngWords = newNgWord + "|" + ngWords;
  1263. } else {
  1264. ngWords += newNgWord;
  1265. }
  1266. return ngWords;
  1267. }
  1268.  
  1269. /**
  1270. * スレ画像NG
  1271. * @param {HTMLImageElement} imgObj NGにするスレ画像のimg要素
  1272. * @param {string} comment NG画像のコメント
  1273. * @param {jQuery} $td NGにするスレ画像の親td要素のjQueryオブジェクト
  1274. */
  1275. function hideNgImageThread(imgObj, comment, $td) {
  1276. var data = convertDataURI(imgObj);
  1277. //console.log("futaba_catalog_NG - data: " + data);
  1278. if (!data) {
  1279. alert("スレ画像の取得に失敗しました");
  1280. return;
  1281. }
  1282. var hexHash = md5(data);
  1283. var dHash = convertDHash(imgObj, true);
  1284. //console.log("futaba_catalog_NG - hexHash: " + hexHash);
  1285. //console.log("futaba_catalog_NG - dHash: " + dHash.toString(16));
  1286. addNgListObj("_futaba_catalog_NG_images", hexHash);
  1287. addNgListObj("_futaba_catalog_NG_comment", comment);
  1288. addNgListObj("_futaba_catalog_NG_date", getDate());
  1289. addNgListObj("_futaba_catalog_NG_dHashes", dHash);
  1290. if (isNgEnable) {
  1291. $td.addClass("GM_fcn_ng_images");
  1292. $td.css("display","none");
  1293. }
  1294. // 非NG画像リストからNG画像を削除
  1295. var okImages = getCurrentIndivValue("OK_images_indiv", []);
  1296. var imgNumber = parseInt($td.find("img").attr("src").match(/(\d+)s\.jpg$/)[1], 10);
  1297. if (USE_DHASH) {
  1298. // 近似画像NG使用時は非NG画像リストを全削除
  1299. GM_setValue("OK_images_indiv", "{}");
  1300. } else {
  1301. var index = okImages.indexOf(imgNumber);
  1302. if (index > -1) {
  1303. okImages.splice(index, 1);
  1304. setIndivValue("OK_images_indiv", okImages);
  1305. }
  1306. }
  1307.  
  1308. /**
  1309. * NGリストにNGデータを追加
  1310. * @param {string} target NGデータを追加するNGリスト名
  1311. * @param {string} val NGリストへ追加するNGデータ
  1312. */
  1313. function addNgListObj(target, val) {
  1314. var ngListObj = GM_getValue(target, "");
  1315. if (ngListObj === ""){
  1316. ngListObj = [];
  1317. }
  1318. ngListObj.unshift(val);
  1319. if (ngListObj.length > MAX_REGISTERED_NG_IMAGES) {
  1320. ngListObj.splice(MAX_REGISTERED_NG_IMAGES);
  1321. }
  1322. GM_setValue(target, ngListObj);
  1323. }
  1324. }
  1325. }
  1326.  
  1327. /**
  1328. * NG番号追加
  1329. * @param {string} number 追加するNG番号
  1330. */
  1331. function addNgNumber(number) {
  1332. var ngNumberObj = getIndivObj("NG_numbers_indiv");
  1333. if (ngNumberObj === ""){
  1334. ngNumberObj = {};
  1335. }
  1336. if (!ngNumberObj[serverFullPath]) {
  1337. ngNumberObj[serverFullPath] = [];
  1338. }
  1339. if (ngNumberObj[serverFullPath].indexOf(number) > -1) {
  1340. return;
  1341. }
  1342. ngNumberObj[serverFullPath].push(number);
  1343. var deleteCount = ngNumberObj[serverFullPath].length - MAX_NG_THREADS;
  1344. if (deleteCount > 0) {
  1345. ngNumberObj[serverFullPath].splice(0, deleteCount);
  1346. }
  1347. var jsonString = JSON.stringify(ngNumberObj);
  1348. GM_setValue("NG_numbers_indiv", jsonString);
  1349. }
  1350.  
  1351. /**
  1352. * dataURI変換
  1353. * @param {HTMLImageElement} imgObj dataURIに変換する画像のimg要素
  1354. * @param {number} width 変換する画像の幅 未指定時は画像の本来の幅
  1355. * @param {number} height 変換する画像の高さ 未指定時は画像の本来の高さ
  1356. * @return {string} 変換したdataURI文字列
  1357. */
  1358. function convertDataURI(imgObj, width = imgObj.naturalWidth, height = imgObj.naturalHeight){
  1359. if (!imgObj || !imgObj.complete || !width || !height) {
  1360. return;
  1361. }
  1362. // canvasを生成してimg要素を反映
  1363. var cvs = document.createElement("canvas");
  1364. cvs.width = width;
  1365. cvs.height = height;
  1366. var ctx = cvs.getContext("2d", {
  1367. alpha: false // 背景不透明で高速化
  1368. });
  1369. try {
  1370. ctx.drawImage(imgObj, 0, 0);
  1371. } catch (e) {
  1372. console.error("futaba_catalog_NG - drawImage error: src= " + imgObj.src + ", " + e.name + ": " + e.message);
  1373. console.dir(e);
  1374. return;
  1375. }
  1376. // canvasをdataURI化
  1377. var data;
  1378. try {
  1379. data = cvs.toDataURL("image/jpeg");
  1380. } catch (e) {
  1381. console.error("futaba_catalog_NG - dataURI convert error: src= " + imgObj.src + ", " + e.name + ": " + e.message);
  1382. console.dir(e);
  1383. return;
  1384. }
  1385. if (data.substr(0,23) !== "data:image/jpeg;base64,") {
  1386. console.error("futaba_catalog_NG - dataURI abnormal: src= " + imgObj.src + ", dataURI= " + data);
  1387. return;
  1388. }
  1389. return data;
  1390. }
  1391.  
  1392. /**
  1393. * dHash変換
  1394. * @param {HTMLImageElement} imgObj dHashに変換する画像のimg要素
  1395. * @param {boolean} force 近似画像NGが無効でも強制的にdHash変換する
  1396. * @return {number} 変換したdHash 0 ~ 2^49 - 1
  1397. * 変換できないときはnullを返す
  1398. * オリジナルは64bitのHash値だがJavaScriptの最大整数値2^53に収める為49bitのHash値を返す
  1399. */
  1400. function convertDHash(imgObj, force = false) {
  1401. if ((!USE_DHASH && !force) || !imgObj || !imgObj.complete || !imgObj.naturalWidth || !imgObj.naturalHeight) {
  1402. return null;
  1403. }
  1404. // 8x7のcanvasを生成してimg要素を反映
  1405. var cvs = document.createElement("canvas");
  1406. cvs.width = 8;
  1407. cvs.height = 7;
  1408. var ctx = cvs.getContext("2d", {
  1409. alpha: false // 背景不透明で高速化
  1410. });
  1411. try {
  1412. ctx.drawImage(imgObj, 0, 0, imgObj.naturalWidth, imgObj.naturalHeight, 0, 0, cvs.width, cvs.height);
  1413. } catch (e) {
  1414. console.error("futaba_catalog_NG - drawImage error: src= " + imgObj.src + ", " + e.name + ": " + e.message);
  1415. console.dir(e);
  1416. return null;
  1417. }
  1418. var pixels = ctx.getImageData(0, 0, cvs.width, cvs.height);
  1419. var grayScale = [];
  1420. for (var y = 0; y < pixels.height; ++y) {
  1421. for (var x = 0; x < pixels.width; ++x) {
  1422. var i = (y * 4) * pixels.width + x * 4;
  1423. var rgb = pixels.data[i] + pixels.data[i + 1] + pixels.data[i + 2]; // 画素をグレイスケール化
  1424. grayScale.push(rgb);
  1425. }
  1426. }
  1427. var dHash = 0;
  1428. var exponent = 0; // べき指数
  1429. grayScale.forEach((v, i) => {
  1430. if ((i + 1) % cvs.width !== 0) { // 右端のピクセルは右隣が無いのでスキップ
  1431. // 右隣の輝度と比較した結果を1bitとしてセット
  1432. dHash += (v < grayScale[i + 1] ? 1 : 0) * 2 ** exponent;
  1433. ++exponent;
  1434. }
  1435. });
  1436. //console.log("futaba_catalog_NG - imgObj.src: " + imgObj.src + ", dHash: 0x" + dHash.toString(16).toUpperCase());
  1437. return dHash;
  1438. }
  1439.  
  1440. /**
  1441. * 日付取得
  1442. * @return {string} 現在の日付の文字列 yy/mm/dd
  1443. */
  1444. function getDate() {
  1445. var now = new Date();
  1446. var date = ("" + (now.getFullYear())).slice(-2) + "/" +
  1447. ("0" + (now.getMonth() + 1)).slice(-2) + "/" +
  1448. ("0" + now.getDate()).slice(-2);
  1449. return date;
  1450. }
  1451.  
  1452. /**
  1453. * カタログを検索してNGスレを非表示
  1454. * @param {boolean} isWordsChanged NGワードを変更したか
  1455. */
  1456. function hideNgThreads(isWordsChanged) {
  1457. if (!isNgEnable) {
  1458. return;
  1459. }
  1460. var Start = new Date().getTime();//count parsing time
  1461. var words = "";
  1462. var wordsCommon = GM_getValue("_futaba_catalog_NG_words", "");
  1463. var wordsIndiv = getCurrentIndivValue("NG_words_indiv", "");
  1464. var numbers = getCurrentIndivValue("NG_numbers_indiv", []);
  1465. images = GM_getValue("_futaba_catalog_NG_images", []);
  1466. ngDate = GM_getValue("_futaba_catalog_NG_date", []);
  1467. okImages = getCurrentIndivValue("OK_images_indiv", []);
  1468. dHashes = GM_getValue("_futaba_catalog_NG_dHashes", []);
  1469.  
  1470. // NGワード
  1471. if( wordsCommon !== "" ) {
  1472. words += wordsCommon;
  1473. if( wordsIndiv !== "" ) {
  1474. words += "|" + wordsIndiv;
  1475. }
  1476. }
  1477. else {
  1478. words += wordsIndiv;
  1479. }
  1480. //console.log(words);
  1481. //console.dir(numbers);
  1482. //console.dir("futaba_catalog_NG - images.length: " + images.length);
  1483. try {
  1484. var re = new RegExp(words, "i");
  1485. }
  1486. catch (e) {
  1487. alert("NGワードのパターンが無効です\n\n" + e);
  1488. editNgWords();
  1489. return;
  1490. }
  1491. if (isWordsChanged) {
  1492. $(".GM_fcn_ng_words").css("display", "");
  1493. $(".GM_fcn_ng_words").removeClass("GM_fcn_ng_words");
  1494. }
  1495. if (words !== "") {
  1496. $("#cattable td small").each(function() {
  1497. if (re.test($(this).text())) {
  1498. if ($(this).parent("a").length) { //文字スレ
  1499. $(this).parent().parent("td").addClass("GM_fcn_ng_words");
  1500. $(this).parent().parent("td").css("display", "none");
  1501. } else {
  1502. $(this).parent("td").addClass("GM_fcn_ng_words");
  1503. $(this).parent("td").css("display", "none");
  1504. }
  1505. //console.log("futaba catalog NG - caption: " + $(this).text() + " NG word: " + $(this).text().match(re)[0]);
  1506. }
  1507. });
  1508. }
  1509. if (isWordsChanged) {
  1510. console.log("futaba_catalog_NG - Parsing@" + serverFullPath + ": "+((new Date()).getTime()-Start) +"msec"); // eslint-disable-line no-console
  1511. return;
  1512. }
  1513.  
  1514. // NG番号
  1515. if (numbers.length) {
  1516. var $catAnchors = $("#cattable td[class!='GM_fcn_ng_words'] > a:first-of-type");
  1517. $catAnchors.each(function() {
  1518. var hrefNum = $(this).attr("href").slice(4,-4);
  1519. if (numbers.indexOf(hrefNum) > -1){
  1520. $(this).closest("td, .cs").addClass("GM_fcn_ng_numbers");
  1521. $(this).closest("td, .cs").css("display", "none");
  1522. }
  1523. });
  1524. }
  1525.  
  1526. // NG画像
  1527. if (USE_NG_IMAGES) {
  1528. if (images.length) {
  1529. var $catImages = $("#cattable td:not([class^='GM_fcn_ng_']) > a:first-of-type > img");
  1530. $catImages.each(function() {
  1531. var $td = $(this).closest("td");
  1532. var imgSrc = this.src.match(/(\d+)s\.jpg$/);
  1533. if (imgSrc) {
  1534. var imgNumber = parseInt(imgSrc[1], 10);
  1535. if (okImages.indexOf(imgNumber) == -1) {
  1536. var data = convertDataURI(this);
  1537. if (data) {
  1538. var hexHash = md5(data);
  1539. var index = images.indexOf(hexHash);
  1540. if (index > -1){
  1541. $td.addClass("GM_fcn_ng_images");
  1542. $td.css("display", "none");
  1543. ngDate[index] = getDate();
  1544. if (dHashes[index] == null) {
  1545. dHashes[index] = convertDHash(this, true);
  1546. }
  1547. } else {
  1548. var isError = false; // md5変換エラーフラグ
  1549. if (hexHash.length !== 32) {
  1550. isError = true;
  1551. console.error("futaba_catalog_NG - hexHash abnormal: image No." + imgNumber + ", hexHash: " + hexHash); // eslint-disable-line no-console
  1552. }
  1553. // dHash判定
  1554. var distance, dHash = convertDHash(this);
  1555. [index, distance] = findIndexOfDHashes(dHash);
  1556. if (index > -1) {
  1557. if (ENABLE_DHASH_TEST) {
  1558. $(this).addClass("GM_fcn_ng_dhash_img");
  1559. $td.children(".GM_fcn_ng_button").attr("title", "distance: " + distance + ", dHash: " + ("000000000000" + dHash.toString(16)).slice(-13));
  1560. $td.addClass("GM_fcn_ng_dhash_td");
  1561. } else {
  1562. $td.addClass("GM_fcn_ng_images");
  1563. $td.css("display", "none");
  1564. }
  1565. ngDate[index] = getDate();
  1566. } else if (!isError) {
  1567. okImages.unshift(imgNumber);
  1568. }
  1569. }
  1570. } else {
  1571. // スレ画像読込完了確認
  1572. this.onload = () => {
  1573. this.onload = null;
  1574. var imgNumber = parseInt(this.src.match(/(\d+)s\.jpg$/)[1], 10);
  1575. var data = convertDataURI(this);
  1576. if (data) {
  1577. var hexHash = md5(data);
  1578. var index = images.indexOf(hexHash);
  1579. if (index > -1){
  1580. $td.addClass("GM_fcn_ng_images");
  1581. $td.css("display", "none");
  1582. ngDate[index] = getDate();
  1583. GM_setValue("_futaba_catalog_NG_date", ngDate);
  1584. if (dHashes[index] == null) {
  1585. dHashes[index] = convertDHash(this, true);
  1586. GM_setValue("_futaba_catalog_NG_dHashes", dHashes);
  1587. }
  1588. } else {
  1589. var isError = false; // md5変換エラーフラグ
  1590. if (hexHash.length !== 32) {
  1591. isError = true;
  1592. console.error("futaba_catalog_NG - hexHash abnormal: image No." + imgNumber + ", hexHash: " + hexHash);
  1593. }
  1594. // dHash判定
  1595. var distance, dHash = convertDHash(this);
  1596. [index, distance] = findIndexOfDHashes(dHash);
  1597. if (index > -1) {
  1598. if (ENABLE_DHASH_TEST) {
  1599. $(this).addClass("GM_fcn_ng_dhash_img");
  1600. $td.children(".GM_fcn_ng_button").attr("title", "distance: " + distance + ", dHash: " + ("000000000000" + dHash.toString(16)).slice(-13));
  1601. $td.addClass("GM_fcn_ng_dhash_td");
  1602. } else {
  1603. $td.addClass("GM_fcn_ng_images");
  1604. $td.css("display", "none");
  1605. }
  1606. ngDate[index] = getDate();
  1607. GM_setValue("_futaba_catalog_NG_date", ngDate);
  1608. } else if (!isError) {
  1609. okImages.unshift(imgNumber);
  1610. }
  1611. }
  1612. } else {
  1613. console.error("futaba_catalog_NG - image data abnormal: image No." + imgNumber);
  1614. }
  1615. };
  1616. if (this.complete && this.width && this.height && this.onload) {
  1617. // onloadセット中に画像読込完了していたらloadをトリガーする
  1618. $(this).trigger("load");
  1619. }
  1620. }
  1621. }
  1622. }
  1623. });
  1624. GM_setValue("_futaba_catalog_NG_date", ngDate);
  1625. GM_setValue("_futaba_catalog_NG_dHashes", dHashes);
  1626. if (okImages.length > MAX_OK_IMAGES) {
  1627. okImages.splice(MAX_OK_IMAGES);
  1628. }
  1629. setIndivValue("OK_images_indiv", okImages);
  1630. } else {
  1631. var $catImg = $("#cattable td a img");
  1632. $catImg.each(function() {
  1633. var imgSrc = this.src.match(/(\d+)s\.jpg$/);
  1634. if (imgSrc) {
  1635. var imgNumber = parseInt(imgSrc[1], 10);
  1636. if (okImages.indexOf(imgNumber) == -1) {
  1637. okImages.unshift(imgNumber);
  1638. }
  1639. }
  1640. });
  1641. if (okImages.length > MAX_OK_IMAGES) {
  1642. okImages.splice(MAX_OK_IMAGES);
  1643. }
  1644. setIndivValue("OK_images_indiv", okImages);
  1645. }
  1646. }
  1647. //console.log("futaba_catalog_NG - okImages.length: " + okImages.length);
  1648. console.log("futaba_catalog_NG - Parsing@" + serverFullPath + ": " + ((new Date()).getTime() - Start) + "msec"); // eslint-disable-line no-console
  1649.  
  1650. /**
  1651. * dHashリストのインデックス探索
  1652. * @param {number} dHash 近似画像NG判定する画像のdHash値
  1653. * @return {Array.<number>} [i, distance]
  1654. * {number} i 近似度が閾値以下のdHashリスト(dHashes)配列内のインデックス
  1655. * {number} distance ハミング距離
  1656. * dHashリストに該当が無ければi, distance共に-1を返す
  1657. */
  1658. function findIndexOfDHashes(dHash) {
  1659. if (dHash != null) {
  1660. for (var i = 0, num = dHashes.length; i < num; ++i) {
  1661. if (dHashes[i]) {
  1662. var distance = getHammingDistance(dHash, dHashes[i]);
  1663. if (distance <= DISTANCE_THRESHOLD) {
  1664. if (ENABLE_DHASH_TEST) {
  1665. console.debug("futaba_catalog_NG - catalog dHash: " + ("000000000000" + dHash.toString(16)).slice(-13) + ", NG list dHash: " + ("000000000000" + dHashes[i].toString(16)).slice(-13));
  1666. console.debug("futaba_catalog_NG - hamming distance: " + distance);
  1667. }
  1668. return [i, distance];
  1669. }
  1670. }
  1671. }
  1672. }
  1673. return [-1, -1];
  1674. }
  1675.  
  1676. /**
  1677. * ハミング距離取得
  1678. * @param {number} hash1 測定するHash(49bit)
  1679. * @param {number} hash2 測定するHash(49bit)
  1680. * @return {number} ハミング距離(0~49)
  1681. */
  1682. function getHammingDistance(hash1, hash2) {
  1683. // 2つのHashを上位17bitと下位32bitに分割して32ビット演算する
  1684. var hash1L = hash1 >>> 0;
  1685. var hash1H = (hash1 - hash1L) / 0x100000000;
  1686. var hash2L = hash2 >>> 0;
  1687. var hash2H = (hash2 - hash2L) / 0x100000000;
  1688.  
  1689. // 下位32bitのビットの異なる位置を抽出
  1690. var xorL = hash1L ^ hash2L;
  1691. var count = 0;
  1692. // 立っている最下位ビットを消してカウント
  1693. while (xorL) {
  1694. xorL &= xorL - 1;
  1695. ++count;
  1696. }
  1697.  
  1698. // 上位17bitのビットの異なる位置を抽出
  1699. var xorH = hash1H ^ hash2H;
  1700. // 立っている最下位ビットを消してカウント
  1701. while (xorH) {
  1702. xorH &= xorH - 1;
  1703. ++count;
  1704. }
  1705.  
  1706. return count;
  1707. }
  1708. }
  1709.  
  1710. /**
  1711. * KOSHIAN del イベント監視
  1712. */
  1713. function listenKoshianDelEvent() {
  1714. $(document).on("KOSHIAN_del", () => {
  1715. // delされたスレをNG登録して非表示
  1716. $(".KOSHIAN_del").each(function() {
  1717. var threadNumber = $(this).find("a:first").length ? $(this).find("a:first").attr("href").slice(4,-4) : "";
  1718. if (threadNumber) {
  1719. addNgNumber(threadNumber);
  1720. }
  1721. if (isNgEnable) {
  1722. $(this).addClass("GM_fcn_ng_numbers");
  1723. $(this).css("display", "none");
  1724. }
  1725. $(this).removeClass("KOSHIAN_del");
  1726. });
  1727. hideNgThreads();
  1728. });
  1729. }
  1730.  
  1731. /**
  1732. * futaba thread highlighter K ピックアップイベント監視
  1733. */
  1734. function listenFthPickupEvent() {
  1735. $(document).on("FutabaTH_pickup", () => {
  1736. // ピックアップされたスレにNGボタンをセット
  1737. $(".GM_fth_pickuped, .GM_fth_opened").each(function() {
  1738. var $ngButton = $(this).find(".GM_fcn_ng_button");
  1739. if ($ngButton.length) {
  1740. var $ngButtonMenu = $ngButton.children(".GM_fcn_ng_menu");
  1741. $ngButton.hover(function() {
  1742. $(this).css("color", "red");
  1743. }, function () {
  1744. $(this).css("color", "blue");
  1745. });
  1746. $ngButton.on("click",function() {
  1747. makeNgButtonMenu($ngButton);
  1748. });
  1749. $(this).hover(function () {
  1750. $ngButton.css("display", "inline");
  1751. $ngButton.siblings(".KOSHIAN_response_increase").css("display", "none");
  1752. $ngButton.siblings(".fvw_num").css("display", "none");
  1753. }, function () {
  1754. $ngButton.css("display", "none");
  1755. $ngButtonMenu.css("display", "none");
  1756. $ngButton.siblings(".KOSHIAN_response_increase").css("display", "inline");
  1757. $ngButton.siblings(".fvw_num").css("display", "");
  1758. });
  1759. }
  1760. });
  1761. });
  1762. }
  1763.  
  1764. /**
  1765. * カタログ非表示
  1766. */
  1767. function hideCatalog() {
  1768. setCatalogHiddenStyle();
  1769. $(function() {
  1770. $("body").attr("__fcn_catalog_visibility", "hidden");
  1771. $("#GM_fth_highlighted_threads").css("visibility", "hidden");
  1772. init();
  1773. });
  1774. $(window).on("load", function() {
  1775. $("body").attr("__fcn_catalog_visibility", "visible");
  1776. setCatalogShownStyle();
  1777. $("#GM_fth_highlighted_threads").css("visibility", "visible");
  1778. });
  1779. }
  1780.  
  1781. /**
  1782. * カタログ非表示スタイル設定
  1783. */
  1784. function setCatalogHiddenStyle() {
  1785. var css =
  1786. "#cattable {" +
  1787. " opacity: 0;" +
  1788. "}";
  1789. GM_addStyle(css);
  1790. }
  1791.  
  1792. /**
  1793. * カタログ表示スタイル設定
  1794. */
  1795. function setCatalogShownStyle() {
  1796. var css =
  1797. "#cattable {" +
  1798. " opacity: 1;" +
  1799. "}";
  1800. GM_addStyle(css);
  1801. }
  1802.  
  1803. /**
  1804. * スタイル設定
  1805. */
  1806. function setStyle() {
  1807. var css =
  1808. // NGワード
  1809. ".GM_fcn_ng_words {" +
  1810. " display: none;" +
  1811. "}" +
  1812. // NG番号
  1813. ".GM_fcn_ng_numbers {" +
  1814. " display: none;" +
  1815. "}" +
  1816. // NG画像
  1817. ".GM_fcn_ng_images {" +
  1818. " display: none;" +
  1819. "}" +
  1820. // 近似画像NGスレ
  1821. ".GM_fcn_ng_dhash_td {" +
  1822. " background-color: #e1b2ec;" +
  1823. " opacity: 0.2;" +
  1824. "}" +
  1825. ".GM_fcn_ng_dhash_td:hover {" +
  1826. " opacity: 1;" +
  1827. "}" +
  1828. // 近似NG画像
  1829. ".GM_fcn_ng_dhash_img {" +
  1830. " opacity: 0.2;" +
  1831. "}" +
  1832. ".GM_fcn_ng_dhash_img:hover {" +
  1833. " opacity: 1;" +
  1834. "}" +
  1835. // NGボタン
  1836. ".GM_fcn_ng_button {" +
  1837. " position: relative;" +
  1838. " font-size: 12px;" +
  1839. " cursor: pointer;" +
  1840. "}" +
  1841. // NGメニュー
  1842. ".GM_fcn_ng_menu {" +
  1843. " font-size: medium;" +
  1844. " background-color: rgba(240, 192, 214, 0.95);" +
  1845. " z-index: 203;" +
  1846. " position: absolute;" +
  1847. " min-width: 140px;" +
  1848. " width: auto;" +
  1849. " border: 1px outset;" +
  1850. " border-radius: 5px;" +
  1851. " padding: 5px;" +
  1852. "}" +
  1853. // NGメニュー項目
  1854. ".GM_fcn_ng_menu_item {" +
  1855. " padding: 5px;" +
  1856. " z-index: 203;" +
  1857. " cursor: pointer;" +
  1858. "}" +
  1859. // NGリストラベル
  1860. ".GM_fcn_ng_list_label {" +
  1861. " display: inline-block;" +
  1862. " width: 100px;" +
  1863. "}" +
  1864. // NGリスト入力
  1865. ".GM_fcn_ng_list_input {" +
  1866. " width: 360px;" +
  1867. " margin-right: 16px;" +
  1868. " font-size: 16px;" +
  1869. "}" +
  1870. // NGリスト編集ボタン
  1871. ".GM_fcn_ng_list_edit_button {" +
  1872. " width: 70px;" +
  1873. " margin-right: 16px;" +
  1874. "}" +
  1875. // NGリスト移動ボタン
  1876. ".GM_fcn_ng_list_move_button {" +
  1877. " margin-right: 16px;" +
  1878. "}" +
  1879. // NGリスト枠
  1880. "#GM_fcn_ng_list_pane {" +
  1881. " width: 928px;" +
  1882. " height: 308px;" +
  1883. " margin-left: 11px;" +
  1884. " border-width: 1px;" +
  1885. " border-style: solid;" +
  1886. " background-color: #eee;" +
  1887. "}" +
  1888. // NGリスト項目行
  1889. "#GM_fcn_ng_list_item_row {" +
  1890. " display: inline-block;" +
  1891. " height: 22px;" +
  1892. " overflow: hidden;" +
  1893. " white-space: nowrap;" +
  1894. "}" +
  1895. // NGリスト項目md5
  1896. "#GM_fcn_ng_list_item_md5 {" +
  1897. " width: 360px;" +
  1898. "}" +
  1899. // NGリスト項目dHash
  1900. "#GM_fcn_ng_list_item_dHash {" +
  1901. " width: 170px;" +
  1902. "}" +
  1903. // NGリスト項目コメント
  1904. "#GM_fcn_ng_list_item_comment {" +
  1905. " width: 250px;" +
  1906. "}" +
  1907. // NGリスト項目最終検出日
  1908. "#GM_fcn_ng_list_item_date {" +
  1909. " width: 130px;" +
  1910. "}" +
  1911. // NGリスト項目スクロールバースペース
  1912. "#GM_fcn_ng_list_item_scrl {" +
  1913. " width: 18px;" +
  1914. "}" +
  1915. // NGリスト項目
  1916. ".GM_fcn_ng_list_item {" +
  1917. " display: inline-block;" +
  1918. " height: 22px;" +
  1919. " border-width: 1px;" +
  1920. " border-style: solid;" +
  1921. " box-sizing: border-box;" +
  1922. " overflow: hidden;" +
  1923. " white-space: nowrap;" +
  1924. " text-overflow: clip;" +
  1925. " cursor: pointer;" +
  1926. "}" +
  1927. // NGリストコンテンツ
  1928. "#GM_fcn_ng_list_content {" +
  1929. " width: 928px;" +
  1930. " height: 286px;" +
  1931. " overflow-x: hidden;" +
  1932. " overflow-y: auto;" +
  1933. "}" +
  1934. // NGリスト行
  1935. ".GM_fcn_ng_list_row {" +
  1936. " width: 928px;" +
  1937. " height: 22px;" +
  1938. " cursor: pointer;" +
  1939. " overflow: hidden;" +
  1940. " white-space: nowrap;" +
  1941. "}" +
  1942. // NGリスト画像
  1943. ".GM_fcn_ng_list_image {" +
  1944. " display: inline-block;" +
  1945. " width: 360px;" +
  1946. " height: 22px;" +
  1947. " border-width: 1px;" +
  1948. " border-style: solid;" +
  1949. " box-sizing: border-box;" +
  1950. " overflow: hidden;" +
  1951. " white-space: nowrap;" +
  1952. " text-overflow: ellipsis;" +
  1953. " font-family: Consolas, 'Courier New', monospace;" +
  1954. "}" +
  1955. // NGリストdHash
  1956. ".GM_fcn_ng_list_dHash {" +
  1957. " display: inline-block;" +
  1958. " width: 170px;" +
  1959. " height: 22px;" +
  1960. " border-width: 1px;" +
  1961. " border-style: solid;" +
  1962. " box-sizing: border-box;" +
  1963. " overflow: hidden;" +
  1964. " white-space: nowrap;" +
  1965. " text-overflow: ellipsis;" +
  1966. " font-family: Consolas, 'Courier New', monospace;" +
  1967. "}" +
  1968. // NGリストコメント
  1969. ".GM_fcn_ng_list_comment {" +
  1970. " display: inline-block;" +
  1971. " width: 250px;" +
  1972. " height: 22px;" +
  1973. " padding-left: 10px;" +
  1974. " border-width: 1px;" +
  1975. " border-style: solid;" +
  1976. " box-sizing: border-box;" +
  1977. " text-align: left;" +
  1978. " overflow: hidden;" +
  1979. " white-space: nowrap;" +
  1980. " text-overflow: ellipsis;" +
  1981. "}" +
  1982. // NGリスト日時
  1983. ".GM_fcn_ng_list_date {" +
  1984. " display: inline-block;" +
  1985. " width: 130px;" +
  1986. " height: 22px;" +
  1987. " border-width: 1px;" +
  1988. " border-style: solid;" +
  1989. " box-sizing: border-box;" +
  1990. " overflow: hidden;" +
  1991. " white-space: nowrap;" +
  1992. " text-overflow: ellipsis;" +
  1993. " font-family: Consolas, 'Courier New', monospace;" +
  1994. "}" +
  1995. // NGリストスクロールバー
  1996. ".GM_fcn_ng_list_scrl {" +
  1997. " display: inline-block;" +
  1998. " width: 18px;" +
  1999. " height: 22px;" +
  2000. " border-width: 0px 1px;" +
  2001. " border-style: solid;" +
  2002. " box-sizing: border-box;" +
  2003. " overflow: hidden;" +
  2004. " white-space: nowrap;" +
  2005. "}" +
  2006. // カタログ下スペース
  2007. "#GM_fcn_catalog_space {" +
  2008. " min-height: 2000px;" +
  2009. "}" +
  2010. // futaba thread highlighter ピックアップ
  2011. ".GM_fth_pickuped {" +
  2012. " overflow: visible !important;" +
  2013. "}" +
  2014. // futaba thread highlighter 既読
  2015. ".GM_fth_opened {" +
  2016. " overflow: visible !important;" +
  2017. "}" +
  2018. // ふたクロNGボタン
  2019. ".fvw_ng {" +
  2020. " display: none !important;" +
  2021. "}" +
  2022. // スレのプルダウンメニューボタン用スペース
  2023. "#cattable > tbody > tr > td {" +
  2024. " padding-bottom: 12px !important;" +
  2025. "}";
  2026. GM_addStyle(css);
  2027. }
  2028.  
  2029. })(jQuery);

QingJ © 2025

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