Hentai Heroes image viewer

Allows you to display any stage image of any harem girl, owned ones or not. Works also in event display and Places of Power. Includes zoom-in feature to display full-size girl images gallery (lightbox).

  1. // ==UserScript==
  2. // @name Hentai Heroes image viewer
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0
  5. // @description Allows you to display any stage image of any harem girl, owned ones or not. Works also in event display and Places of Power. Includes zoom-in feature to display full-size girl images gallery (lightbox).
  6. // @author randomfapper34
  7. // @match http*://nutaku.haremheroes.com/*
  8. // @match http*://*.hentaiheroes.com/*
  9. // @match http*://*.gayharem.com/*
  10. // @match http*://*.comixharem.com/*
  11. // @require https://cdnjs.cloudflare.com/ajax/libs/fancybox/3.5.7/jquery.fancybox.min.js
  12. // @grant none
  13. // @license MIT
  14. // ==/UserScript==
  15.  
  16. // gayharem image link head: gh1
  17. // hentaiharem image link head: hh2
  18. // comixharem image link head: ch
  19.  
  20. var $ = window.jQuery;
  21. var haremHead = (function() {
  22. var haremType = ($('body#hh_gay').length > 0) ?
  23. 'gh1' :
  24. ($('body#hh_comix').length > 0) ? 'ch' : 'hh2';
  25. return 'https://' + haremType;
  26. })();
  27. var wikiLink = (function() {
  28. var haremType = ($('body#hh_gay').length > 0) ?
  29. 'harem-battle.club/wiki/Gay-Harem/GH:' :
  30. ($('body#hh_comix').length > 0) ? '' : 'harem-battle.club/wiki/Harem-Heroes/HH:';
  31. return haremType;
  32. })();
  33. var CurrentPage = window.location.pathname;
  34. var sheet = (function() {
  35. var style = document.createElement('style');
  36. document.head.appendChild(style);
  37. return style.sheet;
  38. })();
  39. var imageExt = '-1200x.webp'; //old ext: '.png';
  40. var icoExt = '-300x.webp';
  41.  
  42. $(document).ready(function() {
  43. //include lightbox css
  44. $(document.head).append(
  45. '<link href="https://cdnjs.cloudflare.com/ajax/libs/fancybox/3.5.7/jquery.fancybox.css" rel="stylesheet" type="text/css">'
  46. );
  47. //define own css
  48. defineCss();
  49. });
  50.  
  51. // current page: Activities (PoP)
  52. if (CurrentPage.indexOf('activities') != -1)
  53. {
  54. if ($('.pop_list').css('display') != 'none') return;
  55.  
  56. setTimeout(async function () {
  57. var popElement = $('#activities #pop.canvas');
  58. var popImage = popElement.find('.pop_left_part img');
  59. var popRewardInfo = popElement.find('.pop_rewards_display.reward_wrap').attr('data-reward-display');
  60. var popImageIcon = popElement.find('.pop_rewards_display .shards_girl_ico img');
  61. //if girl is won, there is no shards data in popRewardInfo, and therefore no id. Use regex to get girl id from image link?
  62. var jsonReward = JSON.parse(popRewardInfo);
  63. if (!jsonReward.hasOwnProperty('shards')) return;
  64. var girlInfo = jsonReward.shards[0];
  65. var girlId = girlInfo.id_girl;
  66. var girlGrades = girlInfo.graded2.split('<g').length - 1;
  67.  
  68. //check for image existance with high grades (always work no matter the webpage display chages)
  69. if (girlGrades == 0) {
  70. girlGrades = 5;
  71. var checkImageLink = haremHead + '.hh-content.com/pictures/girls/' + girlId + '/ava5' + imageExt;
  72. if (await checkUrlResponse(checkImageLink) === false) girlGrades = 3;
  73. }
  74.  
  75. //create diamonds on the top part
  76. popElement.find('.diamond-bar').remove();
  77. var allDiamonds = '';
  78. for (var i = 0; i <= girlGrades; i++) {
  79. var diamondToAdd = '<div class="diamond unlocked" grade="' + i + '"></div>';
  80. allDiamonds += diamondToAdd;
  81. }
  82. popImage.before('<div class="diamond-bar-container"><div class="diamond-bar">' + allDiamonds + '</div></div>');
  83.  
  84. //connect diamonds to image links
  85. var allLinks = popElement.find('.diamond');
  86. var linksArray = [];
  87. for (i = 0; i <= girlGrades; i++) {
  88. var imgLink = haremHead + '.hh-content.com/pictures/girls/' + girlId + '/ava' + i + imageExt;
  89. var icoLink = haremHead + '.hh-content.com/pictures/girls/' + girlId + '/ico' + i + icoExt;
  90. linksArray.push(imgLink);
  91. $(allLinks.get(i)).attr("link", imgLink);
  92. $(allLinks.get(i)).attr("icoLink", icoLink);
  93. }
  94.  
  95. $( ".pop_left_part .diamond-bar .diamond" ).on('mouseenter', function() {
  96. var girlAvatarLink = $(this).attr('link');
  97. var girlIconLink = $(this).attr('icoLink');
  98. popImage.attr('src', girlAvatarLink);
  99. popImageIcon.attr('src', girlIconLink);
  100. });
  101.  
  102. //create zooming event
  103. $(popImage).removeData('allImages');
  104. $(popImage).data('allImages', linksArray);
  105. $(popImage).on('mouseup', zoomIntoImage);
  106. }, 50);
  107. }
  108.  
  109. // current page: Event box
  110. if (CurrentPage.indexOf('event') != -1)
  111. {
  112. var eventGirlElementSelector = ".nc-event-list-rewards-container .nc-event-list-reward-container"
  113. var rewardBox = ".nc-event-reward-container.selected ";
  114. var eventGirlImageSelector = ".canvas " + rewardBox + " .nc-event-reward-preview";
  115. var eventGirlInfoSelector = ".canvas " + rewardBox + " .nc-event-reward-info";
  116.  
  117. setTimeout(function () {
  118. $(eventGirlElementSelector + ".selected").click();
  119. }, 50);
  120.  
  121. $(eventGirlElementSelector).on('click', function() {
  122. setTimeout(async function () {
  123. var girlImageDiv = $(eventGirlImageSelector);
  124. var girlInfoDiv = $(eventGirlInfoSelector);
  125. var girlInfo = girlInfoDiv.find('.new_girl_info .girl_tooltip_grade');
  126. var girlGrades = girlInfo.find('g').length;
  127. var girlIconImage = $(".nc-event-list-rewards-container > .nc-event-list-reward-container.selected img");
  128. var girlImage = girlImageDiv.children('img');
  129. girlImageDiv.find('.diamond-bar').remove();
  130.  
  131. //find girl id from image src
  132. var girlImageSrc = girlImage.attr('src');
  133. var startPosition = girlImageSrc.indexOf('pictures/girls/') + 'pictures/girls/'.length;
  134. var girlIdStr = girlImageSrc.substring(startPosition, girlImageSrc.lastIndexOf('/ava'));
  135. if (isNaN(girlIdStr))
  136. return;
  137. var girlId = parseInt(girlIdStr);
  138.  
  139. //check for image existance with high grades (always work no matter the webpage display chages)
  140. if (girlGrades == 0) {
  141. girlGrades = 5;
  142. var checkImageLink = haremHead + '.hh-content.com/pictures/girls/' + girlId + '/ava' + girlGrades + imageExt;
  143. if (await checkUrlResponse(checkImageLink) === false) girlGrades = 3;
  144. }
  145.  
  146. //create diamonds on the top part
  147. var allDiamonds = '';
  148. for (var i = 0; i <= girlGrades; i++) {
  149. var diamondToAdd = '<div class="diamond unlocked" grade="' + i + '"></div>';
  150. allDiamonds += diamondToAdd;
  151. }
  152. girlImage.before('<div class="diamond-bar">' + allDiamonds + '</div>');
  153.  
  154. //connect diamonds to image links
  155. var allLinks = girlImageDiv.find('.diamond');
  156. var linksArray = [];
  157. for (i = 0; i <= girlGrades; i++) {
  158. var imgLink = haremHead + '.hh-content.com/pictures/girls/' + girlId + '/ava' + i + imageExt;
  159. var icoLink = haremHead + '.hh-content.com/pictures/girls/' + girlId + '/ico' + i + icoExt;
  160. linksArray.push(imgLink);
  161. $(allLinks.get(i)).attr("link", imgLink);
  162. $(allLinks.get(i)).attr("icoLink", icoLink);
  163. }
  164.  
  165. $( rewardBox + " .diamond-bar .diamond" ).on('mouseenter', function() {
  166. var girlAvatarLink = $(this).attr('link');
  167. var girlIconLink = $(this).attr('icoLink');
  168. girlImage.attr('src', girlAvatarLink);
  169. girlIconImage.attr('src', girlIconLink);
  170. });
  171.  
  172. //create zooming event
  173. $(girlImage).removeData('allImages');
  174. $(girlImage).data('allImages', linksArray);
  175. $(girlImage).off('mouseup', zoomIntoImage);
  176. $(girlImage).on('mouseup', zoomIntoImage);
  177. }, 10);
  178. });
  179. }
  180.  
  181. // current page: Harem
  182. if (CurrentPage.indexOf('harem') != -1)
  183. {
  184. var callback = function(mutationsList) {
  185. for (let mutation of mutationsList) {
  186. if (mutation.type === 'childList') {
  187. mutation.addedNodes.forEach(node => {
  188. if (node.outerHTML) {
  189. node.addEventListener('click', onGirlClick, false);
  190. }
  191. });
  192. }
  193. }
  194. };
  195.  
  196. const targetNode = document.querySelector('#harem_left div.girls_list');
  197. const config = { childList: true };
  198. const observer = new MutationObserver(callback);
  199. observer.observe(targetNode, config);
  200. $( ".girls_list div[id_girl]" ).on('click', onGirlClick);
  201.  
  202. function onGirlClick(event) {
  203. var girlId = $(this).children('[girl]').attr('girl');
  204. var girlGrades = $(this).find('.graded').children().length;
  205. var girlName = $(this).find('div.right h4')[0].innerText;
  206. updateInfo(girlId, girlGrades, girlName);
  207. }
  208.  
  209. setTimeout(function () {
  210. //update view of girl currently selected when loading the harem
  211. $("#harem_left div.girls_list div[girl].opened").click();
  212. }, 200);
  213.  
  214. function updateInfo(girlId, girlGrades, girlName)
  215. {
  216. setTimeout(function () {
  217. var haremRight = $('#harem_right');
  218. haremRight.children('[girl]').each( function() {
  219. if (girlId == 0) girlId = $(this).attr('girl');
  220.  
  221. var notOwned = $(this).children('.missing_girl');
  222. var girlImageDiv = $(this).find('.avatar-box');
  223. var girlIconDiv = $("#harem_left div.girls_list div[girl].opened div.left img");
  224.  
  225. if (notOwned.length > 0) {
  226. //create diamonds on the bottom part
  227. var allDiamonds = '';
  228. for (var i = 0; i <= girlGrades; i++) {
  229. var diamondToAdd = '<div class="diamond locked" grade="' + i + '"></div>';
  230. allDiamonds += diamondToAdd;
  231. }
  232.  
  233. $(this).find('.middle_part').css('margin', '0');
  234. $(this).find('.dialog-box').after('<h3>' + girlName + '</h3>');
  235. $(this).find('img.avatar').wrap('<div class="avatar-box"></div>');
  236. $(this).find('.avatar-box').css('margin-top', '23px');
  237. $(this).find('.avatar-box').after('<div class="diamond-bar">' + allDiamonds + '</div>');
  238. }
  239.  
  240. //update for any girl (owned or not)
  241. var wikiBase = wikiLink;
  242. if (wikiBase != '') {
  243. $(this).find('h3').wrap('<div class="WikiLink"></div>').wrap('<a href="https://' + wikiBase + girlName + '" target="_blank"></a>');
  244. }
  245. var allLinks = $(this).find('.diamond');
  246. var linksArray = [];
  247. for (i = 0; i <= girlGrades; i++) {
  248. var imgLink = haremHead + '.hh-content.com/pictures/girls/' + girlId + '/ava' + i + imageExt;
  249. var icoLink = haremHead + '.hh-content.com/pictures/girls/' + girlId + '/ico' + i + icoExt;
  250. linksArray.push(imgLink);
  251. $(allLinks.get(i)).attr("link", imgLink);
  252. $(allLinks.get(i)).attr("icoLink", icoLink);
  253. }
  254. $('.avatar-box img.avatar').removeData('allImages');
  255. $('.avatar-box img.avatar').data('allImages', linksArray);
  256. if (notOwned) $('.avatar-box img.avatar').attr('src', linksArray[0]);
  257.  
  258. $('.variation_block .big_border').on('click', function() {
  259. var girlId = $(this).children('[girl]').attr('girl');
  260. var girlGrades = $(this).find('.graded').children().length;
  261. setTimeout(function() {
  262. updateInfo(girlId, girlGrades, girlName);
  263. }, 50);
  264. });
  265.  
  266. $( ".diamond-bar .diamond" ).on('mouseenter', function() {
  267. var mainParent = $(this).closest('.middle_part');
  268. var girlAvatar = mainParent.find('img.avatar');
  269. var girlAvatarLink = $(this).attr('link');
  270. var girlIconLink = $(this).attr('icoLink');
  271. girlIconDiv.attr('src', girlIconLink);
  272. girlAvatar.attr('src', girlAvatarLink);
  273. });
  274.  
  275. $('.avatar-box img.avatar').on('mouseup', zoomIntoImage);
  276. });
  277. }, 0);
  278. }
  279. }
  280.  
  281. //zoom into image with lightbox, event only on left click
  282. function zoomIntoImage(e)
  283. {
  284. if (e.which != 1) return;
  285.  
  286. var linksArray = $(this).data('allImages');
  287. var girlAvatarLink = $(this).attr('src');
  288. var indexOfQuestion = girlAvatarLink.lastIndexOf('?');
  289. if (indexOfQuestion >= 0) girlAvatarLink = girlAvatarLink.slice(0, indexOfQuestion);
  290. var indexOfCurrent = linksArray.indexOf(girlAvatarLink);
  291.  
  292. var allImages = [];
  293. for (var i = 0; i < linksArray.length; i++) {
  294. allImages.push({
  295. src : linksArray[i].toString(),
  296. type : 'image',
  297. opts : {
  298. caption : i == 0 ? 'Default' : 'Stage ' + i
  299. }
  300. });
  301. }
  302.  
  303. $.fancybox.open(allImages, {
  304. loop : true,
  305. keyboard: true,
  306. transitionEffect: "tube"
  307. }, indexOfCurrent);
  308. }
  309.  
  310. //checks for any errors in url (like image 404)
  311. async function checkUrlResponse(url)
  312. {
  313. let result = false;
  314.  
  315. await fetch(url.toString())
  316. .then(function(response) {
  317. if (response.status >= 200 && response.status <= 299) {
  318. return response;
  319. } else {
  320. throw Error(response.statusText);
  321. }
  322. }).then(function(response) {
  323. result = true;
  324. }).catch(function(error) {
  325. });
  326.  
  327. return result;
  328. }
  329.  
  330. function defineCss()
  331. {
  332. sheet.insertRule('#harem_left div[girl]>.left>img, #harem_right>div[girl] .middle_part div.avatar-box img.avatar, #shops #girls_list .g1 .girl-ico>img {'
  333. + 'image-rendering: initial; }');
  334.  
  335. sheet.insertRule('#harem_right .WikiLink a {'
  336. + 'text-decoration: none; }');
  337.  
  338. sheet.insertRule('#harem_right .diamond-bar {'
  339. + 'margin-top: 4px; }');
  340.  
  341. sheet.insertRule('.rewards-stats .diamond-bar {'
  342. + 'position: static;'
  343. + 'justify-content: center;'
  344. + 'margin-top: 42px;'
  345. + 'margin-bottom: -40px; }');
  346.  
  347. sheet.insertRule('.generic-girl-image .diamond-bar, .nc-event-reward-preview .diamond-bar {'
  348. + 'justify-content: center;'
  349. + 'z-index: 1;'
  350. + 'width: 100%; }');
  351.  
  352. sheet.insertRule('.rewards-stats .avatars-drawn-bottom-part .diamond-bar {'
  353. + 'margin-top: 275px; }');
  354.  
  355. sheet.insertRule('.rewards-stats .avatars-drawn-bottom-part img {'
  356. + 'margin-top: -275px; }');
  357.  
  358. sheet.insertRule('.nc-event-reward-preview .diamond-bar {'
  359. + 'margin-top: -25px; }');
  360.  
  361. sheet.insertRule('.rewards-stats .diamond-bar .diamond.unlocked, .pop_left_part .diamond-bar .diamond.unlocked, .generic-girl-image .diamond-bar .diamond.unlocked {'
  362. + 'cursor: default; }');
  363.  
  364. sheet.insertRule('.pop_left_part .diamond-bar-container {'
  365. + 'z-index: 5;'
  366. + 'position: absolute; }');
  367.  
  368. sheet.insertRule('.pop_left_part .diamond-bar {'
  369. + 'position: relative;'
  370. + 'left: 50%; }');
  371.  
  372. sheet.insertRule('#harem_right .diamond-bar .diamond:hover, .rewards-stats .diamond-bar .diamond:hover, .pop_left_part .diamond-bar .diamond:hover, .generic-girl-image .diamond-bar .diamond:hover, .nc-event-reward-preview .diamond-bar .diamond:hover {'
  373. + 'border: 2px solid #FE00FE; }');
  374.  
  375. sheet.insertRule('.avatar-box img, .event-widget.special-fullscreen-view .widget .rewards-stats .reward img, .generic-girl-image img, .nc-event-reward-preview img {'
  376. + 'cursor: zoom-in; }');
  377.  
  378. sheet.insertRule('#pop.canvas .pop_left_part img.pop_left_fade_page {'
  379. + 'margin-bottom: 10px;'
  380. + 'cursor: zoom-in; }');
  381. }