Greasy Fork is available in English.

GMscrobber

记录在线音乐到 last.fm

Acest script nu ar trebui instalat direct. Aceasta este o bibliotecă pentru alte scripturi care este inclusă prin directiva meta a // @require https://update.greasyfork.org/scripts/7806/183861/GMscrobber.js

  1. var log = function(){},GM_log,////unsafeWindow.console.log,//
  2. getVal = GM_getValue,
  3. setVal = GM_setValue,
  4. delVal = GM_deleteValue,
  5. xhr = GM_xmlhttpRequest,
  6. rmc = GM_registerMenuCommand,
  7.  
  8. _md5 = hex_md5;
  9. //simple scrobbler for userscript
  10. var Scrobbler = function(){
  11. var apikey = "4472aff22b680a870bfd583f99644a03",
  12. secret = "cbc5528721f63b839720633d7c1258d2",
  13. apiurl = "http://ws.audioscrobbler.com/2.0/",
  14. scrate = .9,
  15. tokenreg = /[?&]token=(.+?)(&|#|$)/,
  16. _timer, _shift;
  17. /**
  18. * Scrobbler
  19. * @constructor
  20. * @param {Object} info 该页面 scrobbler 信息
  21. * @param {Number} info.type 1: 手动调用scrobble记录歌曲;
  22. 0(缺省): 在调用 nowplaying 后根据播放时间自动调用 scrobble 记录歌曲
  23. * @param {String} info.name 该 scrobbler 名字, 会显示在 greasemonkey 菜单中
  24. * @param {Function} info.ready 回调. scrobbler sessionid 取得后会调用此函数.
  25. * @param {Number} [info.scrate] 自动记录的百分比, info.type == 1 时无效
  26. */
  27. var fn = function(info){
  28. this.type = info.type;
  29. this.name = info.name || "";
  30. this.ready = info.ready || function(){};
  31. this.scrate = info.scrate || scrate;
  32. this.init();
  33. };
  34. fn.prototype = {
  35. init: function(){
  36. var sk = getVal("session"),
  37. token = document.location.search.match(tokenreg),
  38. that = this;
  39. token = token && token[1];
  40. log(sk + "\n" + token + "\n" + document.location.href);
  41. if(sk){
  42. rmc("停止记录" + that.name, that.delSession);
  43. that.username = sk.split("/")[0];
  44. that.sk = sk.split("/")[1];
  45. setTimeout(function(){that.ready()}, 0);
  46. }else if(token){
  47. that.sk = "wait";
  48. that.ajax({method:"auth.getSession", _sig:"", token: token},
  49. function(d){
  50. log(JSON.stringify(d))
  51. if(d.session && d.session.key){
  52. that.sk = d.session.key;
  53. that.username = d.session.name;
  54. setVal("session",that.username + "/" + that.sk);
  55. rmc("停止记录" + that.name, that.delSession);
  56. that.ready();
  57. }
  58. }, true);
  59. }else{
  60. rmc("开始记录" + that.name, fn.redirect);
  61. }
  62. this.listeners = {};
  63. },
  64. getSession: function(){
  65. return this.sk;
  66. },
  67. delSession: function(){
  68. delVal("session");
  69. document.location = document.location.href.replace(tokenreg, "");
  70. },
  71. /**
  72. * 定期检查页面歌曲信息变化. 将歌曲信息获取函数传给此函数, 即可自动完成歌曲的记录.
  73. * @param {Function} getSongInfo 各页面脚本的歌曲信息获取函数.
  74. 应该返回歌曲信息: {title: '', artist: '', duration: 0, playTime: '', album: ''}
  75. * @param {Object} opts
  76. * @param {Nunber} opts.checktime 定时器周期, 毫秒
  77. */
  78. setSongInfoFN: (function(){
  79. var checkTime;
  80. var fn = function(getSongInfo, opts){
  81. opts = opts || {};
  82. checkTime = opts.checktime || 2000;
  83. var info = {}, that = this;
  84. setInterval(function(){
  85. try{
  86. that.getSongInfo = getSongInfo;
  87. info = getSongInfo();
  88. infoChecker.call(that, info);
  89. }catch(e){
  90. log(e.stack);
  91. }
  92. }, checkTime);
  93. };
  94. var oldSong = {};
  95. var infoChecker = function(song){
  96. if(song.title && song.artist && song.duration){
  97. if(song.title != oldSong.title || song.artist != oldSong.artist){
  98. this.nowPlaying(song);
  99. }else{
  100. //log(this.state)
  101. if(song.playTime != oldSong.playTime){
  102. if(song.playTime <= Math.ceil(checkTime / 1000) && (Date.now() / 1000 - this.timestamp > song.duration)){
  103. log(song.title + ' repeating.');
  104. //单曲重复
  105. this.nowPlaying(song);
  106. }else{
  107. this.state != 'play' && this.play(song.playTime + this.info.offset);
  108. }
  109. }else{
  110. this.state == 'play' && this.pause();
  111. }
  112. }
  113. oldSong = uso.clone(song);
  114. }
  115. };
  116. return fn;
  117. })(),
  118. //song's command
  119. /**
  120. * 向 last.fm 发送正在播放请求
  121. * @param {Object} song 歌曲信息
  122. * @param {String} song.title 曲名
  123. * @param {String} song.artist 歌手(多个歌手用 & 连接)
  124. * @param {String} song.duration 时长. 单位: 秒
  125. * @param {String} [song.album] 专辑名
  126. * @param {String} [song.playTime] 开始播放时的时间
  127. */
  128. nowPlaying: function(song){
  129. var that = this;
  130. this.song = song; //song: {title: "", artist: "", duration: "", album: ""}
  131. this.timestamp = Math.floor(new Date().getTime()/1000);
  132. this.info = {iscrobble: false, offset: 0};
  133. this.play(song.playTime || 0);
  134. //log(JSON.stringify(song));
  135. log(song.title + " now playing");
  136. this.ajax({
  137. method: "track.updateNowPlaying",
  138. track: song.title,
  139. artist: song.artist,
  140. duration: song.duration,
  141. album: song.album,//
  142. _sig:""
  143. },
  144. function(d){
  145. //log(JSON.stringify(d));
  146. },
  147. true);
  148. //typeof meta != 'undefined' && that.record(song, 'nowplaying');
  149. this.fire('nowplaying');
  150. lyr.call(this);
  151. },
  152. //
  153. record: function(song, type){
  154. var query = uso.clone(song), path;
  155. query.source = document.location.host;
  156. query.version = meta.version;
  157. query.username = this.username;
  158. query = uso.paramSerialize(query);
  159. if(type == 'nowplaying'){
  160. path = '/nowplaying?';
  161. }else if(type == 'scrobble'){
  162. path = '/scrobble?';
  163. }else{
  164. return false;
  165. }
  166. xhr({
  167. method: 'GET',
  168. url: meta.namespace.replace('\/', '') + path + query
  169. });
  170. },
  171. /**
  172. * 向 last.fm 发送正在记录请求
  173. * @param {Object} [song] 歌曲信息. 当nowPlaying中的歌曲信息不全时, 应在此补全. last.fm 的播放记录以此为准
  174. */
  175. scrobble: function(song){
  176. var that = this;
  177. song = song || this.song;
  178. this.ajax({
  179. method: "track.scrobble",
  180. track: song.title,
  181. artist: song.artist,
  182. album: song.album,
  183. timestamp: this.timestamp,
  184. _sig:""
  185. },
  186. function(d){
  187. //log(JSON.stringify(d));
  188. that.info.iscrobble = true;
  189. log(song.artist + "'s " + song.title + " / " + song.album + " scrobbled..");
  190. },
  191. true);
  192. //typeof meta != 'undefined' && that.record(song, 'scrobble');
  193. this.fire('scrobble');
  194. },
  195. /** love
  196. * @param {Object} [song] 歌曲信息. 事实上你可以听得是一首歌, love 的却是另一首
  197. */
  198. love: function(song){
  199. song = song || this.song;
  200. this.ajax({
  201. method: "track.love",
  202. track: song.title,
  203. artist: song.artist,
  204. _sig:""
  205. },
  206. function(d){
  207. //log(JSON.stringify(d))
  208. },
  209. true);
  210. this.fire('love');
  211. },
  212. unlove: function(song){
  213. song = song || this.song;
  214. this.ajax({
  215. method: "track.unlove",
  216. track: song.title,
  217. artist: song.artist,
  218. _sig:""
  219. },
  220. function(d){
  221. //log(JSON.stringify(d))
  222. },
  223. true);
  224. this.fire('unlove');
  225. },
  226. ban: function(song){
  227. song = song || this.song;
  228. this.ajax({
  229. method: "track.ban",
  230. track: song.title,
  231. artist: song.artist,
  232. _sig:""
  233. },
  234. function(d){
  235. //log(JSON.stringify(d))
  236. },
  237. true);
  238. this.fire('ban');
  239. },
  240. unban: function(song){
  241. song = song || this.song;
  242. this.ajax({
  243. method: "track.unban",
  244. track: song.title,
  245. artist: song.artist,
  246. _sig:""
  247. },
  248. function(d){
  249. //log(JSON.stringify(d))
  250. },
  251. true);
  252. this.fire('unban');
  253. },
  254. getInfo: function(song, callback){
  255. var that = this;
  256. song = song || this.song;
  257. this.ajax({
  258. method: "track.getInfo",
  259. track: song.title,
  260. artist: song.artist,
  261. username: this.username,
  262. _sig:""
  263. },
  264. function(d){
  265. //log(JSON.stringify(d));
  266. var n, t;
  267. if(d.track){
  268. n = d.track.userplaycount ? d.track.userplaycount : 0;
  269. if(d.track.userloved == "1"){
  270. t = "1";
  271. }else if(d.track.userloved === "0"){
  272. t = "0";
  273. }
  274. }else{
  275. n = 0;
  276. t = "0";
  277. }
  278. typeof callback == "function" && callback({islove: t, len: n});
  279. },
  280. true);
  281. },
  282. //play control
  283. /**
  284. * 开始播放. 从所有停止状态开始播放, 都应该调用此函数
  285. * @param {Number} [realPlayTime] 播放时校正.
  286. 当一首歌暂停次数太多的时候, 记录的播放时间可能会有误差, 传入 realPlayTime 即可校正播放的时间
  287. */
  288. play: function(realPlayTime){
  289. var that = this, rpt = realPlayTime, rt;
  290. this.state = "play";
  291. this.fire(this.state);
  292. if(!rpt){
  293. rpt = Math.floor(new Date().getTime()/1000) - this.timestamp;
  294. }
  295. rt = (Math.min(that.song.duration*this.scrate, 240) - rpt)*1000;//remain time
  296. if(!this.type && !this.info.iscrobble){
  297. clearTimeout(_timer);
  298. log('will scrobbler in: ' + rt/1000 + ' seconds')
  299. _timer = setTimeout(function(){that.scrobble()}, rt);
  300. }
  301. },
  302. pause: function(){
  303. this.type || clearTimeout(_timer);
  304. this.state = "pause";
  305. this.fire(this.state);
  306. },
  307. buffer: function(){
  308. this.type || clearTimeout(_timer);
  309. this.state = "buffer";
  310. this.fire(this.state);
  311. },
  312. stop: function(){
  313. this.type || clearTimeout(_timer);
  314. this.state = "stop";
  315. this.fire(this.state);
  316. },
  317. seek: function(offset){
  318. this.state = "seek";
  319. this.fire(this.state, offset);
  320. this.info.offset += offset;
  321. log('seek, offset: ' + offset + ', totle offset: ' + this.info.offset);
  322. },
  323. ajax: function(params, callback, auth){
  324. if(this.sk){
  325. params.sk = this.sk;
  326. }else{
  327. delete params.sk;
  328. }
  329. fn.ajax(params, callback, auth);
  330. },
  331. on: function(event, handler){
  332. this.listeners[event] = this.listeners[event] || [];
  333. this.listeners[event].push(handler);
  334. return this;
  335. },
  336. off: function(event, handler){
  337. var listeners = this.listeners[event] || [];
  338. if(handler){
  339. for(var i = 0, l = listeners.length; i < l; i++){
  340. if(handler == listeners[i]){
  341. delete listeners[i];
  342. }
  343. }
  344. }else{
  345. delete this.listeners[event];
  346. }
  347. return this;
  348. },
  349. fire: function(event){
  350. var listeners = this.listeners[event] || [];
  351. var args = [].slice.call(arguments);
  352. args.shift();
  353. for(var i = 0, l = listeners.length; i < l; i++){
  354. listeners[i] && listeners[i].apply(this, args);
  355. }
  356. return this;
  357. }
  358. };
  359. fn.redirect = function(){
  360. document.location = "http://www.last.fm/api/auth/?api_key=" + apikey + "&cb=" + encodeURIComponent(document.location.href.replace(/^https/,'http'));
  361. };
  362. fn.ajax = function(params, callback, auth){
  363. var method = "POST",
  364. headers = {"Content-Type": "application/x-www-form-urlencoded"},
  365. url = apiurl + "?format=json",
  366. data = "";
  367. if(!auth){
  368. method = "GET";
  369. headers = {};
  370. url = url + "&" + fn.paramsInit(params);
  371. data = "";
  372. }else{
  373. data = fn.paramsInit(params, true);
  374. }
  375. xhr({
  376. method: method,
  377. headers: headers,
  378. url: url,
  379. data: data,
  380. onload: function(d){
  381. var res = JSON.parse(d.responseText);
  382. //log(JSON.stringify(d));
  383. res.error && log(JSON.stringify(d));
  384. if(res.error == "9"){
  385. //delVal(fn.name);
  386. //fn.redirect();
  387. }
  388. callback(res);
  389. },
  390. onerror: function(e){
  391. //alert(params.method + "failed");
  392. log("[ error ] " + params.method + " request failed.. " + JSON.stringify(e));
  393. }
  394. });
  395. };
  396. fn.paramsInit = function(params){
  397. var keys = [], str1 = "", str2 = "", flag;
  398. if(typeof params._sig != "undefined"){
  399. delete params._sig;
  400. flag = true;
  401. }else{
  402. flag = end;
  403. }
  404. params.api_key = apikey;
  405. for(var key in params){
  406. if(params[key]){
  407. keys.push(key + params[key]);
  408. }
  409. }
  410. str1 = uso.paramSerialize(params);
  411. keys.sort();
  412. str2 = keys.join("") + secret;
  413. //log("str2: " + str2);
  414. //log("str1: " + str1);
  415. if(flag){
  416. return str1 + "&api_sig=" + _md5(str2);
  417. }else{
  418. return str1;
  419. }
  420. };
  421. return fn;
  422. }();
  423.  
  424. /**
  425. * 歌词查询
  426. * 歌词 API 来源于 (@solos)[https://github.com/solos] 的(歌词迷)[http://api.geci.me/en/latest/index.html]
  427. */
  428. var lyr = function(){
  429. var lrc;
  430. var fn = function(){
  431. var title = this.song.title
  432. , artist = this.song.artist
  433. , album = this.song.album
  434. , startTime = this.song.playTime || 0
  435. , that = this
  436. ;
  437. log('lyric for ' + artist + '\'s ' + title + ' / ' + album + ' is getting..');
  438. var t1 = Date.now();
  439. lrc && lrc.stop();
  440. xhr({
  441. method: 'GET',
  442. url: 'http://geci.me/api/lyric/' + title + '/' + artist,
  443. onload: function (res){
  444. //log(res.responseText);
  445. var lyrSrc = '';
  446. res = JSON.parse(res.responseText);
  447. if(res.count){
  448. lyrSrc = res.result[0].lrc;
  449. log('lyrics link: ' + lyrSrc);
  450. xhr({
  451. method: 'GET',
  452. url: lyrSrc,
  453. //overrideMimeType: 'text/plain; charset=gb2312',
  454. onload: function(d){
  455. var txt = d.responseText;
  456. GM_log(txt);
  457. if(typeof Lrc != 'undefined'){
  458. lrc = (new Lrc(txt, function(txt, extra){
  459. txt && lrcOut.call(this, txt, extra);
  460. //that.getSongInfo && lrcOut('player time: ' + that.getSongInfo().playTime)
  461. //lrcOut(lrc.lrc.split('\n')[extra.originLineNum])
  462. }));
  463. //提前1秒显示歌词
  464. that.state === 'play' && lrc.play(startTime * 1000 + Date.now() - t1 + 1000);
  465. }
  466. },
  467. onerror: function(){
  468. lrcOut('some error occured..');
  469. }
  470. });
  471. }else{
  472. lrcOut('no lyrics for ' + title);
  473. }
  474. },
  475. onerror: function (e){
  476. lrcOut('搜索歌词失败! ' + JSON.stringify(e));
  477. }
  478. });
  479. this.off('pause', pause).off('play', pause).off('buffer', pause).
  480. on('pause', pause).on('play', pause).on('buffer', pause).
  481. off('seek', seek).on('seek', seek);
  482. };
  483. function pause(){
  484. log('pause')
  485. lrc && lrc.pauseToggle();
  486. }
  487. function seek(offset){
  488. lrc && lrc.seek(-offset * 1000);
  489. }
  490.  
  491. return fn;
  492. }();
  493.  
  494. //重新此方法可实现自己歌词输出
  495. var lrcOut = function(txt){
  496. unsafeWindow.console && unsafeWindow.console.log(txt);
  497. //GM_log(txt);
  498. };
  499.  
  500. //userscript 自动更新工具
  501. var uso = {
  502. //usersctipt meta 解析工具
  503. metaParse: function(metadataBlock) {
  504. var headers = {};
  505. var line, name, prefix, header, key, value, _t;
  506.  
  507. var lines = metadataBlock.split(/\n/).filter(function(line){return /\/\/ @/.test(line)});
  508. lines.forEach(function(line) {
  509. _t = line.match(/\/\/ @(\S+)\s*(.*)/);
  510. name = _t[1];
  511. value = _t[2];
  512.  
  513. switch (name) {
  514. case "licence":
  515. name = "license";
  516. break;
  517. }
  518.  
  519. _t = name.split(/:/).reverse();
  520. key = _t[0];
  521. prefix = _t[1];
  522.  
  523. if (prefix) {
  524. if (!headers[prefix])
  525. headers[prefix] = new Object;
  526. header = headers[prefix];
  527. } else
  528. header = headers;
  529.  
  530. if (header[key] && !(header[key] instanceof Array))
  531. header[key] = new Array(header[key]);
  532.  
  533. if (header[key] instanceof Array)
  534. header[key].push(value);
  535. else
  536. header[key] = value;
  537. });
  538.  
  539. headers["licence"] = headers["license"];
  540.  
  541. return headers;
  542. },
  543. /**
  544. * 自动升级工具
  545. * @param {String} ver 当前版本号
  546. * @param {String} id userscript.org 上的编号
  547. * @param {Function} cb 检测结果回调
  548. */
  549. check: function(ver, id, cb){
  550. var that = this, self = arguments.callee, flag = false;
  551. xhr({
  552. method:"GET",
  553. url:"https://userscripts.org/scripts/source/" + id + ".meta.js",
  554. headers:{
  555. "Accept":"text/javascript; charset=UTF-8"
  556. },
  557. overrideMimeType:"application/javascript; charset=UTF-8",
  558. onload:function(response) {
  559. var meta = that.metaParse(response.responseText),
  560. ver0 = meta.version, r;
  561. if(that.verCompare(ver, ver0) < 0){
  562. flag = true;
  563. if(meta.initiative == 'true' || meta.initiative == 'yes'){
  564. alert([
  565. meta.name + " ver" + ver0, "",
  566. meta.changelog].join("\n "));
  567. document.location = "http://userscripts.org/scripts/source/" + id + ".user.js";
  568. }else{
  569. rmc("更新" + meta.name + " " + ver + " 至 " + ver0, function(){
  570. r = confirm([
  571. meta.name + " ver: " + ver0, "",
  572. "更新说明: " + meta.changelog, "",
  573. "是否更新?"].join("\n "));
  574. if(r){
  575. document.location = "http://userscripts.org/scripts/source/" + id + ".user.js";
  576. }
  577. });
  578. }
  579. }
  580. typeof cb == "function" && cb(flag);
  581. },
  582. onerror: function(e){
  583. log("check version failed; \n" + JSON.stringify(e));
  584. }
  585. });
  586. },
  587. verCompare: function(ver0, ver1){
  588. var a0 = ver0.split("."), a1 = ver1.split("."),
  589. len = Math.max(a0.length, a1.length);
  590. if(ver0 == ver1){
  591. return 0;
  592. }
  593. for(var i = 0; i < len; i++){
  594. if(a0[i] < a1[i] || typeof a0[i] == "undefined"){
  595. return -1;//ver0 < ver1
  596. }else if(a0[i] != a1[i]){
  597. break;
  598. }
  599. }
  600. return 1;
  601. },
  602. paramSerialize: function(params){
  603. var str = '';
  604. for(var key in params){
  605. if(params[key]){
  606. str += encodeURIComponent(key) + "=" + encodeURIComponent(params[key]) + "&";
  607. }
  608. }
  609. str = str.replace(/&$/, "");
  610. return str;
  611. },
  612. clone: function(obj){
  613. if(obj == null || typeof(obj) != 'object'){ return obj }
  614. var temp = obj.constructor(); // changed
  615. for(var key in obj){ temp[key] = arguments.callee(obj[key]) }
  616. return temp;
  617. },
  618. //str hh:mm:ss
  619. timeParse: function(str) {
  620. var ts = str.trim().match(/(?:(\d+):)?(\d\d?):(\d\d?)/);
  621. return (ts[1] || 0) * 3600 + ts[2] * 60 + ts[3] * 1 || 0;
  622. },
  623. //函数切面
  624. //前面的函数返回值传入 breakCheck 判断, breakCheck 返回值为真时不执行后面的函数
  625. beforeFn: function (oriFn, fn, breakCheck) {
  626. return function() {
  627. var ret = fn.apply(this, arguments);
  628. if(breakCheck && breakCheck.call(this, ret)){
  629. return ret;
  630. }
  631. return oriFn.apply(this, arguments);
  632. };
  633. },
  634.  
  635. afterFn: function (oriFn, fn, breakCheck) {
  636. return function() {
  637. var ret = oriFn.apply(this, arguments);
  638. if(breakCheck && breakCheck.call(this, ret)){
  639. return ret;
  640. }
  641. fn.apply(this, arguments);
  642. return ret;
  643. }
  644. }
  645. };
  646.  
  647.  
  648. /*
  649. * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
  650. * Digest Algorithm, as defined in RFC 1321.
  651. * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
  652. * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
  653. * Distributed under the BSD License
  654. * See http://pajhome.org.uk/crypt/md5 for more info.
  655. */
  656. var hexcase=0;function hex_md5(a){return rstr2hex(rstr_md5(str2rstr_utf8(a)))}function hex_hmac_md5(a,b){return rstr2hex(rstr_hmac_md5(str2rstr_utf8(a),str2rstr_utf8(b)))}function md5_vm_test(){return hex_md5("abc").toLowerCase()=="900150983cd24fb0d6963f7d28e17f72"}function rstr_md5(a){return binl2rstr(binl_md5(rstr2binl(a),a.length*8))}function rstr_hmac_md5(c,f){var e=rstr2binl(c);if(e.length>16){e=binl_md5(e,c.length*8)}var a=Array(16),d=Array(16);for(var b=0;b<16;b++){a[b]=e[b]^909522486;d[b]=e[b]^1549556828}var g=binl_md5(a.concat(rstr2binl(f)),512+f.length*8);return binl2rstr(binl_md5(d.concat(g),512+128))}function rstr2hex(c){try{hexcase}catch(g){hexcase=0}var f=hexcase?"0123456789ABCDEF":"0123456789abcdef";var b="";var a;for(var d=0;d<c.length;d++){a=c.charCodeAt(d);b+=f.charAt((a>>>4)&15)+f.charAt(a&15)}return b}function str2rstr_utf8(c){var b="";var d=-1;var a,e;while(++d<c.length){a=c.charCodeAt(d);e=d+1<c.length?c.charCodeAt(d+1):0;if(55296<=a&&a<=56319&&56320<=e&&e<=57343){a=65536+((a&1023)<<10)+(e&1023);d++}if(a<=127){b+=String.fromCharCode(a)}else{if(a<=2047){b+=String.fromCharCode(192|((a>>>6)&31),128|(a&63))}else{if(a<=65535){b+=String.fromCharCode(224|((a>>>12)&15),128|((a>>>6)&63),128|(a&63))}else{if(a<=2097151){b+=String.fromCharCode(240|((a>>>18)&7),128|((a>>>12)&63),128|((a>>>6)&63),128|(a&63))}}}}}return b}function rstr2binl(b){var a=Array(b.length>>2);for(var c=0;c<a.length;c++){a[c]=0}for(var c=0;c<b.length*8;c+=8){a[c>>5]|=(b.charCodeAt(c/8)&255)<<(c%32)}return a}function binl2rstr(b){var a="";for(var c=0;c<b.length*32;c+=8){a+=String.fromCharCode((b[c>>5]>>>(c%32))&255)}return a}function binl_md5(p,k){p[k>>5]|=128<<((k)%32);p[(((k+64)>>>9)<<4)+14]=k;var o=1732584193;var n=-271733879;var m=-1732584194;var l=271733878;for(var g=0;g<p.length;g+=16){var j=o;var h=n;var f=m;var e=l;o=md5_ff(o,n,m,l,p[g+0],7,-680876936);l=md5_ff(l,o,n,m,p[g+1],12,-389564586);m=md5_ff(m,l,o,n,p[g+2],17,606105819);n=md5_ff(n,m,l,o,p[g+3],22,-1044525330);o=md5_ff(o,n,m,l,p[g+4],7,-176418897);l=md5_ff(l,o,n,m,p[g+5],12,1200080426);m=md5_ff(m,l,o,n,p[g+6],17,-1473231341);n=md5_ff(n,m,l,o,p[g+7],22,-45705983);o=md5_ff(o,n,m,l,p[g+8],7,1770035416);l=md5_ff(l,o,n,m,p[g+9],12,-1958414417);m=md5_ff(m,l,o,n,p[g+10],17,-42063);n=md5_ff(n,m,l,o,p[g+11],22,-1990404162);o=md5_ff(o,n,m,l,p[g+12],7,1804603682);l=md5_ff(l,o,n,m,p[g+13],12,-40341101);m=md5_ff(m,l,o,n,p[g+14],17,-1502002290);n=md5_ff(n,m,l,o,p[g+15],22,1236535329);o=md5_gg(o,n,m,l,p[g+1],5,-165796510);l=md5_gg(l,o,n,m,p[g+6],9,-1069501632);m=md5_gg(m,l,o,n,p[g+11],14,643717713);n=md5_gg(n,m,l,o,p[g+0],20,-373897302);o=md5_gg(o,n,m,l,p[g+5],5,-701558691);l=md5_gg(l,o,n,m,p[g+10],9,38016083);m=md5_gg(m,l,o,n,p[g+15],14,-660478335);n=md5_gg(n,m,l,o,p[g+4],20,-405537848);o=md5_gg(o,n,m,l,p[g+9],5,568446438);l=md5_gg(l,o,n,m,p[g+14],9,-1019803690);m=md5_gg(m,l,o,n,p[g+3],14,-187363961);n=md5_gg(n,m,l,o,p[g+8],20,1163531501);o=md5_gg(o,n,m,l,p[g+13],5,-1444681467);l=md5_gg(l,o,n,m,p[g+2],9,-51403784);m=md5_gg(m,l,o,n,p[g+7],14,1735328473);n=md5_gg(n,m,l,o,p[g+12],20,-1926607734);o=md5_hh(o,n,m,l,p[g+5],4,-378558);l=md5_hh(l,o,n,m,p[g+8],11,-2022574463);m=md5_hh(m,l,o,n,p[g+11],16,1839030562);n=md5_hh(n,m,l,o,p[g+14],23,-35309556);o=md5_hh(o,n,m,l,p[g+1],4,-1530992060);l=md5_hh(l,o,n,m,p[g+4],11,1272893353);m=md5_hh(m,l,o,n,p[g+7],16,-155497632);n=md5_hh(n,m,l,o,p[g+10],23,-1094730640);o=md5_hh(o,n,m,l,p[g+13],4,681279174);l=md5_hh(l,o,n,m,p[g+0],11,-358537222);m=md5_hh(m,l,o,n,p[g+3],16,-722521979);n=md5_hh(n,m,l,o,p[g+6],23,76029189);o=md5_hh(o,n,m,l,p[g+9],4,-640364487);l=md5_hh(l,o,n,m,p[g+12],11,-421815835);m=md5_hh(m,l,o,n,p[g+15],16,530742520);n=md5_hh(n,m,l,o,p[g+2],23,-995338651);o=md5_ii(o,n,m,l,p[g+0],6,-198630844);l=md5_ii(l,o,n,m,p[g+7],10,1126891415);m=md5_ii(m,l,o,n,p[g+14],15,-1416354905);n=md5_ii(n,m,l,o,p[g+5],21,-57434055);o=md5_ii(o,n,m,l,p[g+12],6,1700485571);l=md5_ii(l,o,n,m,p[g+3],10,-1894986606);m=md5_ii(m,l,o,n,p[g+10],15,-1051523);n=md5_ii(n,m,l,o,p[g+1],21,-2054922799);o=md5_ii(o,n,m,l,p[g+8],6,1873313359);l=md5_ii(l,o,n,m,p[g+15],10,-30611744);m=md5_ii(m,l,o,n,p[g+6],15,-1560198380);n=md5_ii(n,m,l,o,p[g+13],21,1309151649);o=md5_ii(o,n,m,l,p[g+4],6,-145523070);l=md5_ii(l,o,n,m,p[g+11],10,-1120210379);m=md5_ii(m,l,o,n,p[g+2],15,718787259);n=md5_ii(n,m,l,o,p[g+9],21,-343485551);o=safe_add(o,j);n=safe_add(n,h);m=safe_add(m,f);l=safe_add(l,e)}return Array(o,n,m,l)}function md5_cmn(h,e,d,c,g,f){return safe_add(bit_rol(safe_add(safe_add(e,h),safe_add(c,f)),g),d)}function md5_ff(g,f,k,j,e,i,h){return md5_cmn((f&k)|((~f)&j),g,f,e,i,h)}function md5_gg(g,f,k,j,e,i,h){return md5_cmn((f&j)|(k&(~j)),g,f,e,i,h)}function md5_hh(g,f,k,j,e,i,h){return md5_cmn(f^k^j,g,f,e,i,h)}function md5_ii(g,f,k,j,e,i,h){return md5_cmn(k^(f|(~j)),g,f,e,i,h)}function safe_add(a,d){var c=(a&65535)+(d&65535);var b=(a>>16)+(d>>16)+(c>>16);return(b<<16)|(c&65535)}function bit_rol(a,b){return(a<<b)|(a>>>(32-b))};