记一次换行引发的血案

话说最近真是流年不利,感觉各种BUG犹如天灾一样全部冒出来了,这不昨天又解了一个非常无语的问题,大概是关于换行和正则的臭虫,下面给大家吐槽一下。

数据“野”了

昨天同事反馈某个页面的数据没有正常显示,最开始我还以为是接口没有返回数据,结果看了下请求发现接口有正常的数据呀。没办法就一路反查回去,最后查到居然代码里接口请求抛错了?!因为定义了 Promise 的 catch 流程,所以也没有把错误抛出来。因为之前这个页面都是正常的,很久都没有改动所以我第一反应是这个数据异常了,查了半天的数据格式问题。可是问题就在于明明看到数据是正常的呀,服务端没有报错,接口数据也是可以正常解析的。最后我突然想起来,我们的接口是 JSONP 的会不会是 JSONP 功能挂了?查了一下果然是这样。

Jietu20180616-104611.jpg

阅读全文

Untrusted 通关攻略!

今天早上刷微博的时候看到 @fakefish 分享了一个游戏微博,游戏的名字叫做《Untrusted》,通过修改JS代码来通关的游戏,作者把游戏代码托管在了Github上,游戏地址在 http://alexnisnevich.github.io/untrusted/

Level 1

这关简单,移动玩家对象@先拾取⌘然后移动到出口就好了。

Level 2

这关看着挺吓人的,路都被#号给各种拦着了,但是其实读一下代码发现也就那么回事。13行的new ROT.Map.DividedMaze(map.getWidth(), map.getHeight())负责根据地图大小生成迷宫,30行到33行在出口的四个方向生成了#号阻拦我们。看着其实挺恐怖的,但是其实我们只要开辟一个新思路不移动@对象到出口而是把出口移动到对象边上就好了。

当然没办法移动现有的这个出口了,我就尝试着再新建了一个出口在@的旁边。map.placeObject(7,6,'exit');,一次性成功!

Level 3

这一关#栅栏把@和出口给隔开来了,首先想到的是把生成#栅栏的代码删除掉。但是很不幸的是过关验证函数validateLeve()上清楚的写着一定要有一定数量的栅栏才行。所以我们转变思路,用栅栏把@和出口都包括进去就好了。为了方便我就直接生成在了边缘了。

for (y = 0; y <= map.getHeight(); y++) {
    map.placeObject(0, y, 'block');
    map.placeObject(map.getWidth(), y, 'block');
}

for (x = 0; x <= map.getWidth(); x++) {
    map.placeObject(x, 0, 'block');
    map.placeObject(x, map.getHeight()-1, 'block');
}

Level 4

这一关和上一关的感觉是一样的,应该可以抄袭上一关的代码。不过你仔细读代码的话会发现比上一关少了过关验证函数。所以我这里就取巧用了第二关的方法,用map.placeObject(map.getWidth() - 5, map.getHeight() - 5, 'exit');在@对象旁边新建了一个出口。

阅读全文

批量下载虾米已下载歌曲(新)

前言

呵呵,没错,公子我又再次光荣的把硬盘搞挂了,然后音乐又没了!鉴于之前已经有过一次经验(批量下载虾米已下载歌曲),所以本以为这次也会轻而易举了的。没想到居然在我写出那篇日志后没多久,官方居然各种更新(更新页面,下载机制,Flash播放器),然后我那篇日志的方法就没法用了。不过官方这个也都是小改,所以我也只要小改下代码就成啦!哈哈!

有什么变化

首先要说的是虾米官方的下载思路还是没变的,基本上还是之前日志里头说的那样:

在网站点击下载之后,网站向你的账户中未下载列表传递下载歌曲信息,之后未下载列表则返回一个以emoun://特有协议开头的文件,用以打开虾歌(这个和迅雷thunder://以及电骡等的ed2k://是一个道理)。虾歌打开后查询未下载的表单,返回表单中的歌曲,然后下载。同时将未下载列表中的歌曲提交到已下载列表中,并在未下载列表中删除。 - 批量下载虾米已下载歌曲

官方改变的就是下载列表和网站点击下载这两步:

下载列表

老版的是直接显示列表显示每首歌的,新版则是按照下载时间来显示,一次下载为一次订单,订单内再显示此次下载的歌曲。这样正则匹配获取歌曲的ID就需要发生变化了。

点击下载提交订单

以前只要登陆后向服务器POST歌曲的ID就能提交订单了。新版则需要提交sign_xiamitoken这两个新参数,三个参数一块POST才行。sign参数暂时还不知道如何算出来的,_xiamitoken则是你的cookies。看源码意外的让我发现了参数居然都直接写在页面里头了,为了简便,我就直接抓取订单页面正则获取这两个参数了。同时还加了判断Referer这种老手段,这个也不算难。

VIP机制

以前是没有VIP的,自从被阿里收购之后就增加了VIP功能。好在现在可以用体验点(以前的红包)购买VIP,15个体验点可以买一个月的VIP,一个月的VIP能免费下100首歌曲。我的歌曲数在400+,花了80个体验店办了半年的,可以免费下600首歌曲。等于用80个体验店下600首歌的意思,变相的节省了下体验点,哈哈!

解题步骤

一、获取登陆COOKIES

这一步和老版还是一样的,直接复制过来吧。

获取登陆后的COOKIES文件,方便之后的抓取工作。修改代码中的第2行和第3行,填入自己的账号和密码。如果成功的话,会在该文件的同级目录下得到一个cookies.txt文件。

<?php
$name = ''; //输入你的账号
$password = ''; //输入你的密码
$curl_post = 'email='.$name.'&amp;password='.$password.'&amp;done=/&amp;submit=登 录';
$cookie_file = dirname(__FILE__).'/cookie.txt';
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, &quot;http://www.xiami.com/member/login&quot;);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $curl_post);
curl_setopt($curl, CURLOPT_COOKIEJAR, $cookie_file);
curl_exec($curl);
curl_close($curl);
?>

二、抓取下载列表并提交到未下载列表

这一步因为列表的改变有些许变化。而且新版订单还有个新订单和旧订单之分,真是蛋疼,还得做两次解析。

<?php
set_time_limit(0); //设置成不限制页面运行时间
function get($url) {
    $cookie_file = dirname(__FILE__).'/cookie.txt';
    $curl = curl_init($url);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($curl, CURLOPT_COOKIEFILE, $cookie_file);
    $data = curl_exec($curl);
    curl_close($curl);
    return $data;
}
$song = array();
$pageinfo = get('http://www.xiami.com/account/myorders-old');
$preg = '/\…\<\/a\>\<a href\=\"\/account\/myorders-old\/page\/(.*?)\" class\=\"p\_num\">/s';
preg_match_all($preg, $pageinfo, $match);
$page = $match[1][0];
for($i=1;$i<$page;$i++) {
    $data = get("http://www.xiami.com/account/myorders-old/page/".$i);
    $preg = '/\<td class\=\"iname\"\>\<a href\=\"\/song\/(.*?)\"/s';
    preg_match_all($preg, $data, $match);
    foreach($match[1] as $item) $song[] = $item;
}
$pageinfo = get('http://www.xiami.com/account/myorders');
$preg = '/\…\<\/a\>\<a href\=\"\/account\/myorders\/page\/(.*?)\" class\=\"p\_num\">/s';
preg_match_all($preg, $pageinfo, $match);
$page = $match[1][0];
for($i=1;$i<$page;$i++) {
    $data = get("http://www.xiami.com/account/myorders-old/page/".$i);
    $preg = '/\<td class\=\"iname\"\>\<a href\=\"\/song\/(.*?)\"/s';
    preg_match_all($preg, $data, $match);
    foreach($match[1] as $item) $song[] = $item;
}
$song = array_unique($song, SORT_NUMERIC);
echo 'var song = new Array('.implode(',', $song).')';
?>

批量提交下载歌曲

我之前也讲了,新版新增sign_xiamitoken两个参数,同时还增加了Referer验证。整体代码还是采用上版中的PHP+AJAX异步提交,只是在PHP部分有稍许修改。

将上一步得到的代码复制替换掉相同的那部分,保存为download.html

<script type="text/javascript">
//将第二步获得的代码复制到下面
var song = new Array(1769629171,3338870,1769902385,1770432131,2868586,1770149760,367633,1769832130,35526,1769962750,1770551145,1770457081,3486103,1769400110,127903,3367334,2078855,71830,376007,376015,2074918,2730,1770450144,3455520,79157,79151,81366,65256,390070,2138604,1770345727,376050,376006,149193,388168,1769665592,3497062,1769236069,3632104,1768923954,1769491757,3608275,2092882,1770354410,3480460,3620143,2095376,2561774,3664677,1770462404,1769056924,3527076,2126340,2089410,1769177482,2083322,1769831952,1769316186,1770524409,1769517803,1190507,1964547,1769107542,3486203,1769686818,1769028326,1858814,2084011,1769235816,1770614545,2083187,2605276,378268,2515019,1122167,1769176497,3302053,2083102,3638520,189072,371017,1770168732,1083760,2122948,2070331,2128868,3319126,2561592,1381654,3441719,1768939471,2091290,2098665,2067235,1770060224,3562953,54342,1769831786,1770145312,76323,2067242,173117,136054,1770068043,1769833104,1769833105,1769833106,1769833107,1769833108,2082305,1768989931,1769356638,1769082273,1769776879,1769740304,1205851,1768984809,1769102373,1769740095,1768989928,1769381931,1769227674,3599015,1768962602,3463177,1769381938,1769381935,1769381934,1769381932,3410377,2067242,3381901,3381903,373969,2072395,373971,373990,374039,3381910,3381911,3381912,382134,378711,193010,162269,143775,127673,120041,89443,52732,382512,383210,1769274527,1769004670,1768958960,3599311,3365855,2314604,2080987,2073790,385907,115384,374057,377936,380029,382560,382852,383962,33806,378041,380287,385729,385760,388406,389153,381833,83126,386773,389072,389080,389077,43542,52726,375183,382821,384595,385074,386954,373818,376387,378307,379302,382777,384646,384670,385137,385976,386073,386347,2342433,3550893,1769699970,2095102,2095107,2017034,2286524,2342421,2286523,3338183,1768988423,2286527,2380720,3464931,1176114,1769334977,2155384,1769839800,3225258,2091936,1769334987,3187867,2095104,3413844,1769072635,1007695,3636957,3467770,1768940269,1769292436,1769850021,1769102619,3409064,1769870173,1769850022,1769850019,1769463117,3446206,2028877,2136483,1769072646,1769291745,1769072643,1769113798,196142,3187959,2079575,2286522,2079581,2385273,1769673320,3513664,1769071426,3502285,1769801604,1769801605,3484274,1769303054,1769303056,1769303057,1769303058,1769303059,1769303060,1769303061,1769303062,1769303055,1768914983,378646,3586293,1769830111,1769830112,1769830113,1769830114,1769830115,1769830116,1769830117,1769830118,1769830119,1769830120,1769570445,);

//复制结束

//下面的代码不需要修改 
var XHR; //定义一个全局对象 
function xm_download(id){  
    if(window.ActiveXObject){//IE的低版本系类 
        XHR=new ActiveXObject('Microsoft.XMLHTTP');
    }else if(window.XMLHttpRequest){//非IE系列的浏览器,但包括IE7 IE8 
        XHR=new XMLHttpRequest(); 
    } 
    XHR.open("GET","download.php?id="+id,true);        
    XHR.send(null); 
} 
for (var i=0;i<song.length;i++) {
    xm_download(song[i]);
}
</script>

将下面的代码保存为download.php,放在与download.html同级目录中

<?php
$cookie_file = dirname(__FILE__).'/cookie.txt'; //登陆COOKIES文件地址
$id = $_GET['id'];
$url = "http://www.xiami.com/download/pay?id=$id";
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_COOKIEFILE, $cookie_file);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
$data = curl_exec($curl);
curl_close($curl);
$preg = '/\<input type\=\"hidden\" name\=\"sign\" id\=\"sign\" value\=\"(.*?)" \/\>/s';
preg_match_all($preg, $data, $match);
$sign = $match[1][0];
$pieces = explode('<input type="hidden" value="', $data);
$piece = end($pieces);
$cookies = explode('"', $piece);
$cookie = $cookies[0];
$data = "song_ids[]=$id&amp;use_vip=1&amp;down=1&amp;inpour_amount=0&amp;sign=$sign&amp;_xiamitoken=$cookie";
$curl = curl_init($url);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_COOKIEFILE, $cookie_file);
curl_setopt($curl, CURLOPT_REFERER, $url);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);    
$data = curl_exec($curl);
curl_close($curl);
?>

四、打开虾歌查询订单

最后一步是打开虾歌,选择“文件”→“检查未完成下载”,静候片刻你就可以欢呼了!之后静静等待虾歌批量下载成功就好了。

红心电台

前两天写了篇Audio标签的文章,想要更深入学习一下,遂决定写一个网页版音乐播放器试试(其实有更深部的动机的,不过我不告诉你们,哼哼!)。在整个网页的编写过程中,我学习到了很多的东西,感觉对jQuery又有了更新的认识。同时新接触了jQuery UI这个非常可靠的工具。不过说到底也还是新手,代码写的很脏,也没想到重构什么的,功能实现出来就好了,至于美观什么的,哈哈,你是在开玩笑么!

放上本篇文章主角:红心电台 - http://imnerd.org/lab/player

简单介绍:红心电台是一个利用HTML5制作的能够存储播放列表的在线音乐播放器。是的,没错,虽然是叫做红心电台,但是基本上也就只是一个能存播放列表的播放器而已。另外,本网页是用HTML5写的,然后由于播放的音乐基本是MP3格式的,所以只支持Webkit系的浏览器(Chrome为首)和IE9+以上的浏览器,对Firefox和Opera的用户感到抱歉了,OGG的音乐库毕竟还是挺小众的。具体浏览器支持可以看红心电台的HELP。

使用说明:

  • 1、左上角有搜索框,用户可自行添加歌曲到播放列表中。
  • 2、默认播放列表的歌曲在用户添加过歌曲后会自行清除(需要刷新页面),用户不必手动清除。
  • 3、默认支持快捷键,左右方向键为上一首下一首,空格键为暂停,Delete键为删除当前播放曲目。

如果对红心电台有什么意见或者是建议,又或者是发现了什么问题,可以随时与我联系(联系方式见关于页面),当然你也可以在本文留言,我都是可以看的到的。

如何批量下载虾米已下载歌曲

本文方法已失效,新版见:如何批量下载虾米已下载歌曲(新)

前言

首先要说明的是,这不是一篇讲述如何免费批量下载虾米高质量音乐的文章,而是讲述一个杯具男如何因为懒惰而DIY出的成果。所以如果抱有前述心理的同学到此可以关闭网页了。另外本文中代码运用到了PHP+CURL+JavaScript,推荐大家在本地环境中进行操作。同时最重要的是,本文不是讨论如何绕过虾米的收费机制下载歌曲,所以你必须保证你有足够的虾米币或者红包用来下载歌曲才行,否则一切免谈。

事出有因

事情是这样的。前两天安装了Linux Mint,在Linux中打开了音乐,没过一会儿就卡掉了,当时我也没在意。结果一回到Windows就提示我文件夹损坏了,里面的文件也自然都悲剧了。用恢复软件恢复了效果也不太理想。400首歌总共2G多的文件也不是说割舍就能割舍的,所以就有了下面这些。

解题思路

虾米下载的机制是这样的:在网站点击下载之后,网站向你的账户中未下载列表传递下载歌曲信息,之后未下载列表则返回一个以emoun://特有协议开头的文件,用以打开虾歌(这个和迅雷thunder://以及电骡等的ed2k://是一个道理)。虾歌打开后查询未下载的表单,返回表单中的歌曲,然后下载。同时将未下载列表中的歌曲提交到已下载列表中,并在未下载列表中删除。这样,一次下载就完成了。

download

如图所示,点击下载歌曲,网站向http://www.xiami.com/download/song页面POST提交了相关Data,同时网页返回一个emoun://协议开头的文件(图中左侧红色方框标记的)。

所以我的想法是这样的:首先登陆网页抓取已下载列表中已经下载过歌曲的信息,然后批量的提交到未下载列表。然后打开虾歌去查询未下载列表中的歌曲就好了。如果和我所想一样的话,虾歌应该会一次性批量得到所有歌曲,那么我们只要等待下载完成就好了。

解题步骤

一、获取登陆COOKIES

获取登陆后的COOKIES文件,方便之后的抓取工作。修改代码中的第2行和第3行,填入自己的账号和密码。如果成功的话,会在该文件的同级目录下得到一个cookies.txt文件。

$name = ''; //输入你的账号
$password = ''; //输入你的密码
$curl_post = 'email='.$name.'&password='.$password.'&done=/&submit=登 录';
$cookie_file = dirname(__FILE__).'/cookie.txt';
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, "http://www.xiami.com/member/login");
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_POSTFIELDS, $curl_post);
curl_setopt($curl, CURLOPT_COOKIEJAR, $cookie_file);
curl_exec($curl);
curl_close($curl);

二、抓取下载列表并提交到未下载列表

有了上一步之后,这一步就变的非常的简单。这里主要考虑的是效率的问题,由于我的歌曲打开400首,虾米一页显示20首。所以我大概有20页要抓取,这对于PHP来说是一个不小的工作量,在本地运行更甚。如果你的页面少可以使用下面的代码,如果你要抓的页面多,可以尝试将其改写成multi\_curl同步抓取,或者改写成AJAX形式,亦或是分批抓取都行。使用代码时候记得修改第三行的抓取页数。

set_time_limit(0); //设置成不限制页面运行时间
$page = ''; //填写你的已经下载页面的页数
$cookie_file = dirname(__FILE__).'/cookie.txt';
       
$song = '';
//如果页数过多可以分批获取,以免等待时间过长
for($i=1;$i<=$page;$i++) {
    $curl = curl_init();
    curl_setopt($curl, CURLOPT_URL, "http://www.xiami.com/account/mysongs/s/2/page/".$i);
    curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
    curl_setopt($curl, CURLOPT_COOKIEFILE, $cookie_file);
    $data = curl_exec($curl);
    curl_close($curl);

    $preg = '/\\(.*?)\<\/a\>/s';
    preg_match_all($preg, $data, $match);
    foreach($match[1] as $item) {
        $song .= $item . ',';
    }
}
$song = str_replace(' ', '', $song);
$song = substr($song, 0, -1);
echo "var song = new Array($song);";
/*end*/

三、批量提交下载歌曲

上一步的代码中,我将下载歌曲的ID输出成了JavaScript数组的形式。因为这一步中我们将以歌曲数为基数向虾米网站POST提交信息,400对于PHP来说仍然是一个庞大的数字,对于本地更甚。你可以修改成multi_curl的形式,不过我第一反应是AJAX异步传输,这就是为什么我将歌曲的ID输出成了JavaScript数组的原因。

原理很简单,JS用来模拟循环,PHP则负责POST提交数据。使用代码时记得将xiami.song.download.html中的第3行替换成你在上一步中获得的代码。


$data = 'pid=&ptype=&song_count=1&id='.$_GET['id'];     
$cookie_file = dirname(__FILE__).'/cookie.txt'; //登陆COOKIES文件地址
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, "http://www.xiami.com/download/song");
curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($curl, CURLOPT_POST, 1);
curl_setopt($curl, CURLOPT_COOKIEFILE, $cookie_file);
curl_setopt($curl, CURLOPT_POSTFIELDS, $data);  
$data = curl_exec($curl);
curl_close($curl);

四、打开虾歌查询订单

最后一步是打开虾歌,选择“文件”→“检查未完成下载”,静候片刻你就可以欢呼了!之后静静等待虾歌批量下载成功就好了。

Firefox 4.0

[1g1g]Anyone of us-Gareth Gates#playID:192280[/]

哈哈,Firefox终于更新到4.0了呢,最重要的是Firebug已经支持4.0啦!(其实1.7版就支持的,只是我等到FB的官方推送后才知道。)在虚拟机里头安装了一下,发现除了easycomment那个插件不兼容(估计以后也不兼容了吧,感觉都没人维护了),其它的都较好。

不得不说,FF是越来越朝Chrome靠拢了,连操作习惯都趋向于Chrome,最明显的要数插件按钮的位置了。之前FF都是放在状态栏上的,我习惯这样,也觉得这样挺好的,觉得Chrome也该放到状态栏上。结果反倒是FF先变了,也放到导航栏上去了!= =!这让我情何以堪啊...好在现在自己还可以选择位置,没有强制。另外Firefox对textarea元素的处理也趋向于Chrome化了,Chrome中用户是可以自定义textarea元素的长宽的,当然有个最小值。而Firefox比Chrome更高一层楼,连最小值都没有了,用户可以任意调整textarea的长宽了。这让做Web Design的情何以堪!然后我发现FF对于javascript的alert方法的支持也发生了改变,以前是弹出一个小窗口,现在是直接页面显示,这个跟Opera有些类似。好吧,Firefox我真的没说你在抄袭!另外FF的启动速度也有了较大的提高。不过我觉得还是好慢啊,记得微博上看到过有人说没有超过Chrome的话谁也不会去关心你相对于上个版本更新了多少。这个讲的真的是精辟呢!

分享一下我现在Firefox的界面,然后向大家推荐一下我觉得比较好的插件。

Movable Firefox Button

这个插件能够更改Firefox4.0左上角菜单按钮的位置和样式,得到如图的效果,做到可视区域最大化。
下载地址:官方下载

Awesome screenshot: Capture and Annotate

一个网页截图插件,从Chrome发展过来的。在Chrome的时候就非常喜欢这个插件,偶然搜索一下,发现FF也有相应的插件,而且在FF下的表现也不俗哦!
下载地址:官方下载

Tab Utilities

在新标签页打开书签、历史、地址、搜索,以及更多增强标签式浏览的使用功能。功能很强大,不过我一般就用它来实现双击标签关闭页面以及双击标签栏新建标签。
下载地址:官方下载

标签管理器

如果只是要实现双击关闭标签的话我推荐使用这个插件,功能简单设置不复杂,是入门级的插件。
下载地址:普通下载

Speed Dial

模仿Opera和Chrome在新建页面上显示你常去的网站。用了Chrome之后就非常的喜欢这个设计,所以用Speed Dial模拟了一下,也是非常的不错呢!
下载地址:官方下载

火狐主页插件0.8

跟Speed Dial有着相同的效果但是比SD更加好看而且加载速度也不错,推荐大家安装这个
下载地址:普通下载

另外,诸如Firebug, Autoproxy, Greasemonkey, Flashgot之类的简直可以说FF标配的插件我就不介绍啦!最后大家再去安装一个主题就非常的完美咯!

小惊喜:打开Firefox按下键盘的Alt键,你们会发现什么呢?呵呵!更多乐趣期待大家发现