越到重要关头的时候,就越没事找事,总是“想做的事”优先级高于正经事,唉,没救了~
早些时候,我就在用LongkeyMusic,很方便,可以下载虾米的精选集,不过很久都不更新,下的精选集也常常是无效的,所以改用了 虾米音乐河蟹姬,地址解析是不错,UI做得也很二次元,就不知道写这程序的人是怎么想的?一个好好的程序,下载的歌曲都是以ID号命名,批量下了一大推,没听过的都不知道叫啥!
跟上次的思路一样,想从外部改进这个程序,控制程序句柄,不过很遗憾,用Spy++查看这个程序,风格也是子窗口嵌入主窗口里的,找不出我要的句柄。没办法了。
在网上搜搜还有没有更好的虾米音乐获取办法,找到一篇日志 虾米音乐文件绝对地址解析 ,按上面试了试,虽有点不一样,不过确实是可以获取出虾米音乐真实下载地址的,所以就想自己也写个程序玩玩。
//*********************************************************************************************//
方法也是参考自:虾米音乐文件绝对地址解析 http://kanoha.org/2011/08/30/xiami-absolute-address/
简单原理如下:
以 http://www.xiami.com/song/3632733?spm=a1z1s.6659513.0.0.zzUExi 为例
1、把song替换成widget/xml-single/sid ,在网页上打开替换后的地址,得到一个XML文件;有兴趣的,可以试试对 专辑 album类的捕捉,XML我不会。。。反正是按图索骥了
2、可以得到下图,有很多信息,最后一个框就是下载地址的密文了。
密文如下:
9hFlc%2521%e213f45lt%eo2FE713y672e3Elt2.mF3532F%938e2-pFx%72%3_a314373%%mi2762_luD184c2535aF4%F1.t4%3%-%EA.m44537mhe5b515-%fi43E68p_3E5E3En2i.3%%363k73eb9%u
3、按上面日志说的,第一个字符是9,可以看成是凯撒方阵的列数,按凯撒方阵来排列,由于字符串中包含有音乐文件的下载地址,很特殊,所以简单一点,一般找出http这几个字母也就得出了凯撒方阵
hFlc%2521%e213f45l
t%eo2FE713y672e3El
t2.mF3532F%938e2-
pFx%72%3_a314373%
%mi2762_luD184c25
35aF4%F1.t4%3%-%E
A.m44537mhe5b515-
%fi43E68p_3E5E3En
2i.3%%363k73eb9%u
4、竖着读,得到
http%3A%2F%2Fm5.file.xiami.com%2F443%2F77443%2F326%5E%5E5%2F3632733_1786112_l.mp3%3Fauth_key%3D4e3726911%5E3173483b5e32834%5Ebfee7c-13943232%5E%5E-%5E-null
5、有木有感觉了?经过在线网页的URL解码,得到
http://m5.file.xiami.com/443/77443/326^^5/3632733_1786112_l.mp3?auth_key=4e3726911^3173483b5e32834^bfee7c-13943232^^-^-null
6、替换^为0,即为真实下载地址了
?auth_key=4e372691103173483b5e328340bfee7c-1394323200-0-null
其中 3632733_1786112_l.mp3 即为默认文件名
//*********************************************************************************************//
//*********************************************************************************************//
有了上面的方法,那就差一个具体的实施了,最简单的办法,可以用网页脚本语言,Java,HTML,css,Python.......
我不会,就用delphi了
界面布局: 显示效果:
感觉UI做得越来越好了。
//*********************************************************************************************//
//*********************************************************************************************//
下面是部分关键代码:
//************************ 点击解析并保存按钮 *****************************************//
procedure TForm1.resolveBtnClick(Sender: TObject); //解析地址
var
temp:String;
strRow:word;
begin
temp:=downloadAddr.Text;
if temp = '' then //判断是否是空地址
begin
ShowMessage('搜索地址无效!');
Exit;
end;
temp:=StringReplace(temp,'song','widget/xml-single/sid',[rfReplaceAll]); //XML 解析
temp:=GetWebPage(temp); //获取URL源代码
Delete(temp,1,Pos('<song_name>',temp)+19);
songName:=Copy(temp,1,Pos(']',temp)-1); //获取歌曲名字
Delete(temp,1,Pos('<album_name>',temp)+20);
albumName:=Copy(temp,1,Pos(']',temp)-1); //获取歌曲所属专辑名字
Delete(temp,1,Pos('<artist_name>',temp)+21);
artistName:=Copy(temp,1,Pos(']',temp)-1); //获取歌手名字
Delete(temp,1,Pos('<location>',temp)+18);
strRow:=word(StrToInt(LeftStr(temp,1))); //获取凯撒矩阵的行
downloadAddrStr:=Copy(temp,2,Pos(']',temp)-2); //获取歌曲伪地址
//downloadAddr.Text:=downloadAddrStr; //调试用
downloadAddrStr:=CaesarShifts(downloadAddrStr,strRow); //解析歌曲地址
temp:=Copy(downloadAddrStr,1,Pos('mp3',downloadAddrStr)+2);
downloadName:=RightStr(temp,Pos('/',ReverseString(temp))-1); //获取默认文件名称
//下面保存ini,txt文件
SaveIniFile('Download Info.ini',downloadName,artistName,SongName,AlbumName);
//记录:ini文件名,默认下载文件名,艺术家,作曲名,专辑名
SaveTxtFile('Download Path.txt',downloadAddrStr);
//记录:下载地址
end;
//************************ 点击粘到剪贴板按钮 *****************************************//
procedure TForm1.clipBtnClick(Sender: TObject); //复制到剪贴板
var
path,temp,downloadAddrTemp:string;
txtFile:Textfile;
begin
downloadAddrTemp:='';//初始化剪贴板缓存
clipboard.AsText:='';//清空剪贴板内容
path:=ExtractFilePath(Application.Exename)+'Download Path.txt';
AssignFile(txtFile,path);
Reset(txtFile); //打开读取txt
SetLength(temp,5); //初始化数组长度
while not eof(txtFile) do
begin
Readln(txtFile,temp);
downloadAddrTemp:=downloadAddrTemp+temp+#13#10;
end;
clipboard.AsText:=downloadAddrTemp; //下载地址复制到剪贴板
CloseFile(txtFile);
end;
//************************ 点击改名并分类按钮 *****************************************//
procedure TForm1.changNameBtnClick(Sender: TObject); //批量改名
var
path,downloadNametemp,artistNametemp,songNametemp,albumNametemp:string;
oldName,newName:string;
iniFile:TIniFile;
I,countI:Integer;
SearchRec:TSearchRec;
dirPath:string;
begin
if (downloaderSaveDir.Text = '') or (musicLibraryDir.Text = '') then
begin
ShowMessage('请确认下载保存目录和音乐库路径是否正确!');
Exit;
end;
path:=ExtractFilePath(Application.Exename)+'Download Info.ini';
iniFile:=Tinifile.Create(path);
countI:=iniFile.ReadInteger('已有','首歌',countI); //获取歌曲数目
for I := 0 to (countI-1) do
begin
downloadNametemp:=iniFile.ReadString('默认文件名',IntToStr(I),downloadNametemp);
if Pos(':',dlAlbumName) > 0 then
dlAlbumName:=StringReplace(dlAlbumName,':','-',[rfReplaceAll]);//防止命名有:
artistNametemp:=iniFile.ReadString('艺术家',IntToStr(I),artistNametemp);
songNametemp:=iniFile.ReadString('作曲名',IntToStr(I),songNametemp);
albumNametemp:=iniFile.ReadString('专辑名',IntToStr(I),albumNametemp);
if FindFirst(downloaderSaveDir.Text+'\'+downloadNametemp,faAnyFile,SearchRec) =0 then
begin //查找相同名字文件
repeat
newName:=downloaderSaveDir.Text+'\'+artistNametemp+' - '+songNametemp+RightStr(downloadNametemp,4);
RenameFile(downloaderSaveDir.Text+'\'+SearchRec.Name,newName); //文件改名
dirPath:=musicLibraryDir.Text+'\'+artistNametemp+'\'+albumNametemp;//在F盘下QQ音乐下载目录新建分类目录文件夹
//目录格式: 艺术家\专辑名\作曲名
ForceDirectories(dirPath);
//downloadAddr.Text:=dirPath; //调试用
MoveFile(PChar(newName),PChar(dirPath+RightStr(newName,Length(newName)-5))); //移动文件到新建目录
until(FindNext(SearchRec)<>0);
FindClose(SearchRec);
end;
end;
iniFile.Free;
end;
//************************ 点击清理记录按钮 *****************************************//
procedure TForm1.deleteBtnClick(Sender: TObject); //删除txt、ini文件
var
path:string;
txtFile:Textfile;
iniFile:TIniFile;
begin
path:=ExtractFilePath(Application.Exename)+'Download Path.txt';
DeleteFile(path);
path:=ExtractFilePath(Application.Exename)+'Download Info.ini';
DeleteFile(path);
end;
//******************* 凯撒移位函数,返回明码 *************************//
function TForm1.CaesarShifts(str: string; row: word): string; //凯撒移位,返回值为明码
var
arr1:array of char;
arr2:array of array of char;
len,n:Integer;
col,remainder,I,J:word;
begin
len:=Length(str); //字符串长度
SetLength(arr1,len); //初始化数组长度
for I := 0 to (len-1) do //字符串转数组
arr1[I]:=str[I+1];
DivMod(len,row,col,remainder);
if remainder <> 0 then //有余数,即没填满矩阵
begin
col:=col+1;
remainder:=word(col*row-len); //多出remainder个
end;
SetLength(arr2,row,col);
n:=0;
for I := 0 to (row-1) do
for J := 0 to (col-1) do
begin
if (I >= (row-remainder)) and (J >= (col-1)) then
begin
arr2[I,J]:=' ';
end
else
begin
arr2[I,J]:=arr1[n]; //矩阵赋值
inc(n);
end;
end;
n:=0;
for J := 0 to (col-1) do
for I := 0 to (row-1) do
begin
if n<len then
Result:=Result+arr2[I,J]; //数组转字符串
inc(n);
end;
Result:=URLDecode(Result);
Result:=StringReplace(Result,'^','0',[rfReplaceAll]); //替换^为0
end;
//******** 监视剪贴板,方便搜索地址栏完成自动粘贴,一条一条的复制粘贴很麻烦的 ************//
procedure TForm1.WMDrawClipBoard(var AMessage: TMessage); //监视剪贴板
begin
SendMessage(NextClipHwnd,AMessage.Msg,AMessage.WParam,AMessage.LParam);
//将WM_DRAWCLIPBOARD消息传递到下一个观察链中的窗口
if Clipboard.HasFormat(CF_TEXT) or Clipboard.HasFormat(CF_OEMTEXT) or Clipboard.HasFormat(CF_UNICODETEXT) then
begin
if Pos('www.xiami.com/song',Clipboard.AsText) > 0 then //过滤不是虾米音乐的内容
begin
if Clipboard.AsText = clipTemp then
ShowMessage('链接已复制粘贴!');
downloadAddr.Text:=Clipboard.AsText;
clipTemp:=Clipboard.AsText;
end;
end;
end;
//********************************************************************************//
//********************************************************************************//
运行效果:
由于迅雷有监视剪贴板的功能,所以很方便,点了下“粘到剪贴板”,新建任务窗口就呼出来了
Download Info.ini 歌曲信息查看,可以看出 中文,英文,日文均显示正常,韩文显示成问号了,是保存编码问题,要用Unicode去写入,没试成功,也不会写到ini文件,写到txt文件中又感觉不好看,算了,这Bug
Download Path.txt 真实下载地址查看
下载成功!
改名到分类目录就不贴了
|