Class101 Subtitle Downloader

Download vtt subtitle from class101.net

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Class101 Subtitle Downloader
// @name:ko      클래스101 자막 다운로더
// @namespace    http://tampermonkey.net/
// @version      1.4
// @description  Download vtt subtitle from class101.net
// @description:ko class101.net에서 vtt자막 다운로드
// @author       Ravenclaw5874
// @match        https://class101.net/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=class101.net
// @grant        GM_registerMenuCommand
// @license      MIT License
// ==/UserScript==

//현재 요소가 몇번째 자식인지 알려줌.
function getIndex(element, childs = element.parentNode.childNodes) {
    for (let i = 0; i < childs.length; i++) {
        if (childs[i] === element) {
            return i;
        }
    }
}

//Xpath로 요소 찾기 확장형
Node.prototype.xpath = function (xpath) {
    return document.evaluate(xpath, this, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
}

function findFirstUniqueValue(arr) {
  // 객체를 사용하여 각 값의 빈도수를 카운트합니다.
  const frequency = arr.reduce((acc, val) => {
    acc[val] = (acc[val] || 0) + 1;
    return acc;
  }, {});

  // 첫 번째로 빈도수가 1인 값을 반환합니다.
  for (let i = 0; i < arr.length; i++) {
    if (frequency[arr[i]] === 1) {
      return arr[i];
    }
  }

  // 유일한 값이 없는 경우 undefined를 반환합니다.
  return undefined;
}

//className을 포함하는 부모를 올라가면서 찾기
function findParentWithClassName(element, className, nth = 1) {
    let parent = element.parentElement;
    let count = 1;

    while (parent) {
        if (parent.classList.contains(className)) {
            // 원하는 className을 가진 부모를 찾았습니다.
            if (nth === count) { return parent; } // n번째 className 부모
            count++; // n번째 부모가 아님.
        }
        parent = parent.parentElement;
    }

    // 원하는 className을 가진 부모를 찾지 못했습니다.
    return null;
}

//파일 이름을 결정. "0101 안녕하세요"
function makeFilename() {
    const css_array = document.xpath("//p[text()='CHAPTER 1']").parentNode.parentNode.parentNode.className;
    const css_current = findFirstUniqueValue(Array.from(document.querySelectorAll(`div.${css_array} > button > div`)).map(e => e.className));

    const current = document.querySelector(`button > div.${css_current}`)
    const chapter = findParentWithClassName(current, css_array, 2);
    const chapter_name = chapter.querySelector("p").textContent;
    const chapters = chapter.parentNode.querySelectorAll(`:scope > div.${chapter.className}`);

    const mainIndex = getIndex(chapter, chapters).toString().padStart(2, '0');
    const subIndex = (getIndex(current.parentNode) + 1).toString().padStart(2, '0');
    const title = current.querySelector("div > div:nth-child(1) > div:nth-child(1) > span").innerText;

    const filename = `${mainIndex}${subIndex} ${title}`;

    return filename;
}

//자막 다운로드
async function downloadSubtitle() {
    const lang = document.documentElement.getAttribute('lang');
    const filename = makeFilename();
    const url = document.querySelector(`track[srclang=${lang}]`).src;

    try {
        const response = await fetch(url);

        if (response.status !== 200) {
            throw new Error(`Unable to download file. HTTP status: ${response.status}`);
        }
        const blob = await response.blob();

        const link = document.createElement('a');
        link.href = URL.createObjectURL(blob);
        link.download = `${filename}.vtt`;

        link.click()
    }
    catch (error) {
        console.error('error:', error.message);
    }
}

(function() {
    'use strict';
    // Your code here...
    const lang = document.documentElement.getAttribute('lang');
    const commandName = navigator.language === 'ko'? '자막 다운로드': 'Download Subtitle';
    GM_registerMenuCommand(commandName, downloadSubtitle)

})();