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.

As of 2018-12-04. See the latest version.

  1. // ==UserScript==
  2. // @name Leetcode Contest Language reavealer
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.2
  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 ids = [];
  133.  
  134. data.submissions.forEach((submission) => {
  135. const one = Object.values(submission)[0];
  136. const id = one.submission_id;
  137. ids.push(id);
  138. });
  139. //console.log("Submission ID's are: ", ids);
  140. fetchLanguageInfo(ids);
  141. }
  142.  
  143. function doSyncCall(id) {
  144. const url = 'https://leetcode.com/api/submissions/';
  145.  
  146. return new Promise((resolve, reject) => {
  147. fetch(url + id)
  148. .then(resp => resp.json())
  149. .then((data) => resolve(data.lang))
  150. .catch((err) => resolve('N/A')); // TODO: Maybe retry promise after xx ms?
  151. // Currently we are resolving the promise && proceeding.
  152. });
  153. }
  154.  
  155. /*
  156. * At this point, this function does the following:
  157. * 1. It takes an array of ID's (leetcode submission ID)
  158. * 2. Fetch respective metadata from the leetcode submission API.
  159. * 3. Delegate the results to anther fxn to append to the DOM.
  160. *
  161. * Note: This is a promisified function that heavily relies on fetching data in sequence.
  162. * This is because the appropriate ID needs to be shown the correct language choice.
  163. * TODO: Further optimization could be fetch everything async and maintain a running ID,
  164. * And append their respective results to the DOM.
  165. */
  166.  
  167. function fetchLanguageInfo(ids) {
  168. const languageArr = [];
  169.  
  170. ids.reduce((promise, id) => {
  171. return promise.then((result) => {
  172. return doSyncCall(id).then(val => {
  173. languageArr.push(val);
  174. });
  175. });
  176. }, Promise.resolve()).then(() => { // TODDO: Optimize with promise.all/race
  177. //console.log("Final Language Array: ", languageArr);
  178. appendLangToDOM(languageArr);
  179. });
  180. }
  181.  
  182. /*
  183. * This method is currently deprecated. Thought of making this a helper method for something else.
  184. */
  185.  
  186. function makeCallAndFetchLanguage(id, arr) {
  187. const url = `https://leetcode.com/api/submissions/${id}`;
  188.  
  189. fetch(url)
  190. .then((resp) => resp.json())
  191. .then(function(data) {
  192. //console.log("Language Results", data);
  193. // Data is a JSON.
  194. arr.push(Promise.resolve(data.lang));
  195. });
  196. }
  197.  
  198. /*
  199. * It does exactly what it sounds like it does ;-)
  200. */
  201.  
  202. function appendLangToDOM(arr) {
  203. let j = 0;
  204.  
  205. // Hide the spinner.
  206. $('.rupesh-loader').css('display', 'none');
  207.  
  208. // Append my submission.
  209. const myTd = document.createElement('td');
  210. myTd.innerHTML = "? (hehe)"; // TODO: Make it dynamic.
  211. myTd.classList.add('td-my-data');
  212. $('.success').append(myTd);
  213.  
  214. $('tbody > tr').each(function(i) {
  215. if (i > 0) {
  216. const td = document.createElement('td');
  217. //console.log("array JAKE", arr[j]);
  218. td.innerHTML = arr[j];
  219. td.classList.add('td-lang-data');
  220. $('tbody > tr')[i].append(td);
  221. j++;
  222. }
  223. });
  224. }
  225. }
  226.  
  227.  
  228. })();