LightShot (prnt.sc) random screenshot

Press R on prnt.sc website to load some random screenshot

  1. // ==UserScript==
  2. // @name LightShot (prnt.sc) random screenshot
  3. // @description Press R on prnt.sc website to load some random screenshot
  4. //
  5. // @name:ru Lightshot (prnt.sc) случайный скриншот
  6. // @description:ru Нажми R на сайте prnt.sc чтобы загрузить какой-то случайный скриншот
  7. //
  8. // @author Konf
  9. // @namespace https://greatest.deepsurf.us/users/424058
  10. // @icon https://t1.gstatic.com/faviconV2?client=SOCIAL&url=http://prnt.sc&size=32
  11. // @version 1.3.1
  12. // @match https://prnt.sc/*
  13. // @compatible Chrome
  14. // @compatible Opera
  15. // @compatible Firefox
  16. // @require https://cdnjs.cloudflare.com/ajax/libs/arrive/2.4.1/arrive.min.js
  17. // @run-at document-body
  18. // @grant GM_addStyle
  19. // @noframes
  20. // ==/UserScript==
  21.  
  22. /* jshint esversion: 8 */
  23.  
  24. (function() {
  25. 'use strict';
  26.  
  27. const langStrings = {
  28. en: {
  29. getNewRandImg: 'Load new random screenshot',
  30. hotkey: 'Hotkey',
  31. scriptGotFailStreak:
  32. 'Oops! Script just have got a huge fail streak. ' +
  33. 'If your internet connection is fine, maybe the script ' +
  34. 'is broken. Please consider to notify the script author ' +
  35. 'about the problem. Also it would be great if you check ' +
  36. 'your browser console and save all the info messages ' +
  37. 'from there. They are contain the errors details',
  38. },
  39. ru: {
  40. getNewRandImg: 'Загрузить новый случайный скриншот',
  41. hotkey: 'Горячая клавиша',
  42. scriptGotFailStreak:
  43. 'Упс! Скрипт много пытался, но так и не смог ' +
  44. 'сработать. Если у тебя всё в порядке с интернетом, ' +
  45. 'то возможно скрипт просто сломан. Пожалуйста, сообщи ' +
  46. 'о проблеме автору скрипта. А ещё было бы супер если ' +
  47. 'бы ты открыл(а) консоль браузера и сохранил(а) оттуда ' +
  48. 'все сообщения. Они содержат описания ошибок',
  49. },
  50. };
  51. const userLang = navigator.language.slice(0, 2);
  52. const i18n = langStrings[userLang || 'en'];
  53.  
  54. const css = [`
  55. body {
  56. /* fix header glitching */
  57. overflow-y: scroll;
  58. }
  59.  
  60. .randsshot-icon {
  61. float: left;
  62. width: 28px;
  63. height: 28px;
  64. margin: 11px 25px 0 0;
  65. color: white;
  66. font-weight: bold;
  67. user-select: none;
  68. cursor: pointer;
  69. border: 2px solid lightgray;
  70. border-radius: 100%;
  71. outline: none;
  72. background: none;
  73. }
  74.  
  75. .randsshot-icon--loading {
  76. /* hide text */
  77. text-indent: -9999em;
  78. white-space: nowrap;
  79. overflow: hidden;
  80.  
  81. border-top: 5px solid rgba(255, 255, 255, 0.2);
  82. border-right: 5px solid rgba(255, 255, 255, 0.2);
  83. border-bottom: 5px solid rgba(255, 255, 255, 0.2);
  84. border-left: 5px solid #ffffff;
  85. transform: translateZ(0);
  86. animation: loading 1.1s infinite linear;
  87. }
  88.  
  89. @keyframes loading {
  90. 0% {
  91. transform: rotate(0deg);
  92. }
  93. 100% {
  94. transform: rotate(360deg);
  95. }
  96. }
  97. `].join();
  98.  
  99. // node queries
  100. const qs = {
  101. garbage: [
  102. 'div.image__title.image-info-item', 'div.additional',
  103. 'div.header-downloads', 'div.social', 'div.image-info',
  104. 'script[src*="image-helper.js"]',
  105. ].join(', '),
  106.  
  107. needed: {
  108. mainImg: 'img.no-click.screenshot-image',
  109. headerLogo: 'a.header-logo',
  110. headerLogoParent: 'div.header > div.page-constrain',
  111. }
  112. };
  113.  
  114. const neededNodes = {
  115. mainImg: null,
  116. headerLogo: null,
  117. headerLogoParent: null,
  118. }
  119.  
  120. document.arrive(qs.garbage, { existing: true }, n => n.remove());
  121.  
  122. let injected = false;
  123.  
  124. for (const nodeId in qs.needed) {
  125. const query = qs.needed[nodeId];
  126.  
  127. document.arrive(query, { existing: true }, (aNode) => {
  128. // unreachable?
  129. if (injected) return;
  130.  
  131. neededNodes[nodeId] = aNode;
  132. document.unbindArrive(query);
  133.  
  134. for (const nodeId in neededNodes) {
  135. const node = neededNodes[nodeId];
  136.  
  137. if (node === null) return;
  138. }
  139.  
  140. injected = true;
  141. main();
  142. });
  143. }
  144.  
  145.  
  146. function main() {
  147. GM_addStyle(css);
  148.  
  149. const b = document.createElement('button');
  150. const bParent = neededNodes.headerLogoParent;
  151. const bNeighbour = neededNodes.headerLogo;
  152.  
  153. b.innerText = 'R';
  154. b.title = `${i18n.getNewRandImg}\n${i18n.hotkey}: R`;
  155. b.className = 'randsshot-icon';
  156. bParent.insertBefore(b, bNeighbour);
  157.  
  158. b.addEventListener('click', loadNewSshot);
  159. document.addEventListener('keydown', ev => {
  160. if (ev.code === 'KeyR' && !ev.ctrlKey) loadNewSshot();
  161. });
  162.  
  163. const parser = new DOMParser();
  164.  
  165. let failsStreak = 0;
  166. let isFetching = false;
  167.  
  168. function closeFetchUX() {
  169. isFetching = false;
  170. b.className = 'randsshot-icon';
  171. }
  172.  
  173. async function loadNewSshot() {
  174. if (isFetching) return;
  175.  
  176. isFetching = true;
  177. b.className = 'randsshot-icon randsshot-icon--loading';
  178.  
  179. const newSshotUrl = `https://prnt.sc/${makeSshotId()}`;
  180.  
  181. try {
  182. const newSshotPage = await fetch(newSshotUrl);
  183. const newSshotPageDOM = parser.parseFromString(
  184. await newSshotPage.text(), 'text/html'
  185. );
  186. const fetchedImgNode = newSshotPageDOM.querySelector(
  187. qs.needed.mainImg
  188. );
  189.  
  190. if (!fetchedImgNode || !fetchedImgNode.src) {
  191. throw new Error(
  192. 'Failed to find a new image in fetched webpage. ' +
  193. 'URL: ' + newSshotUrl
  194. );
  195. }
  196.  
  197. neededNodes.mainImg.src = fetchedImgNode.src;
  198.  
  199. await new Promise((resolve, reject) => {
  200. const listeners = {
  201. load: () => {
  202. turnListeners('off');
  203. history.pushState(null, null, newSshotUrl);
  204. closeFetchUX();
  205. resolve();
  206. },
  207. error: () => {
  208. turnListeners('off');
  209. reject(
  210. `Failed to load ${fetchedImgNode.src} ` +
  211. `that was fetched from ${newSshotUrl}`
  212. );
  213. },
  214. };
  215.  
  216. const turnListeners = (to) => {
  217. for (const type in listeners) {
  218. const listener = listeners[type];
  219.  
  220. if (to === 'on') {
  221. neededNodes.mainImg.addEventListener(type, listener);
  222. } else {
  223. neededNodes.mainImg.removeEventListener(type, listener);
  224. }
  225. }
  226. };
  227.  
  228. turnListeners('on');
  229. });
  230.  
  231. failsStreak = 0;
  232. } catch (e) {
  233. failsStreak += 1;
  234. console.error(e);
  235. closeFetchUX();
  236.  
  237. if (failsStreak < 20) {
  238. // retry immediately (almost)
  239. setTimeout(loadNewSshot, 250);
  240. } else {
  241. alert(`${GM_info.script.name}:\n${i18n.scriptGotFailStreak}`);
  242. failsStreak = 0;
  243. }
  244. }
  245. }
  246.  
  247. window.addEventListener('popstate', reloadPage);
  248. }
  249.  
  250.  
  251. // utils ---------------------------------------------
  252.  
  253. function reloadPage() {
  254. window.location.reload();
  255. }
  256.  
  257. function makeSshotId() {
  258. const chars = {
  259. first: 'abcdefghijklmnopqrstuvwxyz123456789',
  260. rest: 'abcdefghijklmnopqrstuvwxyz0123456789',
  261. };
  262.  
  263. return makeId(chars.first, 1) + makeId(chars.rest, 5);
  264. }
  265.  
  266. function makeId(charset, length) {
  267. let result = '';
  268.  
  269. for (let i = 0, randNum; i < length; i++) {
  270. randNum = Math.floor(Math.random() * charset.length);
  271. result += charset.charAt(randNum);
  272. }
  273.  
  274. return result;
  275. }
  276.  
  277. // ---------------------------------------------------
  278. })();