Greasy Fork is available in English.

Leetcode Contest Language reavealer

The primary aim of this script is to identify in what coding language the user has written the code. For v1, I only consider the first result.

  1. // ==UserScript==
  2. // @name Leetcode Contest Language reavealer
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.3
  5. // @description The primary aim of this script is to identify in what coding language the user has written the code. For v1, I only consider the first result.
  6. // @author Rupesh Dabbir
  7. // @license MIT
  8. // @namespace https://github.com/rupeshdabbir
  9. // @include https://leetcode.com/contest/*/ranking/*
  10. // @include https://leetcode.com/contest/*
  11. // @match https://leetcode.com/contest/weekly-contest-*/ranking*/*
  12. // @grant GM_addStyle
  13. // @grant GM_xmlhttpRequest
  14. // ==/UserScript==
  15. (function() {
  16.  
  17. let firstLoad = false;
  18.  
  19. const pageURLCheckTimer = setInterval(
  20. function() {
  21. if (this.lastPathStr !== location.pathname ||
  22. this.lastQueryStr !== location.search ||
  23. this.lastPathStr === null ||
  24. this.lastQueryStr === null
  25. ) {
  26. this.lastPathStr = location.pathname;
  27. this.lastQueryStr = location.search;
  28. gmMain();
  29. }
  30. }, 222
  31. );
  32.  
  33. function gmMain() {
  34. const isRanking = window.location.pathname.split('ranking').length > 1;
  35. if (isRanking && !firstLoad) {
  36. firstLoad = true;
  37. initMagic();
  38. }
  39. }
  40.  
  41. function initMagic() {
  42. /* Promisified Helper method that waits for a particular DOM elem to be loaded */
  43. const waitFor = (...selectors) => new Promise(resolve => {
  44. const delay = 500
  45. const f = () => {
  46. const elements = selectors.map(selector => document.querySelector(selector))
  47. if (elements.every(element => element != null)) {
  48. resolve(elements)
  49. } else {
  50. setTimeout(f, delay)
  51. }
  52. }
  53. f()
  54. })
  55.  
  56. /* When Pagination has been clicked */
  57. waitFor('.pagination').then(([table]) => {
  58. $('.pagination').click(function() {
  59. $('.td-lang-data').remove();
  60. $('.td-my-data').remove();
  61.  
  62. $('.rupesh-loader').css('display', 'block');
  63. setTimeout(() => { // Time for window.location.href to update.
  64. doAjax();
  65. }, 1000);
  66. });
  67. });
  68.  
  69. /*
  70. * When Table has been loaded, add the language barrier! ;-)
  71. */
  72.  
  73. waitFor('thead > tr').then(([table]) => {
  74. $('thead > tr').append('<th class="language-bar"> Language</th>');
  75. addLoader();
  76. doAjax();
  77. });
  78.  
  79. /*
  80. * Helper to add Spinner on page.
  81. * Note: Once this component has been initialized, it'll stay on the page with
  82. * CSS display being 'none'. Thus, can be re-used without further DOM manipulation.
  83. * TODO: Refactor and move all the inline styling to CSS class.
  84. */
  85.  
  86. function addLoader() {
  87. $('tbody > tr').each(function(i) {
  88. if (i >= 0) {
  89. const img = document.createElement('img');
  90. img.src = "https://image.ibb.co/bLqPSf/Facebook-1s-200px.gif";
  91. img.width = "50";
  92. img.height = "50";
  93. img.style = "margin-left: 10px";
  94. img.classList.add("rupesh-loader");
  95. $('tbody > tr')[i].append(img);
  96. }
  97. });
  98. }
  99.  
  100. function getLastUrlPath() {
  101. let locationLastPart = window.location.pathname
  102. if (locationLastPart.substring(locationLastPart.length - 1) == "/") {
  103. locationLastPart = locationLastPart.substring(0, locationLastPart.length - 1);
  104. }
  105. locationLastPart = locationLastPart.substr(locationLastPart.lastIndexOf('/') + 1);
  106.  
  107. if (isNaN(+locationLastPart)) return "1";
  108.  
  109. return +locationLastPart;
  110. }
  111.  
  112. /*
  113. * @param AJAX with url.
  114. * @returns JSON that contains ID's that can be further fetched to find Lang metadata.
  115. */
  116. function doAjax() {
  117. const contextSuffix = window.location.href.split('/').filter((val) => val.indexOf('weekly-contest') > -1)[0];
  118. const page = getLastUrlPath();
  119. const url = `https://leetcode.com/contest/api/ranking/${contextSuffix}/?pagination=${page}`;
  120. // console.log("You are on page:", page);
  121. // console.info("The Url before making req:", url);
  122.  
  123. fetch(url)
  124. .then((resp) => resp.json())
  125. .then(function(data) {
  126. //console.log("Ranking API data return", data);
  127. fetchSubmissionIDS(data);
  128. });
  129. }
  130.  
  131. function fetchSubmissionIDS(data) {
  132. const submissions = [];
  133.  
  134. data.submissions.forEach((submission, index) => {
  135. const one = Object.values(submission)[0];
  136. const id = one.submission_id;
  137. submissions.push({id, country: data.total_rank[index].data_region });
  138. });
  139. //console.log("Submission ID's are: ", ids);
  140. fetchLanguageInfo(submissions);
  141. }
  142.  
  143. function doSyncCall(id, country) {
  144. let url = ''
  145. if (country === 'CN') {
  146. url = 'https://leetcode-cn.com/api/submissions/';
  147. } else {
  148. url = 'https://leetcode.com/api/submissions/';
  149. }
  150.  
  151. return new Promise((resolve, reject) => {
  152. fetch(url + id)
  153. .then(resp => resp.json())
  154. .then((data) => {
  155. return resolve(data.lang)
  156. })
  157. .catch((err) => resolve('N/A')); // TODO: Maybe retry promise after xx ms?
  158. // Currently we are resolving the promise && proceeding.
  159. });
  160. }
  161.  
  162. /*
  163. * At this point, this function does the following:
  164. * 1. It takes an array of ID's (leetcode submission ID)
  165. * 2. Fetch respective metadata from the leetcode submission API.
  166. * 3. Delegate the results to anther fxn to append to the DOM.
  167. *
  168. * Note: This is a promisified function that heavily relies on fetching data in sequence.
  169. * This is because the appropriate ID needs to be shown the correct language choice.
  170. * TODO: Further optimization could be fetch everything async and maintain a running ID,
  171. * And append their respective results to the DOM.
  172. */
  173.  
  174. function fetchLanguageInfo(submissions) {
  175. const languageArr = [];
  176.  
  177. submissions.reduce((promise, submission) => {
  178. const {id, country} = submission
  179. return promise.then((result) => {
  180. return doSyncCall(id, country).then(val => {
  181. languageArr.push(val);
  182. });
  183. });
  184. }, Promise.resolve()).then(() => { // TODDO: Optimize with promise.all/race
  185. //console.log("Final Language Array: ", languageArr);
  186. appendLangToDOM(languageArr);
  187. });
  188. }
  189.  
  190. /*
  191. * This method is currently deprecated. Thought of making this a helper method for something else.
  192. */
  193.  
  194. function makeCallAndFetchLanguage(id, arr) {
  195. const url = `https://leetcode.com/api/submissions/${id}`;
  196.  
  197. fetch(url)
  198. .then((resp) => resp.json())
  199. .then(function(data) {
  200. //console.log("Language Results", data);
  201. // Data is a JSON.
  202. arr.push(Promise.resolve(data.lang));
  203. });
  204. }
  205.  
  206. /*
  207. * It does exactly what it sounds like it does ;-)
  208. */
  209.  
  210. function appendLangToDOM(arr) {
  211. let j = 0;
  212.  
  213. // Hide the spinner.
  214. $('.rupesh-loader').css('display', 'none');
  215.  
  216. // Append my submission.
  217. let startIndex = 0
  218. if ($("tbody").find(".success").length > 0) {
  219. const myTd = document.createElement('td');
  220. myTd.innerHTML = "You surely know it !!!"; // TODO: Make it dynamic.
  221. myTd.classList.add('td-my-data');
  222. $('.success').append(myTd);
  223. startIndex = 1
  224. }
  225.  
  226. $('tbody > tr').each(function(i) {
  227. if (i >= startIndex) {
  228. const td = document.createElement('td');
  229. //console.log("array JAKE", arr[j]);
  230. td.innerHTML = arr[j];
  231. td.classList.add('td-lang-data');
  232. $('tbody > tr')[i].append(td);
  233. j++;
  234. }
  235. });
  236. }
  237. }
  238.  
  239.  
  240. })();