KIIT-Connect Unlocker

Unlock kiitconnect for free

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         KIIT-Connect Unlocker
// @namespace    https://github.com/rohitaryal/kiitconnect-unlocker
// @version      3.0
// @description  Unlock kiitconnect for free
// @author       rohitaryal
// @match        https://www.kiitconnect.com/academic/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=kiitconnect.com
// @grant        none
// @license      MIT
// ==/UserScript==

const debugMode = true;

const driveFile = "https://drive.google.com/file/d/";
const driveFolder = "https://drive.google.com/drive/folders/";
const youtubePlaylist = "https://www.kiitconnect.com/player?playlistId=";

const pyqsRegex = /academic\/PYQS\/Content\/PYQS\-(cse|csse|csce|it)\-[1-7]/;
const notesRegex = /academic\/PYQS\/Content\/Notes\-(cse|csse|csce|it)\-[1-7]/;

class Utils {
  constructor() { }
  /*
   * Get all script that doesn't have src attribute
   */
  static getScripts() {
    let scriptElements = document.querySelectorAll("script:not([src])");
    return [...scriptElements];
  }

  /*
   * Copy item to clipboard
   */
  static copy(text) {
    document.body.focus();
    navigator.clipboard.writeText(text);
  }

  /*
   * Logger for debug mode
   */
  static log(text) {
    if (debugMode) {
      console.log(text)
    }
  }

  /*
   * Clone node and remove its junk and replace with new one
   */
  static replaceNode(node) {
    if (!node) {
      throw new Error("Invalid node provided to clone");
    }

    const newNode = node.cloneNode(true);
    node.replaceWith(newNode);

    return newNode;
  }

  /*
   * Re-Serialize the json in our own way (For PYQS)
   */
  static parsePYQ(data) {
    if (typeof data !== 'object') {
      throw new Error(`Expected 'object' but obtained '${typeof data}' while parsing`);
    }

    // Skeleton for subjects pyq, syllabus + some extra detail holder
    let pyqDetails = {
      semester: data.semeseter, // Yes, thats the actual spelling
      branch: data.stream,
      subjects: [],
    };

    pyqDetails.subjects = data.data.semesters[0].subjects.map((subject) => {
      // Skeleton for subject detail holder
      let subDetails = {
        subjectName: subject.name,
        subjectCode: subject.SUBCODE,
        folder: driveFolder + subject.folderId, // Makes an accessable drive folder link
        subjectSyllabus: driveFile + subject.syllabus, // Makes an accessable drive file link
        playlist: [],
        papers: [],
      };

      // Parse individual playlist
      subDetails.playlist = subject.youtubePlaylist.map((playlist) => {
        return {
          id: playlist.id,
          title: playlist.title,
          videoCount: playlist.noOfVides,
          videoLink: `${youtubePlaylist}id&playlist=${playlist.title}`
        };
      });

      // Parse individual pyqs
      subDetails.papers = subject.pyqs.map((paper) => {
        return {
          name: paper.name,
          year: Number(paper.year) || 0,
          solution: driveFile + paper.solution, // Makes an accessable file link
          question: driveFile + paper.Question, // Same
        }
      });

      // Sort papers based on years
      subDetails.papers = subDetails.papers.sort((a, b) => a.year - b.year);

      return subDetails;
    });

    return pyqDetails;
  }

  /*
   * Re-Serialize the json in our own way (For Notes)
   */
  static parseNotes(data) {

  }

  /*
   * Parse script content (For PYQS)
   */
  static parseFromScriptPYQ(scriptContent) {
    // Pre-process and beautufy
    scriptContent = scriptContent.replaceAll(`\\"`, `"`);
    scriptContent = scriptContent.replaceAll(`\\n`, ``);

    const parsableContent = scriptContent.slice(
      scriptContent.indexOf(`self.__next_f.push([1,"1e:`) + 26, // <-- 26 is the length of this
      scriptContent.lastIndexOf(`"])`)                          //     string itself.
    );

    const jsonContent = JSON.parse(parsableContent)[3].children[3];

    return this.parsePYQ(jsonContent);
  }

  /*
   * Parse script content (For Notes)
   */
  static parseFromScriptNotes(scriptContent) {

  }

  /*
   * Replace pyq table + headings with from our own serialized form
   */
  static rewritePYQPage(subjectArray) {
    if (typeof subjectArray !== 'object') {
      throw new Error(`Expected 'object' but obtained '${typeof subjectArray}' while rewriting`);
    }

    // After successful injection head will contain this classlist
    // which is an indicator to stop further injection
    if(document.head.classList.contains("injected")) {
      return;
    }

    Utils.log("Re-writing the pyq page.")

    const parentContainer = [...document.querySelector("main div div").childNodes].slice(1);
    
    for (let i = 0; i < parentContainer.length; i++) {
      let totalRows = parentContainer[i].querySelectorAll("table tbody").length; // Get count of rows
      for (let j = 0; j < subjectArray.length; j++) {
          let totalPapers = subjectArray[j].papers.length;
  
          if (totalRows === totalPapers) {
              let temp = subjectArray[j];
              subjectArray[j] = subjectArray[i];
              subjectArray[i] = temp;
              break;
          }
      }
  }
  

    for (let i = 0; i < subjectArray.length; i++) {
      const subject = subjectArray[i];
      const parent = parentContainer[i];
      const [titleDiv, tableContainer] = parent.childNodes;

      const syllabusButton = titleDiv.querySelector("a");
      syllabusButton.href = subject.subjectSyllabus;

      // New button with folder link
      const folderButton = syllabusButton.cloneNode(true);
      folderButton.innerText = "Open Folder";
      folderButton.href = subject.folder;

      
      // Modify title (middle one is TEXT_NODE)
      const title = titleDiv.childNodes[1];
      title.textContent = subject.subjectName;
      
      // Modify the table
      const tableRows = tableContainer.querySelectorAll("table tbody");

      for (let j = 0; j < tableRows.length; j++) {
        const row = tableRows[j];
        const paper = subject.papers[j];

        const td = row.querySelectorAll("td:not(.hidden)");

        // Year of paper
        td[0].textContent = paper.year;

        // Link to question paper
        let paperAnchor = td[1].querySelector("a");
        paperAnchor = this.replaceNode(paperAnchor);

        paperAnchor.textContent = paper.name;
        paperAnchor.href = paper.question;
        paperAnchor.classList.add("text-cyan-500");
        paperAnchor.classList.remove("text-yellow-500", "cursor-not-allowed");

        // Link to solution paper
        let solutionAnchor = td[2].querySelector("a");

        // Since solution is the last element
        if(!solutionAnchor)
          continue;

        solutionAnchor = this.replaceNode(solutionAnchor);

        if (solutionAnchor.classList.contains("text-gray-400")) {
          solutionAnchor.textContent = "Not Available";
        } else {
          solutionAnchor.classList.add("text-cyan-500");
          solutionAnchor.classList.remove("text-yellow-500", "cursor-not-allowed");

          solutionAnchor.href = paper.solution
        }
      }
    }

    // Mark as succrssful injection
    document.head.classList.add("injected");
  }
}

(function () {
  'use strict';
  // Last script will always contain the paper's JSON
  let scriptContent = Utils.getScripts().at(-1).textContent;


  if (!!location.pathname.match(pyqsRegex)) { // Means we are in 'pyqs' page
    const parsedContent = Utils.parseFromScriptPYQ(scriptContent);

    window.addEventListener('load', function () {
      Utils.log("DOMContent has been loaded");
      this.setInterval(function () {
        try {
          Utils.rewritePYQPage(parsedContent.subjects);
        } catch(e) {
          console.log(e)
        }
      }, 2000)
    });

  } else if (!!location.pathname.match(notesRegex)) { // Means we are in 'notes' page

  }
})();