E-H Visited

Marks visited galleries.

اعتبارا من 09-04-2019. شاهد أحدث إصدار.

  1. // ==UserScript==
  2. // @name E-H Visited
  3. // @description Marks visited galleries.
  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. // @require https://code.jquery.com/jquery-3.3.1.min.js
  9. // @require https://greasyfork.org/scripts/381436-gmshim/code/GMshim.js
  10. // @grant GM_setValue
  11. // @grant GM_getValue
  12. // @icon https://i.imgur.com/pMMVGRx.png
  13. // @version 3.7
  14. // ==/UserScript==
  15. // 2019 fork of exvisited (sleazyfork.org/en/scripts/22270)
  16. // bug reports: greasyfork.org/en/forum/post/discussion?script=377945
  17.  
  18. /*════════════════════╗
  19. ║ configuration ║
  20. ╚════════════════════*/
  21. // true: adds column with eye icon to viewed galleries
  22. // false: truly minimal, hover on gallery title for timestamp
  23. var minimalAddColumn = true;
  24. // true: (minimalAddColumn must also be true) replace eye icon with timestamp text
  25. var minimalShowText = false;
  26. /*═══════════════════*/
  27.  
  28. var storageName = "ehVisited";
  29. var sto = localStorage.getItem(storageName) ? localStorage.getItem(storageName) : '{"data":{}}';
  30. var vis = JSON.parse(sto);
  31. var spl = document.URL.split("/");
  32. var d1 = spl[3];
  33. var d2 = spl[4];
  34. var d3 = spl[5];
  35. var css = GM_getValue("css") ? GM_getValue("css") : "box-shadow: inset 0 0 0 500px rgba(2, 129, 255, .2) !important;"; //default highlight colour
  36. var postInfiniteScroll = 0;
  37. var headerGalLen = 1; // yes i=1, this skips the table header
  38. var galLen = 0;
  39. var observer = new MutationObserver(function () {
  40. // track galleries opened on-site
  41. $('a').on('mouseup', function () {
  42. var spl = this.href.split("/");
  43. var d1 = spl[3]
  44. var d2 = spl[4];
  45. var d3 = spl[5];
  46.  
  47. if (d1 == "g") {
  48. var c = d2 + "." + d3;
  49. vis.data[c] = Date.now();
  50. localStorage.setItem(storageName, JSON.stringify(vis));
  51. }
  52. });
  53. postInfiniteScroll = 1;
  54. headerGalLen = galLen = $("table.itg>tbody>tr, .itg .gl1t").length - 50;
  55. ehvTimestamp();
  56. });
  57.  
  58. vis.data = !vis.data ? Array() : vis.data;
  59.  
  60. // convert keywords to CSS
  61. if (css === "initial") {
  62. css = "box-shadow: inset 0 0 0 500px rgba(2, 129, 255, .2) !important;";
  63. } else if (css === "none") {
  64. css = "";
  65. }
  66.  
  67. // localstorage unsupported warning
  68. if (typeof (Storage) == "undefined") {
  69. alert("E-H Visited:\nYour browser does not support localStorage :(");
  70. }
  71.  
  72. // get time difference in words
  73. function timeDifference(current, previous, abbreviate) {
  74. var msPerMinute = 60 * 1000;
  75. var msPerHour = msPerMinute * 60;
  76. var msPerDay = msPerHour * 24;
  77. var msPerMonth = msPerDay * 30;
  78. var msPerYear = msPerDay * 365;
  79. var elapsed = current - previous;
  80.  
  81. if (elapsed < msPerMinute) {
  82. return Math.round(elapsed / 1000) + ((typeof abbreviate !== 'undefined') ? '&nbsp;sec' : ' seconds ago');
  83. } else if (elapsed < msPerHour) {
  84. return Math.round(elapsed / msPerMinute) + ((typeof abbreviate !== 'undefined') ? '&nbsp;min' : ' minutes ago');
  85. } else if (elapsed < msPerDay) {
  86. return Math.round(elapsed / msPerHour) + ((typeof abbreviate !== 'undefined') ? '&nbsp;hrs' : ' hours ago');
  87. } else if (elapsed < msPerMonth) {
  88. return Math.round(elapsed / msPerDay) + ((typeof abbreviate !== 'undefined') ? '&nbsp;days' : ' days ago');
  89. } else if (elapsed < msPerYear) {
  90. return Math.round(elapsed / msPerMonth) + ((typeof abbreviate !== 'undefined') ? '&nbsp;mos' : ' months ago');
  91. } else {
  92. return Math.round(elapsed / msPerYear) + ((typeof abbreviate !== 'undefined') ? '&nbsp;yrs' : ' years ago');
  93. }
  94. }
  95.  
  96. function ehvExport(message) {
  97. var data = "";
  98. for (var d in vis.data) {
  99. data += d + ":" + vis.data[d] + ";";
  100. }
  101. if ($('.ehv-exported-data').length) {
  102. $('.ehv-exported-data').remove();
  103. }
  104. $('.ehv-controls').append('<ehv class="ehv-exported-data"><strong>' + message + '</strong><textarea class="ehv-exported-data-text">' + data + '</textarea></ehv>');
  105. }
  106.  
  107. function ehvTimestamp() {
  108. observer.disconnect();
  109. var list = $("table.itg>tbody>tr").has('.glhide, .gldown'); //present only in list views
  110. var thumb = $(".itg .gl1t"); //present only in thumbnail view
  111. var gid;
  112. var d;
  113. var galleryId;
  114.  
  115. //if viewing favs
  116. if ($('table.itg > tbody > tr:first-child th:contains("Favorited")').length) {
  117. headerGalLen = galLen;
  118. }
  119.  
  120. // check current view
  121. if (list.length > 0) {
  122. if ($('.gl1e').length) { //extended
  123. for (i = galLen; i < list.length; i++) {
  124. gid = $(list[i]).find(".gl1e a").attr("href").split("/");
  125. galleryId = gid[4] + "." + gid[5];
  126. if (vis.data[galleryId] != undefined) {
  127. d = new Date(vis.data[galleryId]);
  128. $(list[i]).addClass("ehv-visited");
  129. //check for fav pages
  130. if ($(list[i]).find('.gl3e').children('div').length >= 7) { //date favourited div is present
  131. $(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>");
  132. } else {
  133. $(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>");
  134. }
  135. }
  136. }
  137. } else if ($('.gl1c').length) { //compact
  138. var borderColour = $('.gl1c').first().css('border-top-color'); //border colour different between domains
  139. if (!postInfiniteScroll) {
  140. $('table.itg tbody>tr:first-child th:nth-child(2)').after('<th>Visited</th>');
  141. }
  142. for (i = headerGalLen; i < list.length; i++) {
  143. gid = $(list[i]).find(".glname a").attr("href").split("/");
  144. galleryId = gid[4] + "." + gid[5];
  145. if (vis.data[galleryId] != undefined) {
  146. d = new Date(vis.data[galleryId]);
  147. $(list[i]).addClass("ehv-visited");
  148. $(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>');
  149. } else {
  150. $(list[i]).children('.gl2c').after('<td class="ehv-compact" style="border-color:' + borderColour + ';">&nbsp;</td>');
  151. }
  152. }
  153. } else { //minimal
  154. if (minimalAddColumn && !postInfiniteScroll) {
  155. $('table.itg tbody>tr:first-child th:nth-child(2)').after('<th title="E-H Visited: Hover for timestamps">\uD83D\uDC41</th>');
  156. }
  157. for (i = headerGalLen; i < list.length; i++) {
  158. gid = $(list[i]).find(".glname a").attr("href").split("/");
  159. galleryId = gid[4] + "." + gid[5];
  160. if (minimalAddColumn) {
  161. if (vis.data[galleryId] != undefined) {
  162. d = new Date(vis.data[galleryId]);
  163. $(list[i]).addClass("ehv-visited");
  164. $(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());
  165. if (minimalShowText) {
  166. $(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>');
  167. } else {
  168. $(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>');
  169. }
  170. } else {
  171. $(list[i]).children('.gl2m').after('<td class="ehv-minimal"></td>');
  172. }
  173. } else {
  174. if (vis.data[galleryId] != undefined) {
  175. d = new Date(vis.data[galleryId]);
  176. $(list[i]).addClass("ehv-visited");
  177. $(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());
  178. }
  179. }
  180. }
  181. }
  182. } else if (thumb.length > 0) { //thumbnail
  183. for (var i = galLen; i < thumb.length; i++) {
  184. gid = $(thumb[i]).find(".gl3t a").attr("href").split("/");
  185. galleryId = gid[4] + "." + gid[5];
  186. if (vis.data[galleryId] != undefined) {
  187. d = new Date(vis.data[galleryId]);
  188. $(thumb[i]).addClass("ehv-visited");
  189. $(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>");
  190. }
  191. }
  192. } else {
  193. 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");
  194. }
  195. observer.observe($('.itg').get(0), {
  196. childList: true,
  197. subtree: true
  198. });
  199. }
  200.  
  201. $(function () {
  202. // track galleries opened on-site
  203. $('a').on('mouseup', function () {
  204. var spl = this.href.split("/");
  205. var d1 = spl[3]
  206. var d2 = spl[4];
  207. var d3 = spl[5];
  208.  
  209. if (d1 == "g") {
  210. var c = d2 + "." + d3;
  211. vis.data[c] = Date.now();
  212. localStorage.setItem(storageName, JSON.stringify(vis));
  213. }
  214. });
  215.  
  216. // track galleries opened indirectly (offsite link, shortcut file, context menu, bookmark, etc.)
  217. $(window).one('click scroll', function () {
  218. if (d1 == "g") {
  219. var c = d2 + "." + d3;
  220. vis.data[c] = Date.now();
  221. localStorage.setItem(storageName, JSON.stringify(vis));
  222. }
  223. });
  224.  
  225. if (d1.substr(0, 1) == "?" || d1.substr(0, 1) == "#" || d1.substr(0, 1) == "f" || d1.substr(0, 1) == "t" || !d1) {
  226. var len = Object.keys(vis.data).length;
  227. var ehvClearConfirm = 0;
  228.  
  229. $("#toppane").append("<ehv class='ehv-controls'>Galleries visited: " + len + " (<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>");
  230.  
  231. $(".ehv-import").click(function () {
  232. var c = prompt("E-H Visited:\nPaste here to import, and overwrite current data.");
  233. if (c) {
  234. var d = JSON.parse('{"data":{}}');
  235. var sp = c.split(";");
  236. //sp = sp.filter(Boolean);
  237. for (var k in sp) {
  238. var s = sp[k].split(":");
  239. d.data[s[0]] = parseInt(s[1]);
  240. }
  241. localStorage.setItem(storageName, JSON.stringify(d))
  242. console.log(d);
  243. alert("E-H Visited:\nImported " + Object.keys(d.data).length + " entries.");
  244. location.reload();
  245. }
  246. });
  247.  
  248. $(".ehv-export").click(function () {
  249. ehvExport('Exported entries:');
  250. });
  251.  
  252. $(".ehv-css").click(function () {
  253. var c = prompt("E-H Visited:\nThis CSS is applied to visited galleries.\n('initial' to reset, or 'none' for no styling)", css);
  254. if (c) {
  255. GM_setValue("css", c);
  256. location.reload();
  257. }
  258. });
  259.  
  260. $(".ehv-merge").click(function () {
  261. var c = prompt("E-H Visited:\nPaste here to import, and merge with current data.");
  262. if (c) {
  263. var d = JSON.parse('{"data":{}}');
  264. var sp = c.split(";");
  265. sp = sp.filter(Boolean);
  266. for (var k in sp) {
  267. var s = sp[k].split(":");
  268. d.data[s[0]] = parseInt(s[1]);
  269. }
  270. for (var i = 0; i < Object.keys(vis.data).length; i++) {
  271. d.data[Object.keys(vis.data)[i]] = vis.data[Object.keys(vis.data)[i]];
  272. }
  273. alert("E-H Visited\nMerged data, " + Object.keys(d.data).length + " unique entries.");
  274. localStorage.setItem(storageName, JSON.stringify(d));
  275. location.reload();
  276. }
  277. });
  278.  
  279. $(".ehv-clear").click(function () {
  280. if (!ehvClearConfirm) {
  281. ehvClearConfirm = 1;
  282. $('.ehv-clear').append(': Are you sure?');
  283. ehvExport('Backup your current data:');
  284. } else {
  285. alert("E-H Visited:\nCleared all entries.");
  286. localStorage.removeItem(storageName);
  287. location.reload();
  288. }
  289. });
  290.  
  291. // append icon friendly fonts to the calculated font stack
  292. var inheritFonts = $('body').css('font-family') + ', arial, symbola';
  293. $(`<style data-jqstyle='ehVisited'>
  294. ehv { font-family:` + inheritFonts + ` }
  295. .gl2c { width: 115px; }
  296. .ehv-visited .gl3e { min-height: 206px; }
  297. .ehv-visited .gl4e { min-height: 264px !important; }
  298. .ehv-exported-data { display: block; }
  299. .ehv-exported-data-text { display: block; margin: 0 auto; height: 5em; width: 50vw; padding: .25em; }
  300. .ehv-minimal-text { text-align: center; display: block; }
  301. .ehv-compact { border-style: solid; border-width: 1px 0; text-align: center; }
  302. .ehv-extended { width: 120px; position: absolute; left: 3px; top: 172px; text-align: center; font-size: 8pt; line-height: 1.5; }
  303. .ehv-extended-favs { padding: 3px 1px; display: block; line-height: 1.5; }
  304. .ehv-thumbnail { display: block; text-align: center; margin: 3px 0 5px; line-height: 12px; }
  305. .ehv-controls { padding: 3px 1px; text-align: center; display: block; }
  306. table.itg > tbody > tr.ehv-visited, .gl1t.ehv-visited { ` + css + ` }
  307. </style>`).appendTo("head");
  308.  
  309. ehvTimestamp();
  310. }
  311. });