pakku.user.js

GreaseMonkey version of pakku.js.

  1. // ==UserScript==
  2. // @name pakku.user.js
  3. // @description GreaseMonkey version of pakku.js.
  4. // @include *://*.bilibili.com/*
  5. // @version 1.1
  6. // @grant none
  7. // @namespace me.fts.pakkujs
  8. // ==/UserScript==
  9.  
  10. (function(){
  11.  
  12. // Configuration
  13. // I am too lazy to add configuration UI :)
  14.  
  15. THRESHOLD=20;
  16. TRIM_ENDING=true;
  17. TAOLUS=[[/^23{2,}$/,"233..."],[/^6{3,}$/,"666..."],[/^[fF]+$/,"FFF..."],[/^[hH]+$/,"hhh..."]];
  18. REMOVE_SEEK=true;
  19. PROC_TYPE7=true;
  20. MAX_COSINE=80;
  21. MAX_DIST=3;
  22. TRIM_ENDING=true;
  23. TRIM_SPACE=true;
  24. WHITELIST=[];
  25. DANMU_MARK='suffix';
  26. ENLARGE=true;
  27.  
  28. //Unused in script:
  29. /*
  30. DANMU_BADGE
  31. FLASH_NOTIF
  32. POPUP_BADGE
  33. //*/
  34.  
  35. function fromholyjson(txt) {
  36. var item=JSON.parse(txt);
  37. for(var i in item)
  38. item[i][0]=RegExp(item[i][0]);
  39. return item;
  40. }
  41. function toholyjson(obj) {
  42. var item=[];
  43. for(var i in obj)
  44. item.push([obj[i][0].source,obj[i][1]]);
  45. return JSON.stringify(item);
  46. }
  47.  
  48.  
  49.  
  50.  
  51. // Copied from edit_distance.js
  52.  
  53. var ed_counts = new Int16Array (0x10ffff);
  54. var ed_a = new Int16Array (1048576);
  55. var ed_b = new Int16Array (1048576);
  56. var ed_t = new Int32Array (2048);
  57.  
  58. var MIN_DANMU_SIZE=10;
  59.  
  60. function hash(a, b) {
  61. return ((a<<10)^b)&1048575;
  62. }
  63.  
  64. function edit_distance (P, Q) {
  65. 'use strict';
  66. // TODO: Make this less hacky
  67.  
  68. if (P.length + Q.length < MIN_DANMU_SIZE)
  69. return (MAX_DIST + 1) * +(P != Q);
  70.  
  71. for (var i = 0; i < P.length; i ++) ed_counts [P.charCodeAt (i)] ++;
  72. for (var i = 0; i < Q.length; i ++) ed_counts [Q.charCodeAt (i)] --;
  73.  
  74. var ans = 0;
  75.  
  76. for (var i = 0; i < P.length; i ++) {
  77. ans += Math.abs (ed_counts[P.charCodeAt (i)]);
  78. ed_counts[P.charCodeAt (i)] = 0;
  79. }
  80.  
  81. for (var i = 0; i < Q.length; i ++) {
  82. ans += Math.abs (ed_counts[Q.charCodeAt (i)]);
  83. ed_counts[Q.charCodeAt (i)] = 0;
  84. }
  85.  
  86. return ans;
  87. }
  88.  
  89. function cosine_distance (P, Q) {
  90.  
  91. 'use strict';
  92.  
  93. var ed_t_p = 0;
  94.  
  95. ed_a[hash(P.charCodeAt(P.length - 1), P.charCodeAt(0))] = 1;
  96. ed_b[hash(Q.charCodeAt(Q.length - 1), Q.charCodeAt(0))] = 1;
  97.  
  98. ed_t[ed_t_p++] = hash(P.charCodeAt(P.length - 1), P.charCodeAt(0));
  99. ed_t[ed_t_p++] = hash(Q.charCodeAt(Q.length - 1), Q.charCodeAt(0));
  100. for (var i = 0; i < P.length - 1; i++) {
  101. var h1 = hash(P.charCodeAt(i), P.charCodeAt(i + 1));
  102. ed_t[ed_t_p++] = h1;
  103. ed_a[h1] += 1;
  104. }
  105. for (var i = 0; i < Q.length - 1; i++) {
  106. var h1 = hash(Q.charCodeAt(i), Q.charCodeAt(i + 1));
  107. ed_t[ed_t_p++] = h1;
  108. ed_b[h1] += 1;
  109. }
  110. var data = Array();
  111.  
  112. var x = 0, y = 0, z = 0;
  113.  
  114. for (var i = 0; i < ed_t_p; i++) {
  115. var h1 = ed_t[i];
  116. if (ed_a[h1]) {
  117. y += ed_a[h1] * ed_a[h1];
  118. if (ed_b[h1]) {
  119. x += ed_a[h1] * ed_b[h1];
  120. z += ed_b[h1] * ed_b[h1];
  121. ed_b[h1] = 0;
  122. }
  123. ed_a[h1] = 0;
  124. }
  125. else {
  126. if (ed_b[h1]) {
  127. z += ed_b[h1] * ed_b[h1];
  128. ed_b[h1] = 0;
  129. };
  130. }
  131. }
  132. //console.log(x,y,z);
  133. return x * x / y / z;
  134. }
  135.  
  136. function similar(P,Q) {
  137. return edit_distance(P,Q)<=MAX_DIST || cosine_distance(P,Q)*100>=MAX_COSINE;
  138. }
  139.  
  140. // Copied from core.js
  141.  
  142. var trim_ending_re=/^(.+?)[\.。,,/\??!!~~@\^、+=\-_♂♀ ]*$/;
  143. var trim_space_re=/[  ]/g;
  144. var LOG_VERBOSE=true;
  145.  
  146. function parse(old_dom,tabid) {
  147. TAOLUS_len=TAOLUS.length;
  148. WHITELIST_len=WHITELIST.length;
  149. //chrome.browserAction.setTitle({
  150. // title: '正在处理弹幕…', // if u can see this, pakku might not be working correctly :)
  151. // tabId: tabid
  152. //});
  153. console.time('parse');
  154. function enlarge(size,count) {
  155. return count<=10 ? size : Math.floor(size*Math.log10(count));
  156. }
  157.  
  158. function make_mark(txt,cnt) {
  159. return DANMU_MARK=='suffix' ? txt+' [x'+cnt+']' :
  160. DANMU_MARK=='prefix' ? '[x'+cnt+'] '+txt : txt;
  161. }
  162. function detaolu(text) {
  163. for(var i=0;i<TAOLUS_len;i++)
  164. if(TAOLUS[i][0].test(text))
  165. return TAOLUS[i][1];
  166. text = TRIM_ENDING ? text.replace(trim_ending_re,'$1') : text;
  167. text = TRIM_SPACE ? text.replace(trim_space_re,'') : text;
  168. return text;
  169. }
  170. function whitelisted(text) {
  171. for(var i=0;i<WHITELIST_len;i++)
  172. if(WHITELIST[i][0].test(text))
  173. return true;
  174. return false;
  175. }
  176. function ext_special_danmu(text) {
  177. try {
  178. return JSON.parse(text)[4];
  179. } catch(e) {
  180. return text;
  181. }
  182. }
  183. function build_text(elem) {
  184. var count=elem.count;
  185. var dumped=null;
  186. if(elem.mode=='7') // special danmu, need more parsing
  187. try {
  188. dumped=JSON.parse(elem.orig_str);
  189. } catch(e) {}
  190. if(dumped) {
  191. dumped[4]=count==1?dumped[4]:make_mark(elem.str,count);
  192. return JSON.stringify(dumped);
  193. } else // normal case
  194. return count==1?elem.orig_str:make_mark(elem.str,count);
  195. }
  196.  
  197. var parser=new DOMParser();
  198. var dom=parser.parseFromString(old_dom,'text/xml');
  199. var new_dom=parser.parseFromString('<i></i>','text/xml');
  200. var i_elem=new_dom.childNodes[0];
  201.  
  202. var danmus=[];
  203. [].slice.call(dom.childNodes[0].children).forEach(function(elem) {
  204. if(elem.tagName=='d') { // danmu
  205. var attr=elem.attributes['p'].value.split(',');
  206. var str=elem.childNodes[0] ? elem.childNodes[0].data : '';
  207.  
  208. if(!PROC_TYPE7 && attr[1]=='7') // special danmu
  209. i_elem.appendChild(elem);
  210. else if(attr[1]=='8') { // code danmu
  211. if(REMOVE_SEEK && str.indexOf('Player.seek(')!=-1)
  212. elem.childNodes[0].data='/* player.seek filtered by pakku */';
  213. i_elem.appendChild(elem);
  214. } else if(whitelisted(str)) {
  215. i_elem.appendChild(elem);
  216. } else
  217. danmus.push({
  218. attr: attr, // thus we can build it into new_dom again
  219. str: attr[1]=='7' ? detaolu(ext_special_danmu(str)) : detaolu(str),
  220. time: parseFloat(attr[0]),
  221. orig_str: str,
  222. mode: attr[1],
  223. count: 1,
  224. });
  225. } else
  226. i_elem.appendChild(elem);
  227. });
  228. danmus.sort(function(x,y) {return x.time-y.time;});
  229.  
  230. var danmu_chunk=Array();
  231. var last_time=0;
  232. var counter=0;
  233.  
  234. function apply_item(dm) {
  235. counter+=dm.count-1;
  236. var d=new_dom.createElement('d');
  237. var tn=new_dom.createTextNode(build_text(dm));
  238.  
  239. d.appendChild(tn);
  240. if(ENLARGE)
  241. dm.attr[2]=''+enlarge(parseInt(dm.attr[2]),dm.count);
  242. d.setAttribute('p',dm.attr.join(','));
  243. i_elem.appendChild(d);
  244. }
  245. danmus.forEach(function(dm) {
  246. while(danmu_chunk.length && dm.time-danmu_chunk[0].time>THRESHOLD)
  247. apply_item(danmu_chunk.shift());
  248. for(var i=0;i<danmu_chunk.length;i++) {
  249. if(similar(dm.str,danmu_chunk[i].str)) {
  250. if(LOG_VERBOSE) {
  251. if(edit_distance(dm.str,danmu_chunk[i].str)>MAX_DIST)
  252. console.log('cosine_dis',dm.str,'to',danmu_chunk[i].str);
  253. else
  254. console.log('edit_dis',dm.str,'to',danmu_chunk[i].str);
  255. }
  256. danmu_chunk[i].count++;
  257. return; // aka continue
  258. }
  259. }
  260. danmu_chunk.push(dm);
  261. });
  262. for(var i=0;i<danmu_chunk.length;i++)
  263. apply_item(danmu_chunk[i]);
  264.  
  265. //setbadge((
  266. // POPUP_BADGE=='count' ? ''+counter :
  267. // POPUP_BADGE=='percent' ? (danmus.length ? (counter*100/danmus.length).toFixed(0)+'%' : '0%') :
  268. // ''
  269. // ),SUCCESS_COLOR,tabid
  270. //);
  271. //chrome.browserAction.setTitle({
  272. // title: '已过滤 '+counter+'/'+danmus.length+' 弹幕',
  273. // tabId: tabid
  274. //});
  275. var serializer=new XMLSerializer();
  276. console.timeEnd('parse');
  277. return [serializer.serializeToString(new_dom), new_dom];
  278. }
  279.  
  280. // Hijacking XMLHttpRequest to make this script work
  281.  
  282. WINDOW = window;
  283. if(WINDOW._fts_xhr)return;
  284. WINDOW._fts_xhr = WINDOW.XMLHttpRequest;
  285. WINDOW.XMLHttpRequest = function() {
  286. //cannot use apply directly since we want a 'new' version
  287. var wrapped = new(Function.prototype.bind.apply(WINDOW._fts_xhr, arguments));
  288.  
  289. wrapped.addEventListener("readystatechange", function () {
  290. var r;
  291. try{
  292. if(wrapped.readyState == 4) {
  293. //console.log(wrapped);
  294. if( wrapped.responseURL.match(/^(https:|http:|)?(\/\/)?comment.bilibili.com\/.*\.xml/)) {
  295. console.log("processing danmoku", wrapped);
  296. r = parse(wrapped.response, 0);
  297. Object.defineProperty(wrapped, 'responseText', {
  298. writable: true
  299. });
  300. Object.defineProperty(wrapped, 'responseXML', {
  301. writable: true
  302. });
  303. Object.defineProperty(wrapped, 'response', {
  304. writable: true
  305. });
  306. wrapped.responseXML = r[1];
  307. wrapped.responseText = r[0];
  308. wrapped.response = r[0];
  309. }
  310. }
  311. } catch(e) {
  312. console.log(e, r);
  313. }
  314. });
  315. //*/
  316.  
  317. return wrapped;
  318. };
  319. console.log("r1 loaded");
  320.  
  321.  
  322. })();