bilibiliDanmaku

在哔哩哔哩视频标题下方增加弹幕查看和下载

目前為 2018-02-18 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name		bilibiliDanmaku
// @name:zh-CN	哔哩哔哩弹幕姬
// @namespace	https://github.com/sakuyaa/gm_scripts
// @author		sakuyaa
// @description	在哔哩哔哩视频标题下方增加弹幕查看和下载
// @include		http*://www.bilibili.com/video/av*
// @include		http*://www.bilibili.com/watchlater/#/av*
// @version		2018.2.18
// @compatible	firefox 52
// @grant		none
// @run-at		document-end
// ==/UserScript==
(function() {
	const MAX_CONNECTIONS = 50;   //最大并发连接数
	let fetchFunc = url => {
		return fetch(url).then(response => {
			if(response.ok) {
				return response.text();
			}
			throw new Error('无法加载弹幕:' + url);
		});
	};
	
	let node;
	let code = setInterval(() => {
		node = document.querySelector('.tminfo');
		if (node) {
			clearInterval(code);
			let view = document.createElement('a');
			view.setAttribute('target', '_blank');
			view.textContent = '查看弹幕';
			let download = document.createElement('a');
			download.textContent = '下载弹幕';
			let downloadAll = document.createElement('a');
			downloadAll.textContent = '全弹幕下载';
			node.appendChild(document.createTextNode(' | '));
			node.appendChild(view);
			node.appendChild(document.createTextNode(' | '));
			node.appendChild(download);
			node.appendChild(document.createTextNode(' | '));
			node.appendChild(downloadAll);
			
			let func = () => {
				view.setAttribute('href', 'https://comment.bilibili.com/' + window.cid + '.xml');
				
				download.removeAttribute('download');
				download.setAttribute('href', 'javascript:;');
				download.onclick = () => {
					let xhr = new XMLHttpRequest();
					xhr.responseType = 'blob';
					xhr.open('GET', 'https://comment.bilibili.com/' + window.cid + '.xml?bilibiliDanmaku', true);
					xhr.onload = () => {
						if (xhr.status == 200) {
							download.onclick = null;
							download.setAttribute('download', document.title.split('_')[0] + '.xml');
							download.setAttribute('href', URL.createObjectURL(xhr.response));
							download.dispatchEvent(new MouseEvent('click'));
						} else {
							console.log(new Error(xhr.statusText));
						}
					};
					xhr.send(null);
				};
				
				downloadAll.removeAttribute('download');
				downloadAll.setAttribute('href', 'javascript:;');
				downloadAll.onclick = async () => {
					try {
						//加载历史弹幕池
						let response = await fetch('https://comment.bilibili.com/rolldate,' + window.cid);
						if(!response.ok) {
							throw new Error('无法加载历史弹幕列表');
						}
						let dates;
						try {
							dates = await response.json();
						} catch(e) {   //无历史弹幕,直接下载当前弹幕池弹幕
							download.dispatchEvent(new MouseEvent('click'));
							return;
						}
						if (dates.length > MAX_CONNECTIONS && !confirm('投稿时间越早/弹幕越多,全弹幕下载耗时越多,是否继续?')) {
							return;
						}
						
						//进度条
						let progress = document.createElement('progress');
						progress.setAttribute('max', dates.length);
						progress.setAttribute('value', 0);
						progress.style.position = 'fixed';
						progress.style.margin = 'auto';
						progress.style.left = progress.style.right = 0;
						progress.style.top = progress.style.bottom = 0;
						progress.style.zIndex = 99;   //进度条置顶
						document.body.appendChild(progress);
						//并发获取
						let exp, match, danmakuAll = '', index = 0, currentIndex;
						for (let i = 0; i < dates.length; i += MAX_CONNECTIONS) {
							let array = [];
							for (let date of dates.slice(i, i + MAX_CONNECTIONS)) {
								array.push(fetchFunc('https://comment.bilibili.com/dmroll,' + date.timestamp +
									',' + window.cid + '?bilibiliDanmaku'));
							}
							for (let danmaku of await Promise.all(array)) {
								exp = new RegExp('<d p="[^"]+,(\\d+)">.+?</d>', 'g');
								while ((match = exp.exec(danmaku)) != null) {
									currentIndex = parseInt(match[1]);
									if (currentIndex > index) {   //跳过重复的项目
										index = currentIndex;
										danmakuAll += match[0] + '\n';
									}
								}
							}
							progress.setAttribute('value', i);   //最后剩下当前弹幕池
						}
						//加载当前弹幕池
						let danmaku = await fetchFunc('https://comment.bilibili.com/' + window.cid +
							'.xml?bilibiliDanmaku');
						exp = new RegExp('<d p="[^"]+,(\\d+)">.+?</d>', 'g');
						while ((match = exp.exec(danmaku)) != null) {
							currentIndex = parseInt(match[1]);
							if (currentIndex > index) {   //跳过重复的项目
								index = currentIndex;
								danmakuAll += match[0] + '\n';
							}
						}
						//合成弹幕
						document.body.removeChild(progress);
						danmakuAll = danmaku.substring(0, danmaku.indexOf('<d p=')) + danmakuAll + '</i>';
						//设置下载链接
						downloadAll.onclick = null;
						downloadAll.setAttribute('download', document.title.split('_')[0] + '.xml');
						downloadAll.setAttribute('href', URL.createObjectURL(new Blob([danmakuAll])));
						downloadAll.dispatchEvent(new MouseEvent('click'));
					} catch(e) {
						console.log(e);
					}
				};
			};
			
			func();
			addEventListener('hashchange', func, false);
		}
	}, 500);
})();