bilibiliDanmaku

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

目前为 2018-02-18 提交的版本。查看 最新版本

// ==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);
})();