KIIT-Connect Unlocker

Unlock kiitconnect for free

  1. // ==UserScript==
  2. // @name KIIT-Connect Unlocker
  3. // @namespace https://github.com/rohitaryal/kiitconnect-unlocker
  4. // @version 3.0
  5. // @description Unlock kiitconnect for free
  6. // @author rohitaryal
  7. // @match https://www.kiitconnect.com/academic/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=kiitconnect.com
  9. // @grant none
  10. // @license MIT
  11. // ==/UserScript==
  12.  
  13. const debugMode = true;
  14.  
  15. const driveFile = "https://drive.google.com/file/d/";
  16. const driveFolder = "https://drive.google.com/drive/folders/";
  17. const youtubePlaylist = "https://www.kiitconnect.com/player?playlistId=";
  18.  
  19. const pyqsRegex = /academic\/PYQS\/Content\/PYQS\-(cse|csse|csce|it)\-[1-7]/;
  20. const notesRegex = /academic\/PYQS\/Content\/Notes\-(cse|csse|csce|it)\-[1-7]/;
  21.  
  22. class Utils {
  23. constructor() { }
  24. /*
  25. * Get all script that doesn't have src attribute
  26. */
  27. static getScripts() {
  28. let scriptElements = document.querySelectorAll("script:not([src])");
  29. return [...scriptElements];
  30. }
  31.  
  32. /*
  33. * Copy item to clipboard
  34. */
  35. static copy(text) {
  36. document.body.focus();
  37. navigator.clipboard.writeText(text);
  38. }
  39.  
  40. /*
  41. * Logger for debug mode
  42. */
  43. static log(text) {
  44. if (debugMode) {
  45. console.log(text)
  46. }
  47. }
  48.  
  49. /*
  50. * Clone node and remove its junk and replace with new one
  51. */
  52. static replaceNode(node) {
  53. if (!node) {
  54. throw new Error("Invalid node provided to clone");
  55. }
  56.  
  57. const newNode = node.cloneNode(true);
  58. node.replaceWith(newNode);
  59.  
  60. return newNode;
  61. }
  62.  
  63. /*
  64. * Re-Serialize the json in our own way (For PYQS)
  65. */
  66. static parsePYQ(data) {
  67. if (typeof data !== 'object') {
  68. throw new Error(`Expected 'object' but obtained '${typeof data}' while parsing`);
  69. }
  70.  
  71. // Skeleton for subjects pyq, syllabus + some extra detail holder
  72. let pyqDetails = {
  73. semester: data.semeseter, // Yes, thats the actual spelling
  74. branch: data.stream,
  75. subjects: [],
  76. };
  77.  
  78. pyqDetails.subjects = data.data.semesters[0].subjects.map((subject) => {
  79. // Skeleton for subject detail holder
  80. let subDetails = {
  81. subjectName: subject.name,
  82. subjectCode: subject.SUBCODE,
  83. folder: driveFolder + subject.folderId, // Makes an accessable drive folder link
  84. subjectSyllabus: driveFile + subject.syllabus, // Makes an accessable drive file link
  85. playlist: [],
  86. papers: [],
  87. };
  88.  
  89. // Parse individual playlist
  90. subDetails.playlist = subject.youtubePlaylist.map((playlist) => {
  91. return {
  92. id: playlist.id,
  93. title: playlist.title,
  94. videoCount: playlist.noOfVides,
  95. videoLink: `${youtubePlaylist}id&playlist=${playlist.title}`
  96. };
  97. });
  98.  
  99. // Parse individual pyqs
  100. subDetails.papers = subject.pyqs.map((paper) => {
  101. return {
  102. name: paper.name,
  103. year: Number(paper.year) || 0,
  104. solution: driveFile + paper.solution, // Makes an accessable file link
  105. question: driveFile + paper.Question, // Same
  106. }
  107. });
  108.  
  109. // Sort papers based on years
  110. subDetails.papers = subDetails.papers.sort((a, b) => a.year - b.year);
  111.  
  112. return subDetails;
  113. });
  114.  
  115. return pyqDetails;
  116. }
  117.  
  118. /*
  119. * Re-Serialize the json in our own way (For Notes)
  120. */
  121. static parseNotes(data) {
  122.  
  123. }
  124.  
  125. /*
  126. * Parse script content (For PYQS)
  127. */
  128. static parseFromScriptPYQ(scriptContent) {
  129. // Pre-process and beautufy
  130. scriptContent = scriptContent.replaceAll(`\\"`, `"`);
  131. scriptContent = scriptContent.replaceAll(`\\n`, ``);
  132.  
  133. const parsableContent = scriptContent.slice(
  134. scriptContent.indexOf(`self.__next_f.push([1,"1e:`) + 26, // <-- 26 is the length of this
  135. scriptContent.lastIndexOf(`"])`) // string itself.
  136. );
  137.  
  138. const jsonContent = JSON.parse(parsableContent)[3].children[3];
  139.  
  140. return this.parsePYQ(jsonContent);
  141. }
  142.  
  143. /*
  144. * Parse script content (For Notes)
  145. */
  146. static parseFromScriptNotes(scriptContent) {
  147.  
  148. }
  149.  
  150. /*
  151. * Replace pyq table + headings with from our own serialized form
  152. */
  153. static rewritePYQPage(subjectArray) {
  154. if (typeof subjectArray !== 'object') {
  155. throw new Error(`Expected 'object' but obtained '${typeof subjectArray}' while rewriting`);
  156. }
  157.  
  158. // After successful injection head will contain this classlist
  159. // which is an indicator to stop further injection
  160. if(document.head.classList.contains("injected")) {
  161. return;
  162. }
  163.  
  164. Utils.log("Re-writing the pyq page.")
  165.  
  166. const parentContainer = [...document.querySelector("main div div").childNodes].slice(1);
  167. for (let i = 0; i < parentContainer.length; i++) {
  168. let totalRows = parentContainer[i].querySelectorAll("table tbody").length; // Get count of rows
  169. for (let j = 0; j < subjectArray.length; j++) {
  170. let totalPapers = subjectArray[j].papers.length;
  171. if (totalRows === totalPapers) {
  172. let temp = subjectArray[j];
  173. subjectArray[j] = subjectArray[i];
  174. subjectArray[i] = temp;
  175. break;
  176. }
  177. }
  178. }
  179.  
  180. for (let i = 0; i < subjectArray.length; i++) {
  181. const subject = subjectArray[i];
  182. const parent = parentContainer[i];
  183. const [titleDiv, tableContainer] = parent.childNodes;
  184.  
  185. const syllabusButton = titleDiv.querySelector("a");
  186. syllabusButton.href = subject.subjectSyllabus;
  187.  
  188. // New button with folder link
  189. const folderButton = syllabusButton.cloneNode(true);
  190. folderButton.innerText = "Open Folder";
  191. folderButton.href = subject.folder;
  192.  
  193. // Modify title (middle one is TEXT_NODE)
  194. const title = titleDiv.childNodes[1];
  195. title.textContent = subject.subjectName;
  196. // Modify the table
  197. const tableRows = tableContainer.querySelectorAll("table tbody");
  198.  
  199. for (let j = 0; j < tableRows.length; j++) {
  200. const row = tableRows[j];
  201. const paper = subject.papers[j];
  202.  
  203. const td = row.querySelectorAll("td:not(.hidden)");
  204.  
  205. // Year of paper
  206. td[0].textContent = paper.year;
  207.  
  208. // Link to question paper
  209. let paperAnchor = td[1].querySelector("a");
  210. paperAnchor = this.replaceNode(paperAnchor);
  211.  
  212. paperAnchor.textContent = paper.name;
  213. paperAnchor.href = paper.question;
  214. paperAnchor.classList.add("text-cyan-500");
  215. paperAnchor.classList.remove("text-yellow-500", "cursor-not-allowed");
  216.  
  217. // Link to solution paper
  218. let solutionAnchor = td[2].querySelector("a");
  219.  
  220. // Since solution is the last element
  221. if(!solutionAnchor)
  222. continue;
  223.  
  224. solutionAnchor = this.replaceNode(solutionAnchor);
  225.  
  226. if (solutionAnchor.classList.contains("text-gray-400")) {
  227. solutionAnchor.textContent = "Not Available";
  228. } else {
  229. solutionAnchor.classList.add("text-cyan-500");
  230. solutionAnchor.classList.remove("text-yellow-500", "cursor-not-allowed");
  231.  
  232. solutionAnchor.href = paper.solution
  233. }
  234. }
  235. }
  236.  
  237. // Mark as succrssful injection
  238. document.head.classList.add("injected");
  239. }
  240. }
  241.  
  242. (function () {
  243. 'use strict';
  244. // Last script will always contain the paper's JSON
  245. let scriptContent = Utils.getScripts().at(-1).textContent;
  246.  
  247.  
  248. if (!!location.pathname.match(pyqsRegex)) { // Means we are in 'pyqs' page
  249. const parsedContent = Utils.parseFromScriptPYQ(scriptContent);
  250.  
  251. window.addEventListener('load', function () {
  252. Utils.log("DOMContent has been loaded");
  253. this.setInterval(function () {
  254. try {
  255. Utils.rewritePYQPage(parsedContent.subjects);
  256. } catch(e) {
  257. console.log(e)
  258. }
  259. }, 2000)
  260. });
  261.  
  262. } else if (!!location.pathname.match(notesRegex)) { // Means we are in 'notes' page
  263.  
  264. }
  265. })();