您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
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) })();