AcFun 弹幕 下载

AcFun弹幕下载

目前为 2022-02-22 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name AcFun弹幕下载
  3. // @name:zh-CN AcFun 弹幕 下载
  4. // @name:zh-TW AcFun弹 幕 下 载
  5. // @namespace
  6. // @description AcFun 弹幕 下载
  7. // @description:zh-TW AcFun弹幕下载
  8. // @description:zh-CN AcFun弹幕下载
  9. // @include https://www.acfun.cn/v/*
  10. // @include https://www.acfun.cn/bangumi/*
  11. // @version 1.0
  12. // @grant GM_addStyle
  13. // @grant GM_xmlhttpRequest
  14. // @run-at document-start
  15. // @copyright unset
  16. // @license Mozilla Public License 2.0; http://www.mozilla.org/MPL/2.0/
  17. // @license CC Attribution-ShareAlike 4.0 International; http://creativecommons.org/licenses/by-sa/4.0/
  18. // @connect-src danmu.aixifan.com
  19. // ==/UserScript==
  20.  
  21. /*
  22. * Common
  23. */
  24.  
  25. // 设置项
  26. var config = {
  27. 'playResX': 560, // 屏幕分辨率宽(像素)
  28. 'playResY': 420, // 屏幕分辨率高(像素)
  29. 'fontlist': [ // 字形(会自动选择最前面一个可用的)
  30. 'Microsoft YaHei UI',
  31. 'Microsoft YaHei',
  32. '文泉驿正黑',
  33. 'STHeitiSC',
  34. '黑体',
  35. ],
  36. 'font_size': 1.0, // 字号(比例)
  37. 'r2ltime': 8, // 右到左弹幕持续时间(秒)
  38. 'fixtime': 4, // 固定弹幕持续时间(秒)
  39. 'opacity': 0.6, // 不透明度(比例)
  40. 'space': 0, // 弹幕间隔的最小水平距离(像素)
  41. 'max_delay': 6, // 最多允许延迟几秒出现弹幕
  42. 'bottom': 50, // 底端给字幕保留的空间(像素)
  43. 'use_canvas': null, // 是否使用canvas计算文本宽度(布尔值,Linux下的火狐默认否,其他默认是,Firefox bug #561361)
  44. 'debug': false, // 打印调试信息
  45. };
  46.  
  47. var debug = config.debug && console && console.log && console.log.bind(console) || function () { };
  48.  
  49. // 将字典中的值填入字符串
  50. var fillStr = function (str) {
  51. var dict = Array.apply(Array, arguments);
  52. return str.replace(/{{([^}]+)}}/g, function (r, o) {
  53. var ret;
  54. dict.some(function (i) { return ret = i[o]; });
  55. return ret || '';
  56. });
  57. };
  58.  
  59. // 将颜色的数值化为十六进制字符串表示
  60. var RRGGBB = function (color) {
  61. var t = Number(color).toString(16).toUpperCase();
  62. return Array(7 - t.length).join('0') + t;
  63. };
  64.  
  65. // 将可见度转换为透明度
  66. var hexAlpha = function (opacity) {
  67. var alpha = Math.round(0xFF * (1 - opacity)).toString(16).toUpperCase();
  68. return Array(3 - alpha.length).join('0') + alpha;
  69. };
  70.  
  71. // 字符串
  72. var funStr = function (fun) {
  73. return fun.toString().split(/\r\n|\n|\r/).slice(1, -1).join('\n');
  74. };
  75.  
  76. // 平方和开根
  77. var hypot = Math.hypot ? Math.hypot.bind(Math) : function () {
  78. return Math.sqrt([0].concat(Array.apply(Array, arguments))
  79. .reduce(function (x, y) { return x + y * y; }));
  80. };
  81.  
  82. // 创建下载
  83. var startDownload = function (data, filename) {
  84. var blob = new Blob([data], { type: 'application/octet-stream' });
  85. var url = window.URL.createObjectURL(blob);
  86. var saveas = document.createElement('a');
  87. saveas.href = url;
  88. saveas.style.display = 'none';
  89. document.body.appendChild(saveas);
  90. saveas.download = filename;
  91. saveas.click();
  92. setTimeout(function () { saveas.parentNode.removeChild(saveas); }, 0)
  93. document.addEventListener('unload', function () { window.URL.revokeObjectURL(url); });
  94. };
  95.  
  96. // 计算文字宽度
  97. var calcWidth = (function () {
  98.  
  99. // 使用Canvas计算
  100. var calcWidthCanvas = function () {
  101. var canvas = document.createElement("canvas");
  102. var context = canvas.getContext("2d");
  103. return function (fontname, text, fontsize) {
  104. context.font = 'bold ' + fontsize + 'px ' + fontname;
  105. return Math.ceil(context.measureText(text).width + config.space);
  106. };
  107. }
  108.  
  109. // 使用Div计算
  110. var calcWidthDiv = function () {
  111. var d = document.createElement('div');
  112. d.setAttribute('style', [
  113. 'all: unset', 'top: -10000px', 'left: -10000px',
  114. 'width: auto', 'height: auto', 'position: absolute',
  115. '', ].join(' !important; '));
  116. var ld = function () { document.body.parentNode.appendChild(d); }
  117. if (!document.body) document.addEventListener('DOMContentLoaded', ld);
  118. else ld();
  119. return function (fontname, text, fontsize) {
  120. d.textContent = text;
  121. d.style.font = 'bold ' + fontsize + 'px ' + fontname;
  122. return d.clientWidth + config.space;
  123. };
  124. };
  125.  
  126. // 检查使用哪个测量文字宽度的方法
  127. if (config.use_canvas === null) {
  128. if (navigator.platform.match(/linux/i) &&
  129. !navigator.userAgent.match(/chrome/i)) config.use_canvas = false;
  130. }
  131. debug('use canvas: %o', config.use_canvas !== false);
  132. if (config.use_canvas === false) return calcWidthDiv();
  133. return calcWidthCanvas();
  134.  
  135. }());
  136.  
  137. // 选择合适的字体
  138. var choseFont = function (fontlist) {
  139. // 检查这个字串的宽度来检查字体是否存在
  140. var sampleText =
  141. 'The quick brown fox jumps over the lazy dog' +
  142. '7531902468' + ',.!-' + ',。:!' +
  143. '天地玄黄' + '則近道矣';
  144. // 和这些字体进行比较
  145. var sampleFont = [
  146. 'monospace', 'sans-serif', 'sans',
  147. 'Symbol', 'Arial', 'Comic Sans MS', 'Fixed', 'Terminal',
  148. 'Times', 'Times New Roman',
  149. '宋体', '黑体', '文泉驿正黑', 'Microsoft YaHei'
  150. ];
  151. // 如果被检查的字体和基准字体可以渲染出不同的宽度
  152. // 那么说明被检查的字体总是存在的
  153. var diffFont = function (base, test) {
  154. var baseSize = calcWidth(base, sampleText, 72);
  155. var testSize = calcWidth(test + ',' + base, sampleText, 72);
  156. return baseSize !== testSize;
  157. };
  158. var validFont = function (test) {
  159. var valid = sampleFont.some(function (base) {
  160. return diffFont(base, test);
  161. });
  162. debug('font %s: %o', test, valid);
  163. return valid;
  164. };
  165. // 找一个能用的字体
  166. var f = fontlist[fontlist.length - 1];
  167. fontlist = fontlist.filter(validFont);
  168. debug('fontlist: %o', fontlist);
  169. return fontlist[0] || f;
  170. };
  171.  
  172. // 从备选的字体中选择一个机器上提供了的字体
  173. var initFont = (function () {
  174. var done = false;
  175. return function () {
  176. if (done) return; done = true;
  177. calcWidth = calcWidth.bind(window,
  178. config.font = choseFont(config.fontlist)
  179. );
  180. };
  181. }());
  182.  
  183. var generateASS = function (danmaku, info) {
  184. var assHeader = fillStr(funStr(function () {/*! ASS弹幕文件文件头
  185. [Script Info]
  186. Title: {{title}}
  187. Original Script: 根据 {{ori}} 的弹幕信息,由 https://github.com/tiansh/us-danmaku 生成
  188. ScriptType: v4.00+
  189. Collisions: Normal
  190. PlayResX: {{playResX}}
  191. PlayResY: {{playResY}}
  192. Timer: 10.0000
  193.  
  194. [V4+ Styles]
  195. Format: Name, Fontname, Fontsize, PrimaryColour, SecondaryColour, OutlineColour, BackColour, Bold, Italic, Underline, StrikeOut, ScaleX, ScaleY, Spacing, Angle, BorderStyle, Outline, Shadow, Alignment, MarginL, MarginR, MarginV, Encoding
  196. Style: Fix,{{font}},25,&H{{alpha}}FFFFFF,&H{{alpha}}FFFFFF,&H{{alpha}}000000,&H{{alpha}}000000,1,0,0,0,100,100,0,0,1,2,0,2,20,20,2,0
  197. Style: R2L,{{font}},25,&H{{alpha}}FFFFFF,&H{{alpha}}FFFFFF,&H{{alpha}}000000,&H{{alpha}}000000,1,0,0,0,100,100,0,0,1,2,0,2,20,20,2,0
  198.  
  199. [Events]
  200. Format: Layer, Start, End, Style, Name, MarginL, MarginR, MarginV, Effect, Text
  201.  
  202. */}), config, info, { 'alpha': hexAlpha(config.opacity) });
  203. // 补齐数字开头的0
  204. var paddingNum = function (num, len) {
  205. num = '' + num;
  206. while (num.length < len) num = '0' + num;
  207. return num;
  208. };
  209. // 格式化时间
  210. var formatTime = function (time) {
  211. time = 100 * time ^ 0;
  212. var l = [[100, 2], [60, 2], [60, 2], [Infinity, 0]].map(function (c) {
  213. var r = time % c[0];
  214. time = (time - r) / c[0];
  215. return paddingNum(r, c[1]);
  216. }).reverse();
  217. return l.slice(0, -1).join(':') + '.' + l[3];
  218. };
  219. // 格式化特效
  220. var format = (function () {
  221. // 适用于所有弹幕
  222. var common = function (line) {
  223. var s = '';
  224. var rgb = line.color.split(/(..)/).filter(function (x) { return x; })
  225. .map(function (x) { return parseInt(x, 16); });
  226. // 如果不是白色,要指定弹幕特殊的颜色
  227. if (line.color !== 'FFFFFF') // line.color 是 RRGGBB 格式
  228. s += '\\c&H' + line.color.split(/(..)/).reverse().join('');
  229. // 如果弹幕颜色比较深,用白色的外边框
  230. var dark = rgb[0] * 0.299 + rgb[1] * 0.587 + rgb[2] * 0.114 < 0x30;
  231. if (dark) s += '\\3c&HFFFFFF';
  232. if (line.size !== 25) s += '\\fs' + line.size;
  233. return s;
  234. };
  235. // 适用于从右到左弹幕
  236. var r2l = function (line) {
  237. return '\\move(' + [
  238. line.poss.x, line.poss.y, line.posd.x, line.posd.y
  239. ].join(',') + ')';
  240. };
  241. // 适用于固定位置弹幕
  242. var fix = function (line) {
  243. return '\\pos(' + [
  244. line.poss.x, line.poss.y
  245. ].join(',') + ')';
  246. };
  247. var withCommon = function (f) {
  248. return function (line) { return f(line) + common(line); };
  249. };
  250. return {
  251. 'R2L': withCommon(r2l),
  252. 'Fix': withCommon(fix),
  253. };
  254. }());
  255. // 转义一些字符
  256. var escapeAssText = function (s) {
  257. // "{"、"}"字符libass可以转义,但是VSFilter不可以,所以直接用全角补上
  258. return s.replace(/{/g, '{').replace(/}/g, '}').replace(/\r|\n/g, '');
  259. };
  260. // 将一行转换为ASS的事件
  261. var convert2Ass = function (line) {
  262. return 'Dialogue: ' + [
  263. 0,
  264. formatTime(line.stime),
  265. formatTime(line.dtime),
  266. line.type,
  267. ',20,20,2,,',
  268. ].join(',')
  269. + '{' + format[line.type](line) + '}'
  270. + escapeAssText(line.text);
  271. };
  272. return assHeader +
  273. danmaku.map(convert2Ass)
  274. .filter(function (x) { return x; })
  275. .join('\n');
  276. };
  277.  
  278. /*
  279.  
  280. 下文字母含义:
  281. 0 ||----------------------x---------------------->
  282. _____________________c_____________________
  283. = / wc \ 0
  284. | | |--v--| wv | |--v--|
  285. | d |--v--| d f |--v--|
  286. y |--v--| l f | s _ p
  287. | | VIDEO |--v--| |--v--| _ m
  288. v | AREA (x ^ y) |
  289.  
  290. v: 弹幕
  291. c: 屏幕
  292.  
  293. 0: 弹幕发送
  294. a: 可行方案
  295.  
  296. s: 开始出现
  297. f: 出现完全
  298. l: 开始消失
  299. d: 消失完全
  300.  
  301. p: 上边缘(含)
  302. m: 下边缘(不含)
  303.  
  304. w: 宽度
  305. h: 高度
  306. b: 底端保留
  307.  
  308. t: 时间点
  309. u: 时间段
  310. r: 延迟
  311.  
  312. 并规定
  313. ts := t0s + r
  314. tf := wv / (wc + ws) * p + ts
  315. tl := ws / (wc + ws) * p + ts
  316. td := p + ts
  317.  
  318. */
  319.  
  320. // 滚动弹幕
  321. var normalDanmaku = (function (wc, hc, b, u, maxr) {
  322. return function () {
  323. // 初始化屏幕外面是不可用的
  324. var used = [
  325. { 'p': -Infinity, 'm': 0, 'tf': Infinity, 'td': Infinity, 'b': false },
  326. { 'p': hc, 'm': Infinity, 'tf': Infinity, 'td': Infinity, 'b': false },
  327. { 'p': hc - b, 'm': hc, 'tf': Infinity, 'td': Infinity, 'b': true },
  328. ];
  329. // 检查一些可用的位置
  330. var available = function (hv, t0s, t0l, b) {
  331. var suggestion = [];
  332. // 这些上边缘总之别的块的下边缘
  333. used.forEach(function (i) {
  334. if (i.m > hc) return;
  335. var p = i.m;
  336. var m = p + hv;
  337. var tas = t0s;
  338. var tal = t0l;
  339. // 这些块的左边缘总是这个区域里面最大的边缘
  340. used.forEach(function (j) {
  341. if (j.p >= m) return;
  342. if (j.m <= p) return;
  343. if (j.b && b) return;
  344. tas = Math.max(tas, j.tf);
  345. tal = Math.max(tal, j.td);
  346. });
  347. // 最后作为一种备选留下来
  348. suggestion.push({
  349. 'p': p,
  350. 'r': Math.max(tas - t0s, tal - t0l),
  351. });
  352. });
  353. // 根据高度排序
  354. suggestion.sort(function (x, y) { return x.p - y.p; });
  355. var mr = maxr;
  356. // 又靠右又靠下的选择可以忽略,剩下的返回
  357. suggestion = suggestion.filter(function (i) {
  358. if (i.r >= mr) return false;
  359. mr = i.r;
  360. return true;
  361. });
  362. return suggestion;
  363. };
  364. // 添加一个被使用的
  365. var use = function (p, m, tf, td) {
  366. used.push({ 'p': p, 'm': m, 'tf': tf, 'td': td, 'b': false });
  367. };
  368. // 根据时间同步掉无用的
  369. var syn = function (t0s, t0l) {
  370. used = used.filter(function (i) { return i.tf > t0s || i.td > t0l; });
  371. };
  372. // 给所有可能的位置打分,分数是[0, 1)的
  373. var score = function (i) {
  374. if (i.r > maxr) return -Infinity;
  375. return 1 - hypot(i.r / maxr, i.p / hc) * Math.SQRT1_2;
  376. };
  377. // 添加一条
  378. return function (t0s, wv, hv, b) {
  379. var t0l = wc / (wv + wc) * u + t0s;
  380. syn(t0s, t0l);
  381. var al = available(hv, t0s, t0l, b);
  382. if (!al.length) return null;
  383. var scored = al.map(function (i) { return [score(i), i]; });
  384. var best = scored.reduce(function (x, y) {
  385. return x[0] > y[0] ? x : y;
  386. })[1];
  387. var ts = t0s + best.r;
  388. var tf = wv / (wv + wc) * u + ts;
  389. var td = u + ts;
  390. use(best.p, best.p + hv, tf, td);
  391. return {
  392. 'top': best.p,
  393. 'time': ts,
  394. };
  395. };
  396. };
  397. }(config.playResX, config.playResY, config.bottom, config.r2ltime, config.max_delay));
  398.  
  399. // 顶部、底部弹幕
  400. var sideDanmaku = (function (hc, b, u, maxr) {
  401. return function () {
  402. var used = [
  403. { 'p': -Infinity, 'm': 0, 'td': Infinity, 'b': false },
  404. { 'p': hc, 'm': Infinity, 'td': Infinity, 'b': false },
  405. { 'p': hc - b, 'm': hc, 'td': Infinity, 'b': true },
  406. ];
  407. // 查找可用的位置
  408. var fr = function (p, m, t0s, b) {
  409. var tas = t0s;
  410. used.forEach(function (j) {
  411. if (j.p >= m) return;
  412. if (j.m <= p) return;
  413. if (j.b && b) return;
  414. tas = Math.max(tas, j.td);
  415. });
  416. return { 'r': tas - t0s, 'p': p, 'm': m };
  417. };
  418. // 顶部
  419. var top = function (hv, t0s, b) {
  420. var suggestion = [];
  421. used.forEach(function (i) {
  422. if (i.m > hc) return;
  423. suggestion.push(fr(i.m, i.m + hv, t0s, b));
  424. });
  425. return suggestion;
  426. };
  427. // 底部
  428. var bottom = function (hv, t0s, b) {
  429. var suggestion = [];
  430. used.forEach(function (i) {
  431. if (i.p < 0) return;
  432. suggestion.push(fr(i.p - hv, i.p, t0s, b));
  433. });
  434. return suggestion;
  435. };
  436. var use = function (p, m, td) {
  437. used.push({ 'p': p, 'm': m, 'td': td, 'b': false });
  438. };
  439. var syn = function (t0s) {
  440. used = used.filter(function (i) { return i.td > t0s; });
  441. };
  442. // 挑选最好的方案:延迟小的优先,位置不重要
  443. var score = function (i, is_top) {
  444. if (i.r > maxr) return -Infinity;
  445. var f = function (p) { return is_top ? p : (hc - p); };
  446. return 1 - (i.r / maxr * (31 / 32) + f(i.p) / hc * (1 / 32));
  447. };
  448. return function (t0s, hv, is_top, b) {
  449. syn(t0s);
  450. var al = (is_top ? top : bottom)(hv, t0s, b);
  451. if (!al.length) return null;
  452. var scored = al.map(function (i) { return [score(i, is_top), i]; });
  453. var best = scored.reduce(function (x, y) {
  454. return x[0] > y[0] ? x : y;
  455. })[1];
  456. use(best.p, best.m, best.r + t0s + u)
  457. return { 'top': best.p, 'time': best.r + t0s };
  458. };
  459. };
  460. }(config.playResY, config.bottom, config.fixtime, config.max_delay));
  461.  
  462. // 为每条弹幕安置位置
  463. var setPosition = function (danmaku) {
  464. var normal = normalDanmaku(), side = sideDanmaku();
  465. return danmaku
  466. .sort(function (x, y) { return x.time - y.time; })
  467. .map(function (line) {
  468. var font_size = Math.round(line.size * config.font_size);
  469. var width = calcWidth(line.text, font_size);
  470. switch (line.mode) {
  471. case 'R2L': return (function () {
  472. var pos = normal(line.time, width, font_size, line.bottom);
  473. if (!pos) return null;
  474. line.type = 'R2L';
  475. line.stime = pos.time;
  476. line.poss = {
  477. 'x': config.playResX + width / 2,
  478. 'y': pos.top + font_size,
  479. };
  480. line.posd = {
  481. 'x': -width / 2,
  482. 'y': pos.top + font_size,
  483. };
  484. line.dtime = config.r2ltime + line.stime;
  485. return line;
  486. }());
  487. case 'TOP': case 'BOTTOM': return (function (isTop) {
  488. var pos = side(line.time, font_size, isTop, line.bottom);
  489. if (!pos) return null;
  490. line.type = 'Fix';
  491. line.stime = pos.time;
  492. line.posd = line.poss = {
  493. 'x': Math.round(config.playResX / 2),
  494. 'y': pos.top + font_size,
  495. };
  496. line.dtime = config.fixtime + line.stime;
  497. return line;
  498. }(line.mode === 'TOP'));
  499. default: return null;
  500. };
  501. })
  502. .filter(function (l) { return l; })
  503. .sort(function (x, y) { return x.stime - y.stime; });
  504. };
  505.  
  506. /*
  507. * AcFun
  508. */
  509.  
  510. // 添加图标样式
  511. var addStyle = function () {
  512. GM_addStyle(funStr(function () {/*!
  513. #area-title-view .r #btn-danmaku-view .img { background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAABKCAMAAACfBMRMAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAECUExURUxpccvKys7Ozp+yPtTU1MzMzMzMzLi3ucHEvNTUzqOrRMjIyMvLysnJyc7OzszMzMzMzM3Nzby5W8vLyrm2Xru4YLu4YMvLy4+KLdDQ0IyFI83Nzc3NzcrKycvLy8zMzMzMzMzMzMzMzM3NzczMzM3Nzbq3V7m2V4QypouEIoyFI8rKyouEIouEIszMzMi7usvLy8zMzMWurc/Pz6GdTZuWSpaRP4uEIvDv2bu4WIuEIu3s0ry5W6GcO7y5WqyoR7OvUKeiQZmTNOrpzOzrz+noyczJgevqz8rIfczKgc/NieDfs8TBberpy+7t1M3LhMfFdsK/acTBcMXCcLu4Wb26XdtPStcAAAA4dFJOUwA4RhIHDhIBBQIrCx4ZJiMqcOJGTZLBDU0W4TFaVmpgdRR9gkqH/msEa74uk/sQcj9omi6SyumMu7+DfQAAAgJJREFUSMfNlFlzmzAUhQEDkry2duysjtOkdWxnT5MUqQXi7Fv39v//lSIBQlcIP2Z6XhjmG3SE7tGxLEX1waBumVXr00T9mgHhXZppF+tsqUuluksWNKNAinVqBpVZF2ZQ3Fo1g0qs6QJl8O4+UHR/B+DXAOgWwCB4KFb7EQQajOiXTPxNg5R+zmSEr/9lpJxMCT5JGn3X4DU8oWsAH29UdvMIoFj524N5KhzevpRGJof9HJWHvTAmiwNmIUTK0SQICUawu+zDUPvLLiZIQNdrdfZsuXbf3uu0PDeFuLHatnvD0ZFYe3A0Gvbs9moDc0iw11x3Nja3tndOu93Tne2tzQ1nvelhAuGHRBDKZU/eCp2Mho5cNt/Qcc95n8jpHdsfiw2JX2k1O+03Qu1OsyV/hR8Cdhveoe+/S+T7h16DI5RdUEQIwdjF9dmsnjxw8orUu40Qqu2z+Zzt1xBCeilMpgfhmNJxeDCd6GxlLbxMT+8yXFuBhTG7iouDj69mSmF8YnM4Mm4tzUqzHoeMWxdmUMKaxVV1ETOLVRdGDo2FkUNjYRTQUBgFNFxeFb7+l7Aw/kAIC+OvCvXC+K1CrTCefqkQFkb0M99QXFUYycjksLXCSHM2mTKeST0meUJ5misCJgqjFE1QGDDUpcI4Y+l1YGfGwjjnF+m8sjAuLv7vwvgHtqBLvPYBzOUAAAAASUVORK5CYII=); }
  514. #area-title-view .l { margin-right: -80px; }
  515. #btn-danmaku-toolbar {
  516. width: 60px;
  517. position: fixed;
  518. right: 20px;
  519. top: 50px;
  520. bottom: unset;
  521. z-index: 999;
  522. background-color: #fff;
  523. border-radius: 3px 0 0 3px;
  524. box-shadow: 0 2px 5px 0 rgb(0 0 0 / 30%);
  525. opacity: .5;
  526. display: flex;
  527. flex-direction: column;
  528. align-items: center;
  529. justify-content: center;
  530. cursor: pointer;
  531. }
  532. */}));
  533. };
  534.  
  535. // 获取弹幕id
  536. var getVid = function (callback) {
  537. var player, m, vid = null;
  538. try {
  539. if(window.location.href.includes("bangumi")){
  540. vid=unsafeWindow.pageInfo.videoId;
  541. }
  542. else{
  543. vid=unsafeWindow.pageInfo.currentVideoId;
  544. }
  545. /*player = document.querySelector('iframe#ACFlashPlayer-re');
  546. m = player.src.match(/vid=(\d+)/);
  547. vid = Number(m[1]);*/
  548. } catch (e) { }
  549. if (!vid) try {
  550. player = document.querySelector('object#ACFlashPlayer-re');
  551. m = player.querySelector('param[name="flashvars"]').getAttribute('value').match(/videoId=(\d+)/);
  552. vid = Number(m[1]);
  553. } catch (e) { }
  554. if (!vid) setTimeout(function () {
  555. getVid(callback);
  556. }, 1000); else callback(vid);
  557. };
  558.  
  559. // 通过弹幕id获取弹幕内容
  560. // 弹幕内容是A站直接提供的数据
  561. var requestDanmaku = function (vid, page, callback) {
  562. var f = new FormData()
  563. f.set('resourceId',27324154)
  564. f.set('resourceType',9)
  565. f.set('enableAdvanced',true)
  566. f.set('pcursor',page)
  567. f.set('count',200)
  568. f.set('sortType',1)
  569. f.set('asc',false)
  570. GM_xmlhttpRequest({
  571. 'method': 'POST',
  572. //'url': 'http://danmu.aixifan.com/V2/' + vid + '?pageSize=500&pageNo=' + page,
  573. url: '/rest/pc-direct/new-danmaku/list',
  574. /*data:{
  575. resourceId: 27324154,
  576. resourceType: 9,
  577. enableAdvanced: true,
  578. pcursor: page,
  579. count: 500,
  580. sortType: 1,
  581. asc: false
  582. },*/
  583. data: f,
  584. 'onload': function (resp) {
  585. var data;
  586. try {
  587. data = JSON.parse(resp.response);
  588. data = data.danmakus.reduce(function (x, y) { return x.concat(y); }, []);
  589. } catch (e) { data = null; }
  590. console.log('data=>', data)
  591. if (!data || typeof data.length !== 'number') callback(vid);
  592. else callback(vid, data);
  593. },
  594. 'onerror': function () {
  595. callback(vid);
  596. },
  597. });
  598. };
  599. var getDanmaku = function (vid, callback) {
  600. var danmaku = [];
  601. (function collectDanmaku(i) {
  602. requestDanmaku(vid, i, function (vid, data) {
  603. if (!data || !data.length) {
  604. return callback(vid, danmaku);
  605. }
  606. danmaku = danmaku.concat(data);
  607. collectDanmaku(i + 1);
  608. });
  609. }(1));
  610. };
  611.  
  612. // 将弹幕内容转换为程序内部的表达方式
  613. var mina = function (vid, danmaku) {
  614. console.log('danmaku =>', danmaku)
  615. danmaku = danmaku.map(function (line) {
  616. var text = line.body;
  617. var color = line.color
  618. var mode = line.mode
  619. var size = line.size
  620. return {
  621. 'text': text,
  622. 'time': line.position,
  623. 'color': 'FFFFFF',
  624. 'mode': [undefined, 'R2L', undefined, undefined, 'BOTTOM', 'TOP'][mode],
  625. 'size': size,
  626. 'bottom': false,
  627. // 'sender': String(info[4]),
  628. // 'create': new Date(Number(info[5]) * 1000),
  629. // 'danmakuid': info[6], // format as uuid
  630. };
  631. });
  632. var name;
  633. try { name = document.querySelector('h1.title').textContent; }
  634. catch (e) { name = '' + vid; }
  635. var ass = generateASS(setPosition(danmaku), {
  636. 'title': document.title,
  637. 'ori': location.href,
  638. });
  639. startDownload('\ufeff' + ass, name + '.ass');
  640. };
  641.  
  642. // 显示按钮
  643. var showButton = function (vid, danmaku) {
  644. if (!danmaku) return;
  645. var click = function (e) {
  646. e.preventDefault();
  647. getDanmaku(vid, function (vid, danmaku0) {
  648. mina(vid, danmaku0 || danmaku);
  649. });
  650. };
  651. var n = danmaku.length.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');;
  652. //var r = document.querySelector('#area-title-view .r'), d;
  653. var r = document.querySelector('body'), d;
  654. if (r) {
  655. d = document.createElement('div');
  656. d.innerHTML = '<a class="toolbar" id="btn-danmaku-view"><div class="img"></div><p>弹幕</p><span class="pts">' + n + '</span></a>';
  657. d.firstChild.addEventListener('click', click);
  658. //r.insertBefore(d.firstChild, r.firstChild);
  659. }
  660. //var l = document.querySelector('#area-toolbar-view .l');
  661. var l = document.querySelector('body');
  662. if (l) {
  663. var t = l.querySelector('#txt-info-title') || null;
  664. d = document.createElement('div');
  665. d.innerHTML = '<div id="btn-danmaku-toolbar" class="a toolbar"><p>弹幕下载</p><span class="pts">'+n+'</span></div>';
  666. d.firstChild.addEventListener('click', click);
  667. l.insertBefore(d.firstChild, t);
  668. }
  669. };
  670.  
  671. // 初始化按钮
  672. var initButton = function () {
  673. addStyle();
  674. getVid(function (vid) {
  675. getDanmaku(vid, showButton);
  676. });
  677. };
  678.  
  679. /*
  680. * Common
  681. */
  682.  
  683. // 初始化
  684. var init = function () {
  685. initFont();
  686. initButton();
  687. };
  688.  
  689. if (document.body) init();
  690. else window.addEventListener('DOMContentLoaded', init);