// 等待页面加载完成
document.addEventListener('DOMContentLoaded', async () => {
// 检查当前页面是否是 YouTube 视频页面
if (!location.href.includes("watch")) {
console.log("Not a video page");
return;
}
// 查找页面上的特定元素
const secondaryElement = await waitForElement("#secondary");
if (!secondaryElement) {
console.log("Element not found");
return;
}
// 创建并插入自定义组件
const container = document.createElement('div');
container.id = "crxThumbnailToEagle";
secondaryElement.insertBefore(container, secondaryElement.firstChild);
// 挂载 Svelte 组件
new Ls({ target: container });
});
// 等待元素出现的函数
function waitForElement(selector) {
return new Promise((resolve) => {
const element = document.querySelector(selector);
if (element) {
resolve(element);
} else {
const observer = new MutationObserver((mutations, obs) => {
const element = document.querySelector(selector);
if (element) {
obs.disconnect();
resolve(element);
}
});
observer.observe(document, { childList: true, subtree: true });
}
});
}
引入依赖和工具函数
// 引入wxt/storage用于存储和读取数据
import { defineItem } from 'wxt/storage';
// 引入iconify用于处理图标
import { Icon } from '@iconify/svelte';
定义常量和配置
// 定义常量用于模板替换
const VIDEO_TITLE = "VIDEO_TITLE";
const CHANNEL_NAME = "CHANNEL_NAME";
// 定义存储项用于存储用户自定义的模板
const eagleItemTemplate = defineItem('local:eagleItemTemplate', {
defaultValue: { title: "VIDEO_TITLE by CHANNEL_NAME", annotation: "" }
});
主要功能函数
or 函数
async function or(title, thumbnailUrl, videoUrl, annotation) {
const data = {
type: "image",
title: title,
src: thumbnailUrl,
url: videoUrl,
annotation: annotation
};
// 如果有注释,尝试复制到剪贴板
if (annotation !== "") {
try {
const clipboardItem = [
new ClipboardItem({
"text/plain": new Blob([annotation], { type: "text/plain" })
})
];
await navigator.clipboard.write(clipboardItem);
} catch (error) {
console.log("Failed to copy annotation text", error);
}
}
// 发送数据到 Eagle 应用
const options = {
method: "POST",
body: new URLSearchParams(data),
headers: { "Content-Type": "application/x-www-form-urlencoded" }
};
const eagleUrl = "http://localhost:41593";
try {
await fetch(eagleUrl, options);
} catch (error) {
console.log("Failed to save to Eagle", error);
}
}
ir 函数
async function ir(videoTitle, thumbnailUrl, videoId) {
const videoUrl = `https://www.youtube.com/watch?v=${videoId}`;
const channelName = document.querySelector("span[itemprop='author'] link[itemprop='name']")?.getAttribute("content") || "";
const { title: templateTitle, annotation: templateAnnotation } = await eagleItemTemplate.getValue();
const title = templateTitle.replace(VIDEO_TITLE, videoTitle.replace(/ - YouTube$/, "")).replace(CHANNEL_NAME, channelName);
const annotation = templateAnnotation.replace(VIDEO_TITLE, videoTitle.replace(/ - YouTube$/, "")).replace(CHANNEL_NAME, channelName);
await or(title, thumbnailUrl, videoUrl, annotation);
}
cr 函数
async function cr(videoId) {
const resolutions = ["maxresdefault", "sddefault", "hqdefault", "mqdefault", "default"];
for (const res of resolutions) {
const url = `https://img.youtube.com/vi/${videoId}/${res}.jpg`;
try {
const img = await loadImage(url);
if (img.width > 120) {
return url;
}
} catch {
// 忽略错误,尝试下一个分辨率
}
}
return "";
// 辅助函数:加载图片
function loadImage(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(img);
img.onerror = reject;
img.src = url;
});
}
}
主函数
const Fs = {
matches: ["https://www.youtube.com/*"],
async main(e) {
// 检查当前页面是否是 YouTube 视频页面
if (!location.href.includes("watch")) {
console.log("Not a video page");
return;
}
// 查找页面上的特定元素
const secondaryElement = await waitForElement("#secondary");
if (!secondaryElement) {
console.log("Element not found");
return;
}
// 创建并插入自定义组件
const container = document.createElement('div');
container.id = "crxThumbnailToEagle";
secondaryElement.insertBefore(container, secondaryElement.firstChild);
// 挂载 Svelte 组件
new Ls({ target: container });
},
};
等待元素出现的函数
function waitForElement(selector) {
return new Promise((resolve) => {
const element = document.querySelector(selector);
if (element) {
resolve(element);
} else {
const observer = new MutationObserver((mutations, obs) => {
const element = document.querySelector(selector);
if (element) {
obs.disconnect();
resolve(element);
}
});
observer.observe(document, { childList: true, subtree: true });
}
});
}
初始化和启动
// 从Fs对象中解构出main函数和其他属性
const { main: e, ...t } = Fs;
// 创建一个新的内容脚本实例
const n = new Cn("content", t);
// 使用异步函数调用main函数,并处理可能的错误
(async () => {
try {
await e(n);
} catch (error) {
console.error('The content script "content" crashed on startup!', error);
}
})();
通过这些代码,你可以实现将 YouTube 视频信息保存到 Eagle 应用的功能,并在 YouTube 视频页面上添加一个自定义组件。
以下是一个简化的 background.js 示例代码,展示了上述功能:
// 引入 browser-polyfill 库
import browser from 'webextension-polyfill';
// 定义一些常量和配置
const VIDEO_TITLE = "VIDEO_TITLE";
const CHANNEL_NAME = "CHANNEL_NAME";
// 定义存储项用于存储用户自定义的模板
const eagleItemTemplate = defineItem('local:eagleItemTemplate', {
defaultValue: { title: "VIDEO_TITLE by CHANNEL_NAME", annotation: "" }
});
// 消息处理函数
function handleMessage(message, sender, sendResponse) {
switch (message.type) {
case 'openOptionsPage':
browser.runtime.openOptionsPage();
break;
// 其他消息类型处理
default:
console.log('Unknown message type:', message.type);
}
}
// 监听消息
browser.runtime.onMessage.addListener(handleMessage);
// 初始化函数
function init() {
console.log('Background script initialized');
// 其他初始化操作
}
// 执行初始化
init();