Last active
November 2, 2025 09:56
-
-
Save wbmins/9e30e6c48bd583459846665cc410819b to your computer and use it in GitHub Desktop.
[tampermonkey ] jable download 脚本,配合 https://github.com/nilaoda/N_m3u8DL-RE使用
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name Jable 视频下载 | |
| // @namespace http://tampermonkey.net/ | |
| // @version 1.4 | |
| // @description 在 Jable 视频页添加大下载图标按钮,捕获到 m3u8 后再添加按钮 | |
| // @author Pluto | |
| // @match https://en.jable.tv/videos/* | |
| // @grant GM_xmlhttpRequest | |
| // @connect 192.168.1.6 //这里指向下面go程序的ip | |
| // @run-at document-start | |
| // ==/UserScript== | |
| (function() { | |
| 'use strict'; | |
| let m3u8Url = null; | |
| let btnCreated = false; | |
| // 创建全局浮动提示函数 | |
| function showTip(message, color='green', duration=2000) { | |
| const tip = document.createElement('div'); | |
| tip.innerText = message; | |
| tip.style.cssText = ` | |
| position: fixed; | |
| top: 20px; | |
| right: 20px; | |
| padding: 8px 12px; | |
| background: ${color}; | |
| color: #fff; | |
| font-size: 14px; | |
| border-radius: 6px; | |
| box-shadow: 0 2px 8px rgba(0,0,0,0.3); | |
| z-index: 9999; | |
| opacity: 0; | |
| transition: opacity 0.3s; | |
| `; | |
| document.body.appendChild(tip); | |
| requestAnimationFrame(() => tip.style.opacity = 1); | |
| setTimeout(() => { | |
| tip.style.opacity = 0; | |
| setTimeout(() => document.body.removeChild(tip), 300); | |
| }, duration); | |
| } | |
| // 等待元素出现 | |
| function waitForElement(selector, timeout = 5000) { | |
| return new Promise((resolve, reject) => { | |
| const interval = 100; | |
| let elapsed = 0; | |
| const timer = setInterval(() => { | |
| const el = document.querySelector(selector); | |
| if (el) { | |
| clearInterval(timer); | |
| resolve(el); | |
| } else if ((elapsed += interval) >= timeout) { | |
| clearInterval(timer); | |
| reject('元素超时未找到: ' + selector); | |
| } | |
| }, interval); | |
| }); | |
| } | |
| // 创建下载按钮 | |
| async function createDownloadButton() { | |
| if (btnCreated) return; // 避免重复创建 | |
| try { | |
| const container = await waitForElement('.my-3'); | |
| const titleEl = document.querySelector('.header-left h4'); | |
| let title = '未知'; | |
| if (titleEl) { | |
| const parts = titleEl.innerText.trim().split(' '); | |
| title = parts.length > 0 ? parts[0] : '未知'; | |
| } | |
| const btn = document.createElement('button'); | |
| btn.innerText = '⬇️'; | |
| btn.style.cssText = ` | |
| cursor: pointer; | |
| background-color: transparent; | |
| color: inherit; | |
| border: none; | |
| border-radius: 16px; | |
| font-size: 28px; | |
| `; | |
| const tip = document.createElement('span'); | |
| tip.style.cssText = ` | |
| margin-left: 8px; | |
| font-size: 14px; | |
| color: green; | |
| opacity: 0; | |
| transition: opacity 0.3s; | |
| `; | |
| container.appendChild(tip); | |
| btn.addEventListener('click', () => { | |
| if (m3u8Url) { //这里指向下面go程序的ip | |
| const apiUrl = `http://192.168.1.6:8080/add?url=${encodeURIComponent(m3u8Url)}&filename=${encodeURIComponent(title)}`; | |
| console.log('发送请求到:', apiUrl); | |
| GM_xmlhttpRequest({ | |
| method: 'GET', | |
| url: apiUrl, | |
| onload: function(res) { | |
| try { | |
| const data = JSON.parse(res.responseText); | |
| if (data.message === '下载任务添加成功') { | |
| showTip(`✅ ${data.message}(队列: ${data.queue_size})`, 'green'); | |
| } else { | |
| showTip(`❌ ${data.message}(队列: ${data.queue_size})`, 'orange'); | |
| } | |
| } catch (e) { | |
| showTip('⚠️ 返回解析失败', 'orange'); | |
| } | |
| }, | |
| onerror: function(err) { | |
| showTip('❌ 请求失败', 'red'); | |
| console.error(err); | |
| } | |
| }); | |
| } else { | |
| showTip('⚠️ m3u8 URL 未捕获', 'orange'); | |
| console.warn('⚠️ m3u8 URL 还未捕获到,请稍候刷新页面或等待视频请求发出。'); | |
| } | |
| }); | |
| container.appendChild(btn); | |
| btnCreated = true; | |
| } catch (err) { | |
| console.error(err); | |
| } | |
| } | |
| // 拦截 fetch 请求,获取 m3u8 链接 | |
| const originalFetch = window.fetch; | |
| window.fetch = async function(input, init) { | |
| const response = await originalFetch(input, init); | |
| try { | |
| let url = typeof input === 'string' ? input : input.url; | |
| if (url.includes('.m3u8')) { | |
| if (!m3u8Url) { | |
| m3u8Url = url; | |
| console.log('捕获到 m3u8 URL:', m3u8Url); | |
| createDownloadButton(); // 捕获后创建按钮 | |
| } | |
| } | |
| } catch(e) { | |
| console.error(e); | |
| } | |
| return response; | |
| }; | |
| // 监听 XMLHttpRequest | |
| const originalXHROpen = XMLHttpRequest.prototype.open; | |
| XMLHttpRequest.prototype.open = function(method, url, async, user, password) { | |
| this.addEventListener('load', function() { | |
| if (url.includes('.m3u8')) { | |
| if (!m3u8Url) { | |
| m3u8Url = url; | |
| console.log('捕获到 m3u8 URL (XHR):', m3u8Url); | |
| createDownloadButton(); // 捕获后创建按钮 | |
| } | |
| } | |
| }); | |
| originalXHROpen.apply(this, arguments); | |
| }; | |
| })(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| package main | |
| import ( | |
| "encoding/json" | |
| "fmt" | |
| "net/http" | |
| "os/exec" | |
| "regexp" | |
| "sync" | |
| ) | |
| type DownloadTask struct { | |
| URL string `json:"url"` | |
| Filename string `json:"filename"` | |
| } | |
| // 单消费者队列 | |
| var ( | |
| taskQueue = make(chan DownloadTask, 100) // 实际队列 | |
| taskMap = make(map[string]bool) // key: Filename,用于去重 | |
| queueLock sync.Mutex | |
| ) | |
| // 校验文件名安全性 | |
| var validName = regexp.MustCompile(`^[\w\-.]+$`) | |
| // 消费者线程:顺序下载 | |
| func consumer() { | |
| for task := range taskQueue { | |
| safeDownload(task) | |
| // 下载完成后从 map 删除 | |
| queueLock.Lock() | |
| delete(taskMap, task.Filename) | |
| queueLock.Unlock() | |
| } | |
| } | |
| // 安全下载函数 | |
| func safeDownload(task DownloadTask) { | |
| fmt.Printf("🚀 Start downloading: %s -> %s\n", task.URL, task.Filename) | |
| cmd := exec.Command( | |
| "./N_m3u8DL-RE", | |
| task.URL, | |
| "--save-dir", "./mp4", | |
| "--save-name", task.Filename, | |
| ) | |
| output, err := cmd.CombinedOutput() | |
| if err != nil { | |
| fmt.Printf("❌ Download failed for %s: %v\nOutput: %s\n", task.Filename, err, string(output)) | |
| return | |
| } | |
| fmt.Printf("✅ Download finished: %s\n", task.Filename) | |
| } | |
| // HTTP 添加任务接口 | |
| func addTaskHandler(w http.ResponseWriter, r *http.Request) { | |
| url := r.FormValue("url") | |
| filename := r.FormValue("filename") | |
| resp := make(map[string]any) | |
| if url == "" || filename == "" { | |
| resp["message"] = "missing url or filename" | |
| queueLock.Lock() | |
| resp["queue_size"] = len(taskMap) | |
| queueLock.Unlock() | |
| w.Header().Set("Content-Type", "application/json") | |
| json.NewEncoder(w).Encode(resp) | |
| return | |
| } | |
| if !validName.MatchString(filename) { | |
| resp["message"] = "invalid filename" | |
| queueLock.Lock() | |
| resp["queue_size"] = len(taskMap) | |
| queueLock.Unlock() | |
| w.Header().Set("Content-Type", "application/json") | |
| json.NewEncoder(w).Encode(resp) | |
| return | |
| } | |
| queueLock.Lock() | |
| defer queueLock.Unlock() | |
| // 文件名去重 | |
| if taskMap[filename] { | |
| resp["message"] = "下载任务已经添加" | |
| resp["queue_size"] = len(taskMap) | |
| w.Header().Set("Content-Type", "application/json") | |
| json.NewEncoder(w).Encode(resp) | |
| return | |
| } | |
| // 添加任务 | |
| task := DownloadTask{URL: url, Filename: filename} | |
| taskQueue <- task | |
| taskMap[filename] = true | |
| resp["message"] = "下载任务添加成功" | |
| resp["queue_size"] = len(taskMap) | |
| w.Header().Set("Content-Type", "application/json") | |
| json.NewEncoder(w).Encode(resp) | |
| fmt.Printf("✅ Added task: %+v (queue size: %d)\n", task, len(taskMap)) | |
| } | |
| func main() { | |
| // 启动单消费者线程 | |
| go consumer() | |
| http.HandleFunc("/add", addTaskHandler) | |
| fmt.Println("🚀 Server running on :8080") | |
| if err := http.ListenAndServe(":8080", nil); err != nil { | |
| panic(err) | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment