Advertisement
Guest User

Untitled

a guest
Jun 20th, 2019
8,676
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 15.42 KB | None | 0 0
  1. // ==UserScript==
  2. // @name VoiceLinks 2019 Remaster SOTY Edition
  3. // @namespace Sanya
  4. // @description Makes RJ codes more usefuler.
  5. // @include *://*/*
  6. // @version 1.0.0
  7. // @grant GM.xmlHttpRequest
  8. // @grant GM_xmlhttpRequest
  9. // @run-at document-start
  10. // ==/UserScript==
  11.  
  12. (function () {
  13. 'use strict';
  14. const RJ_REGEX = new RegExp("R[JE][0-9]{6}", "gi");
  15. const VOICELINK_CLASS = 'voicelink';
  16. const RJCODE_ATTRIBUTE = 'rjcode';
  17. const css = `
  18. .voicepopup {
  19. min-width: 600px !important;
  20. z-index: 50000 !important;
  21. max-width: 80% !important;
  22. position: fixed !important;
  23. line-height: 1.4em;
  24. font-size: 1.1em;
  25. margin-bottom: 10px;
  26. box-shadow: 0 0 .125em 0 rgba(0,0,0,.5);
  27. border-radius: 0.5em;
  28. background-color: inherit;
  29. padding: 10px;
  30. }
  31.  
  32. .voicepopup img {
  33. width: 270px;
  34. height: auto;
  35. margin: 3px 15px 3px 3px;
  36. }
  37.  
  38. .voice-title {
  39. font-size: 1.4em;
  40. font-weight: bold;
  41. text-align: center;
  42. margin: 5px 10px 0 0;
  43. display: block;
  44. }
  45.  
  46. .rjcode {
  47. text-align: center;
  48. font-size: 1.2em;
  49. font-style: italic;
  50. opacity: 0.3;
  51. }
  52.  
  53. .error {
  54. height: 210px;
  55. line-height: 210px;
  56. text-align: center;
  57. }
  58.  
  59. .discord-dark {
  60. background-color: #36393f;
  61. color: #dcddde;
  62. font-size: 0.9375rem;
  63. }
  64. `
  65.  
  66. function getAdditionalPopupClasses() {
  67. const hostname = document.location.hostname;
  68. switch (hostname) {
  69. case "boards.4chan.org": return "post reply";
  70. case "discordapp.com": return "discord-dark";
  71. default: return null;
  72. }
  73. }
  74.  
  75. function getXmlHttpRequest() {
  76. return (typeof GM !== "undefined" && GM !== null ? GM.xmlHttpRequest : GM_xmlhttpRequest);
  77. }
  78.  
  79. const Parser = {
  80. walkNodes: function (elem) {
  81. const rjNodeTreeWalker = document.createTreeWalker(
  82. elem,
  83. NodeFilter.SHOW_TEXT,
  84. {
  85. acceptNode: function (node) {
  86. if (node.parentElement.classList.contains(VOICELINK_CLASS))
  87. return NodeFilter.FILTER_ACCEPT;
  88. if (node.nodeValue.match(RJ_REGEX))
  89. return NodeFilter.FILTER_ACCEPT;
  90. }
  91. },
  92. false,
  93. );
  94. while (rjNodeTreeWalker.nextNode()) {
  95. const node = rjNodeTreeWalker.currentNode;
  96. if (node.parentElement.classList.contains(VOICELINK_CLASS))
  97. Parser.rebindEvents(node.parentElement);
  98. else
  99. Parser.linkify(node);
  100. }
  101. },
  102.  
  103. wrapRJCode: function (rjCode) {
  104. var e;
  105. e = document.createElement("a");
  106. e.classList = VOICELINK_CLASS;
  107. e.href = `https://www.dlsite.com/maniax/work/=/product_id/${rjCode}.html`
  108. e.innerHTML = rjCode;
  109. e.target = "_blank";
  110. e.rel = "noreferrer";
  111. e.setAttribute(RJCODE_ATTRIBUTE, rjCode.toUpperCase());
  112. e.addEventListener("mouseover", Popup.over);
  113. e.addEventListener("mouseout", Popup.out);
  114. e.addEventListener("mousemove", Popup.move);
  115. return e;
  116. },
  117.  
  118. linkify: function (textNode) {
  119. const nodeOriginalText = textNode.nodeValue;
  120. const matches = [];
  121.  
  122. let match;
  123. while (match = RJ_REGEX.exec(nodeOriginalText)) {
  124. matches.push({
  125. index: match.index,
  126. value: match[0],
  127. });
  128. }
  129.  
  130. // Keep text in text node until first RJ code
  131. textNode.nodeValue = nodeOriginalText.substring(0, matches[0].index);
  132.  
  133. // Insert rest of text while linkifying RJ codes
  134. let prevNode = null;
  135. for (let i = 0; i < matches.length; ++i) {
  136. // Insert linkified RJ code
  137. const rjLinkNode = Parser.wrapRJCode(matches[i].value);
  138. textNode.parentNode.insertBefore(
  139. rjLinkNode,
  140. prevNode ? prevNode.nextSibling : textNode.nextSibling,
  141. );
  142.  
  143. // Insert text after if there is any
  144. let upper;
  145. if (i === matches.length - 1)
  146. upper = undefined;
  147. else
  148. upper = matches[i + 1].index;
  149. let substring;
  150. if (substring = nodeOriginalText.substring(matches[i].index + 8, upper)) {
  151. const subtextNode = document.createTextNode(substring);
  152. textNode.parentNode.insertBefore(
  153. subtextNode,
  154. rjLinkNode.nextElementSibling,
  155. );
  156. prevNode = subtextNode;
  157. }
  158. else {
  159. prevNode = rjLinkNode;
  160. }
  161. }
  162. },
  163.  
  164. rebindEvents: function (elem) {
  165. if (elem.nodeName === "A") {
  166. elem.addEventListener("mouseover", Popup.over);
  167. elem.addEventListener("mouseout", Popup.out);
  168. elem.addEventListener("mousemove", Popup.move);
  169. }
  170. else {
  171. const voicelinks = elem.querySelectorAll("." + VOICELINK_CLASS);
  172. for (var i = 0, ii = voicelinks.length; i < ii; i++) {
  173. const voicelink = voicelinks[i];
  174. voicelink.addEventListener("mouseover", Popup.over);
  175. voicelink.addEventListener("mouseout", Popup.out);
  176. voicelink.addEventListener("mousemove", Popup.move);
  177. }
  178. }
  179. },
  180.  
  181. }
  182.  
  183. const Popup = {
  184. makePopup: function (e, rjCode) {
  185. const popup = document.createElement("div");
  186. popup.className = "voicepopup " + (getAdditionalPopupClasses() || '');
  187. popup.id = "voice-" + rjCode;
  188. popup.style = "display: flex";
  189. document.body.appendChild(popup);
  190. DLsite.request(rjCode, function (workInfo) {
  191. if (workInfo === null)
  192. popup.innerHTML = "<div class='error'>Work not found.</span>";
  193. else {
  194. const imgContainer = document.createElement("div")
  195. const img = document.createElement("img");
  196. img.src = workInfo.img;
  197. imgContainer.appendChild(img);
  198.  
  199. let html = `
  200. <div>
  201. <div class='voice-title'>${workInfo.title}</div>
  202. <div class='rjcode'>[${workInfo.rj}]</div>
  203. <br />
  204. Circle: <a>${workInfo.circle}</a>
  205. <br />
  206. `;
  207. if (workInfo.date)
  208. html += `Release: <a>${workInfo.date}</a> <br />`;
  209. else if (workInfo.dateAnnounce)
  210. html += `Scheduled Release: <a>${workInfo.dateAnnounce}</a> <br />`;
  211.  
  212. html += `Age rating: <a>${workInfo.rating}</a><br />`
  213.  
  214. if (workInfo.cv)
  215. html += `CV: <a>${workInfo.cv}</a> <br />`;
  216.  
  217. html += `Tags: <a>`
  218. workInfo.tags.forEach(tag => {
  219. html += tag + "\u3000";
  220. });
  221. html += "</a><br />";
  222.  
  223. if (workInfo.filesize)
  224. html += `File size: ${workInfo.filesize}<br />`;
  225.  
  226. html += "</div>"
  227. popup.innerHTML = html;
  228.  
  229. popup.insertBefore(imgContainer, popup.childNodes[0]);
  230. }
  231.  
  232. Popup.move(e);
  233. });
  234. },
  235.  
  236. over: function (e) {
  237. const rjCode = e.target.getAttribute(RJCODE_ATTRIBUTE);
  238. const popup = document.querySelector("div#voice-" + rjCode);
  239. if (popup) {
  240. const style = popup.getAttribute("style").replace("none", "flex");
  241. popup.setAttribute("style", style);
  242. }
  243. else {
  244. Popup.makePopup(e, rjCode);
  245. }
  246. },
  247.  
  248. out: function (e) {
  249. const rjCode = e.target.getAttribute("rjcode");
  250. const popup = document.querySelector("div#voice-" + rjCode);
  251. if (popup) {
  252.  
  253. const style = popup.getAttribute("style").replace("flex", "none");;
  254. popup.setAttribute("style", style);
  255. }
  256. },
  257.  
  258. move: function (e) {
  259. const rjCode = e.target.getAttribute("rjcode");
  260. const popup = document.querySelector("div#voice-" + rjCode);
  261. if (popup) {
  262. if (popup.offsetWidth + e.clientX + 10 < window.innerWidth - 10) {
  263. popup.style.left = (e.clientX + 10) + "px";
  264. }
  265. else {
  266. popup.style.left = (window.innerWidth - popup.offsetWidth - 10) + "px";
  267. }
  268.  
  269. if (popup.offsetHeight + e.clientY + 50 > window.innerHeight) {
  270. popup.style.top = (e.clientY - popup.offsetHeight - 8) + "px";
  271. }
  272. else {
  273. popup.style.top = (e.clientY + 20) + "px";
  274. }
  275. }
  276. },
  277. }
  278.  
  279. const DLsite = {
  280. parseWorkDOM: function (dom, rj) {
  281. // workInfo: {
  282. // rj: any;
  283. // img: string;
  284. // title: any;
  285. // circle: any;
  286. // date: any;
  287. // rating: any;
  288. // tags: any[];
  289. // cv: any;
  290. // filesize: any;
  291. // dateAnnounce: any;
  292. // }
  293. const workInfo = {};
  294. workInfo.rj = rj;
  295.  
  296. let rj_group;
  297. if (rj.slice(5) == "000")
  298. rj_group = rj;
  299. else {
  300. rj_group = (parseInt(rj.slice(2, 5)) + 1).toString() + "000";
  301. rj_group = "RJ" + ("000000" + rj_group).substring(rj_group.length);
  302. }
  303.  
  304. workInfo.img = "https://img.dlsite.jp/modpub/images2/work/doujin/" + rj_group + "/" + rj + "_img_main.jpg";
  305. workInfo.title = dom.getElementById("work_name").innerText;
  306. workInfo.circle = dom.querySelector("span.maker_name").innerText;
  307.  
  308. const table_outline = dom.querySelector("table#work_outline");
  309. for (var i = 0, ii = table_outline.rows.length; i < ii; i++) {
  310. const row = table_outline.rows[i];
  311. const row_header = row.cells[0].innerText;
  312. const row_data = row.cells[1];
  313. switch (true) {
  314. case (row_header.includes("販売日")):
  315. workInfo.date = row_data.innerText;
  316. break;
  317. case (row_header.includes("年齢指定")):
  318. workInfo.rating = row_data.innerText;
  319. break;
  320. case (row_header.includes("ジャンル")):
  321. const tag_nodes = row_data.querySelectorAll("a");
  322. workInfo.tags = [...tag_nodes].map(a => { return a.innerText });
  323. break;
  324. case (row_header.includes("声優")):
  325. workInfo.cv = row_data.innerText;
  326. break;
  327. case (row_header.includes("ファイル容量")):
  328. workInfo.filesize = row_data.innerText.replace("総計", "").trim();
  329. break;
  330. default:
  331. break;
  332. }
  333. }
  334.  
  335. const work_date_ana = dom.querySelector("strong.work_date_ana");
  336. if (work_date_ana) {
  337. workInfo.dateAnnounce = work_date_ana.innerText;
  338. workInfo.img = "https://img.dlsite.jp/modpub/images2/ana/doujin/" + rj_group + "/" + rj + "_ana_img_main.jpg"
  339. }
  340.  
  341. return workInfo;
  342. },
  343.  
  344. request: function (rjCode, callback) {
  345. const url = `https://www.dlsite.com/maniax/work/=/product_id/${rjCode}.html`;
  346. getXmlHttpRequest()({
  347. method: "GET",
  348. url,
  349. headers: {
  350. "Accept": "text/xml",
  351. "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:67.0)"
  352. },
  353. onload: function (resp) {
  354. if (resp.readyState === 4 && resp.status === 200) {
  355. const dom = new DOMParser().parseFromString(resp.responseText, "text/html");
  356. const workInfo = DLsite.parseWorkDOM(dom, rjCode);
  357. callback(workInfo);
  358. }
  359. else if (resp.readyState === 4 && resp.status === 404)
  360. DLsite.requestAnnounce(rjCode, callback);
  361. },
  362. });
  363. },
  364.  
  365. requestAnnounce: function (rjCode, callback) {
  366. const url = `https://www.dlsite.com/maniax/announce/=/product_id/${rjCode}.html`;
  367. getXmlHttpRequest()({
  368. method: "GET",
  369. url,
  370. headers: {
  371. "Accept": "text/xml",
  372. "User-Agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:67.0)"
  373. },
  374. onload: function (resp) {
  375. if (resp.readyState === 4 && resp.status === 200) {
  376. const dom = new DOMParser().parseFromString(resp.responseText, "text/html");
  377. const workInfo = DLsite.parseWorkDOM(dom, rjCode);
  378. callback(workInfo);
  379. }
  380. else if (resp.readyState === 4 && resp.status === 404)
  381. callback(null);
  382. },
  383. });
  384. },
  385. }
  386.  
  387.  
  388. document.addEventListener("DOMContentLoaded", function () {
  389. const style = document.createElement("style");
  390. style.innerHTML = css;
  391. document.head.appendChild(style);
  392.  
  393. Parser.walkNodes(document.body);
  394.  
  395. const observer = new MutationObserver(function (m) {
  396. for (let i = 0; i < m.length; ++i) {
  397. let addedNodes = m[i].addedNodes;
  398.  
  399. for (let j = 0; j < addedNodes.length; ++j) {
  400. Parser.walkNodes(addedNodes[j]);
  401. }
  402. }
  403. });
  404.  
  405. document.addEventListener("securitypolicyviolation", function (e) {
  406. if (e.blockedURI.includes("img.dlsite.jp")) {
  407. const img = document.querySelector(`img[src="${e.blockedURI}"]`);
  408. img.remove();
  409. }
  410. });
  411.  
  412. observer.observe(document.body, { childList: true, subtree: true })
  413. });
  414. })();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement