您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Extracts information from a YouTube video and creates a new entry in Obsidian (locally), making it easier to create notes about the video.
- // ==UserScript==
- // @name YouTube | Send to Obsidian
- // @description Extracts information from a YouTube video and creates a new entry in Obsidian (locally), making it easier to create notes about the video.
- // @name:az YouTube | Obsidian'a Göndər
- // @description:az YouTube videosundan məlumat çıxarır və yeni bir qeydi Obsidian'da yaradır, videolar haqqında qeydləri asanlaşdırır.
- // @name:sq YouTube | Dërgo në Obsidian
- // @description:sq Nxjerr informacion nga një video në YouTube dhe krijon një regjistrim të ri në Obsidian (lokalisht), duke lehtësuar krijimin e shënimeve për videon.
- // @name:am YouTube | እቢዲያን ውስጥ ላክ
- // @description:am ከYouTube ቪዲዮ መረጃ ይላቀቀዋል እና አዲስ መዝገብ በ Obsidian (በእርሱ) ውስጥ ይፈጥራል፣ እንደዚህ እያለችን የቪዲዮውን ማስታወሻዎችን ቀላል አድርጎአል።
- // @name:en YouTube | Send to Obsidian
- // @description:en Extracts information from a YouTube video and creates a new entry in Obsidian (locally), simplifying note-taking for videos.
- // @name:ar YouTube | إرسال إلى Obsidian
- // @description:ar يستخرج المعلومات من فيديو YouTube وينشئ مدخلاً جديدًا في Obsidian (محليًا)، مما يسهل تدوين الملاحظات حول الفيديو.
- // @name:hy YouTube | Ուղարկել Obsidian-ում
- // @description:hy Վերահանում է տեղեկությունը YouTube վիդեոյից և ստեղծում նոր գրառում Obsidian-ում (տեղայնացված), պարզեցնելով վիդեոյի նշումների ստեղծումը.
- // @name:af YouTube | Stuur na Obsidian
- // @description:af Haal inligting uit 'n YouTube-video uit en skep 'n nuwe inskrywing in Obsidian (plaaslik), wat die maak van aantekeninge oor video's vereenvoudig.
- // @name:eu YouTube | Bidali Obsidian-era
- // @description:eu YouTube bideo batetik informazioa ateratzen du eta sarrera berri bat sortzen du Obsidian-en (tokian), bideoen oharrak sortzea erraztuz.
- // @name:be YouTube | Адправіць у Obsidian
- // @description:be Выцягвае інфармацыю з відэа на YouTube і стварае новую запіс у Obsidian (лакальна), палягчаючы стварэнне нататак пра відэа.
- // @name:bn YouTube | Obsidian-এ পাঠান
- // @description:bn YouTube ভিডিও থেকে তথ্য সংগ্রহ করে এবং Obsidian-এ নতুন এন্ট্রি তৈরি করে (স্থানীয়ভাবে), ভিডিওর নোট তৈরি সহজতর করে।
- // @name:my YouTube | Obsidian သို့ပို့ပါ
- // @description:my YouTube ဗီဒီယိုမှအချက်အလက်ကိုရယူပြီး Obsidian တွင်အသစ်သောအချက်အလက်ကိုဖန်တီးသည် (ဒေသတွင်း), ဗီဒီယိုမှတ်စုများကိုလွယ်ကူစေသည်။
- // @name:bg YouTube | Изпращане в Obsidian
- // @description:bg Извлича информация от видеоклип в YouTube и създава нов запис в Obsidian (локално), улеснявайки създаването на бележки за видеото.
- // @name:bs YouTube | Pošaljite u Obsidian
- // @description:bs Izvlači informacije iz YouTube videa i kreira novi unos u Obsidian (lokalno), olakšavajući kreiranje bilješki o videu.
- // @name:cy YouTube | Anfon i Obsidian
- // @description:cy Yn tynnu gwybodaeth o fideo YouTube ac yn creu cofnod newydd yn Obsidian (yn lleol), gan symleiddio creu nodiadau ar gyfer fideos.
- // @name:hu YouTube | Küldés Obsidianba
- // @description:hu Információt nyer ki egy YouTube videóból, és új bejegyzést hoz létre Obsidianban (helyileg), egyszerűsítve a videók megjegyzéseinek létrehozását.
- // @name:vi YouTube | Gửi đến Obsidian
- // @description:vi Trích xuất thông tin từ video YouTube và tạo một mục mới trong Obsidian (cục bộ), đơn giản hóa việc ghi chú về video.
- // @name:gl YouTube | Enviar a Obsidian
- // @description:gl Extrae información dun vídeo de YouTube e crea unha nova entrada en Obsidian (localmente), simplificando a creación de notas sobre o vídeo.
- // @name:el YouTube | Αποστολή στο Obsidian
- // @description:el Εξάγει πληροφορίες από ένα βίντεο στο YouTube και δημιουργεί μια νέα καταχώριση στο Obsidian (τοπικά), απλοποιώντας τη δημιουργία σημειώσεων για βίντεο.
- // @name:ka YouTube | გაგზავნა Obsidian-ში
- // @description:ka იყენებს ინფორმაციას YouTube ვიდეოდან და ქმნის ახალ ჩანაწერს Obsidian-ში (ადგილობრივად), რაც ამარტივებს ვიდეოზე შენიშვნების შექმნას.
- // @name:gu YouTube | Obsidian પર મોકલો
- // @description:gu YouTube વિડિયોમાંથી માહિતી કાઢે છે અને Obsidian (સ્થાનિક રીતે) માં નવો એન્ટ્રી બનાવે છે, વિડિયોના નોંધ બનાવવી સરળ બનાવે છે.
- // @name:da YouTube | Send til Obsidian
- // @description:da Uddrager oplysninger fra en YouTube-video og opretter en ny post i Obsidian (lokalt), hvilket gør det nemmere at oprette noter om videoen.
- // @name:zu YouTube | Thumela ku-Obsidian
- // @description:zu Ukhipha ulwazi kuvidiyo ye-YouTube bese edala irekhodi elisha ku-Obsidian (endaweni), okwenza kube lula ukudala amanothi wevidiyo.
- // @name:he YouTube | שלח לאובסידיאן
- // @description:he שולף מידע מתוך סרטון YouTube ויוצר ערך חדש ב-Obsidian (מקומית), מה שמקל על יצירת הערות עבור סרטונים.
- // @name:ig YouTube | Zipu na Obsidian
- // @description:ig Na-ewepụta ozi sitere na vidiyo YouTube wee mepụta ndekọ ọhụrụ na Obsidian (n'ebe), na-eme ka ọ dị mfe ịmepụta ndetu maka vidiyo.
- // @name:yi YouTube | שיקן צו Obsidian
- // @description:yi דערקלערט אינפֿאָרמאַציע פון אַ יאָוטובע ווידעא און שאַפֿט אַ נייַע איינסן אין Obsidian (אָרטלעך), סימפּליפיינג די שאַפונג פון טאָן וועגן ווידעא.
- // @name:id YouTube | Kirim ke Obsidian
- // @description:id Menarik informasi dari video YouTube dan membuat entri baru di Obsidian (lokal), menyederhanakan pembuatan catatan untuk video.
- // @name:ga YouTube | Seol chuig Obsidian
- // @description:ga Bainfidh eolas as físeán YouTube agus cruthaíonn sé iontráil nua in Obsidian (go háitiúil), ag éascú cruthú nótaí faoi fhíseáin.
- // @name:is YouTube | Senda til Obsidian
- // @description:is Dregur upplýsingar úr YouTube myndbandi og býr til nýjan þátt í Obsidian (staðbundið), sem auðveldar gerð athugasemda um myndbönd.
- // @name:es YouTube | Enviar a Obsidian
- // @description:es Extrae información de un video de YouTube y crea una nueva entrada en Obsidian (localmente), simplificando la creación de notas sobre el video.
- // @name:it YouTube | Invia a Obsidian
- // @description:it Estrae informazioni da un video YouTube e crea una nuova voce in Obsidian (localmente), semplificando la creazione di appunti sui video.
- // @name:kn YouTube | Obsidian ಗೆ ಕಳುಹಿಸು
- // @description:kn YouTube ವೀಡಿಯೋದಿಂದ ಮಾಹಿತಿಯನ್ನು ಹೊರತೆಗೆದು Obsidian ನಲ್ಲಿ ಹೊಸ ದಾಖಲೆ ಸೃಷ್ಟಿಸುತ್ತದೆ (ಸ್ಥಳೀಯವಾಗಿ), ವೀಡಿಯೋಗಳ ಕುರಿತು ಟಿಪ್ಪಣಿಗಳನ್ನು ಸರಳಗೊಳಿಸುತ್ತದೆ.
- // @name:fr YouTube | Envoyer vers Obsidian
- // @description:fr Extrait des informations d'une vidéo YouTube et crée une nouvelle entrée dans Obsidian (localement), simplifiant la prise de notes pour les vidéos.
- // @name:ja YouTube | Obsidianに送信
- // @description:ja YouTubeビデオから情報を抽出し、Obsidianに新しいエントリを作成して、ビデオに関するノート作成を簡単にします。
- // @name:ko YouTube | Obsidian으로 보내기
- // @description:ko YouTube 동영상에서 정보를 추출하고 Obsidian에 새 항목을 생성하여 동영상 메모 작성 작업을 단순화합니다.
- // @name:pt YouTube | Enviar para o Obsidian
- // @description:pt Extrai informações de um vídeo do YouTube e cria uma nova entrada no Obsidian (localmente), simplificando a criação de anotações sobre o vídeo.
- // @name:pl YouTube | Wyślij do Obsidian
- // @description:pl Wyciąga informacje z filmu YouTube i tworzy nowy wpis w Obsidian (lokalnie), ułatwiając tworzenie notatek o filmach.
- // @name:fa YouTube | ارسال به Obsidian
- // @description:fa اطلاعات را از ویدئوی یوتیوب استخراج کرده و یک ورودی جدید در Obsidian (محلی) ایجاد میکند، و یادداشتبرداری برای ویدئو را سادهتر میسازد.
- // @name:ps YouTube | Obsidian ته ولیږئ
- // @description:ps د یوټیوب ویډیو څخه معلومات راوباسي او په Obsidian (محلي) کې نوی ریکارډ جوړوي، د ویډیو یادداشتونو جوړولو کار اسانوي.
- // @name:pt-BR YouTube | Enviar para o Obsidian
- // @description:pt-BR Extrai informações de um vídeo do YouTube e cria uma nova entrada no Obsidian (localmente), simplificando a criação de anotações sobre o vídeo.
- // @name:pa YouTube | Obsidian ਨੂੰ ਭੇਜੋ
- // @description:pa YouTube ਵੀਡੀਓ ਤੋਂ ਜਾਣਕਾਰੀ ਕੱਢਦਾ ਹੈ ਅਤੇ Obsidian ਵਿੱਚ ਨਵੀਂ ਐਂਟਰੀ ਬਣਾਉਂਦਾ ਹੈ (ਸਥਾਨਕ), ਵੀਡੀਓ ਨੋਟਾਂ ਬਣਾਉਣ ਨੂੰ ਸੌਖਾ ਬਣਾਉਂਦਾ ਹੈ.
- // @name:ro YouTube | Trimite în Obsidian
- // @description:ro Extrage informații dintr-un videoclip YouTube și creează o nouă intrare în Obsidian (local), simplificând crearea de note despre videoclip.
- // @name:ru YouTube | Отправить в Obsidian
- // @description:ru Извлекает информацию из видеоролика на YouTube и создает новую запись в Obsidian (локально), упрощая создание заметок о видео.
- // @name:sv YouTube | Skicka till Obsidian
- // @description:sv Extraherar information från en YouTube-video och skapar ett nytt inlägg i Obsidian (lokalt), vilket förenklar anteckningar om videon.
- // @name:ta YouTube | Obsidianக்கு அனுப்பு
- // @description:ta YouTube வீடியோவிலிருந்து தகவலை எடுத்து Obsidian இல் புதிய பதிவை உருவாக்குகிறது (உள்ளூரில்), வீடியோவுக்கான குறிப்புகளை எளிதாக்குகிறது.
- // @name:th YouTube | ส่งไปที่ Obsidian
- // @description:th ดึงข้อมูลจากวิดีโอ YouTube และสร้างรายการใหม่ใน Obsidian (ในเครื่อง) เพื่อช่วยให้ง่ายขึ้นในการจดบันทึกเกี่ยวกับวิดีโอ
- // @name:tr YouTube | Obsidian'a Gönder
- // @description:tr YouTube videosundan bilgi alır ve Obsidian'da yeni bir giriş oluşturur (yerel olarak), video notlarını oluşturmayı kolaylaştırır.
- // @name:uk YouTube | Відправити в Obsidian
- // @description:uk Витягує інформацію з відео на YouTube і створює новий запис в Obsidian (локально), спрощуючи створення нотаток про відео.
- // @name:ur YouTube | Obsidian میں بھیجیں
- // @description:ur یوٹیوب ویڈیو سے معلومات نکالتا ہے اور Obsidian میں ایک نیا اندراج تخلیق کرتا ہے (مقامی طور پر)، ویڈیو کے بارے میں نوٹ لینے کو آسان بناتا ہے.
- // @name:uz YouTube | Obsidian-ga yuborish
- // @description:uz YouTube videodan ma'lumot chiqaradi va Obsidian-da yangi yozuv yaratadi (mahalliy), videoga eslatmalar yozishni osonlashtiradi.
- // @name:fi YouTube | Lähetä Obsidianille
- // @description:fi Hakee tietoa YouTube-videosta ja luo uuden merkinnän Obsidianissa (paikallisesti), yksinkertaistaen muistiinpanojen luomista videosta.
- // @name:fr YouTube | Envoyer vers Obsidian
- // @description:fr Extrait des informations d'une vidéo YouTube et crée une nouvelle entrée dans Obsidian (localement), simplifiant la prise de notes pour les vidéos.
- // @name:fy YouTube | Stjoer nei Obsidian
- // @description:fy Ekstraheert ynformaasje fan in YouTube-fideo en makket in nije ynfier yn Obsidian (lokaal), wat it notearjen oer de fideo makliker makket.
- // @name:ha YouTube | Aika zuwa Obsidian
- // @description:ha Yana cire bayanai daga bidiyon YouTube kuma yana ƙirƙirar sabon shigarwa a cikin Obsidian (lokal), yana sauƙaƙa rubuta bayanai game da bidiyon.
- // @name:hi YouTube | ओब्सीडियन में भेजें
- // @description:hi YouTube वीडियो से जानकारी निकालता है और Obsidian में एक नई प्रविष्टि बनाता है (स्थानीय रूप से), जिससे वीडियो पर नोट्स बनाना आसान हो जाता है.
- // @name:hr YouTube | Pošalji u Obsidian
- // @description:hr Izvlači informacije iz YouTube videozapisa i stvara novi unos u Obsidianu (lokalno), olakšavajući bilježenje o videu.
- // @name:cs YouTube | Odeslat do Obsidianu
- // @description:cs Extrahuje informace z YouTube videa a vytvoří nový záznam v Obsidianu (lokálně), což zjednodušuje vytváření poznámek k videu.
- // @name:sv YouTube | Skicka till Obsidian
- // @description:sv Extraherar information från en YouTube-video och skapar ett nytt inlägg i Obsidian (lokalt), vilket förenklar anteckningar om videon.
- // @name:sn YouTube | Tumira ku Obsidian
- // @description:sn Inobvisa ruzivo kubva kuYouTube vhidhiyo uye inogadzira rekodhi itsva muObsidian (panzvimbo), zvichiita kuti chinyorwa nezvevhidhiyo zvive nyore kuita.
- // @name:eo YouTube | Sendi al Obsidian
- // @description:eo Ekstraktas informojn el YouTube-video kaj kreas novan eniron en Obsidian (loke), simpligante notadon pri la video.
- // @name:et YouTube | Saada Obsidiansse
- // @description:et Ekstraheerib teavet YouTube'i videost ja loob uue kirje Obsidians (kohapeal), muutes videot puudutavate märkmete tegemise lihtsamaks.
- // @name:jv YouTube | Kirim menyang Obsidian
- // @description:jv Ngekstrak informasi saka video YouTube lan nggawe entri anyar ing Obsidian (lokal), nyederhanakake nggawe cathetan babagan video.
- // @name:ja YouTube | Obsidianに送信
- // @description:ja YouTubeビデオから情報を抽出し、Obsidianに新しいエントリを作成して、ビデオに関するノート作成を簡単にします。
- // @version 1.0.0
- // @match https://www.youtube.com/watch?*
- // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com
- // @grant GM_addStyle
- // @noframes
- // @namespace https://maksymstoianov.com/
- // @supportURL https://maksymstoianov.com/
- // @contributionURL https://maksymstoianov.com/
- // @author Maksym Stoianov
- // @developer Maksym Stoianov
- // @license MIT
- // @compatible chrome
- // @compatible firefox
- // @compatible opera
- // @compatible safaricom
- // ==/UserScript==
- (function () {
- 'use strict';
- class Obsidian {
- static preloadImages(urls) {
- const images = [];
- urls.forEach(url => {
- const img = new Image();
- img.src = url;
- images.push(img);
- });
- }
- /**
- * @param {string} input
- * @returns {string}
- */
- static sanitizeTitle(input) {
- return (input.replace(/[:\/\\^|#]/g, ".") ?? "");
- }
- static merge(message = "", fields = {}, ...args) {
- return message.replace(/{{([^}]+?)}}/g, (match, p1) => {
- try {
- let key, defaultValue, format;
- if (p1.includes(":")) {
- const parts = p1
- .split(/(?<!\\):/)
- .map((part) => part.replace(/\\:/g, ":"));
- // {{key:defaultValue:format}}
- [key, defaultValue, ...format] = parts;
- format = (format.length ? format.join(":") : null);
- if (typeof format === "string" && !format.length) {
- format = null;
- }
- } else {
- // {{key}}
- key = p1;
- }
- // Получаем значение из fields или используем defaultValue, если значение отсутствует или пусто
- let value = fields[key];
- if (value === undefined || value === null || value === "") {
- value = defaultValue ?? "";
- }
- if (value instanceof Date) {
- value = this.formatDate(value, format ?? "yyyy-MM-dd");
- }
- else if (["string", "number"].includes(typeof value)) {
- if (defaultValue === "" && value === "") {
- value = match.replace(/:/g, "");
- } else if (this.isNumberLike(value)) {
- value = Number(value);
- }
- value = this.sprintf(format ?? "%s", value);
- }
- else if (typeof value === "object") {
- value = JSON.stringify(value);
- }
- return value;
- } catch (error) {
- console.warn(`Ошибка при обработке метки ${match}:`, error.message);
- }
- return match;
- });
- }
- /**
- * @param {string} url
- * @returns {boolean}
- */
- static isYouTube(url) {
- return (url.hostname === "www.youtube.com");
- }
- /**
- * Отслеживает появление элемента в DOM.
- * @param {string} selector
- * @param {function} callback
- */
- static onElementInDOM(selector, callback) {
- if (!(typeof selector === "string" && selector.length)) {
- return false;
- }
- new MutationObserver((mutationsList, observer) => {
- for (const mutation of mutationsList) {
- if (mutation.type !== "childList") continue;
- mutation.addedNodes.forEach(node => {
- if (!(node instanceof Element)) {
- return;
- }
- if (node.matches(selector) || node.querySelector(selector)) {
- callback.apply(this, [{
- selector,
- target: node,
- observer
- }]);
- }
- });
- }
- }).observe(document.body, {
- childList: true,
- subtree: true
- });
- return true;
- }
- /**
- * Отслеживает появление элемента на экране.
- * @param {string} selector
- * @param {function} callback
- */
- static onElementVisible(selector, callback) {
- if (!(typeof selector === "string" && selector.length)) {
- return false;
- }
- const target = document.querySelector(selector);
- if (!target) {
- return this.onElementInDOM(selector, function () {
- this.onElementVisible(selector, callback);
- });
- }
- new IntersectionObserver(
- (entries, observer) => {
- entries.forEach(entry => {
- if (!entry.isIntersecting) return;
- callback.apply(this, [{
- selector,
- target: entry.target,
- observer
- }]);
- });
- },
- {
- root: null,
- rootMargin: "0px",
- threshold: 0.1
- }
- ).observe(target);
- return true;
- }
- static run() {
- if (this.isYouTube(window.location)) {
- new Obsidian.YouTube(window.location);
- }
- }
- }
- Obsidian.YouTube = class YouTube {
- /**
- * @param {string} timeString
- * @returns {number}
- */
- static timeToSeconds(timeString) {
- const [minutes, seconds] = timeString
- .split(":")
- .map(Number);
- return (minutes * 60 + seconds);
- }
- /**
- * @param {string} url
- */
- constructor(url) {
- this.url = url;
- this.elements = {
- video: {
- element: "video",
- id: null,
- },
- channel: {
- id: "head meta[itemprop='identifier']",
- url: "head link[itemprop='url']",
- rssUrl: "link[title='RSS'][type='application/rss+xml']",
- author: "ytd-channel-name a",
- },
- segments: "#segments-container > *",
- episodes: "#structured-description #shelf-container #items > *",
- microformat: "#microformat script[type='application/ld+json']",
- button1: "#structured-description #primary-button button",
- transcript: `[target-id="engagement-panel-searchable-transcript"]`,
- shareTargets: "#share-targets"
- };
- /**
- * Запускаем отслеживание для элемента.
- */
- Obsidian.onElementInDOM(this.elements.button1,
- ({ target, observer }) => {
- // Запрос транскрипции.
- target.click();
- observer.disconnect();
- }
- );
- /**
- * Запускаем отслеживание для элемента.
- */
- Obsidian.onElementVisible(this.elements.transcript,
- ({ target, observer }) => {
- // Спрятать транскрипцию.
- target.setAttribute(
- "visibility",
- "ENGAGEMENT_PANEL_VISIBILITY_HIDDEN"
- );
- observer.disconnect();
- }
- );
- /**
- * Запускаем отслеживание для элемента.
- */
- Obsidian.onElementVisible(this.elements.shareTargets,
- ({ target }) => {
- const containerId = "obsidian-button-container";
- if (document.getElementById(containerId)) {
- return;
- }
- const container = document.createElement("div");
- container.id = containerId;
- const button = document.createElement("button");
- button.classList.add("style-scope");
- button.classList.add("yt-share-target-renderer");
- button.onclick = () => this.createNote();
- const img = document.createElement("img");
- img.src = "https://www.google.com/s2/favicons?sz=64&domain=obsidian.md";
- button.appendChild(img);
- const span = document.createElement("span");
- span.classList.add("style-scope");
- span.classList.add("yt-share-target-renderer");
- span.setAttribute("style-targe", "title");
- span.textContent = "Obsidian";
- button.appendChild(span);
- container.appendChild(button);
- target
- .querySelector("yt-third-party-share-target-section-renderer")
- ?.appendChild(container);
- }
- );
- Obsidian.preloadImages([
- "https://www.google.com/s2/favicons?sz=64&domain=obsidian.md"
- ]);
- GM_addStyle(`
- #obsidian-button-container button {
- color: var(--yt-spec-text-primary);
- display: inline-flex;
- flex-direction: column;
- justify-content: center;
- align-items: center;
- flex-wrap: nowrap;
- margin: 1px 0;
- border: none;
- border-radius: 3px;
- padding: 5px 1px 2px;
- outline: none;
- text-align: inherit;
- font-family: inherit;
- background-color: transparent;
- cursor: pointer;
- }
- #obsidian-button-container button img {
- display: inline-flex;
- align-items: center;
- justify-content: center;
- position: relative;
- vertical-align: middle;
- width: var(--iron-icon-width, 24px);
- height: var(--iron-icon-height, 24px);
- animation: var(--iron-icon-animation);
- padding: var(--iron-icon-padding);
- border-radius: 100%;
- --iron-icon-height: 60px;
- --iron-icon-width: 60px;
- margin-top: var(--iron-icon-margin-top);
- margin-left: var(--ytd-margin-base);
- margin-right: var(--ytd-margin-base);
- margin-bottom: var(--ytd-margin-2x);
- }
- #obsidian-button-container button span {
- color: var(--yt-spec-text-primary);
- margin: auto;
- width: 68px;
- max-height: 42px;
- text-align: center;
- white-space: normal;
- overflow: hidden;
- font-family: "Roboto", "Arial", sans-serif;
- font-size: 1.2rem;
- line-height: 1.8rem;
- font-weight: 400;
- }
- `);
- }
- /**
- * @returns {string}
- */
- getId() {
- const searchParams = this.getUrl()?.search;
- return (
- (searchParams
- ? new URLSearchParams(searchParams).get("v")
- : null
- ) ??
- (this.getShortLinkUrl()?.match(/\/([^\/]*)$/) ?? [])[1] ??
- null
- );
- }
- /**
- * @returns {URL}
- */
- getUrl() {
- return (this.url ?? null);
- }
- /**
- * @returns {string}
- */
- getTitle() {
- return (document?.title
- ?.replace(/\s*-\s*YouTube\s*$/, "") ?? null);
- }
- /**
- * @returns {string}
- */
- getChannelId() {
- const channelUrl = (
- this.getChannelUrl() ??
- document.querySelector("#social-links #items a[href^='/channel/']").getAttribute("href")
- );
- return (
- (channelUrl?.match(/channel\/([^\/]+)(\/|$)/) ?? [])[1] ??
- null
- );
- }
- /**
- * @returns {string}
- */
- getChannelName() {
- const selector = this.elements?.channel?.author;
- return (
- this.getJson().author ??
- (selector
- ? document.querySelector(selector)?.textContent?.trim()
- : null) ??
- null
- );
- }
- /**
- * @returns {string}
- */
- getChannelUrl() {
- const selector = this.elements?.channel?.url;
- return (selector
- ? document.querySelector(selector)?.getAttribute("href")?.trim()
- : null) ?? null;
- }
- /**
- * @returns {string}
- */
- getChannelRssUrl() {
- let result = null;
- const selector = this.elements?.channel?.rssUrl;
- if (selector) {
- result = (document.querySelector(selector)
- ?.getAttribute("href")
- ?.trim() ?? null);
- }
- if (!result) {
- const channelId = this.getChannelId();
- if (channelId) {
- result = "https://www.youtube.com/feeds/videos.xml?channel_id=" + channelId;
- }
- }
- return result;
- }
- /**
- * @returns {string}
- */
- getPublishedDate() {
- return (
- this.getJson().datePublished ??
- this.getMetaContent("datePublished") ??
- null
- );
- }
- /**
- * @returns {string}
- */
- getUploadDate() {
- return (
- this.getJson().uploadDate ??
- this.getMetaContent("uploadDate") ??
- null
- );
- }
- /**
- * @returns {string}
- */
- getDate() {
- return (
- (
- (
- this.getPublishedDate() ??
- this.getUploadDate()
- )?.split("T") ??
- []
- )[0] ??
- null
- );
- }
- /**
- * @returns {string[]}
- */
- getKeywords() {
- const keywords = this.getMetaContent("keywords");
- if (!keywords) {
- return [];
- }
- const regex = /\s*("[^"]+"|'[^']+'|[^, ]+)\s*,?\s*/g;
- const matches = [];
- let match;
- while ((match = regex.exec(keywords)) !== null) {
- // Убираем кавычки с начала и конца, если они есть
- matches.push(match[1].replace(/^["']|["']$/g, ""));
- }
- // Проверка последнего элемента на троеточие
- if (matches[matches.length - 1]?.endsWith("...")) {
- matches.pop();
- }
- return matches;
- }
- /**
- * @returns {string}
- */
- getShortLinkUrl() {
- return (this.getMetaContent("shortlinkUrl") ?? null);
- }
- /**
- * @returns {string}
- */
- getCategory() {
- return (
- this.getJson().genre ??
- this.getMetaContent("genre") ??
- null
- );
- }
- /**
- * @returns {string}
- */
- getDescription() {
- return (
- this.getJson().description ??
- this.getMetaContent("description") ??
- null
- );
- }
- /**
- * @param {boolean} flag
- * - `true` – Array
- * - `false` – String
- * @returns {(string[]|string)}
- */
- getEpisodes(flag) {
- let values = [];
- const selector = this.elements?.episodes;
- if (!selector) {
- return null;
- }
- document
- .querySelectorAll(selector)
- ?.forEach(element => {
- try {
- const result = {
- level: 0,
- time: null,
- url: null,
- text: null
- };
- result.time = element
- ?.querySelector("#details #time")
- ?.textContent
- ?.trim() ?? "";
- result.url = "https://www.youtube.com/watch?"
- + "&v=" + this.getId()
- + "&t=" + this.constructor.timeToSeconds(result.time);
- result.text = element
- ?.querySelector("#details h4.macro-markers")
- ?.textContent
- ?.trim() ?? "";
- result.episode = new Obsidian.YouTube.Episode(result);
- values.push(episode);
- } catch (error) {
- console.warn(error.message);
- }
- });
- if (!values.length) {
- return null;
- }
- if (flag !== true) {
- return "\n## Episodes\n" + values
- .map(item => item.toString())
- .join("\n");
- }
- return values;
- }
- /**
- * @param {boolean} flag
- * - `true` – Array
- * - `false` – String
- * @returns {(string[]|string)}
- */
- getTranscript(flag) {
- let values = [];
- const selector = this.elements?.segments;
- if (!selector) {
- return null;
- }
- const episodes = this.getEpisodes(true);
- document.querySelectorAll(selector)
- ?.forEach(element => {
- try {
- const result = {
- level: 0,
- time: null,
- url: null,
- text: null
- };
- if (element.hasAttribute("rounded-container")) {
- result.level = 0;
- result.time = element
- ?.querySelector(".segment-timestamp")
- ?.textContent
- ?.trim() ?? "";
- result.url = "https://www.youtube.com/watch?"
- + "&v=" + this.getId()
- + "&t=" + this.constructor.timeToSeconds(result.time);
- result.text = element
- ?.querySelector(".segment-text")
- ?.textContent
- ?.trim() ?? "";
- } else {
- /* Эпизоды (заголовки) */
- result.level = 3;
- result.text = element
- ?.querySelector("h2")
- ?.textContent
- ?.trim() ?? "";
- const episode = (episodes ?? [])
- .find(item => item.text === result.text) ?? {};
- result.time = episode.time;
- result.url = episode.url;
- }
- const transcript = new Obsidian.YouTube.Transcript(result);
- values.push(transcript);
- } catch (error) {
- console.warn(error.message);
- }
- });
- if (!values.length) {
- return null;
- }
- if (flag !== true) {
- return "\n## Transcript\n" + values
- .map(item => item.toString())
- .join("\n");
- }
- return values;
- }
- /**
- * @returns {string}
- */
- getMetaContent(input) {
- return document
- ?.querySelector("meta[itemprop='" + input + "'], meta[name='" + input + "']")
- ?.getAttribute("content")
- ?.trim() ?? null;
- }
- /**
- * @returns {object}
- */
- getJson() {
- const selector = this.elements?.microformat;
- if (!selector) return {};
- let values = document
- .querySelector(selector)
- ?.textContent;
- try {
- values = (values ? JSON.parse(values) : {});
- } catch (error) { }
- return (values !== null && typeof values === "object" ? values : {});
- }
- /**
- * @returns {string}
- */
- getObsidianUrl() {
- const videoId = this.getId();
- if (!videoId) {
- return;
- }
- if (this.elements?.video?.element?.paused) {
- this.elements.video.element.pause();
- }
- const _escape = input => (input ?? "")
- .replace(/"/g, '\\"');
- const url = this.getUrl();
- const title = this.getTitle();
- const date = this.getDate();
- const publishedDate = this.getPublishedDate();
- const uploadDate = this.getUploadDate();
- const channelName = this.getChannelName();
- const keywords = this.getKeywords();
- const tags = [
- "Video",
- "YouTube"
- ];
- const path = [
- "RSS",
- encodeURIComponent(Obsidian.sanitizeTitle(channelName ?? "")),
- "YouTube",
- encodeURIComponent((date ?? "") + " " + Obsidian.sanitizeTitle(title ?? videoId ?? "").trim() + ".md")
- ].join("/");
- const content = [
- "---",
- `media_link: ${url}`,
- `channel: "${_escape(channelName ?? "")}"`,
- `category: "${_escape(this.getCategory() ?? "")}"`,
- "published_date: " + (publishedDate ?? ""),
- "upload_date: " + (uploadDate ?? ""),
- (keywords.length
- ? "keywords:\n" + keywords
- .map(item => ` - "${_escape(item)}"`)
- .join("\n") + "\n"
- : ""),
- (tags.length
- ? "tags:\n" + tags
- .map(item => ` - "${_escape(item)}"`)
- .join("\n") + "\n"
- : ""),
- `rss_link: ${this.getChannelRssUrl() ?? ""}`,
- "---",
- `# ${title ?? ""}`,
- `\n## Description`,
- `${this.getDescription() ?? ""}`,
- (this.getTranscript() ?? this.getEpisodes() ?? "")
- ].join("\n");
- return `obsidian://new?file=${path}&content=${encodeURIComponent(content)}`;
- }
- /**
- * @returns {string}
- */
- createNote() {
- return window.open(this.getObsidianUrl());
- }
- };
- Obsidian.YouTube.Episode = class Episode {
- constructor({ level, time, url, text }) {
- this.level = (level ?? 0);
- this.time = (time ?? null);
- this.url = (url ?? null);
- this.text = (text ?? null);
- }
- toString() {
- return `${this.level > 0 ? "#".repeat(this.level) : "-"} [${this.time}](${this.url ?? "#"}) ${this.text}`;
- }
- };
- Obsidian.YouTube.Transcript = class Transcript {
- constructor({ level, time, url, text }) {
- this.level = (level ?? 0);
- this.time = (time ?? null);
- this.url = (url ?? null);
- this.text = (text ?? null);
- }
- toString() {
- return `${this.level > 0 ? "#".repeat(this.level) : "-"} [${this.time}](${this.url ?? "#"}) ${this.text}`;
- }
- };
- Obsidian.run();
- })();