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. })();