futaba ID+IP popup

同じIDやIPのレスをポップアップしちゃう

  1. // ==UserScript==
  2. // @name futaba ID+IP popup
  3. // @namespace https://github.com/himuro-majika
  4. // @description 同じIDやIPのレスをポップアップしちゃう
  5. // @author himuro_majika
  6. // @include http://*.2chan.net/*/res/*.htm
  7. // @include https://*.2chan.net/*/res/*.htm
  8. // @version 1.3.1
  9. // @license MIT
  10. // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAMAAAAoLQ9TAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAAPUExURYv4i2PQYy2aLUe0R////zorx9oAAAAFdFJOU/////8A+7YOUwAAAElJREFUeNqUj1EOwDAIQoHn/c88bX+2fq0kRsAoUXVAfwzCttWsDWzw0kNVWd2tZ5K9gqmMZB8libt4pSg6YlO3RnTzyxePAAMAzqMDgTX8hYYAAAAASUVORK5CYII=
  11. // ==/UserScript==
  12. (function() {
  13. 'use strict';
  14.  
  15. const USE_COUNTER = true; // IDカウンターを表示する
  16. const USE_COUNTER_CURRENT = true; // IDカウンターに現在の出現数を表示
  17. const scriptName = "GM_FIP";
  18.  
  19. let Start = new Date().getTime();//count parsing time
  20. let saba = location.host.replace(".2chan.net","") + location.pathname.replace("futaba.htm","");
  21. let timer_show, timer_hide;
  22. let isIDIPThread = false;
  23. let isImg = false;
  24.  
  25. init();
  26. console.log(scriptName + " Parsing " + saba + ": " + ((new Date()).getTime() - Start) + "msec");//log parsing time
  27.  
  28. function init() {
  29. checkIsImg();
  30. checkThreadMail();
  31. setClassAndNameThread();
  32. createCounter();
  33. observeInserted();
  34. }
  35.  
  36. //img鯖
  37. function checkIsImg() {
  38. isImg = document.querySelector(".cnm") === null;
  39. }
  40.  
  41. // ID表示・IP表示スレかどうか
  42. function checkThreadMail() {
  43. let mail = !isImg ? document.querySelector(".cnm a") : document.querySelector(".thre .cnw a");
  44. isIDIPThread = mail !== null && mail.href.match(/^mailto:i[dp]/i) !== null;
  45. }
  46.  
  47. // ID/IPにclass,nameを設定する
  48. // 本文
  49. function setClassAndNameThread() {
  50. let cnw = document.querySelectorAll(".thre .cnw");
  51. if (!cnw) return;
  52. setClassAndName(cnw);
  53. }
  54. // レス
  55. function setClassAndNameRes(node) {
  56. let resIdNode = arguments.length ? node.querySelectorAll(".rtd .cnw") : document.querySelectorAll(".rtd .cnw");
  57. if (!resIdNode) return;
  58. setClassAndName(resIdNode);
  59. }
  60.  
  61. function setClassAndName(node) {
  62. if(!node.length) return;
  63.  
  64. node.forEach((item) => {
  65. if (item.querySelector(".GM_fip_name")) return;
  66. let matchText = item.textContent.match(/(.+)(I[DP]:\S+)/);
  67. if (!matchText) return;
  68. let dateEle = document.createTextNode(matchText[1]);
  69. let mailEle = item.querySelector("a");
  70. let idText = matchText[2];
  71. let idEle = document.createElement("a");
  72. idEle.textContent = idText;
  73. idEle.classList.add("GM_fip_name");
  74. idEle.setAttribute("name", idText);
  75. idEle.style.color = "#F00";
  76. idEle.addEventListener("mouseover", openPopup, true);
  77. idEle.addEventListener("mouseout", closePopup, true);
  78. item.textContent = "";
  79. if (mailEle) {
  80. item.appendChild(mailEle); //imgメ欄
  81. } else {
  82. item.appendChild(dateEle);
  83. }
  84. item.appendChild(idEle);
  85. })
  86. }
  87. // 出現数の表示
  88. function createCounter() {
  89. if (!USE_COUNTER) return;
  90. let a = document.querySelectorAll(".thre .GM_fip_name");
  91. let ids = {};
  92. for (let i = 0; i < a.length; i++) {
  93. let node = a[i];
  94. let id = node.name;
  95. if (USE_COUNTER_CURRENT) {
  96. if (ids[id]) {
  97. ids[id]++;
  98. } else {
  99. ids[id] = 1;
  100. }
  101. }
  102. let name = document.querySelectorAll(".thre [name='" + id + "']");
  103. let span;
  104. if (node.childNodes[1]) {
  105. span = node.childNodes[1];
  106. } else {
  107. span = document.createElement("span");
  108. span.classList.add("GM_fip_counter");
  109. span.style.margin = "0 5px";
  110. node.appendChild(span);
  111. }
  112. if (USE_COUNTER_CURRENT) {
  113. span.textContent = "[" + ids[id] + "/" + name.length + "]";
  114. } else {
  115. span.textContent = "[" + name.length + "]";
  116. }
  117. }
  118. }
  119. // ポップアップを表示する
  120. function openPopup(event) {
  121. clearTimeout(timer_show);
  122. delPopup();
  123. timer_show = setTimeout(() => {
  124. let name = this.name;
  125. let popup = makePopupContainer();
  126. let divThread = document.createElement("div");
  127. let table = document.createElement("table");
  128. let tbody = document.createElement("tbody");
  129. let tda = document.querySelectorAll(".thre [name='" + name + "']");
  130. for (let i = 0; i < tda.length; i++) {
  131. if (tda[i].parentNode.parentNode.className === "thre") {
  132. // スレ
  133. let form;
  134. if (document.querySelector(".cnw")) {
  135. form = tda[i].parentNode.parentNode.cloneNode(true);
  136. } else {
  137. form = tda[i].parentNode.cloneNode(true);
  138. }
  139. for (let j = 0; j < form.childNodes.length; j++) {
  140. // 広告
  141. if (form.childNodes[j].className !== "tue") {
  142. divThread.appendChild(form.childNodes[j].cloneNode(true));
  143. }
  144. if (form.childNodes[j].tagName == "BLOCKQUOTE") {
  145. break;
  146. }
  147. }
  148. } else {
  149. // レス
  150. let tr;
  151. if (document.querySelector(".cnw")) {
  152. tr = tda[i].parentNode.parentNode.parentNode.cloneNode(true);
  153. } else {
  154. tr = tda[i].parentNode.parentNode.cloneNode(true);
  155. }
  156. setQtJump(tr);
  157. tbody.appendChild(tr);
  158. }
  159. }
  160. table.appendChild(tbody);
  161. popup.appendChild(divThread);
  162. popup.appendChild(table);
  163. if (checkAkahukuEnabled()) {
  164. let bq = popup.querySelectorAll("blockquote");
  165. bq.forEach(q => {
  166. let td = q.parentNode;
  167. let gtdiv = document.createElement("div");
  168. gtdiv.style.maxWidth = "800px";
  169. gtdiv.classList.add("akahuku_popup_content_blockquote");
  170. gtdiv.innerHTML = q.innerHTML;
  171. q.remove();
  172. td.appendChild(gtdiv);
  173. })
  174. }
  175. // this.parentNode.appendChild(popup);
  176. document.querySelector("html body").appendChild(popup);
  177. document.querySelectorAll("#GM_fip_pop .GM_fip_name").forEach((item) => {
  178. item.className = ("GM_fip_name_pop");
  179. })
  180. let wX = this.getBoundingClientRect().left + window.scrollX; //ポップアップ表示位置X
  181. let wY = this.getBoundingClientRect().top + window.scrollY - popup.clientHeight; //ポップアップ表示位置Y
  182. if ( wY < 0 ) { //ポップアップが上に見きれる時は下に表示
  183. wY = this.getBoundingClientRect().bottom + window.scrollY
  184. }
  185. popup.style.top = wY + "px";
  186. popup.style.left = wX + "px";
  187. }, 100);
  188. //ポップアップ内レス番号クリックでジャンプ
  189. function setQtJump(qt) {
  190. let rsc = qt.querySelector(".rsc");
  191. rsc.classList.add("qtjmp");
  192. let jumpid = rsc.id;
  193. rsc.removeAttribute("id");
  194. rsc.addEventListener("click", () => {
  195. let jumptarget = document.getElementById(jumpid).parentNode;
  196. window.scroll(0, jumptarget.getBoundingClientRect().top + window.pageYOffset);
  197. delPopup();
  198. });
  199. }
  200. }
  201.  
  202. function makePopupContainer() {
  203. let container = document.createElement("div");
  204. container.id = "GM_fip_pop";
  205. container.classList.add("GM_fip_pop");
  206. container.style.position = "absolute";
  207. container.style.zIndex = 350;
  208. container.style.backgroundColor = "#eeaa88";
  209. container.style.fontSize = "0.85em";
  210. container.addEventListener("mouseover",() => {
  211. clearTimeout(timer_hide);
  212. }, true);
  213. container.addEventListener("mouseout",closePopup,true);
  214.  
  215. return container;
  216. }
  217. // ポップアップを消す
  218. function closePopup() {
  219. clearTimeout(timer_show);
  220. clearTimeout(timer_hide);
  221. timer_hide = setTimeout(delPopup, 300);
  222. }
  223.  
  224. function delPopup() {
  225. clearTimeout(timer_hide);
  226. let doc_pop = document.getElementsByClassName("GM_fip_pop");
  227. if ( doc_pop ) {
  228. for (let i = 0; i < doc_pop.length; i++) {
  229. doc_pop[i].remove();
  230. }
  231. }
  232. }
  233. // 続きを読むで追加されるレスを監視
  234. function observeInserted() {
  235. let target = document.querySelector(".thre");
  236. let timer_reload;
  237. let observer = new MutationObserver(function(mutations) {
  238. mutations.forEach((mutation) => {
  239. if (!mutation.addedNodes.length) return;
  240. let nodes = mutation.addedNodes[0];
  241. if (nodes.tagName == "TABLE" && nodes.id !== "akahuku_bottom_container") {
  242. setClassAndNameRes(nodes);
  243. clearTimeout(timer_reload);
  244. timer_reload = setTimeout(rel, 50);
  245. }
  246. });
  247. });
  248. observer.observe(target, { childList: true });
  249. function rel() {
  250. if (!isIDIPThread) {
  251. setClassAndNameThread();
  252. setClassAndNameRes();
  253. }
  254. createCounter();
  255. }
  256. }
  257.  
  258. function checkAkahukuEnabled() {
  259. return document.getElementById("akahuku_postform") != null
  260. }
  261.  
  262. })();

QingJ © 2025

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