Download vtt subtitle from class101.net
// ==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)
})();