E-H Visited

Upgrade to EhxVisited (sleazyfork.org/en/scripts/377945)

  1. // ==UserScript==
  2. // @name E-H Visited
  3. // @description Upgrade to EhxVisited (sleazyfork.org/en/scripts/377945)
  4. // @author Hen-Tie
  5. // @homepage https://hen-tie.tumblr.com/
  6. // @namespace https://greasyfork.org/en/users/8336
  7. // @include /https?:\/\/(e-|ex)hentai\.org\/.*/
  8. // @exclude https://e-hentai.org/toplist.php?tl=*
  9. // @require https://code.jquery.com/jquery-3.3.1.min.js
  10. // @require https://greasyfork.org/scripts/381436-gmshim/code/GMshim.js
  11. // @grant GM_setValue
  12. // @grant GM_getValue
  13. // @icon https://i.imgur.com/pMMVGRx.png
  14. // @version 3.17
  15. // ==/UserScript==
  16. // 2019 fork of exvisited (sleazyfork.org/en/scripts/22270)
  17. // 2020 please upgrade to EhxVisited (sleazyfork.org/en/scripts/377945)
  18. // will still fix bug reports: greasyfork.org/en/forum/post/discussion?script=377945
  19.  
  20. /*════════════════════╗
  21. ║ configuration ║
  22. ╚════════════════════*/
  23. // true: (default) adds column with eye icon in minimal/minimal+ views
  24. // false: no added column or icons, hover on gallery title for timestamp
  25. var minimalAddColumn = true;
  26.  
  27. // true: (minimalAddColumn must also be true) shows full timestamp text
  28. // false: (default) shows eye icon, hover for timestamp
  29. var minimalShowText = true;
  30.  
  31. // true: subtle reminder to backup data after every N visits
  32. // false: (default) no reminders
  33. var exportReminder = 100;
  34. /*═══════════════════*/
  35.  
  36. var storageName = "ehVisited";
  37. var sto = localStorage.getItem(storageName) ? localStorage.getItem(storageName) : '{"data":{}}';
  38. var vis = JSON.parse(sto);
  39. var spl = document.URL.split("/");
  40. var d1 = spl[3];
  41. var d2 = spl[4];
  42. var d3 = spl[5];
  43. var css = GM_getValue("css") ? GM_getValue("css") : "box-shadow: inset 0 0 0 500px rgba(2, 129, 255, .2) !important;"; //default highlight colour
  44. var postInfiniteScroll = 0;
  45. var observer = new MutationObserver(function () {
  46. // track galleries opened on-site
  47. $('a').on('mouseup', function () {
  48. var spl = this.href.split("/");
  49. var d1 = spl[3];
  50. var d2 = spl[4];
  51. var d3 = spl[5];
  52.  
  53. if (d1 == "g") {
  54. var c = d2 + "." + d3;
  55. vis = JSON.parse(localStorage.getItem(storageName));
  56. vis.data[c] = Date.now();
  57. localStorage.setItem(storageName, JSON.stringify(vis));
  58. }
  59. });
  60. postInfiniteScroll = 1;
  61. ehvTimestamp();
  62. });
  63.  
  64. vis.data = !vis.data ? Array() : vis.data; //necessary?
  65.  
  66. if (localStorage.getItem(storageName) === null) {
  67. localStorage.setItem(storageName, JSON.stringify(vis));
  68. console.log('Initializing localStorage item.');
  69. }
  70.  
  71.  
  72. // convert keywords to CSS
  73. if (css === "initial") {
  74. css = "box-shadow: inset 0 0 0 500px rgba(2, 129, 255, .2) !important;";
  75. } else if (css === "none") {
  76. css = "";
  77. }
  78.  
  79. // localstorage unsupported warning
  80. if (typeof (Storage) == "undefined") {
  81. alert("E-H Visited:\nYour browser does not support localStorage :(");
  82. }
  83.  
  84. // get time difference in words
  85. function timeDifference(current, previous, abbreviate) {
  86. var msPerMinute = 60 * 1000;
  87. var msPerHour = msPerMinute * 60;
  88. var msPerDay = msPerHour * 24;
  89. var msPerMonth = msPerDay * 30;
  90. var msPerYear = msPerDay * 365;
  91. var elapsed = current - previous;
  92.  
  93. if (elapsed < msPerMinute) {
  94. return Math.round(elapsed / 1000) + ((typeof abbreviate !== 'undefined') ? '&nbsp;sec' : ' seconds ago');
  95. } else if (elapsed < msPerHour) {
  96. return Math.round(elapsed / msPerMinute) + ((typeof abbreviate !== 'undefined') ? '&nbsp;min' : ' minutes ago');
  97. } else if (elapsed < msPerDay) {
  98. return Math.round(elapsed / msPerHour) + ((typeof abbreviate !== 'undefined') ? '&nbsp;hrs' : ' hours ago');
  99. } else if (elapsed < msPerMonth) {
  100. return Math.round(elapsed / msPerDay) + ((typeof abbreviate !== 'undefined') ? '&nbsp;days' : ' days ago');
  101. } else if (elapsed < msPerYear) {
  102. return Math.round(elapsed / msPerMonth) + ((typeof abbreviate !== 'undefined') ? '&nbsp;mos' : ' months ago');
  103. } else {
  104. return Math.round(elapsed / msPerYear) + ((typeof abbreviate !== 'undefined') ? '&nbsp;yrs' : ' years ago');
  105. }
  106. }
  107.  
  108. function ehvExport(message) {
  109. var data = "";
  110. for (var d in vis.data) {
  111. if (vis.data.hasOwnProperty(d)) {
  112. data += d + ":" + vis.data[d] + ";";
  113. }
  114. }
  115. if ($('.ehv-exported-data').length) {
  116. $('.ehv-exported-data').remove();
  117. }
  118. GM_setValue("archive", data);
  119. console.log("E-H Visited data:");
  120. console.log(GM_getValue("archive"));
  121. $('.ehv-controls').append('<ehv class="ehv-exported-data"><strong>' + message + '</strong><textarea class="ehv-exported-data-text">' + data + '</textarea><a class="ehv-exported-data-button cs" href="data:text,'+data+'" download="E-H Visited Data">Download Text File</a></ehv>');
  122. }
  123.  
  124. function ehvTimestamp() {
  125. observer.disconnect();
  126. var list = $("table.itg>tbody>tr").has('.glhide, .gldown, th'); //present only in list views
  127. var thumb = $(".itg .gl1t"); //present only in thumbnail view
  128. var gid;
  129. var d;
  130. var galleryId;
  131. var onFavs = 0;
  132.  
  133. // check current view
  134. if (list.length > 0) {
  135. if ($('.gl1e').length) { //extended
  136. if ($('h1').text() === "Favorites") {
  137. onFavs = 1;
  138. }
  139. for (var i = 0; i < list.length; i++) {
  140. gid = $(list[i]).find(".gl1e a").attr("href").split("/");
  141. galleryId = gid[4] + "." + gid[5];
  142. if ($(list[i])[0].children.length === 2 && onFavs) {
  143. $(list[i]).append('<td></td>');
  144. }
  145. if (vis.data[galleryId] != undefined) {
  146. d = new Date(vis.data[galleryId]);
  147. if (!$(list[i]).hasClass('ehv-visited')) {
  148. $(list[i]).addClass("ehv-visited");
  149. //check for fav pages
  150. if ($(list[i]).find('.gl3e').children('div').length >= 7) { //date favourited div is present
  151. $(list[i]).find('.gl3e > div:last-child').append("<br><ehv class='ehv-extended-favs'>\uD83D\uDC41" + timeDifference(Date.now(), vis.data[galleryId]) + "<br>" + d.getFullYear().toString() + "\u2011" + (d.getMonth() + 1) + "\u2011" + d.getDate() + " (" + d.getHours().toString().padStart(2, '0') + ":" + d.getMinutes().toString().padStart(2, '0') + ")</ehv>");
  152. } else {
  153. $(list[i]).find('.gl3e').append("<ehv class='ehv-extended'>\uD83D\uDC41" + timeDifference(Date.now(), vis.data[galleryId]) + "<br>" + d.getFullYear().toString() + "\u2011" + (d.getMonth() + 1) + "\u2011" + d.getDate() + " (" + d.getHours().toString().padStart(2, '0') + ":" + d.getMinutes().toString().padStart(2, '0') + ")</ehv>");
  154. }
  155. }
  156. }
  157. }
  158. } else if ($('.gl1c').length) { //compact
  159. var borderColour = $('.gl1c').first().css('border-top-color'); //border colour different between domains
  160. if (!postInfiniteScroll) {
  161. $('table.itg tbody>tr:first-child th:nth-child(2)').after('<th>Visited</th>');
  162. }
  163. if ($('h1').text() === "Favorites") {
  164. onFavs = 1;
  165. }
  166. for (i = 1; i < list.length; i++) {
  167. gid = $(list[i]).find(".glname a").attr("href").split("/");
  168. galleryId = gid[4] + "." + gid[5];
  169. if ($(list[i])[0].children.length === 4 || $(list[i])[0].children.length === 5 && onFavs) {
  170. if ($(list[i])[0].children.length === 4 && onFavs) {
  171. $(list[i]).append('<td></td>');
  172. }
  173. if (vis.data[galleryId] != undefined) {
  174. d = new Date(vis.data[galleryId]);
  175. $(list[i]).addClass("ehv-visited");
  176. $(list[i]).children('.gl2c').after('<td class="ehv-compact" style="border-color:' + borderColour + ';"><ehv>' + timeDifference(Date.now(), vis.data[galleryId], true) + "<br>(" + d.getHours().toString().padStart(2, '0') + ":" + d.getMinutes().toString().padStart(2, '0') + ')<br>' + d.getFullYear().toString().substr(2) + "\u2011" + (d.getMonth() + 1) + "\u2011" + d.getDate() + '</ehv></td>');
  177. } else {
  178. $(list[i]).children('.gl2c').after('<td class="ehv-compact" style="border-color:' + borderColour + ';"></td>');
  179. }
  180. }
  181. }
  182. } else { //minimal
  183. if (minimalAddColumn && !postInfiniteScroll) {
  184. $('table.itg tbody>tr:first-child th:nth-child(2)').after('<th title="E-H Visited: Hover for timestamps">\uD83D\uDC41</th>');
  185. }
  186. if ($('h1').text() === "Favorites") {
  187. onFavs = 1;
  188. }
  189. for (i = 1; i < list.length; i++) {
  190. gid = $(list[i]).find(".glname a").attr("href").split("/");
  191. galleryId = gid[4] + "." + gid[5];
  192. if ($(list[i])[0].children.length === 6 || $(list[i])[0].children.length === 7 && onFavs) {
  193. if ($(list[i])[0].children.length === 6 && onFavs) {
  194. $(list[i]).append('<td></td>');
  195. }
  196. if (minimalAddColumn) { //append viewed column
  197. if (vis.data[galleryId] != undefined) {
  198. d = new Date(vis.data[galleryId]);
  199. $(list[i]).addClass("ehv-visited");
  200. $(list[i]).children('.glname')[0].setAttribute("title", 'E-H Visited: ' + timeDifference(Date.now(), vis.data[galleryId]) + " (" + d.getHours().toString().padStart(2, '0') + ":" + d.getMinutes().toString().padStart(2, '0') + ") " + d.getFullYear().toString() + "\u2011" + (d.getMonth() + 1) + "\u2011" + d.getDate());
  201. if (minimalShowText) { //show text in appended column
  202. $(list[i]).children('.gl2m').after('<td class="ehv-minimal-text"><ehv>' + timeDifference(Date.now(), vis.data[galleryId], true) + "<br>(" + d.getHours().toString().padStart(2, '0') + ":" + d.getMinutes().toString().padStart(2, '0') + ')<br>' + d.getFullYear().toString().substr(2) + "\u2011" + (d.getMonth() + 1) + "\u2011" + d.getDate() + '</ehv></td>');
  203. } else { //show icon in appended column
  204. $(list[i]).children('.gl2m').after('<td class="ehv-minimal" title="E-H Visited: ' + timeDifference(Date.now(), vis.data[galleryId]) + " (" + d.getHours().toString().padStart(2, '0') + ":" + d.getMinutes().toString().padStart(2, '0') + ") " + d.getFullYear().toString() + "\u2011" + (d.getMonth() + 1) + "\u2011" + d.getDate() + '"><ehv>\uD83D\uDC41</ehv></td>');
  205. }
  206. } else { //not viewed
  207. $(list[i]).children('.gl2m').after('<td class="ehv-minimal"></td>');
  208. }
  209. } else { //append nothing, highlight only
  210. if (vis.data[galleryId] != undefined) {
  211. d = new Date(vis.data[galleryId]);
  212. $(list[i]).addClass("ehv-visited");
  213. $(list[i]).children('.glname')[0].setAttribute("title", 'E-H Visited: ' + timeDifference(Date.now(), vis.data[galleryId]) + " (" + d.getHours().toString().padStart(2, '0') + ":" + d.getMinutes().toString().padStart(2, '0') + ") " + d.getFullYear().toString() + "\u2011" + (d.getMonth() + 1) + "\u2011" + d.getDate());
  214. }
  215. }
  216. }
  217. }
  218. }
  219. } else if (thumb.length > 0) { //thumbnail
  220. for (i = 0; i < thumb.length; i++) {
  221. gid = $(thumb[i]).find(".gl3t a").attr("href").split("/");
  222. galleryId = gid[4] + "." + gid[5];
  223. if (!$(thumb[i]).hasClass('ehv-visited')) {
  224. if (vis.data[galleryId] != undefined) {
  225. d = new Date(vis.data[galleryId]);
  226. $(thumb[i]).addClass("ehv-visited");
  227. $(thumb[i]).children('.gl5t').after("<ehv class='ehv-thumbnail'>\uD83D\uDC41" + timeDifference(Date.now(), vis.data[galleryId]) + " (" + d.getHours().toString().padStart(2, '0') + ":" + d.getMinutes().toString().padStart(2, '0') + ") " + d.getFullYear().toString() + "\u2011" + (d.getMonth() + 1) + "\u2011" + d.getDate() + "</ehv>");
  228. }
  229. }
  230. }
  231. } else {
  232. console.log("E-H Visited:\n Something is wrong, I don't know what view mode this is!\n Bug reports: greasyfork.org/en/forum/post/discussion?script=377945");
  233. }
  234. observer.observe($('.itg').get(0), {
  235. childList: true,
  236. subtree: true
  237. });
  238. }
  239.  
  240. $(function () {
  241. var d = JSON.parse('{"data":{}}');
  242.  
  243. // track galleries opened on-site
  244. $('a').on('mouseup', function () {
  245. var spl = this.href.split("/");
  246. var d1 = spl[3];
  247. var d2 = spl[4];
  248. var d3 = spl[5];
  249.  
  250. if (d1 == "g") {
  251. var c = d2 + "." + d3;
  252. vis = JSON.parse(localStorage.getItem(storageName));
  253. vis.data[c] = Date.now();
  254. localStorage.setItem(storageName, JSON.stringify(vis));
  255. }
  256. });
  257.  
  258. // track galleries opened indirectly (offsite link, shortcut file, context menu, bookmark, etc.)
  259. $(window).one('click scroll', function () {
  260. if (d1 == "g") {
  261. var c = d2 + "." + d3;
  262. vis = JSON.parse(localStorage.getItem(storageName));
  263. vis.data[c] = Date.now();
  264. localStorage.setItem(storageName, JSON.stringify(vis));
  265. }
  266. });
  267. if (/[?#ft]/.test(d1.substr(0, 1)) || /^watched/.test(d1) || /^uploader/.test(d1) || !d1) {
  268. var visitCount = Object.keys(vis.data).length;
  269. var ehvClearConfirm = 0;
  270. var controlsHTML = "<ehv class='ehv-controls'>Galleries visited: " + visitCount + " (<a href='javascript:;' class='ehv-import'>Import</a> / <a href='javascript:;' class='ehv-export'>Export</a> / <a href='javascript:;' class='ehv-merge'>Merge</a> / <a href='javascript:;' class='ehv-clear'>Clear</a> / <a href='javascript:;' class='ehv-css'>CSS</a>)</ehv>";
  271. if ($('body > img[src="https://exhentai.org/img/kokomade.jpg"]').length > 0) {
  272. $('body').prepend(controlsHTML);
  273. } else {
  274. $('#toppane').append(controlsHTML);
  275. }
  276.  
  277. // show export alert during first 5 of every 100 gallery visits
  278. if (visitCount % 100 >= 0 && visitCount % 100 <= 5) {
  279. $('.ehv-export').css({'background':'rgba(2, 129, 255, .3)','box-shadow':'0 0 0 2px rgba(2, 129, 255, .3)'});
  280. }
  281.  
  282. $(".ehv-import").click(function () {
  283. var c = prompt("E-H Visited:\nPaste here to import, and overwrite current data.");
  284. if (c) {
  285. var sp = c.split(";");
  286. //sp = sp.filter(Boolean);
  287. for (var k in sp) {
  288. if (sp.hasOwnProperty(k)) {
  289. var s = sp[k].split(":");
  290. d.data[s[0]] = parseInt(s[1]);
  291. }
  292. }
  293. localStorage.setItem(storageName, JSON.stringify(d));
  294. console.log(d);
  295. alert("E-H Visited:\nImported " + Object.keys(d.data).length + " entries.");
  296. location.reload();
  297. }
  298. });
  299.  
  300. $(".ehv-export").click(function () {
  301. ehvExport('Exported entries:');
  302. });
  303.  
  304. $(".ehv-css").click(function () {
  305. var c = prompt("E-H Visited:\nThis CSS is applied to visited galleries.\n('initial' to reset, or 'none' for no styling)", css);
  306. if (c) {
  307. GM_setValue("css", c);
  308. location.reload();
  309. }
  310. });
  311.  
  312. $(".ehv-merge").click(function () {
  313. var c = prompt("E-H Visited:\nPaste here to import, and merge with current data.");
  314. if (c) {
  315. var sp = c.split(";");
  316. sp = sp.filter(Boolean);
  317. for (var k in sp) {
  318. if (sp.hasOwnProperty(k)) {
  319. var s = sp[k].split(":");
  320. d.data[s[0]] = parseInt(s[1]);
  321. }
  322. }
  323. for (var i = 0; i < Object.keys(vis.data).length; i++) {
  324. d.data[Object.keys(vis.data)[i]] = vis.data[Object.keys(vis.data)[i]];
  325. }
  326. alert("E-H Visited\nMerged data, " + Object.keys(d.data).length + " unique entries.");
  327. localStorage.setItem(storageName, JSON.stringify(d));
  328. location.reload();
  329. }
  330. });
  331.  
  332. $(".ehv-clear").click(function () {
  333. if (!ehvClearConfirm) {
  334. ehvClearConfirm = 1;
  335. $('.ehv-clear').append(': Are you sure?');
  336. ehvExport('Backup your current data:');
  337. } else {
  338. alert("E-H Visited:\nCleared all entries.");
  339. localStorage.removeItem(storageName);
  340. location.reload();
  341. }
  342. });
  343.  
  344. // append icon friendly fonts to the calculated font stack
  345. var inheritFonts = $('body').css('font-family') + ', "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", symbola';
  346. $(`<style data-jqstyle='ehVisited'>
  347. ehv { font-family:` + inheritFonts + ` }
  348. .gl2c { width: 115px; }
  349. .ehv-visited .gl3e { min-height: 206px; }
  350. .ehv-visited .gl4e { min-height: 264px !important; }
  351. .ehv-exported-data { display: block; }
  352. .ehv-exported-data-text { display: block; margin: 0 auto; height: 5em; width: 50vw; padding: .25em; }
  353. .ehv-exported-data-button { padding: 0 1em; width:unset; text-decoration:none; background:#777777; margin-top:.5em; }
  354. .ehv-minimal-text { text-align: center; display: block; }
  355. .ehv-compact { border-style: solid; border-width: 1px 0; text-align: center; }
  356. .ehv-extended { width: 120px; position: absolute; left: 3px; top: 172px; text-align: center; font-size: 8pt; line-height: 1.5; }
  357. .ehv-extended-favs { padding: 3px 1px; display: block; line-height: 1.5; }
  358. .ehv-thumbnail { display: block; text-align: center; margin: 3px 0 5px; line-height: 12px; }
  359. .ehv-controls { padding: 3px 1px; text-align: center; display: block; }
  360. table.itg > tbody > tr.ehv-visited, .gl1t.ehv-visited { ` + css + ` }
  361. </style>`).appendTo("head");
  362.  
  363. ehvTimestamp();
  364. }
  365. });