Skip to content

Instantly share code, notes, and snippets.

@ApoGouv
Last active April 3, 2025 06:56
Show Gist options
  • Save ApoGouv/5590c800009301ae8d531f0f65a633b3 to your computer and use it in GitHub Desktop.
Save ApoGouv/5590c800009301ae8d531f0f65a633b3 to your computer and use it in GitHub Desktop.
Udemy course content info to markdown

Introduction:

This document provides a collection of helper classes and functions designed to streamline the note-taking process while taking a Udemy course. These utilities are focused on extracting and organizing key course information to help you consolidate your notes efficiently. The functionalities include:

  1. UdemyCourseCurriculumExtractor: A class that extracts essential information from a Udemy course landing page, such as the course title, section titles, and subcontent titles. It then formats this data in markdown, allowing you to easily capture and organize your course notes.

  2. UdemyTranscriptCopier: An object that adds a floating button to the page, enabling you to quickly copy the transcript of the currently playing Udemy course to the clipboard. This functionality is useful when you need to capture course transcripts for further note-taking or review.

These helpers are designed to enhance your learning experience by simplifying the process of gathering and organizing course content as you progress through Udemy courses.


/**
 * UdemyCourseCurriculumExtractor
 *
 * A class to extract course title, section titles, and subcontent titles from a Udemy course landing page.
 * It supports chaining of methods and provides functionality to generate and display markdown-formatted output.
 *
 * Usage Example:
 *
 * Visit the course landing page on Udemy, expand the sections list, and run the following code in the browser console:
 *
 * const extractor = new UdemyCourseCurriculumExtractor();
 *
 * @class UdemyCourseCurriculumExtractor
 */
class UdemyCourseCurriculumExtractor {
  constructor() {
    // Define class properties for reusable selectors
    this.courseCarriculumContentSelector = '[data-purpose="course-curriculum"]';
    this.expandCourseCarriculumContentSelector = '[data-purpose="show-more"]';
    this.courseTitleSelector =
      '.course-landing-page__main-content .ud-heading-xxl';
    this.sectionSelector =
      'div[data-purpose="course-curriculum"] [class^="accordion-panel-module--panel--"]';
    this.sectionTitleSelector =
      '.ud-accordion-panel-heading [class^="section--section-title--"]';
    this.subSectionsSelector =
      '.ud-accordion-panel-content ul.ud-block-list > li';
    this.subSectionTitleSelector =
      '.ud-block-list-item-content [class*="section--item-title--"]';

    // Initialize extracted data properties
    this.sectionsData = [];
    this.courseTitle = '';

    // Markdown output container properties
    this.mdOutputContainerID = 'ucde_markdown-output-container';
    this.mdOutputContainer = null;
    this.mdOutputContainerCloseButton = null;
    this.mdOutputContainerPre = null;
    this.mdOutputContainerFooter = null;

    // Early check if the current page is a course landing page
    if (!this.isOnCourseLandingPage()) {
      alert('Not on a course landing page. Skipping further execution of Udemy Course Curriculum Extractor.');
      console.warn('Not on a course landing page. Skipping further extraction.');
      return; // Bail out early if not on the course landing page
    }
    
    if (!this.checkAndExpandCurriculum()) {
      console.warn("Curriculum panel not found. Please ensure you're on the course landing page.");
      return; // Bail out early if carriculum not found
    }

    // If on a valid course landing page, proceed with extraction
    this.extractCourseTitle().extractSectionsData().displayMarkdown();
  }

  /**
   * Checks if we are on the course landing page.
   *
   * @returns {boolean} True if on the course landing page, false otherwise.
   */
  isOnCourseLandingPage() {
    const body = document.body;

    // Check if the page has the relevant body attributes and div
    return (
      body.getAttribute('data-module-id') === 'course-landing-page' &&
      body.id === 'udemy' &&
      document.querySelector('.ud-component--course-landing-page--course-landing-page')
    );
  }

  /**
   * Checks if the curriculum panel exists and expands it if necessary.
   */
  checkAndExpandCurriculum() {
    const curriculumPanel = document.querySelector(this.courseCarriculumContentSelector);
    if (!curriculumPanel) {
      alert("Curriculum panel not found. Please ensure you're on the course landing page.");
      return false;
    }

    const showMoreButton = curriculumPanel.querySelector(this.expandCourseCarriculumContentSelector);
    if (showMoreButton) {
      // If the "show more" button is found, click it to expand the curriculum
      showMoreButton.click();
      alert("Curriculum expanded!");
    } else {
      alert("Curriculum is already fully expanded.");
    }

    return true;
  }

  /**
   * Extracts the course title from the landing page.
   * If the title is not found, sets a default title.
   *
   * @returns {UdemyCourseCurriculumExtractor} The instance for method chaining.
   */
  extractCourseTitle() {
    const courseTitleElement = document.querySelector(this.courseTitleSelector);

    if (courseTitleElement && courseTitleElement.textContent.trim()) {
      this.courseTitle = courseTitleElement.textContent.trim();
    } else {
      console.warn('Course title not found or empty. Setting to default.');
      this.courseTitle = 'Untitled Course';
    }

    return this;
  }

  getSubSectionType(iconType = '') {
    let type = 'unknown';

    if (iconType === '#icon-video' || iconType === '#icon-article') {
        type = 'video';
    } else if (iconType === '#icon-code') {
        type = 'coding';
    } else {
        console.warn(`Unknown subsection type for: "${subSectionTitle}"`);
    }

    return type;
  }

  /**
   * Extracts the sections and subsections from the curriculum.
   * If a section or subsection title is not found, sets a default title.
   *
   * @returns {UdemyCourseCurriculumExtractor} The instance for method chaining.
   */
  extractSectionsData() {
    const sections = document.querySelectorAll(this.sectionSelector);

    if (!sections.length) {
      console.warn('No sections found.');
      return this; // Return early if no sections are found
    }

    sections.forEach((sectionElement) => {
      let sectionTitle = sectionElement
        .querySelector(this.sectionTitleSelector)
        ?.textContent.trim();

      if (!sectionTitle) {
        console.warn('Section title not found. Setting to default.');
        sectionTitle = 'Untitled Section';
      }

      const subSectionsData = [];
      const subSections = sectionElement.querySelectorAll(
        this.subSectionsSelector
      );

      // If no subsections exist, log a warning
      if (!subSections.length) {
        console.warn(`No subsections found for section: "${sectionTitle}"`);
      }

      subSections.forEach((subSection) => {
        const subSectionTitle = subSection
          .querySelector(this.subSectionTitleSelector)
          ?.textContent.trim();

        const svgIcon = subSection.querySelector('svg use');
        const iconType = svgIcon ? svgIcon.getAttribute('xlink:href') : '';
        const type = this.getSubSectionType(iconType);

        if (subSectionTitle) {
          subSectionsData.push({ title: subSectionTitle, type });
        } else {
          console.warn(
            `Subsection title not found in section: "${sectionTitle}". Skipping.`
          );
        }
      });

      // Add the extracted section and its subsections to the data
      this.sectionsData.push({
        sectionTitle,
        subSections: subSectionsData,
      });
    });

    return this;
  }

  /**
   * Retrieves the extracted course data.
   *
   * @returns {Object} The extracted course data.
   */
  getExtractedData() {
    return {
      courseTitle: this.courseTitle,
      sections: this.sectionsData,
    };
  }

  /**
   * Generates a markdown-formatted string for the extracted course data.
   *
   * @returns {string} Markdown-formatted course data.
   */
  generateMarkdown() {
    if (!this.courseTitle || !this.sectionsData.length) {
      console.warn('No data available to generate markdown.');
      return 'No data available.';
    }

    let markdownContent = `# ${this.courseTitle}\n\n## Course content\n\n`;

    // Continuous numbering counter for Videos and Coding exercises
    let subVideoSectionCounter = 0;
    let subCodingSectionCounter = 0;

    this.sectionsData.forEach((section, index) => {
      // Section header
      markdownContent += `### Section ${index + 1}: ${
        section.sectionTitle
      }\n\n`;

      section.subSections.forEach((subSection, subIndex) => {
        // Subsection header
        let mdSubSectionTitle = `#### (${index + 1}.${
                subIndex + 1
            }). ${subSection.title}\n\n`;
        
        if (subSection.type === 'video') {
            subVideoSectionCounter++;
            mdSubSectionTitle = `#### ${subVideoSectionCounter}. (${index + 1}.${
                subIndex + 1
            }) ${subSection.title}\n\n`;
        } else if (subSection.type === 'coding') {
            subCodingSectionCounter++;
            mdSubSectionTitle = `#### Coding Exercise ${subCodingSectionCounter}: (${index + 1}.${
                subIndex + 1
            }) ${subSection.title}\n\n`;
        }

        // Subsection title with both continuous and hierarchical numbering
        markdownContent += mdSubSectionTitle;
      });
      markdownContent += '\n---\n\n'; // Add separator between sections
    });

    return markdownContent;
  }

  /**
   * Displays the markdown-formatted course data in a floating container.
   */
  displayMarkdown() {
    const markdown = this.generateMarkdown();

    this.removeExistingContainer()
      .createMarkdownDisplayContainer()
      .createCloseButton()
      .createPre(markdown)
      .createFooter(markdown);

    // Append elements
    this.mdOutputContainer.appendChild(this.mdOutputContainerCloseButton);
    this.mdOutputContainer.appendChild(this.mdOutputContainerPre);
    this.mdOutputContainer.appendChild(this.mdOutputContainerFooter);
    document.body.appendChild(this.mdOutputContainer);
  }

  /**
   * Removes the existing markdown output container if it exists.
   *
   * @returns {UdemyCourseCurriculumExtractor} The instance for method chaining.
   */
  removeExistingContainer() {
    const existingContainer = document.getElementById(this.mdOutputContainerID);
    if (existingContainer) {
      existingContainer.remove();
    }

    return this;
  }

  /**
   * Creates the markdown display container.
   *
   * @returns {UdemyCourseCurriculumExtractor} The instance for method chaining.
   */
  createMarkdownDisplayContainer() {
    this.mdOutputContainer = document.createElement('div');
    this.mdOutputContainer.id = this.mdOutputContainerID;
    this.mdOutputContainer.style = `
      position: fixed;
      bottom: 20px;
      right: 20px;
      width: 400px;
      max-height: 500px;
      overflow-y: auto;
      padding: 10px;
      background-color: rgba(0, 0, 0, 0.85);
      color: #ffffff;
      font-family: monospace;
      font-size: 14px;
      border-radius: 8px;
      box-shadow: 0 0 10px rgba(0, 0, 0, 0.5);
      z-index: 1000;
      display: flex;
      flex-direction: column;
    `;

    return this;
  }

  /**
   * Creates a close button for the markdown container.
   * @param {HTMLElement} container - The markdown display container.
   * @returns {HTMLButtonElement} The created close button.
   */
  createCloseButton() {
    this.mdOutputContainerCloseButton = document.createElement('button');
    this.mdOutputContainerCloseButton.innerText = '×';
    this.mdOutputContainerCloseButton.style = `
      position: absolute;
      top: 0px;
      right: 0px;
      border: none;
      background: #a93c3ca6;
      color: rgb(255, 255, 255);
      font-size: 18px;
      cursor: pointer;
      width: 20px;
      height: 20px;
      border-radius: 8px 8px 8px 50px;
    `;

    this.mdOutputContainerCloseButton.addEventListener('click', () => {
      this.mdOutputContainer.remove();
    });

    return this;
  }

  createPre(markdown) {
    // Create a preformatted text area for displaying markdown
    this.mdOutputContainerPre = document.createElement('pre');
    this.mdOutputContainerPre.style = `
      overflow: auto;
      max-height: 420px;
      padding: 10px;
      background-color: rgba(0, 0, 0, 0.9);
      color: rgb(255, 255, 255);
      font-family: monospace;
      font-size: 14px;
      border-radius: 4px;
      white-space: pre-wrap;
      overflow-wrap: break-word;
      margin: 10px 0px 10px;
    `;

    this.mdOutputContainerPre.textContent = markdown;

    return this;
  }

  /**
   * Creates the fixed footer section with a title and a copy button.
   * @param {string} markdown - The markdown content to copy.
   * @returns {HTMLDivElement} The created footer element.
   */
  createFooter(markdown) {
    this.mdOutputContainerFooter = document.createElement('div');
    this.mdOutputContainerFooter.style = `
      position: sticky;
      bottom: 0px;
      left: 0px;
      width: 100%;
      padding: 8px;
      background-color: rgba(0, 0, 0, 0.9);
      border-top: 1px solid rgba(255, 255, 255, 0.2);
      display: flex;
      justify-content: space-between;
      align-items: center;
    `;

    // Title
    const mdContainerFooterTitle = document.createElement('span');
    mdContainerFooterTitle.innerText = '▶️ Markdown Output';
    mdContainerFooterTitle.style = `
      font-size: 12px;
      color: rgb(221, 221, 221);
    `;

    // Copy button
    const mdContainerFooterCopyButton = document.createElement('button');
    mdContainerFooterCopyButton.innerText = '📋 Copy Markdown';
    mdContainerFooterCopyButton.style = `
      font-size: 12px;
      color: rgb(255, 255, 255);
      border: none;
      border-radius: 4px;
      padding: 8px 12px;
      font-weight: bold;
      background: rgb(86, 36, 208);
      cursor: pointer;
      box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
      opacity: 0.9;
      transition: opacity 0.2s;
    `;

    // Click event to copy markdown to clipboard
    mdContainerFooterCopyButton.addEventListener('click', () => {
      this.copyToClipboard(markdown);
    });

    this.mdOutputContainerFooter.appendChild(mdContainerFooterTitle);
    this.mdOutputContainerFooter.appendChild(mdContainerFooterCopyButton);

    return this;
  }

  /**
   * Copies the given text to the clipboard.
   * @param {string} text - The text to copy.
   */
  copyToClipboard(text) {
    navigator.clipboard
      .writeText(text)
      .then(() => {
        alert('Markdown copied to clipboard!');
      })
      .catch((err) => {
        console.error('Failed to copy markdown:', err);
      });
  }
}

// Usage
const extractor = new UdemyCourseCurriculumExtractor();

/**
 * UdemyTranscriptCopier is an object responsible for creating a floating button
 * that copies the transcript of the currently playing Udemy course to the clipboard.
 * It requires that the transcript panel is open!
 */
const UdemyTranscriptCopier = {
  button: null,

  /**
   * Initializes the script by creating the floating button.
   * This method is called when the script is first loaded.
   */
  init() {
    this.createFloatingButton();
  },

  /**
   * Creates a floating button that, when clicked, will copy the transcript to the clipboard.
   * The button is styled and appended to the body of the page.
   */
  createFloatingButton() {
    this.button = document.createElement('button');
    this.button.innerText = '📋 Copy Transcript';
    this.button.style = `
        position: fixed;
        bottom: 20px;
        right: 20px;
        padding: 10px 15px;
        font-size: 16px;
        font-weight: bold;
        background: rgb(86, 36, 208);
        color: white;
        border: none;
        border-radius: 5px;
        cursor: pointer;
        box-shadow: 0px 4px 6px rgba(0, 0, 0, 0.1);
        z-index: 9999;
        opacity: 0.9;
        transition: opacity 0.2s;
      `;

    this.button.onmouseover = () => (this.button.style.opacity = '1');
    this.button.onmouseout = () => (this.button.style.opacity = '0.9');
    this.button.onclick = () => this.copyTranscript();

    document.body.appendChild(this.button);
  },

  /**
   * Copies the transcript text from the currently open transcript panel to the clipboard.
   * The transcript is formatted with the lecture title and the individual cue texts.
   * If no transcript panel is found or the panel is empty, an alert is shown.
   */
  copyTranscript() {
    const transcriptPanel = document.querySelector(
      '[data-purpose="transcript-panel"]'
    );
    if (!transcriptPanel) {
      alert("Transcript panel not found! Make sure it's open.");
      return;
    }

    const transcriptTexts = Array.from(
      transcriptPanel.querySelectorAll('[data-purpose="cue-text"]')
    )
      .map((el) => el.innerText.trim())
      .filter((text) => text.length > 0)
      .join('\n');

    if (!transcriptTexts) {
      alert('No transcript text found.');
      return;
    }

    const activeLectureTitleElement = document.querySelector(
      '.ud-accordion-panel-content > ul > li[aria-current="true"] [data-purpose="item-title"]'
    );
    const lectureTitle = activeLectureTitleElement
      ? activeLectureTitleElement.innerText.trim()
      : 'Untitled Lecture';

    const finalText = `## ${lectureTitle}\n\n${transcriptTexts}`;

    navigator.clipboard
      .writeText(finalText)
      .then(() => alert('Transcript copied!'))
      .catch(() => alert('Failed to copy transcript.'));
  },
};

// Initialize the script
UdemyTranscriptCopier.init();

/**
 * We can use the copied sub-section tile and transcript with an AI to help us generate notes of the sub-section. 
 * Here is sample prompt for this task:
    You are NotesTaker 2.2, an AI assistant specialized in creating structured, concise, and easy-to-understand notes from Udemy course transcripts.  

    ## **Instructions:**  
    - Each section begins with `##`, followed by the Udemy course sub-section (video) title and its transcript.  
    - Your task is to generate structured notes that summarize **all key concepts, explanations, and important details** from the transcript.  

    ## **Formatting Guidelines:**  
    1. Use **GitHub-supported Markdown** for structure and clarity:  
       - **Headings (`##`, `###`)** to organize topics.  
       - **Bullet points (`-`)** for listing key takeaways and steps.  
       - **Bold (`**bold**`)** for key terms and definitions.  
       - **Italics (`*italics*`)** for emphasis where needed.  
       - **Code blocks (```)** for Java examples, syntax, and best practices.  
       - **Tables (if needed)** for comparisons (e.g., primitive vs. reference types).  
    2. The **sub-section title** will always be marked with `##`. Do not modify it.  
    3. **Be concise but ensure completeness**—summarize effectively without omitting any key explanations.  
    4. **Preserve the order of concepts as presented in the transcript** to maintain logical flow.  
    5. **Adapt explanations to be clear for both new learners and those revising.**  
    6. **End each set of notes with:**"End_of_Notes"**

    You are NotesTaker 2.0, an AI assistant skilled at creating structured, concise, and easy-to-understand notes for Udemy courses transcripts. When provided with with "##", it will be followed by a Udemy course sub-section (video) title and it's transcript, so use them as input, and your task is to:

    1. Generate advanced notes summarizing the key concepts, explanations, and important details from the section.
    2. Use any GitHub supported markdown markup to separate, emphasize, group and structure the notes.
    3. Format notes clearly, using headings when necessary for better organization, taking into account that the sub-section title will start with a heading 2 markup (##).
    4. Stay strictly within the provided content, without adding external information or assumptions.
    5. End each set of notes with "End_of_Notes" to indicate completion.
    
    ## **Additional Notes for Java Topics:**  
    - Include **code examples** whenever they are given.  
    - Summarize **key Java concepts** like OOP principles, memory management, best practices.  
    - Use **comments (`//`) in code blocks** when helpful.

 * Then start pasting you copied text and enjoy good notes/summaries of the sub-sections.
 */
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment