🏠 Home 

動畫瘋下載器

取得動畫的 m3u8 網址,並可使用 PotPlayer 播放

// ==UserScript==
// @name        動畫瘋下載器
// @namespace
// @description 取得動畫的 m3u8 網址,並可使用 PotPlayer 播放
// @version     1.6.4
// @author
// @match       https://ani.gamer.com.tw/animeVideo.php?sn=*
// @connect     ani.gamer.com.tw
// @grant       none
// @namespace
// ==/UserScript==
(function () {
'use strict';
//保存路徑
const path = '%USERPROFILE%/Downloads'
//複製模式(0:複製完整指令|1:複製URL+名稱)
const mode = 0;
//注入樣式到頁面中
function injectStyles(css) {
const style = document.createElement('style'); // 創建 <style> 元素
style.textContent = css; // 設置樣式內容
document.head.appendChild(style); // 將 <style> 添加到 <head>
}
//解析 m3u8 播放列表,並在頁面上生成按鈕供使用者複製鏈接或使用 PotPlayer 播放
async function parsePlaylist() {
const req = playlist.src; // 獲取播放列表的 URL
const response = await fetch(req); // 請求播放列表
const text = await response.text(); // 獲取回應的文字內容
const urlPrefix = req.replace(/playlist.+/, ''); // 提取 URL 前綴
const m3u8List = text.match(/=\d+x\d+\n.+/g); // 匹配所有清晰度的 m3u8 連結
// 生成動畫名稱,作為文件名使用
const Name = document.title.replace(" 線上看 - 巴哈姆特動畫瘋", "").replace(/[\/:*?"<>|]/g, '_');
titleDisplay.textContent = '複製下載指令或使用外部播放器';
let m3u8_url2
// 遍歷每個 m3u8 連結,生成對應的按鈕
for (const item of m3u8List) {
let key = item.match(/=\d+x(\d+)/)[1]; // 提取清晰度(如 720)
let m3u8_url = item.match(/.*chunklist.+/)[0]; // 提取 m3u8 文件的相對路徑
m3u8_url = urlPrefix + m3u8_url; // 拼接成完整的 m3u8 URL
m3u8_url2 = m3u8_url;
// 創建複製鏈接的按鈕
const copyLink = document.createElement('a');
copyLink.classList.add('anig-tb');
copyLink.textContent = `${key}p`;
copyLink.title = '複製ffmpeg下載指令';
copyLink.addEventListener('click', function () {
let ffmpegUrl;
if (mode==0){
ffmpegUrl = `ffmpeg -headers "Origin: https://ani.gamer.com.tw" -i "${m3u8_url}" -c copy "${path}/${Name}.mkv"`; // 構建 PotPlayer 協議的 URL
}else{
ffmpegUrl = `${m3u8_url}@${Name}.mkv"`; // 構建 PotPlayer 協議的 URL
}
navigator.clipboard.writeText(ffmpegUrl); // 複製鏈接
titleDisplay.textContent = '複製成功!'; // 提示成功
setTimeout(() => {
titleDisplay.textContent = '複製下載指令或使用外部播放器'; // 恢復提示文字
}, 500);
});
m3u8Container.appendChild(copyLink);
}
// 創建使用 MPV 播放的按鈕
const MPVLink = document.createElement('a');
MPVLink.classList.add('anig-tb');
MPVLink.textContent = 'MPV';
MPVLink.title = '使用 MPV 播放';
MPVLink.addEventListener('click', function () {
const MPVUrl = `${m3u8_url2} --http-header-fields="origin: https://ani.gamer.com.tw" --force-media-title="${Name}"`; // 構建 MPV 協議的 URL
navigator.clipboard.writeText(MPVUrl);
window.open('mpv:', '_self'); // 開啟 PotPlayer
});
m3u8Container.appendChild(MPVLink);
// 創建使用 PotPlayer 播放的按鈕
const potplayerLink = document.createElement('a');
potplayerLink.classList.add('anig-tb');
potplayerLink.textContent = 'PotPlayer';
potplayerLink.title = '使用 PotPlayer 播放';
potplayerLink.addEventListener('click', function () {
const potplayerUrl = `${m3u8_url2} /sub="" /headers="origin: https://ani.gamer.com.tw" /current /title="${Name}"`; // 構建 PotPlayer 協議的 URL
navigator.clipboard.writeText(potplayerUrl);
window.open('potplayer:', '_self'); // 開啟 PotPlayer
});
m3u8Container.appendChild(potplayerLink);
}
/**
* 獲取播放列表,並等待廣告結束
*/
async function getPlaylist() {
const req = `https://ani.gamer.com.tw/ajax/m3u8.php?sn=${AniVideoSn}&device=${DeviceID}`; // 構建請求 URL
titleDisplay.textContent = '等待廣告...'; // 提示使用者等待廣告
let retries = 0; // 重試次數計數器
const maxRetries = 20; // 最多嘗試次數,防止無限循環
// 循環請求播放列表,直到獲取到有效的播放地址或達到最大重試次數
while (retries < maxRetries) {
const response = await fetch(req); // 發送請求
playlist = await response.json(); // 解析 JSON 資料
// 如果獲取到有效的播放地址(不包含廣告)
if (playlist.src && playlist.src.includes('https')) {
break; // 跳出循環
}
await new Promise(resolve => setTimeout(resolve, 3000)); // 等待 3 秒再重試
retries++; // 增加重試次數
}
// 判斷是否成功獲取播放列表
if (playlist.src && playlist.src.includes('https')) {
await parsePlaylist(); // 解析播放列表並生成按鈕
} else {
titleDisplay.textContent = '獲取播放列表失敗'; // 提示使用者失敗
}
}
/**
* 獲取設備 ID,這是請求播放列表所需的參數
*/
async function getDeviceId() {
const req = 'https://ani.gamer.com.tw/ajax/getdeviceid.php'; // 請求設備 ID 的 URL
const response = await fetch(req); // 發送請求
const data = await response.json(); // 解析 JSON 資料
DeviceID = data.deviceid; // 提取設備 ID
await getPlaylist(); // 繼續獲取播放列表
}
// Main
// 從 URL 中獲取動畫的編號(AniVideoSn)
let AniVideoSn = new URLSearchParams(window.location.search).get('sn');
// 定義全域變數
let DeviceID; // 設備 ID
let playlist; // 播放列表資料
// 定義樣式
const css =`
.anig-ct {
margin: 5px;
}
.anig-tb {
display: inline-block;
padding: 5px;
background: #50b2d7;
color: #FFF;
margin-right: 5px;
cursor: pointer;
}`;
// 創建顯示提示信息的元素
const titleDisplay = document.createElement('div');
titleDisplay.classList.add('anig-tb');
titleDisplay.textContent = '載入中...'; // 初始提示文字
// 創建一個容器來包裹提示信息
const container = document.createElement('div');
container.classList.add('anig-ct');
container.appendChild(titleDisplay); // 將提示元素添加到容器中
// 創建容器,用於放置清晰度按鈕和 PotPlayer 按鈕
const m3u8Container = document.createElement('div');
m3u8Container.classList.add('anig-ct');
// 將容器添加到頁面中的指定位置
const animeName = document.querySelector('.anime_name');
animeName.appendChild(container); // 添加提示容器
animeName.appendChild(m3u8Container); // 添加按鈕容器
/**
* 為頁面中的集數連結添加點擊事件監聽
* 當使用者點擊不同的集數時,更新 AniVideoSn 並重新獲取播放列表
*/
document.querySelectorAll('a[data-ani-video-sn]').forEach(link => {
link.addEventListener('click', function () {
AniVideoSn = this.getAttribute('data-ani-video-sn'); // 更新 AniVideoSn
m3u8Container.innerHTML = ''; // 清空按鈕容器
titleDisplay.textContent = '載入中...'; // 更新提示文字
getDeviceId(); // 重新獲取設備 ID 並獲取播放列表
});
});
/**
* 新增檢查機制,監測 URL 和 AniVideoSn 是否發生變化
* 如果發生變化,則重新獲取播放列表,確保資料的及時更新
*/
let lastAniVideoSn = AniVideoSn; // 保存上一次的 AniVideoSn
let lastUrl = window.location.href; // 保存上一次的 URL
setInterval(() => {
const currentUrl = window.location.href; // 獲取當前的 URL
const currentAniVideoSn = new URLSearchParams(window.location.search).get('sn'); // 獲取當前的 AniVideoSn
// 如果 URL 或 AniVideoSn 發生變化
if (currentUrl !== lastUrl || currentAniVideoSn !== lastAniVideoSn) {
lastUrl = currentUrl; // 更新 URL
lastAniVideoSn = currentAniVideoSn; // 更新 AniVideoSn
AniVideoSn = currentAniVideoSn; // 更新全域變數
m3u8Container.innerHTML = ''; // 清空按鈕容器
titleDisplay.textContent = '載入中...'; // 更新提示文字
getDeviceId(); // 重新獲取設備 ID 並獲取播放列表
}
}, 1000); // 每秒檢查一次
// 開始執行程式
getDeviceId(); // 獲取設備 ID 並開始流程
injectStyles(css); // 注入自定義樣式到頁面
})();