diff --git a/115.py b/115.py index 2149add..6156e0d 100755 --- a/115.py +++ b/115.py @@ -32,13 +32,13 @@ ############################################################ # file extensions -mediatype = { +mediatype = [ ".wma", ".wav", ".mp3", ".aac", ".ra", ".ram", ".mp2", ".ogg", ".aif", ".mpega", ".amr", ".mid", ".midi", ".m4a", ".m4v", ".wmv", ".rmvb", ".mpeg4", ".mpeg2", ".flv", ".avi", ".3gp", ".mpga", ".qt", ".rm", ".wmz", ".wmd", ".wvx", ".wmx", ".wm", ".swf", ".mpg", ".mp4", ".mkv", ".mpeg", ".mov", ".mdf", ".iso", ".asf" -} +] s = '\x1b[%d;%dm%s\x1b[0m' # terminual color template @@ -261,7 +261,7 @@ def download(infos): if os.path.exists(infos['file']): return 0 - num = random.randint(0, 7) % 7 + num = random.randint(0, 7) % 8 col = s % (2, num + 90, infos['file']) infos['nn'] = infos['nn'] if infos.get('nn') else 1 infos['total_file'] = infos['total_file'] \ @@ -273,13 +273,16 @@ def download(infos): if args.aria2c: # 115 普通用户只能有4下载通道。 + quiet = ' --quiet=true' if args.quiet else '' + taria2c = ' -x %s -s %s' % (args.aria2c, args.aria2c) tlimit = ' --max-download-limit %s' \ % args.limit if args.limit else '' - cmd = 'aria2c -c -s4 -x4%s ' \ + cmd = 'aria2c -c%s%s%s ' \ + '-m 0 ' \ '-o "%s.tmp" -d "%s" ' \ '--user-agent "%s" ' \ '--header "Referer:http://m.115.com/" "%s"' \ - % (tlimit, infos['name'], infos['dir_'],\ + % (quiet, taria2c, tlimit, infos['name'], infos['dir_'],\ headers['User-Agent'], infos['dlink']) else: tlimit = ' --limit-rate %s' % args.limit if args.limit else '' @@ -302,7 +305,7 @@ def download(infos): @staticmethod def play(infos): - num = random.randint(0, 7) % 7 + num = random.randint(0, 7) % 8 col = s % (2, num + 90, infos['name']) infos['nn'] = infos['nn'] if infos.get('nn') else 1 infos['total_file'] = infos['total_file'] \ @@ -413,10 +416,12 @@ def main(argv): description='download from 115.com reversely') p.add_argument('xxx', type=str, nargs='*', \ help='命令对象.') - p.add_argument('-a', '--aria2c', action='store_true', \ - help='download with aria2c') + p.add_argument('-a', '--aria2c', action='store', default=None, \ + type=int, help='aria2c分段下载数量') p.add_argument('-p', '--play', action='store_true', \ help='play with mpv') + p.add_argument('-q', '--quiet', action='store_true', \ + help='quiet for download and play') p.add_argument('-f', '--from_', action='store', \ default=1, type=int, \ help='从第几个开始下载,eg: -f 42') @@ -436,11 +441,11 @@ def main(argv): account = raw_input(s % (1, 97, ' account: ')) password = getpass(s % (1, 97, 'password: ')) elif len(xxx[1:]) == 1: - account = xxx[0] + account = xxx[1] password = getpass(s % (1, 97, ' password: ')) elif len(xxx[1:]) == 2: - account = xxx[0] - password = xxx[1] + account = xxx[1] + password = xxx[2] else: print s % (1, 91, ' login\n login account\n \ login account password') diff --git a/91porn.py b/91porn.py index d7550bf..c649171 100755 --- a/91porn.py +++ b/91porn.py @@ -9,6 +9,7 @@ import argparse import random import select +import urllib2 ############################################################ # wget exit status @@ -60,24 +61,32 @@ def get_infos(self): params = { 'VID': n1.group(1), - 'mp4': 1, + 'mp4': '1', 'seccode': n2.group(1), 'max_vid': n3.group(1), } - r = ss.get(apiurl, params=params) + #tapiurl = apiurl + '?' + \ + #'&'.join(['='.join(item) for item in params.items()]) + #print tapiurl + + r = requests.get(apiurl, params=params) if r.ok: dlink = re.search( - r'file=(http.+?\.mp4)', r.content).group(1) + r'file=(http.+?)&', r.content).group(1) + dlink = urllib2.unquote(dlink) name = re.search( r'viewkey=([\d\w]+)', self.url).group(1) infos = { 'name': '%s.mp4' % name, 'file': os.path.join(os.getcwd(), '%s.mp4' % name), 'dir_': os.getcwd(), - 'dlink': dlink + 'dlink': dlink, } - self.download(infos) + if not args.get_url: + self.download(infos) + else: + print dlink else: print s % (1, 91, ' Error at get(apiurl)') else: @@ -133,6 +142,11 @@ def do(self): self.get_infos() def main(url): + if args.proxy: + ss.proxies = { + 'http': args.proxy, + 'https': args.proxy + } x = nrop19(url) x.do() @@ -144,5 +158,9 @@ def main(url): help='download with aria2c') p.add_argument('-p', '--play', action='store_true', \ help='play with mpv') + p.add_argument('-u', '--get_url', action='store_true', \ + help='print download_url without download') + p.add_argument('--proxy', action='store', type=str, default=None, \ + help='print download_url without download') args = p.parse_args() main(args.url) diff --git a/README.md b/README.md index 110e14a..81fa0af 100644 --- a/README.md +++ b/README.md @@ -1,149 +1,208 @@ -## iScript +# iScript -包含项目: +## pan.baidu.com.py 已经重构,不再维护 + +[**BaiduPCS-Py**](https://github.com/PeterDing/BaiduPCS-Py) 是 pan.baidu.com.py 的重构版,运行在 Python>= 3.6 + +[![Join the chat at https://gitter.im/PeterDing/iScript](https://badges.gitter.im/PeterDing/iScript.svg)](https://gitter.im/PeterDing/iScript?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) > *[L]* *[W]* *[LW]* 分别表示,在linux, windows, linux和windows 下通过测试。 + > ***windows用户可在babun (https://github.com/babun/babun) 下运行。*** - | | | ---------|---------|---------| -*[L]* | [xiami.py](#xiami.py) | 下载或播放高品质虾米音乐(xiami.com) | -*[L]* | [pan.baidu.com.py](#pan.baidu.com.py) | 百度网盘的下载、离线下载、上传、播放、转存、文件操作 | -*[L]* | [bt.py](#bt.py) | magnet torrent 互转、及 过滤敏.感.词 | -*[L]* | [115.py](#115.py) | 115网盘的下载和播放 | -*[L]* | [yunpan.360.cn.py](#yunpan.360.cn.py) | 360网盘的下载 | -*[L]* | [music.baidu.com.py](#music.baidu.com.py) | 下载或播放高品质百度音乐(music.baidu.com) | -*[L]* | [music.163.com.py](#music.163.com.py) | 下载或播放高品质网易音乐(music.163.com) | -*[L]* | [flvxz_cl.py](#flvxz_cl.py) | flvxz.com 视频解析 client - 支持下载、播放 | -*[L]* | [tumblr.py](#tumblr.py) | 下载某个tumblr.com的所有图片 | -*[L]* | [unzip.py](#unzip.py) | 解决linux下unzip乱码的问题 | -*[L]* | [ed2k_search.py](#ed2k_search.py) | 基于 donkey4u.com 的emule搜索 | -*[L]* | [91porn.py](#91porn.py) | 下载或播放91porn | -*[L]* | [ThunderLixianExporter.user.js](#ThunderLixianExporter.user.js) | A fork of https://github.com/binux/ThunderLixianExporter - 增加了mpv和mplayer的导出。 | - | 待续 | | + +*[L]* - [leetcode_problems.py](#leetcode_problems.py) - 下载Leetcode的算法题 +*[L]* - [xiami.py](#xiami.py) - 下载或播放高品质虾米音乐(xiami.com) +*[L]* - [pan.baidu.com.py](#pan.baidu.com.py) - 百度网盘的下载、离线下载、上传、播放、转存、文件操作 +*[L]* - [bt.py](#bt.py) - magnet torrent 互转、及 过滤敏.感.词 +*[L]* - [115.py](#115.py) - 115网盘的下载和播放 +*[L]* - [yunpan.360.cn.py](#yunpan.360.cn.py) - 360网盘的下载 +*[L]* - [music.baidu.com.py](#music.baidu.com.py) - 下载或播放高品质百度音乐(music.baidu.com) +*[L]* - [music.163.com.py](#music.163.com.py) - 下载或播放高品质网易音乐(music.163.com) +*[L]* - [flv_cmd.py](#flv_cmd.py) - 基于在线服务的视频解析 client - 支持下载、播放 +*[L]* - [tumblr.py](#tumblr.py) - 下载某个tumblr.com的所有图片、视频、音频 +*[L]* - [unzip.py](#unzip.py) - 解决linux下unzip乱码的问题 +*[L]* - [ed2k_search.py](#ed2k_search.py) - 基于 donkey4u.com 的emule搜索 +*[L]* - [91porn.py](#91porn.py) - 下载或播放91porn +*[L]* - [ThunderLixianExporter.user.js](#ThunderLixianExporter.user.js) - A fork of https://github.com/binux/ThunderLixianExporter - 增加了mpv和mplayer的导出。 --- + +### leetcode_problems.py - 下载Leetcode的算法题 + +#### 依赖 + +``` +python2-requests (https://github.com/kennethreitz/requests) + +python2-lxml + +``` + +#### 参数: + +``` + --index sort by index + --level sort by level + --tag sort by tag + --title sort by title + --rm_blank 移除题中的空行 + --line LINE 两题之间的空行 + -r, --redownload 重新下载数据 +``` + +下载的数据保持在 ./leecode_problems.pk +转成的txt在 './leecode problems.txt' + --- ### xiami.py - 下载或播放高品质虾米音乐(xiami.com) -1. 依赖 +#### 1. 依赖 + +``` +wget + +python2-requests (https://github.com/kennethreitz/requests) + +python2-mutagen (https://code.google.com/p/mutagen/) + +mpv (http://mpv.io) +``` - wget +#### 2. 使用说明 - python2-requests (https://github.com/kennethreitz/requests) +xiami.py 是一个虾米音乐的命令行(CLI)客户端。提供登录、下载、播放、收藏的功能。 - python2-mutagen (https://code.google.com/p/mutagen/) +**提供对[落网 luoo.net](http://www.luoo.net)的分析** - mpv (http://mpv.io) +初次使用需要登录 xm login (原xiami账号) -2. 使用说明 +~~**支持淘宝账户** xm logintaobao~~ - **从2014年9月30日起,需要下载或播放高品质音乐的用户,按虾米要求,要绑定taobao账号,并用淘宝账号登陆** +~~**对于淘宝账户,登录后只保存有关虾米的cookies,删除了有关淘宝的cookies**~~ - 初次使用需要登录 xm login (原xiami账号) +**淘宝登录加密算法无法破解,需要手动获取cookies (方法见下 手动添加cookie登录)** - **支持淘宝账户** xm logintaobao +**vip账户**支持高品质音乐的下载和播放。 - **对于淘宝账户,登录后只保存有关虾米的cookies,删除了有关淘宝的cookies** +**原虾米vip用户如果不能获得高品质音乐,请用关联的淘宝帐号登录。** - **vip账户**支持高品质音乐的下载和播放。 +下载的MP3默认添加id3 tags,保存在当前目录下。 - 下载的MP3默认添加id3 tags,保存在当前目录下。 +cookies保存在 ~/.Xiami.cookies。 - cookies保存在 ~/.Xiami.cookies。 +关于播放操作: - 关于播放操作: +> 在运行脚本的终端,输入1次Enter,关闭当前播放并播放下一个文件,连续输入2次Enter,关闭当前播放并退出。 -> 在运行脚本的终端,输入1次Enter,关闭当前播放并播放下一个文件,连续输入2次Enter,关闭当前播放并退出。 +#### 命令: - 命令: +``` +# 虾米账号登录 +g +login +login username +login username password - # 虾米账号登录 - g - login - login username - login username password +signout # 退出登录 - # 淘宝账号登录 - gt - logintaobao - logintaobao username - logintaobao username password +d 或 download url1 url2 # 下载 +p 或 play url1 url2 # 播放 +s 或 save url1 url2 # 收藏 +``` - signout # 退出登录 +#### 参数: - d 或 download url1 url2 .. # 下载 - p 或 play url1 url2 .. # 播放 - s 或 save url1 url2 .. # 收藏 +``` +-p, --play 按顺序播放 +-pp 按歌曲被播放的次数,从高到低播放 +-l, --low 低品质mp3 +-d, --undescription 不加入disk的描述 +-f num, --from_ num 从第num个开始 +-t TAGS, --tags TAGS 收藏用的tags,用英文逗号分开, eg: -t piano,cello,guitar +-n, --undownload 不下载,用于修改已存在的MP3的id3 tags +``` - 参数: +#### 3. 用法 - -p, --play play with mpv - -l, --low 低品质mp3 - -d, --undescription 不加入disk的描述 - -f num, --from_ num 从第num个开始 - -t TAGS, --tags TAGS 收藏用的tags,用英文逗号分开, eg: -t piano,cello,guitar - -n, --undownload 不下载,用于修改已存在的MP3的id3 tags +xm 是xiami.py的马甲 (alias xm='python2 /path/to/xiami.py') -3. 用法 +``` +# 登录 +xm g +xm login +xm login username +xm login username password - \# xm 是xiami.py的马甲 (alias xm='python2 /path/to/xiami.py') +# 手动添加cookie登录 +1. 用浏览器登录后,按F12,然后访问 https://www.xiami.com/album/123456 +2. 选择‘网络’或network,找到 123456,在其中找到 Cookie: xxx +3. 然后在终端运行 xm g "xxx" - # 登录 - xm g - xm login - xm login username - xm login username password +# 退出登录 +xm signout - # 退出登录 - xm signout +# 下载专辑 +xm d http://www.xiami.com/album/168709?spm=a1z1s.6928801.1561534521.114.ShN6mD - # 下载专辑 - xm d http://www.xiami.com/album/168709?spm=a1z1s.6928801.1561534521.114.ShN6mD +# 下载单曲 +xm d http://www.xiami.com/song/2082998?spm=a1z1s.6659513.0.0.DT2j7T - # 下载单曲 - xm d http://www.xiami.com/song/2082998?spm=a1z1s.6659513.0.0.DT2j7T +# 下载精选集 +xm d http://www.xiami.com/song/showcollect/id/30374035?spm=a1z1s.3061701.6856305.16.fvh75t - # 下载精选集 - xm d http://www.xiami.com/song/showcollect/id/30374035?spm=a1z1s.3061701.6856305.16.fvh75t +# 下载该艺术家所有专辑, Top 20 歌曲, radio +xm d http://www.xiami.com/artist/23460?spm=a1z1s.6928801.1561534521.115.ShW08b - # 下载该艺术家所有专辑, Top 20 歌曲, radio - xm d http://www.xiami.com/artist/23460?spm=a1z1s.6928801.1561534521.115.ShW08b +# 下载用户的收藏, 虾米推荐, radio, 推荐 +xm d http://www.xiami.com/u/141825?spm=a1z1s.3521917.0.0.zI0APP - # 下载用户的收藏, 虾米推荐, radio, 推荐 - xm d http://www.xiami.com/u/141825?spm=a1z1s.3521917.0.0.zI0APP +# 下载排行榜 +xm d http://www.xiami.com/chart/index/c/2?spm=a1z1s.2943549.6827465.6.VrEAoY - # 下载排行榜 - xm d http://www.xiami.com/chart/index/c/2?spm=a1z1s.2943549.6827465.6.VrEAoY +# 下载 风格 genre, radio +xm d http://www.xiami.com/genre/detail/gid/2?spm=a1z1s.3057857.6850221.1.g9ySan +xm d http://www.xiami.com/genre/detail/sid/2970?spm=a1z1s.3057857.6850221.4.pkepgt - # 下载 风格 genre, radio - xm d http://www.xiami.com/genre/detail/gid/2?spm=a1z1s.3057857.6850221.1.g9ySan - xm d http://www.xiami.com/genre/detail/sid/2970?spm=a1z1s.3057857.6850221.4.pkepgt +# 下载 widget (虾米播播) +xm d http://www.xiami.com/widget/player-multi?uid=4350663&sid=1774531852,378713,3294421,1771778464,378728,378717,378727,1773346501,&width=990&height=346&mainColor=e29833&backColor=60362a&widget_from=4350663 - 播放: +# 下载落网期刊 +# 分析落网期刊的音乐后,在虾米上搜索并下载 +xm d http://www.luoo.net/music/706 +``` - # url 是上面的 - xm p url +#### 播放: - 收藏: +``` +# url 是上面的 +xm p url +``` - xm s http://www.xiami.com/album/168709?spm=a1z1s.6928801.1561534521.114.ShN6mD - xm s -t 'tag1,tag 2,tag 3' http://www.xiami.com/song/2082998?spm=a1z1s.6659513.0.0.DT2j7T - xm s http://www.xiami.com/song/showcollect/id/30374035?spm=a1z1s.3061701.6856305.16.fvh75t - xm s http://www.xiami.com/artist/23460?spm=a1z1s.6928801.1561534521.115.ShW08b +#### 收藏: -4. 参考: +``` +xm s http://www.xiami.com/album/168709?spm=a1z1s.6928801.1561534521.114.ShN6mD +xm s -t 'tag1,tag 2,tag 3' http://www.xiami.com/song/2082998?spm=a1z1s.6659513.0.0.DT2j7T +xm s http://www.xiami.com/song/showcollect/id/30374035?spm=a1z1s.3061701.6856305.16.fvh75t +xm s http://www.xiami.com/artist/23460?spm=a1z1s.6928801.1561534521.115.ShW08b +``` + +#### 4. 参考: > http://kanoha.org/2011/08/30/xiami-absolute-address/ + > http://www.blackglory.me/xiami-vip-audition-with-no-quality-difference-between-downloading/ + > https://gist.github.com/lepture/1014329 + > 淘宝登录代码: https://github.com/ly0/xiami-tools --- @@ -151,593 +210,806 @@ ### pan.baidu.com.py - 百度网盘的下载、离线下载、上传、播放、转存、文件操作 -1. 依赖 +**pan.baidu.com.py 已经重构,不再维护** - wget +[**BaiduPCS-Py**](https://github.com/PeterDing/BaiduPCS-Py) 是 pan.baidu.com.py 的重构版,运行在 Python>= 3.6 - aria2 (~ 1.18) +#### 1. 依赖 - python2-rsa +``` +wget - python2-pyasn1 +aria2 (~ 1.18) - python2-requests (https://github.com/kennethreitz/requests) +aget-rs (https://github.com/PeterDing/aget-rs/releases) - requests-toolbelt (https://github.com/sigmavirus24/requests-toolbelt) +pip2 install rsa pyasn1 requests requests-toolbelt - mpv (http://mpv.io) +mpv (http://mpv.io) - mplayer # 我的linux上mpv播放wmv出错,换用mplayer +# 可选依赖 +shadowsocks # 用于加密上传。 + # 用 python2 的 pip 安装 +pip2 install shadowsocks -2. 使用说明 +# 除了用pip安装包,还可以手动: +https://github.com/PeterDing/iScript/wiki/%E6%89%8B%E5%8A%A8%E8%A7%A3%E5%86%B3pan.baidu.com.py%E4%BE%9D%E8%B5%96%E5%8C%85 +``` - 初次使用需要登录 bp login +#### other - **支持多帐号登录** +[尝试解决百度网盘下载速度问题](https://github.com/PeterDing/iScript/wiki/解决百度网盘下载速度问题) - 他人分享的网盘连接,只支持单个的下载。 +#### 2. 使用说明 - 下载工具默认为wget, 可用参数-a num选用aria2 +pan.baidu.com.py 是一个百度网盘的命令行客户端。 - 下载的文件,保存在当前目录下。 +初次使用需要登录 bp login - 下载默认为非递归,递归下载加 -R +**支持多帐号登录** - 搜索时,默认在 / +**现在只支持[用cookie登录](#cookie_login)** - 搜索支持高亮 +**支持cookie登录** - 上传模式默认是 c (续传)。 +**支持加密上传**, 需要 shadowsocks - **开启证实(verification) 用参数 -V** +**cd, ls 功能完全支持** - 理论上,上传的单个文件最大支持 2T +**所有路径可以是 相对路径 或 绝对路径** - cookies保存在 ~/.bp.cookies +他人分享的网盘连接,只支持单个的下载。 - 上传数据保存在 ~/.bp.pickle +下载工具默认为wget, 可用参数-a num选用aria2 - 关于播放操作: +**支持用 aget 加速下载, 用法见下** -> 在运行脚本的终端,输入1次Enter,关闭当前播放并播放下一个文件,连续输入2次Enter,关闭当前播放并退出。 +下载的文件,保存在当前目录下。 - - 命令: +下载默认为非递归,递归下载加 -R - **!!注意:命令参数中,所有网盘的路径必须是 绝对路径** - - # 登录 - g - login - login username - login username password - - # 删除帐号 - userdelete 或 ud - - # 切换帐号 - userchange 或 uc - - # 帐号信息 - user - - p 或 play url1 url2 .. path1 path2 .. 播放 - u 或 upload localpath remotepath 上传 - s 或 save url remotepath [-s secret] 转存 - - # 下载 - d 或 download url1 url2 .. path1 path2 .. 非递归下载 到当前目录(cwd) - d 或 download url1 url2 .. path1 path2 .. -R 递归下载 到当前目录(cwd) - # !! 注意: - # d /path/to/download -R 递归下载 *download文件夹* 到当前目录(cwd) - # d /path/to/download/ -R 递归下载 *download文件夹中的文件* 到当前目录(cwd) - - # 文件操作 - md 或 mkdir path1 path2 .. 创建文件夹 - rn 或 rename path new_path 重命名 - rm 或 remove path1 path2 .. 删除 - mv 或 move path1 path2 .. /path/to/directory 移动 - cp 或 copy path /path/to/directory_or_file 复制 - cp 或 copy path1 path2 .. /path/to/directory 复制 - - # 使用正则表达式进行文件操作 - rnr 或 rnre foo bar dir1 dir2 .. -I re1 re2 .. 重命名文件夹中的文件名 - rmr 或 rmre dir1 dir2 .. -E re1 re2 .. 删除文件夹下匹配到的文件 - mvr 或 mvre dir1 dir2 .. /path/to/dir -H head1 head2 .. 移动文件夹下匹配到的文件 - cpr 或 cpre dir1 dir2 .. /path/to/dir -T tail1 tail2 .. 复制文件夹下匹配到的文件 - # 递归加 -R - # rmr, mvr, cpr 中 -t, -I, -E, -H, -T 至少要有一个,放在命令行末尾 - # -I, -E, -H, -T 后可跟多个匹配式 - # 可以用 -t 指定操作的文件类型 - -t f # 文件 - -t d # 文件夹 - # rnr 中 foo bar 都是 regex - # -y, --yes # 不显示警示,直接进行。 !!注意,除非你知道你做什么,否则请不要使用。 - rmr / -I '.*' -y # !! 删除网盘中的所有文件 - - # 回复用bt.py做base64加密的文件 - rnr /path/to/decode1 /path/to/decode2 .. -t f,bd64 - - # 搜索 - f 或 find keyword1 keyword2 .. [directory] 非递归搜索 - ff keyword1 keyword2 .. [directory] 非递归搜索 反序 - ft keyword1 keyword2 .. [directory] 非递归搜索 by time - ftt keyword1 keyword2 .. [directory] 非递归搜索 by time 反序 - fs keyword1 keyword2 .. [directory] 非递归搜索 by size - fss keyword1 keyword2 .. [directory] 非递归搜索 by size 反序 - fn keyword1 keyword2 .. [directory] 非递归搜索 by name - fnn keyword1 keyword2 .. [directory] 非递归搜索 by name 反序 - # 递归搜索加 -R - f 'ice and fire' /doc -R - # 搜索所有的账户加 -t all - f keyword1 keyword2 .. [directory] -t all -R - f keyword1 keyword2 .. [directory] -t f,all -R - # directory 默认为 / - # 关于-H, -T, -I, -E - # -I, -E, -H, -T 后可跟多个匹配式, 需要放在命令行末尾 - f keyword1 keyword2 ... [directory] -H head -T tail -I "re(gul.*) ex(p|g)ress$" - f keyword1 keyword2 ... [directory] -H head -T tail -E "re(gul.*) ex(p|g)ress$" - # 搜索 加 通道(只支持 donwload, play, rnre, rm, mv) - f keyword1 keyword2 .. [directory] \| d -R 递归搜索后递归下载 - ftt keyword1 keyword2 .. [directory] \| p -R 递归搜索(by time 反序)后递归播放 - f keyword1 keyword2 .. [directory] \| rnr foo bar -R 递归搜索后rename by regex - f keyword1 keyword2 .. [directory] \| rm -R -T tail 递归搜索后删除 - f keyword1 keyword2 .. [directory] \| mv /path/to -R 递归搜索后移动 - - # 列出文件 - l path1 path2 .. ls by name - ll path1 path2 .. ls by name 反序 - ln path1 path2 .. ls by name - lnn path1 path2 .. ls by name 反序 - lt path1 path2 .. ls by time - ltt path1 path2 .. ls by time 反序 - ls path1 path2 .. ls by size - lss path1 path2 .. ls by size 反序 - l /doc/books /videos - # 以下是只列出文件或文件夹 - l path1 path2 .. -t f ls files - l path1 path2 .. -t d ls directorys - # 关于-H, -T, -I, -E - # -I, -E, -H, -T 后可跟多个匹配式, 需要放在命令行末尾 - l path1 path2 .. -H head -T tail -I "^re(gul.*) ex(p|g)ress$" - l path1 path2 .. -H head -T tail -E "^re(gul.*) ex(p|g)ress$" - # 显示文件size, md5 - l path1 path2 .. -v - # 空文件夹 - l path1 path2 -t e,d - # 非空文件夹 - l path1 path2 -t ne,d - - # 查看文件占用空间 - du path1 path2 .. 文件夹下所有*文件(不包含下层文件夹)*总大小 - du path1 path2 .. -R 文件夹下所有*文件(包含下层文件夹)*总大小 - 如果下层文件多,会花一些时间 - # 相当于 l path1 path2 .. -t du [-R] - # eg: - du /doc /videos -R - - # 离线下载 - a 或 add http https ftp ed2k .. remotepath - a 或 add magnet .. remotepath [-t {m,i,d,p}] - a 或 add remote_torrent .. [-t {m,i,d,p}] # 使用网盘中torrent - - # 离线任务操作 - j 或 job # 列出离线下载任务 - jd 或 jobdump # 清除全部 *非正在下载中的任务* - jc 或 jobclear taskid1 taskid2 .. # 清除 *正在下载中的任务* - jca 或 jobclearall # 清除 *全部任务* - - 参数: - - -a num, --aria2c num aria2c分段下载数量: eg: -a 10 - -p, --play play with mpv - -y, --yes yes # 用于 rmre, mvre, cpre, rnre !!慎用 - -q, --quiet 无输出模式, 用于 download, play - -V, --VERIFY verification - -v, --view view detail - eg: - a magnet /path -v # 离线下载并显示下载的文件 - d -p url1 url2 .. -v # 显示播放文件的完整路径 - l path1 path2 .. -v # 显示文件的size, md5 - -s SECRET, --secret SECRET 提取密码 - -f number, --from_ number 从第几个开始(用于download, play),eg: p /video -f 42 - -t ext, --type_ ext 类型参数, 用 "," 分隔 - eg: - p -t m3 # 播放流媒体(m3u8) - s -t c # 连续转存 (如果转存出错,再次运行命令 - # 可以从出错的地方开始,用于转存大量文件时) - l -t f # 文件 - l -t d # 文件夹 - l -t du # 查看文件占用空间 - l -t e,d # 空文件夹 - f -t all # 搜索所有账户 - a -t m,d,p,a - u -t r # 只进行 rapidupload - u -t e # 如果云端已经存在则不上传(不比对md5) - u -t r,e - -t s # shuffle,乱序 - -l amount, --limit amount 下载速度限制,eg: -l 100k - -m {o,c}, --uploadmode {o,c} 上传模式: o # 重新上传. c # 连续上传. - -R, --recursive 递归, 用于download, play, ls, find, rmre, rnre, rmre, cpre - -H HEADS, --head HEADS 匹配开头的字符,eg: -H Head1 Head2 .. - -T TAILS, --tail TAILS 匹配结尾的字符,eg: -T Tail1 Tail2 .. - -I INCLUDES, --include INCLUDES 不排除匹配到表达的文件名, 可以是正则表达式,eg: -I "*.mp3" "*.avi" .. - -E EXCLUDES, --exclude EXCLUDES 排除匹配到表达的文件名, 可以是正则表达式,eg: -E "*.html" "*.jpg" .. - -c {on, off}, --ls_color {on, off} ls 颜色,默认是on - - # -t, -H, -T, -I, -E 都能用于 download, play, ls, find, rnre, rmre, cpre, mvre - -3. 用法 - - \# bp 是pan.baidu.com.py的马甲 (alias bp='python2 /path/to/pan.baidu.com.py') - - 登录: - - bp g - bp login - bp login username - bp login username password - - # 多帐号登录 - # 一直用 bp login 即可 - - 删除帐号: - - bp ud - - 切换帐号: - - bp uc - - 帐号信息: - - bp user - - 下载: - - # 下载自己网盘中的*单个或多个文件* - bp d http://pan.baidu.com/disk/home#dir/path=/path/to/filename1 http://pan.baidu.com/disk/home#dir/path=/path/to/filename2 .. - # or - bp d /path/to/filename1 /path/to/filename2 .. - - # 递归下载自己网盘中的*单个或多个文件夹* - bp d -R http://pan.baidu.com/disk/home#dir/path=/path/to/directory1 http://pan.baidu.com/disk/home#dir/path=/path/to/directory2 .. - # or - bp d -R /path/to/directory1 /path/to/directory2 .. - # 递归下载后缀为 .mp3 的文件 - bp d -R /path/to/directory1 /path/to/directory2 .. -T .mp3 - - # 非递归下载 - bp d /path/to/directory1 /path/to/directory2 .. - - # 下载别人分享的*单个文件* - bp d http://pan.baidu.com/s/1o6psfnxx .. - bp d 'http://pan.baidu.com/share/link?shareid=1622654699&uk=1026372002&fid=2112674284' .. - - # 下载别人加密分享的*单个文件*,密码参数-s - bp d http://pan.baidu.com/s/1i3FVlw5 -s vuej - - # 用aria2下载 - bp d http://pan.baidu.com/s/1i3FVlw5 -s vuej -a 5 - bp d /movie/her.mkv -a 4 - bp d url -s [secret] -a 10 - - 播放: - - bp p /movie/her.mkv - bp p http://pan.baidu.com/s/xxxxxxxxx -s [secret] - bp p /movie -R # 递归播放 /movie 中所有媒体文件 - - # 播放流媒体(m3u8) - 上面的命令后加 -t m3 - 清晰度与在浏览器上播放的一样. - 如果源文件是高清的(720P,1280P),那么流媒体会自动转为480P. - - 离线下载: - - bp a http://mirrors.kernel.org/archlinux/iso/latest/archlinux-2014年06月01日-dual.iso /path/to/save - bp a https://github.com/PeterDing/iScript/archive/master.zip /path/to/save - bp a ftp://ftp.netscape.com/testfile /path/to/save +搜索时,默认在 cwd - bp a 'magnet:?xt=urn:btih:64b7700828fd44b37c0c045091939a2c0258ddc2' /path/to/save -v -t a - bp a 'ed2k://|file|[美]徐中約《中国近代史》第六版原版PDF.rar|547821118|D09FC5F70DEA63E585A74FBDFBD7598F|/' /path/to/save +搜索支持高亮 - bp a /path/to/a.torrent .. -v -t m,i # 使用网盘中torrent,下载到/path/to - # 注意 --------------------- - ↓ - 网盘中的torrent +上传模式默认是 c (续传)。 - magnet离线下载 -- 文件选择: +**开启证实(verification) 用参数 -V** - -t m # 视频文件 (默认), 如: mkv, avi ..etc - -t i # 图像文件, 如: jpg, png ..etc - -t d # 文档文件, 如: pdf, doc, docx, epub, mobi ..etc - -t p # 压缩文件, 如: rar, zip ..etc - -t a # 所有文件 - m, i, d, p, a 可以任意组合(用,分隔), 如: -t m,i,d -t d,p -t i,p - remotepath 默认为 / +理论上,上传的单个文件最大支持 2T - bp a 'magnet:?xt=urn:btih:64b7700828fd44b37c0c045091939a2c0258ddc2' /path/to/save -v -t p,d - bp a /download/a.torrent -v -t m,i,d # 使用网盘中torrent,下载到/download +cookies保存在 ~/.bp.cookies - 离线任务操作: +上传数据保存在 ~/.bp.pickle - bp j - bp j 3482938 8302833 - bp jd - bp jc taskid1 taskid2 .. - bp jc 1208382 58239221 .. - bp jca +关于播放操作: - 上传: +> 在运行脚本的终端,输入1次Enter,关闭当前播放并播放下一个文件,连续输入2次Enter,关闭当前播放并退出。 - bp u ~/Documents/reading/三体\ by\ 刘慈欣.mobi /doc -m o - # 上传模式: - # -m o --> 重传 - # -m c --> 续传 (默认) + +#### 命令: - bp u ~/Videos/*.mkv /videos -t r - # 只进行rapidupload +**!!注意:** +**命令参数中,所有网盘的路径和本地路径可以是 相对路径 或 绝对路径** - bp u ~/Documents ~/Videos ~/Documents /backup -t e - # 如果云端已经存在则不上传(不比对md5) - # 用 -t e 时, -m o 无效 +``` +# 登录 +g +login +login username +login username password +login username cookie - bp u ~/Documents ~/Videos ~/Documents /backup -t r,e # 以上两种模式 +# 删除帐号 +userdelete 或 ud - 转存: +# 切换帐号 +userchange 或 uc - bp s url remotepath [-s secret] - # url是他人分享的连接, 如: http://pan.baidu.com/share/link?shareid=xxxxxxx&uk=xxxxxxx, http://pan.baidu.com/s/xxxxxxxx - bp s 'http://pan.baidu.com/share/link?shareid=xxxxxxx&uk=xxxxxxx' /path/to/save - bp s http://pan.baidu.com/s/xxxxxxxx /path/to/save - bp s http://pan.baidu.com/s/xxxxxxxx /path/to/save -s xxxx - bp s http://pan.baidu.com/s/xxxxxxxx#dir/path=/path/to/anything /path/to/save -s xxxx +# 帐号信息 +user - bp s http://pan.baidu.com/inbox/i/xxxxxxxx /path/to/save +# 显示当前工作目录 +cwd - # -t c 连续转存 (如果转存出错,再次运行命令可以从出错的地方开始,用于转存大量文件时) - bp s 'http://pan.baidu.com/share/link?shareid=2705944270&uk=708312363' /path/to/save -t c - # 注意:再次运行时,命令要一样。 +# 切换当前工作目录 +cd path # 支持 ./../... - 搜索: +# 播放 +p 或 play url1 url2 path1 path2 - bp f keyword1 keyword2 - bp f "this is one keyword" "this is another keyword" /path/to/search +# 上传 +u 或 upload localpath remotepath - bp f ooxx -R - bp f 三体 /doc/fiction -R - bp f 晓波 /doc -R +# 加密上传 +u localpath remotepath [-P password] -t ec -R - bp ff keyword1 keyword2 .. /path/to/music 非递归搜索 反序 - bp ft keyword1 keyword2 .. /path/to/doc 非递归搜索 by time - bp ftt keyword1 keyword2 .. /path/to/other 非递归搜索 by time 反序 - bp fs keyword1 keyword2 .. 非递归搜索 by size - bp fss keyword1 keyword2 .. 非递归搜索 by size 反序 - bp fn keyword1 keyword2 .. 非递归搜索 by name - bp fnn keyword1 keyword2 .. 非递归搜索 by name 反序 +# 转存 +s 或 save url remotepath [-s secret] + +# 下载 +d 或 download url1 url2 path1 path2 非递归下载 到当前本地目录 +d 或 download url1 url2 path1 path2 -R 递归下载 到当前本地目录 +# !! 注意: +# d /path/to/download -R 递归下载 *download文件夹* 到当前本地目录 +# d /path/to/download/ -R 递归下载 *download文件夹中的文件* 到当前本地目录 + +# 下载并解密 +d /path/to/download -R -t dc [-P password] [-m aes-256-cfb] + +# 解密已下载的文件 +dc path1 path2 -R [-P password] [-m aes-256-cfb] + +# 文件操作 +md 或 mkdir path1 path2 创建文件夹 +rn 或 rename path new_path 重命名 +rm 或 remove path1 path2 删除 +mv 或 move path1 path2 /path/to/directory 移动 +cp 或 copy path /path/to/directory_or_file 复制 +cp 或 copy path1 path2 /path/to/directory 复制 + +# 使用正则表达式进行文件操作 +rnr 或 rnre foo bar dir1 dir2 -I re1 re2 重命名文件夹中的文件名 +rmr 或 rmre dir1 dir2 -E re1 re2 删除文件夹下匹配到的文件 +mvr 或 mvre dir1 dir2 /path/to/dir -H head1 head2 移动文件夹下匹配到的文件 +cpr 或 cpre dir1 dir2 /path/to/dir -T tail1 tail2 复制文件夹下匹配到的文件 +# 递归加 -R +# rmr, mvr, cpr 中 -t, -I, -E, -H, -T 至少要有一个,放在命令行末尾 +# -I, -E, -H, -T 后可跟多个匹配式 +# 可以用 -t 指定操作的文件类型 + -t f # 文件 + -t d # 文件夹 +# rnr 中 foo bar 都是 regex +# -y, --yes # 不显示警示,直接进行。 !!注意,除非你知道你做什么,否则请不要使用。 +rmr / -I '.*' -y # !! 删除网盘中的所有文件 + +# 回复用bt.py做base64加密的文件 +rnr /path/to/decode1 /path/to/decode2 -t f,bd64 + +# 搜索 +# directory 必须是绝对路径, 默认是 cwd +f 或 find keyword1 keyword2 [directory] 非递归搜索 +ff keyword1 keyword2 [directory] 非递归搜索 反序 +ft keyword1 keyword2 [directory] 非递归搜索 by time +ftt keyword1 keyword2 [directory] 非递归搜索 by time 反序 +fs keyword1 keyword2 [directory] 非递归搜索 by size +fss keyword1 keyword2 [directory] 非递归搜索 by size 反序 +fn keyword1 keyword2 [directory] 非递归搜索 by name +fnn keyword1 keyword2 [directory] 非递归搜索 by name 反序 +# 递归搜索加 -R +f 'ice and fire' /doc -R +# 搜索所有的账户加 -t all +f keyword1 keyword2 [directory] -t all -R +f keyword1 keyword2 [directory] -t f,all -R +# directory 默认为 / +# 关于-H, -T, -I, -E +# -I, -E, -H, -T 后可跟多个匹配式, 需要放在命令行末尾 +f keyword1 keyword2 [directory] -H head -T tail -I "re(gul.*) ex(p|g)ress$" +f keyword1 keyword2 [directory] -H head -T tail -E "re(gul.*) ex(p|g)ress$" +# 搜索 加 通道(只支持 donwload, play, rnre, rm, mv) +f keyword1 keyword2 [directory] \| d -R 递归搜索后递归下载 +ftt keyword1 keyword2 [directory] \| p -R 递归搜索(by time 反序)后递归播放 +f keyword1 keyword2 [directory] \| rnr foo bar -R 递归搜索后rename by regex +f keyword1 keyword2 [directory] \| rm -R -T tail 递归搜索后删除 +f keyword1 keyword2 [directory] \| mv /path/to -R 递归搜索后移动 + +# 列出文件 +l path1 path2 ls by name +ll path1 path2 ls by name 反序 +ln path1 path2 ls by name +lnn path1 path2 ls by name 反序 +lt path1 path2 ls by time +ltt path1 path2 ls by time 反序 +ls path1 path2 ls by size +lss path1 path2 ls by size 反序 +l /doc/books /videos +# 以下是只列出文件或文件夹 +l path1 path2 -t f ls files +l path1 path2 -t d ls directorys +# 关于-H, -T, -I, -E +# -I, -E, -H, -T 后可跟多个匹配式, 需要放在命令行末尾 +l path1 path2 -H head -T tail -I "^re(gul.*) ex(p|g)ress$" +l path1 path2 -H head -T tail -E "^re(gul.*) ex(p|g)ress$" +# 显示绝对路径 +l path1 path2 -v +# 显示文件size, md5 +l path1 path2 -vv +# 空文件夹 +l path1 path2 -t e,d +# 非空文件夹 +l path1 path2 -t ne,d + +# 分享文件 +S 或 share path1 path2 为每个提供的文件路劲创建分享链接 +S 或 share [-P pawd 或 --passwd pawd] path1 path2 为每个提供的路径创建加密的分享链接 + +# 查看文件占用空间 +du path1 path2 文件夹下所有*文件(不包含下层文件夹)*总大小 +du path1 path2 -R 文件夹下所有*文件(包含下层文件夹)*总大小 + 如果下层文件多,会花一些时间 +# 相当于 l path1 path2 -t du [-R] +# eg: +du /doc /videos -R + +# 离线下载 +a 或 add http https ftp ed2k remotepath +a 或 add magnet remotepath [-t {m,i,d,p}] +a 或 add remote_torrent [-t {m,i,d,p}] # 使用网盘中torrent + +# 离线任务操作 +j 或 job # 列出离线下载任务 +jd 或 jobdump # 清除全部 *非正在下载中的任务* +jc 或 jobclear taskid1 taskid2 # 清除 *正在下载中的任务* +jca 或 jobclearall # 清除 *全部任务* +``` + +#### 参数: + +``` +-a num, --aria2c num aria2c 分段下载数量: eg: -a 10 +-g num, --aget_s num aget 分段下载数量: eg: -g 100 +-k num, --aget_k size aget 分段大小: eg: -k 200K + -k 1M + -k 2M +--appid num 设置 app-id. 如果无法下载或下载慢, 尝试设置为 778750 +-o path, --outdir path 指定下周目录: eg: -o /path/to/directory +-p, --play play with mpv +-P password, --passwd password 分享密码,加密密码 +-y, --yes yes # 用于 rmre, mvre, cpre, rnre !!慎用 +-q, --quiet 无输出模式, 用于 download, play +-V, --VERIFY verification +-v, --view view detail + eg: + l -v # 显示绝对路径 + a magnet /path -v # 离线下载并显示下载的文件 + d -p url1 url2 -v # 显示播放文件的完整路径 + l path1 path2 -vv # 显示文件的size, md5 +-s SECRET, --secret SECRET 提取密码 +-f number, --from_ number 从第几个开始(用于download, play),eg: p /video -f 42 +-t ext, --type_ ext 类型参数, 用 "," 分隔 + eg: + -t fs # 换用下载服务器,用于下载、播放 + # 如果wiki中的速度解决方法不管用,可以试试加该参数 + d -t dc # 下载并解密,覆盖加密文件(默认) + d -t dc,no # 下载并解密,不覆盖加密文件 + dc -t no # 解密,不覆盖加密文件 + d -t ie # ignore error, 忽略除Ctrl-C以外的下载错误 + d -t 8s # 检测文件是否是"百度8秒",如果是则不下载 + p -t m3 # 播放流媒体(m3u8) + s -t c # 连续转存 (如果转存出错,再次运行命令 + # 可以从出错的地方开始,用于转存大量文件时) + l -t f # 文件 + l -t d # 文件夹 + l -t du # 查看文件占用空间 + l -t e,d # 空文件夹 + f -t all # 搜索所有账户 + a -t m,d,p,a + u -t ec # encrypt, 加密上传, 默认加前缀 + u -t ec,np # encrypt, 加密上传, 不加前缀 + u -t r # 只进行 rapidupload + u -t e # 如果云端已经存在则不上传(不比对md5) + u -t r,e + -t s # shuffle,乱序 +-l amount, --limit amount 下载速度限制,eg: -l 100k +-m {o,c}, --mode {o,c} 模式: o # 重新上传. c # 连续上传. + 加密方法: https://github.com/shadowsocks/shadowsocks/wiki/Encryption +-R, --recursive 递归, 用于download, play, upload, ls, find, rmre, rnre, rmre, cpre +-H HEADS, --head HEADS 匹配开头的字符,eg: -H Head1 Head2 +-T TAILS, --tail TAILS 匹配结尾的字符,eg: -T Tail1 Tail2 +-I INCLUDES, --include INCLUDES 不排除匹配到表达的文件名, 可以是正则表达式,eg: -I ".*.mp3" ".*.avi" +-E EXCLUDES, --exclude EXCLUDES 排除匹配到表达的文件名, 可以是正则表达式,eg: -E ".*.html" ".*.jpg" +-c {on, off}, --ls_color {on, off} ls 颜色,默认是on + +# -t, -H, -T, -I, -E 都能用于 download, play, ls, find, rnre, rmre, cpre, mvre +``` + +#### 3. 用法 + +bp 是pan.baidu.com.py的马甲 (alias bp='python2 /path/to/pan.baidu.com.py') + +#### 登录: + +``` +bp g +bp login +bp login username +bp login username password + +# 多帐号登录 +# 一直用 bp login 即可 +``` + + +#### cookie 登录: + +1. 打开 chrome 隐身模式窗口 +2. 在隐身模式窗口登录 pan.baidu.com +3. 在登录后的页面打开 chrome 开发者工具(怎么打开自行google),选择 `Network` ,然后刷新页面。在刷新后的 `Network` 的 `Name` 列表中选中 `list?dir=...` 开头的一项,然后在右侧找到 `Cookie:` ,复制 `Cookie:` 后面的所有内容。 +4. 用 `pan.baidu.com.py` 登录,`password / cookie:` 处粘贴上面复制的内容。(粘贴后是看不见的)。 +5. 不要退出 pan.baidu.com,只是关闭隐身模式窗口就可以。 + +> 如果使用 cookie 登录,`username` 可以是任意的东西。 + +#### 删除帐号: + +``` +bp ud +``` + +#### 切换帐号: + +``` +bp uc +``` + +#### 帐号信息: + +``` +bp user +``` + +#### 显示当前工作目录 + +``` +bp cwd +``` + +#### 切换当前工作目录 + +``` +bp cd # 切换到 / +bp cd path # 支持 ./../... +bp cd .. +bp cd ../../Music +bp cd ... +``` + +#### 下载: + +``` +## 下载、播放速度慢? +如果无法下载或下载慢, 尝试设置参数 --appid 778750 +bp d /path/file --appid 778750 + +# 下载当前工作目录 (递归) +bp d . -R + +# 下载自己网盘中的*单个或多个文件* +bp d http://pan.baidu.com/disk/home#dir/path=/path/to/filename1 http://pan.baidu.com/disk/home#dir/path=/path/to/filename2 +# or +bp d /path/to/filename1 /path/to/filename2 + +# 递归下载自己网盘中的*单个或多个文件夹* +bp d -R http://pan.baidu.com/disk/home#dir/path=/path/to/directory1 http://pan.baidu.com/disk/home#dir/path=/path/to/directory2 +# or +bp d -R /path/to/directory1 /path/to/directory2 +# 递归下载后缀为 .mp3 的文件 +bp d -R /path/to/directory1 /path/to/directory2 -T .mp3 + +# 非递归下载 +bp d relative_path/to/directory1 /path/to/directory2 + +# 下载别人分享的*单个文件* +bp d http://pan.baidu.com/s/1o6psfnxx +bp d 'http://pan.baidu.com/share/link?shareid=1622654699&uk=1026372002&fid=2112674284' + +# 下载别人加密分享的*单个文件*,密码参数-s +bp d http://pan.baidu.com/s/1i3FVlw5 -s vuej + +# 用aria2 下载 +bp d http://pan.baidu.com/s/1i3FVlw5 -s vuej -a 5 +bp d /movie/her.mkv -a 4 +bp d url -s [secret] -a 10 + +# 用 aget 下载 +bp d http://pan.baidu.com/s/1i3FVlw5 -s vuej -g 100 +bp d /movie/her.mkv -g 100 -k 200K +bp d url -s [secret] -g 100 -k 100K +如果下载速度很慢,可以试试加大 -g, 减小 -k, -k 一般在 100K ~ 300K 之间合适 + +# 下载并解码 +## 默认加密方法为 aes-256-cfb +bp d /path/to/encrypted_file -t dc [-P password] # 覆盖加密文件 (默认) +bp d /path/to/encrypted_file -t dc,no [-P password] # 不覆盖加密文件 +## 设置加密方法 +bp d /path/to/encrypted_file -t dc [-P password] -m 'rc4-md5' +bp d /path/to/directory -t dc [-P password] -m 'rc4-md5' +``` + +#### 解码已下载的加密文件: + +``` +bp dc /local/to/encrypted_file [-P password] -m 'aes-256-cfb' +bp dc /local/to/encrypted_file [-P password] +bp dc /local/to/directory [-P password] +``` + +#### 播放: + +``` +bp p /movie/her.mkv +bp p http://pan.baidu.com/s/xxxxxxxxx -s [secret] + +bp cd /movie +bp p movie -R # 递归播放 /movie 中所有媒体文件 + +# 播放流媒体(m3u8) +上面的命令后加 -t m3 +清晰度与在浏览器上播放的一样. +如果源文件是高清的(720P,1280P),那么流媒体会自动转为480P. +``` + +#### 离线下载: + +``` +bp a http://mirrors.kernel.org/archlinux/iso/latest/archlinux-2014年06月01日-dual.iso /path/to/save +bp a https://github.com/PeterDing/iScript/archive/master.zip /path/to/save +bp a ftp://ftp.netscape.com/testfile /path/to/save + +bp a 'magnet:?xt=urn:btih:64b7700828fd44b37c0c045091939a2c0258ddc2' /path/to/save -v -t a +bp a 'ed2k://|file|[美]徐中約《中国近代史》第六版原版PDF.rar|547821118|D09FC5F70DEA63E585A74FBDFBD7598F|/' /path/to/save + +bp a /path/to/a.torrent -v -t m,i # 使用网盘中torrent,下载到/path/to +# 注意 ------------------ + ↓ + 网盘中的torrent +``` + +#### magnet离线下载 -- 文件选择: + +``` +-t m # 视频文件 (默认), 如: mkv, avi ..etc +-t i # 图像文件, 如: jpg, png ..etc +-t d # 文档文件, 如: pdf, doc, docx, epub, mobi ..etc +-t p # 压缩文件, 如: rar, zip ..etc +-t a # 所有文件 +m, i, d, p, a 可以任意组合(用,分隔), 如: -t m,i,d -t d,p -t i,p +remotepath 默认为 / + +bp a 'magnet:?xt=urn:btih:64b7700828fd44b37c0c045091939a2c0258ddc2' /path/to/save -v -t p,d +bp a /download/a.torrent -v -t m,i,d # 使用网盘中torrent,下载到/download +``` + +#### 离线任务操作: + +``` +bp j +bp j 3482938 8302833 +bp jd +bp jc taskid1 taskid2 +bp jc 1208382 58239221 +bp jca +``` + +#### 上传: (默认为非递归,递归加 -R) + +``` +# 支持文件类型选择 +bp u ~/Documents/* # 默认上传所以文件 +bp u ~/Documents/* -t f # 不上传文件夹 +bp u ~/Documents/* -t d # 不上传文件 +bp u ~/Documents/* -t f,d # 不上传文件和文件夹 + +bp u ~/Documents/reading/三体\ by\ 刘慈欣.mobi /doc -m o +# 上传模式: +# -m o --> 重传 +# -m c --> 续传 (默认) +# 递归加-R + +bp u ~/Videos/*.mkv /videos -t r +# 只进行rapidupload + +bp u ~/Documents ~/Videos ~/Documents /backup -t e -R +# 如果云端已经存在则不上传(不比对md5) +# 用 -t e 时, -m o 无效 + +bp u ~/Documents ~/Videos ~/Documents /backup -t r,e # 以上两种模式 +``` + +#### 加密上传: (默认为非递归,递归加 -R) + +``` +bp u ~/{p1,p2,p3} -t ec [-P password] # 默认加密方法 'aes-256-cfb' +bp u ~/{p1,p2,p3} -t ec [-P password] -m 'rc4-md5' + +# 注意: +# 上传后的文件名会默认加上前缀 encrypted_ +# 不加前缀用 -t ec,np +``` + +#### 转存: + +``` +bp s url remotepath [-s secret] +# url是他人分享的连接, 如: http://pan.baidu.com/share/link?shareid=xxxxxxx&uk=xxxxxxx, http://pan.baidu.com/s/xxxxxxxx +bp s 'http://pan.baidu.com/share/link?shareid=xxxxxxx&uk=xxxxxxx' /path/to/save +bp s http://pan.baidu.com/s/xxxxxxxx /path/to/save +bp s http://pan.baidu.com/s/xxxxxxxx /path/to/save -s xxxx +bp s http://pan.baidu.com/s/xxxxxxxx#dir/path=/path/to/anything /path/to/save -s xxxx + +bp s http://pan.baidu.com/inbox/i/xxxxxxxx /path/to/save + +# -t c 连续转存 (如果转存出错,再次运行命令可以从出错的地方开始,用于转存大量文件时) +bp s 'http://pan.baidu.com/share/link?shareid=2705944270&uk=708312363' /path/to/save -t c +# 注意:再次运行时,命令要一样。 +``` + +#### 搜索: + +``` +# 默认搜索当前服务器工作目录 cwd +bp f keyword1 keyword2 +bp f "this is one keyword" "this is another keyword" /path/to/search + +bp f ooxx -R +bp f 三体 /doc/fiction -R +bp f 晓波 /doc -R + +bp ff keyword1 keyword2 /path/to/music 非递归搜索 反序 +bp ft keyword1 keyword2 /path/to/doc 非递归搜索 by time +bp ftt keyword1 keyword2 /path/to/other 非递归搜索 by time 反序 +bp fs keyword1 keyword2 非递归搜索 by size +bp fss keyword1 keyword2 非递归搜索 by size 反序 +bp fn keyword1 keyword2 非递归搜索 by name +bp fnn keyword1 keyword2 非递归搜索 by name 反序 + +# 递归搜索加 -R +# 关于-H, -T, -I, -E +bp f mp3 /path/to/search -H "[" "01" -T ".tmp" -I ".*-.*" -R + +# 搜索所有的账户 +bp f iDoNotKnow [directory] -t all -R +bp f archlinux ubuntu [directory] -t f,all -T .iso -R + +# 搜索 加 通道(只支持 donwload, play, rnre, rm, mv) +bp f bioloy \| d -R 递归搜索后递归下载 +bp ftt ooxx \| p -R -t f 递归搜索(by time 反序)后递归播放 +bp f sound \| rnr mp3 mp4 -R 递归搜索后rename by regex +bp f ccav \| rm -R -T avi 递归搜索后删除 +bp f 新闻联播(大结局) \| mv /Favor -R 递归搜索后移动 +``` - # 递归搜索加 -R - # 关于-H, -T, -I, -E - bp f mp3 /path/to/search -H "[" "01" -T ".tmp" -I ".*-.*" -R +#### 恢复用bt.py做base64加密的文件: + +``` +rnr /ooxx -t f,bd64 +!! 注意: /ooxx 中的所有文件都必须是被base64加密的,且加密段要有.base64后缀 +# 可以参考 by.py 的用法 +``` - # 搜索所有的账户 - bp f iDoNotKnow .. [directory] -t all -R - bp f archlinux ubuntu .. [directory] -t f,all -T .iso -R +ls、重命名、移动、删除、复制、使用正则表达式进行文件操作: - # 搜索 加 通道(只支持 donwload, play, rnre, rm, mv) - bp f bioloy \| d -R 递归搜索后递归下载 - bp ftt ooxx \| p -R -t f 递归搜索(by time 反序)后递归播放 - bp f sound \| rnr mp3 mp4 -R 递归搜索后rename by regex - bp f ccav \| rm -R -T avi 递归搜索后删除 - bp f 新闻联播(大结局) \| mv /Favor -R 递归搜索后移动 +见[命令](#cmd) - 恢复用bt.py做base64加密的文件: +#### 4. 参考: - rnr /ooxx -t f,bd64 - !! 注意: /ooxx 中的所有文件都必须是被base64加密的,且加密段要有.base64后缀 - # 可以参考 by.py 的用法 +> https://gist.github.com/HououinRedflag/6191023 - ls、重命名、移动、删除、复制、使用正则表达式进行文件操作: - 见[命令](#cmd) +> https://github.com/banbanchs/pan-baidu-download/blob/master/bddown_core.py -4. 参考: -> https://gist.github.com/HououinRedflag/6191023 +> https://github.com/houtianze/bypy -> https://github.com/banbanchs/pan-baidu-download/blob/master/bddown_core.py -> https://github.com/houtianze/bypy +> 3个方法解决百度网盘限速: https://www.runningcheese.com/baiduyun + --- ### bt.py - magnet torrent 互转、及 过滤敏.感.词 -1. 依赖 +#### 1. 依赖 + +``` +python2-requests (https://github.com/kennethreitz/requests) +bencode (https://github.com/bittorrent/bencode) +``` - python2-requests (https://github.com/kennethreitz/requests) - bencode (https://github.com/bittorrent/bencode) +#### 2. 使用说明 -2. 使用说明 +magnet 和 torrent 的相互转换 - magnet 和 torrent 的相互转换 +过滤敏.感.词功能用于净网时期的 baidu, xunlei - ~~过滤敏.感.词功能用于净网时期的 baidu, xunlei~~ +在中国大陆使用代理可能有更好的效果: +使用代理有两种方法: +1. shadowsocks + proxychains +2. -p protocol://ip:port - **8.30日后,无法使用。 见 http://tieba.baidu.com/p/3265467666** +~~8.30日后,无法使用。 见 http://tieba.baidu.com/p/3265467666~~ - ~~**!! 注意:过滤后生成的torrent在百度网盘只能用一次,如果需要再次使用,则需用 -n 改顶层目录名**~~ +[**百度云疑似解封,百度网盘内八秒视频部分恢复**](http://fuli.ba/baiduyunhuifuguankan.html) - 磁力连接转种子,用的是 +**!! 注意:过滤后生成的torrent在百度网盘只能用一次,如果需要再次使用,则需用 -n 改顶层目录名** - http://bt.box.n0808.com - http://btcache.me - http://www.sobt.org # 302 --> http://www.win8down.com/url.php?hash= - http://www.31bt.com - http://178.73.198.210 - http://www.btspread.com # link to http://btcache.me - http://torcache.net - http://zoink.it - http://torrage.com # 用torrage.com需要设置代理, eg: -p 127.0.0.1:8087 - http://torrentproject.se - http://istoretor.com - http://torrentbox.sx - http://www.torrenthound.com - http://www.silvertorrent.org - http://magnet.vuze.com +磁力连接转种子,用的是 - 如果有更好的种子库,请提交issue +``` +http://bt.box.n0808.com +http://btcache.me +http://www.sobt.org # 302 --> http://www.win8down.com/url.php?hash= +http://www.31bt.com +http://178.73.198.210 +http://www.btspread.com # link to http://btcache.me +http://torcache.net +http://zoink.it +http://torrage.com # 用torrage.com需要设置代理, eg: -p 127.0.0.1:8087 +http://torrentproject.se +http://istoretor.com +http://torrentbox.sx +http://www.torrenthound.com +http://www.silvertorrent.org +http://magnet.vuze.com +``` -> 对于baidu, 加入离线任务后,需等待一段时间才会下载完成。 +如果有更好的种子库,请提交issue - 命令: +> 对于baidu, 加入离线任务后,需等待一段时间才会下载完成。 - # magnet 2 torrent - m 或 mt magnet_link1 magnet_link2 .. [-d /path/to/save] - m -i /there/are/files -d new +#### 命令: - # torrent 2 magnet, 输出magnet - t 或 tm path1 path2 .. +``` +# magnet 2 torrent +m 或 mt magnet_link1 magnet_link2 [-d /path/to/save] +m -i /there/are/files -d new - # 过滤敏.感.词 - # 有2种模式 - # -t n (默认) 用数字替换文件名 - # -t be64 用base64加密文件名,torrent用百度下载后,可用 pan.baidu.com.py rnr /path -t f,bd64 改回原名字 - c 或 ct magnet_link1 magnet_link2 .. /path/to/torrent1 /path/to/torrent2 .. [-d /path/to/save] - c -i /there/are/files and_other_dir -d new # 从文件或文件夹中寻找 magnet,再过滤 - # 过滤敏.感.词 - 将magnet或torrent转成不敏感的 torrent - # /path/to/save 默认为 . +# torrent 2 magnet, 输出magnet +t 或 tm path1 path2 - # 用base64加密的文件名: - c magnet_link1 magnet_link2 .. /path/to/torrent1 /path/to/torrent2 .. [-d /path/to/save] -t be64 +# 过滤敏.感.词 +# 有2种模式 +# -t n (默认) 用数字替换文件名 +# -t be64 用base64加密文件名,torrent用百度下载后,可用 pan.baidu.com.py rnr /path -t f,bd64 改回原名字 +c 或 ct magnet_link1 magnet_link2 /path/to/torrent1 /path/to/torrent2 [-d /path/to/save] +c -i /there/are/files and_other_dir -d new # 从文件或文件夹中寻找 magnet,再过滤 +# 过滤敏.感.词 - 将magnet或torrent转成不敏感的 torrent +# /path/to/save 默认为 . - # 使用正则表达式过滤敏.感.词 - cr 或 ctre foo bar magnet_link1 /path/to/torrent1 .. [-d /path/to/save] - # foo bar 都是 regex +# 用base64加密的文件名: +c magnet_link1 magnet_link2 /path/to/torrent1 /path/to/torrent2 [-d /path/to/save] -t be64 - 参数: +# 使用正则表达式过滤敏.感.词 +cr 或 ctre foo bar magnet_link1 /path/to/torrent1 [-d /path/to/save] +# foo bar 都是 regex +``` - -p PROXY, --proxy PROXY proxy for torrage.com, eg: -p 127.0.0.1:8087 (默认) - -t TYPE_, --type_ TYPE_ 类型参数: - -t n (默认) 用数字替换文件名 - -t be64 用base64加密文件名,torrent用百度下载后,可用 pan.baidu.com.py rnr /path -t f,bd64 改回原名字 - -d DIRECTORY, --directory DIRECTORY 指定torrents的保存路径, eg: -d /path/to/save - -n NAME, --name NAME 顶级文件夹名称, eg: -m thistopdirectory - -i localpath1 localpath2 .., --import_from localpath1 localpath2 .. 从本地文本文件导入magnet (用正则表达式匹配) +#### 参数: -3. 用法 +``` +-p PROXY, --proxy PROXY proxy for torrage.com, eg: -p "sooks5://127.0.0.1:8883" +-t TYPE_, --type_ TYPE_ 类型参数: + -t n (默认) 用数字替换文件名 + -t be64 用base64加密文件名,torrent用百度下载后,可用 pan.baidu.com.py rnr /path -t f,bd64 改回原名字 +-d DIRECTORY, --directory DIRECTORY 指定torrents的保存路径, eg: -d /path/to/save +-n NAME, --name NAME 顶级文件夹名称, eg: -m thistopdirectory +-i localpath1 localpath2, --import_from localpath1 localpath2 从本地文本文件导入magnet (用正则表达式匹配) +``` - \# bt 是bt.py的马甲 (alias bt='python2 /path/to/bt.py') +#### 3. 用法 - bt mt magnet_link1 magnet_link2 .. [-d /path/to/save] - bt tm path1 path2 .. - bt ct magnet_link1 path1 .. [-d /path/to/save] +bt 是bt.py的马甲 (alias bt='python2 /path/to/bt.py') - bt m magnet_link1 magnet_link2 .. [-d /path/to/save] - bt t path1 path2 .. - bt c magnet_link1 path1 .. [-d /path/to/save] +``` +bt mt magnet_link1 magnet_link2 [-d /path/to/save] +bt tm path1 path2 +bt ct magnet_link1 path1 [-d /path/to/save] - # 用torrage.com - bt m magnet_link1 path1 .. -p 127.0.0.1:8087 - bt c magnet_link1 path1 .. -p 127.0.0.1:8087 +bt m magnet_link1 magnet_link2 [-d /path/to/save] +bt t path1 path2 +bt c magnet_link1 path1 [-d /path/to/save] - # 从文件或文件夹中寻找 magnet,再过滤 - bt c -i ~/Downloads -d new +# 用torrage.com +bt m magnet_link1 path1 -p 127.0.0.1:8087 +bt c magnet_link1 path1 -p 127.0.0.1:8087 - # 使用正则表达式过滤敏.感.词 - bt cr '.*(old).*' '1円' magnet_link - bt cr 'old.iso' 'new.iso' /path/to/torrent +# 从文件或文件夹中寻找 magnet,再过滤 +bt c -i ~/Downloads -d new - # 用base64加密的文件名: - bt c magnet_link -t be64 +# 使用正则表达式过滤敏.感.词 +bt cr '.*(old).*' '1円' magnet_link +bt cr 'old.iso' 'new.iso' /path/to/torrent -4. 参考: +# 用base64加密的文件名: +bt c magnet_link -t be64 +``` + +#### 4. 参考: > http://blog.chinaunix.net/uid-28450123-id-4051635.html + > http://en.wikipedia.org/wiki/Torrent_file + --- ### 115.py - 115网盘的下载和播放 -1. 依赖 +#### 1. 依赖 - wget +``` +wget - aria2 (~ 1.18) +aria2 (~ 1.18) - python2-requests (https://github.com/kennethreitz/requests) +python2-requests (https://github.com/kennethreitz/requests) - mpv (http://mpv.io) +mpv (http://mpv.io) - mplayer # 我的linux上mpv播放wmv出错,换用mplayer +mplayer # 我的linux上mpv播放wmv出错,换用mplayer +``` -2. 使用说明 +#### 2. 使用说明 - 初次使用需要登录 pan115 login +初次使用需要登录 pan115 login - **脚本是用于下载自己的115网盘文件,不支持他人分享文件。** +**脚本是用于下载自己的115网盘文件,不支持他人分享文件。** - 下载工具默认为wget, 可用参数-a选用aria2。 +下载工具默认为wget, 可用参数-a选用aria2。 - **现在vip和非vip用户下载只能有1个通道,用aria2下载已经无意义。** +**现在vip和非vip用户下载只能有1个通道,用aria2下载已经无意义。** - 对所有文件,默认执行下载(用wget),如要播放媒体文件,加参数-p。 +对所有文件,默认执行下载(用wget),如要播放媒体文件,加参数-p。 - **非vip用户下载太慢,已经不支持播放。 vip播放正常** +**非vip用户下载太慢,已经不支持播放。 vip播放正常** - 下载的文件,保存在当前目录下。 +下载的文件,保存在当前目录下。 - cookies保存在 ~/.115.cookies +cookies保存在 ~/.115.cookies - 关于播放操作: +关于播放操作: -> 在运行脚本的终端,输入1次Enter,关闭当前播放并播放下一个文件,连续输入2次Enter,关闭当前播放并退出。 +> 在运行脚本的终端,输入1次Enter,关闭当前播放并播放下一个文件,连续输入2次Enter,关闭当前播放并退出。 - 参数: +#### 参数: - -a, --aria2c download with aria2c - -p, --play play with mpv - -f number, --from_ number 从第几个开始下载,eg: -f 42 - -t ext, --type_ ext 要下载的文件的后缀,eg: -t mp3 - -l amount, --limit amount 下载速度限制,eg: -l 100k - -d "url" 增加离线下载 "http/ftp/magnet/ed2k" +``` +-a, --aria2c download with aria2c +-p, --play play with mpv +-f number, --from_ number 从第几个开始下载,eg: -f 42 +-t ext, --type_ ext 要下载的文件的后缀,eg: -t mp3 +-l amount, --limit amount 下载速度限制,eg: -l 100k +-d "url" 增加离线下载 "http/ftp/magnet/ed2k" +``` -3. 用法 +#### 3. 用法 - \# pan115 是115.py的马甲 (alias pan115='python2 /path/to/115.py') +pan115 是115.py的马甲 (alias pan115='python2 /path/to/115.py') - # 登录 - pan115 g - pan115 login - pan115 login username - pan115 login username password +``` +# 登录 +pan115 g +pan115 login +pan115 login username +pan115 login username password - # 退出登录 - pan115 signout +# 退出登录 +pan115 signout - # 递归下载自己网盘中的*文件夹* - pan115 http://115.com/?cid=xxxxxxxxxxxx&offset=0&mode=wangpan +# 递归下载自己网盘中的*文件夹* +pan115 http://115.com/?cid=xxxxxxxxxxxx&offset=0&mode=wangpan - # 下载自己网盘中的*单个文件* -- 只能是115上可单独打开的文件,如pdf,视频 - pan115 http://wenku.115.com/preview/?pickcode=xxxxxxxxxxxx +# 下载自己网盘中的*单个文件* -- 只能是115上可单独打开的文件,如pdf,视频 +pan115 http://wenku.115.com/preview/?pickcode=xxxxxxxxxxxx - # 下载用aria2, url 是上面的 - pan115 -a url +# 下载用aria2, url 是上面的 +pan115 -a url - # 增加离线下载 - pan115 -d "magnet:?xt=urn:btih:757fc565c56462b28b4f9c86b21ac753500eb2a7&dn=archlinux-2014年04月01日-dual.iso" +# 增加离线下载 +pan115 -d "magnet:?xt=urn:btih:757fc565c56462b28b4f9c86b21ac753500eb2a7&dn=archlinux-2014年04月01日-dual.iso" +``` - 播放 +#### 播放 - # url 是上面的 - pan115 -p url +``` +# url 是上面的 +pan115 -p url +``` -4. 参考: +#### 4. 参考: > http://passport.115.com/static/wap/js/common.js?v=1.6.39 @@ -746,62 +1018,69 @@ ### yunpan.360.cn.py - 360网盘的下载 -1. 依赖 +**!!!脚本已不再维护!!!** - wget +#### 1. 依赖 - aria2 (~ 1.18) +``` +wget - python2-requests (https://github.com/kennethreitz/requests) +aria2 (~ 1.18) +python2-requests (https://github.com/kennethreitz/requests) +``` -2. 使用说明 +#### 2. 使用说明 - 初次使用需要登录 yp login +初次使用需要登录 yp login - **!!!!!! 万恶的360不支持断点续传 !!!!!!** +**!!!!!! 万恶的360不支持断点续传 !!!!!!** - 由于上面的原因,不能播放媒体文件。 +由于上面的原因,不能播放媒体文件。 - 只支持自己的\*文件夹\*的递归下载。 +只支持自己的\*文件夹\*的递归下载。 - 下载工具默认为wget, 可用参数-a选用aria2 +下载工具默认为wget, 可用参数-a选用aria2 - 下载的文件,保存在当前目录下。 +下载的文件,保存在当前目录下。 - cookies保存在 ~/.360.cookies +cookies保存在 ~/.360.cookies - 参数: +#### 参数: - -a, --aria2c download with aria2c - -f number, --from_ number 从第几个开始下载,eg: -f 42 - -t ext, --type_ ext 要下载的文件的后缀,eg: -t mp3 - -l amount, --limit amount 下载速度限制,eg: -l 100k +``` +-a, --aria2c download with aria2c +-f number, --from_ number 从第几个开始下载,eg: -f 42 +-t ext, --type_ ext 要下载的文件的后缀,eg: -t mp3 +-l amount, --limit amount 下载速度限制,eg: -l 100k +``` -3. 用法 +#### 3. 用法 - \# yp 是yunpan.360.cn.py的马甲 (alias yp='python2 /path/to/yunpan.360.cn.py') +yp 是yunpan.360.cn.py的马甲 (alias yp='python2 /path/to/yunpan.360.cn.py') - # 登录 - yp g - yp login - yp login username - yp login username password +``` +# 登录 +yp g +yp login +yp login username +yp login username password - # 退出登录 - yp signout +# 退出登录 +yp signout - # 递归下载自己网盘中的*文件夹* - yp http://c17.yunpan.360.cn/my/?sid=#/path/to/directory - yp http://c17.yunpan.360.cn/my/?sid=#%2Fpath%3D%2Fpath%2Fto%2Fdirectory - # or - yp sid=/path/to/directory - yp sid%3D%2Fpath%2Fto%2Fdirectory +# 递归下载自己网盘中的*文件夹* +yp http://c17.yunpan.360.cn/my/?sid=#/path/to/directory +yp http://c17.yunpan.360.cn/my/?sid=#%2Fpath%3D%2Fpath%2Fto%2Fdirectory +# or +yp sid=/path/to/directory +yp sid%3D%2Fpath%2Fto%2Fdirectory - # 下载用aria2, url 是上面的 - yp -a url +# 下载用aria2, url 是上面的 +yp -a url +``` -4. 参考: +#### 4. 参考: > https://github.com/Shu-Ji/gorthon/blob/master/_3rdapp/CloudDisk360/main.py @@ -810,47 +1089,55 @@ ### music.baidu.com.py - 下载或播放高品质百度音乐(music.baidu.com) -1. 依赖 +#### 1. 依赖 - wget +``` +wget - python2-mutagen (https://code.google.com/p/mutagen/) +python2-mutagen (https://code.google.com/p/mutagen/) - mpv (http://mpv.io) +mpv (http://mpv.io) +``` -2. 使用说明 +#### 2. 使用说明 - 默认执行下载,如要播放,加参数-p。 +默认执行下载,如要播放,加参数-p。 - 参数: +#### 参数: - -f, --flac download flac - -i, --high download 320, default - -l, --low download 128 - -p, --play play with mpv +``` +-f, --flac download flac +-i, --high download 320, default +-l, --low download 128 +-p, --play play with mpv +``` - 下载的MP3默认添加id3 tags,保存在当前目录下。 +下载的MP3默认添加id3 tags,保存在当前目录下。 - 关于播放操作: +关于播放操作: -> 在运行脚本的终端,输入1次Enter,关闭当前播放并播放下一个文件,连续输入2次Enter,关闭当前播放并退出。 +> 在运行脚本的终端,输入1次Enter,关闭当前播放并播放下一个文件,连续输入2次Enter,关闭当前播放并退出。 -3. 用法 +#### 3. 用法 - \# bm 是music.baidu.com.py的马甲 (alias bm='python2 /path/to/music.baidu.com.py') +bm 是music.baidu.com.py的马甲 (alias bm='python2 /path/to/music.baidu.com.py') - # 下载专辑 - bm http://music.baidu.com/album/115032005 +``` +# 下载专辑 +bm http://music.baidu.com/album/115032005 - # 下载单曲 - bm http://music.baidu.com/song/117948039 +# 下载单曲 +bm http://music.baidu.com/song/117948039 +``` - 播放: +#### 播放: - # url 是上面的 - bm -p url +``` +# url 是上面的 +bm -p url +``` -4. 参考: +#### 4. 参考: > http://v2ex.com/t/77685 # 第9楼 @@ -859,167 +1146,239 @@ ### music.163.com.py - 下载或播放高品质网易音乐(music.163.com) -1. 依赖 +#### 1. 依赖 - wget +``` +wget - python2-requests (https://github.com/kennethreitz/requests) +python2-requests (https://github.com/kennethreitz/requests) - python2-mutagen (https://code.google.com/p/mutagen/) +python2-mutagen (https://code.google.com/p/mutagen/) - mpv (http://mpv.io) +mpv (http://mpv.io) +``` -2. 使用说明 +#### 2. 使用说明 - **默认下载和播放高品质音乐,如果服务器没有高品质音乐则转到低品质音乐。** +**默认下载和播放高品质音乐,如果服务器没有高品质音乐则转到低品质音乐。** - 默认执行下载,如要播放,加参数-p。 +默认执行下载,如要播放,加参数-p。 - 下载的MP3默认添加id3 tags,保存在当前目录下。 +下载的MP3默认添加id3 tags,保存在当前目录下。 - 关于播放操作: +关于播放操作: -> 在运行脚本的终端,输入1次Enter,关闭当前播放并播放下一个文件,连续输入2次Enter,关闭当前播放并退出。 +> 在运行脚本的终端,输入1次Enter,关闭当前播放并播放下一个文件,连续输入2次Enter,关闭当前播放并退出。 -3. 用法 +#### 3. 用法 - \# nm 是music.163.com.py的马甲 (alias nm='python2 /path/to/music.163.com.py') +nm 是music.163.com.py的马甲 (alias nm='python2 /path/to/music.163.com.py') - # 下载专辑 - nm http://music.163.com/#/album?id=18915 +``` +# 下载专辑 +nm http://music.163.com/#/album?id=18915 - # 下载单曲 - nm http://music.163.com/#/song?id=186114 +# 下载单曲 +nm http://music.163.com/#/song?id=186114 - # 下载歌单 - nm http://music.163.com/#/playlist?id=12214308 +# 下载歌单 +nm http://music.163.com/#/playlist?id=12214308 - # 下载该艺术家所有专辑或 Top 50 歌曲 - nm http://music.163.com/#/artist?id=6452 +# 下载该艺术家所有专辑或 Top 50 歌曲 +nm http://music.163.com/#/artist?id=6452 - # 下载DJ节目 - nm http://music.163.com/#/dj?id=675051 +# 下载DJ节目 +nm http://music.163.com/#/dj?id=675051 - # 下载排行榜 - nm http://music.163.com/#/discover/toplist?id=11641012 +# 下载排行榜 +nm http://music.163.com/#/discover/toplist?id=11641012 +``` +#### 播放: - 播放: +``` +# url 是上面的 +nm -p url +``` - # url 是上面的 - nm -p url - -4. 参考: +#### 4. 参考: > https://github.com/yanunon/NeteaseCloudMusic/wiki/%E7%BD%91%E6%98%93%E4%BA%91%E9%9F%B3%E4%B9%90API%E5%88%86%E6%9E%90 + > http://s3.music.126.net/s/2/core.js --- - -### flvxz_cl.py - flvxz.com 视频解析 client - 支持下载、播放 + +### flv_cmd.py - 基于在线服务的视频解析 client - 支持下载、播放 -1. 依赖 +**!!!脚本已不再维护!!!** - wget +**请使用 youtube-dl or you-get** - python2-requests (https://github.com/kennethreitz/requests) +#### 1. 依赖 - mpv (http://mpv.io) +``` +wget -2. 使用说明 +python2-requests (https://github.com/kennethreitz/requests) - flvxz.com 视频解析 +mpv (http://mpv.io) +``` - **不提供视频合并操作** +#### 2. 使用说明 - 支持的网站: +~~flvxz.com 视频解析~~ 不能用。 - """ - 已知支持120个以上视频网站,覆盖大多数国内视频站点,少量国外视频站点 - """ - -- flvxz.com +flvgo.com 视频解析 - 关于播放操作: +**不提供视频合并操作** -> 在运行脚本的终端,输入1次Enter,关闭当前播放并播放下一个文件,连续输入2次Enter,关闭当前播放并退出。 +#### 支持的网站: -3. 用法 +http://flvgo.com/sites - \# fl是flvxz_cl.py的马甲 (alias fl='python2 /path/to/flvxz_cl.py') +关于播放操作: - 下载: +> 在运行脚本的终端,输入1次Enter,关闭当前播放并播放下一个文件,连续输入2次Enter,关闭当前播放并退出。 - fl http://v.youku.com/v_show/id_XNTI2Mzg4NjAw.html - fl http://www.tudou.com/albumplay/Lqfme5hSolM/tJ_Gl3POz7Y.html +#### 3. 用法 - 播放: +fl是flv_cmd.py的马甲 (alias fl='python2 /path/to/flv_cmd.py') - # url 是上面的 - fl url -p +#### 下载: -4. 相关脚本: +``` +fl http://v.youku.com/v_show/id_XNTI2Mzg4NjAw.html +fl http://www.tudou.com/albumplay/Lqfme5hSolM/tJ_Gl3POz7Y.html +``` -> https://github.com/iambus/youku-lixian +#### 播放: -> https://github.com/rg3/youtube-dl +``` +# url 是上面的 +fl url -p +``` + +#### 4. 相关脚本: > https://github.com/soimort/you-get + +> https://github.com/iambus/youku-lixian + + +> https://github.com/rg3/youtube-dl + --- -### tumblr.py - 下载某个tumblr.com的所有图片 +### tumblr.py - 下载某个tumblr.com的所有图片、视频、音频 + +#### 1. 依赖 + +``` +wget + +mpv (http://mpv.io) + +python2-requests (https://github.com/kennethreitz/requests) +``` -1. 依赖 +#### 2. 使用说明 - wget +* 使用前需用在 http://www.tumblr.com/oauth/apps 加入一个app,证实后得到api_key,再在源码中填入,完成后则可使用。 - python2-requests (https://github.com/kennethreitz/requests) +* 或者用 http://www.tumblr.com/docs/en/api/v2 提供的api_key ( fuiKNFp9vQFvjLNvx4sUwti4Yb5yGutBN4Xh10LXZhhRKjWlV4 ) -2. 使用说明 +默认开10个进程,如需改变用参数-p [num]。 - * 使用前需用在 http://www.tumblr.com/oauth/apps 加入一个app,证实后得到api_key,再在源码中填入,完成后则可使用。 +下载的文件,保存在当前目录下。 - * 或者用 http://www.tumblr.com/docs/en/api/v2 提供的api_key ( fuiKNFp9vQFvjLNvx4sUwti4Yb5yGutBN4Xh10LXZhhRKjWlV4 ) +默认下载图片(原图)。 - 默认开10个进程,如需改变用参数-p [num]。 +支持连续下载,下载进度储存在下载文件夹内的 json.json。 - 下载的文件,保存在当前目录下。 +**正确退出程序使用 Ctrl-C** +**下载 更新的图片或其他 用 tumblr --update URL, 或 删除 json.json** - 默认下载原图。 +#### 参数: - 支持连续下载,下载进度储存在下载文件夹内的 json.json。 +``` +-p PROCESSES, --processes PROCESSES 指定多进程数,默认为10个,最多为20个 eg: -p 20 +-c, --check 尝试修复未下载成功的图片 +-t TAG, --tag TAG 下载特定tag的图片, eg: -t beautiful - 参数: +-P, --play play with mpv +-A, --audio download audios +-V, --video download videos +-q, --quiet quiet - -p PROCESSES, --processes PROCESSES 指定多进程数,默认为10个,最多为20个 eg: -p 20 - -c, --check 尝试修复未下载成功的图片 - -t TAG, --tag TAG 下载特定tag的图片, eg: -t beautiful +--update 下载新发布的东西 +--redownload 重新遍历所有的东西,如果有漏掉的东西则下载 +--proxy protocol://address:port 设置代理 -3. 用法 +-f OFFSET, --offset OFFSET 从第offset个开始,只对 -V 有用。 +``` - \# tm是tumblr.py的马甲 (alias tm='python2 /path/to/tumblr.py') +#### 3. 用法 - # 下载某个tumblr - tm http://sosuperawesome.tumblr.com/ - tm http://sosuperawesome.tumblr.com/ -t beautiful +tm是tumblr.py的马甲 (alias tm='python2 /path/to/tumblr.py') - # 指定tag下载 - tm beautiful - tm cool +``` +# 下载图片 +tm http://sosuperawesome.tumblr.com +tm http://sosuperawesome.tumblr.com -t beautiful + +# 下载图片(使用代理) +tm http://sosuperawesome.tumblr.com -x socks5://127.0.0.1:1024 +tm http://sosuperawesome.tumblr.com -t beautiful -x socks5://127.0.0.1:1024 + +# 下载单张图片 +tm http://sosuperawesome.tumblr.com/post/121467716523/murosvur-on-etsy + +# 下载视频 +tm url -V +tm url -V -f 42 +tm url -V -t tag + +# 下载单个视频 +tm url/post/1234567890 -V + +# 播放视频 +tm url -VP +tm url -VP -f 42 + +# 下载音频 +tm url -A +tm url -A -f 42 +tm url -A -t tag + +# 下载单个音频 +tm url/post/1234567890 -A + +# 播放音频 +tm url -AP +tm url -AP -f 42 + +# 播放音频(quiet) +tm url -APq + +``` --- ### unzip.py - 解决linux下unzip乱码的问题 -用法 +#### 用法 - python2 unzip.py azipfile1.zip azipfile2.zip .. - python2 unzip.py azipfile.zip -s secret - # -s 密码 +``` +python2 unzip.py azipfile1.zip azipfile2.zip +python2 unzip.py azipfile.zip -s secret +# -s 密码 +``` 代码来自以下连接,我改了一点。 @@ -1030,16 +1389,21 @@ ### ed2k_search.py - 基于 donkey4u.com 的emule搜索 -1. 依赖 - python2 +#### 1. 依赖 -2. 用法 +``` +python2 +``` - \# ed 是ed2k_search.py的马甲 (alias ed='python2 /path/to/ed2k_search.py') +#### 2. 用法 - ed this is a keyword - or - ed "this is a keyword" +ed 是ed2k_search.py的马甲 (alias ed='python2 /path/to/ed2k_search.py') + +``` +ed this is a keyword +or +ed "this is a keyword" +``` --- @@ -1048,41 +1412,57 @@ **警告: 18岁以下者,请自觉远离。** -1. 依赖 +#### 1. 依赖 + +``` +wget + +aria2 (~ 1.18) + +python2-requests (https://github.com/kennethreitz/requests) + +mpv (http://mpv.io) +``` - wget +#### 2. 使用说明 - aria2 (~ 1.18) +> youtube-dl 已支持91porn - python2-requests (https://github.com/kennethreitz/requests) +没有解决每个ip *10个/day* 限制 - mpv (http://mpv.io) +下载工具默认为wget, 可用参数-a选用aria2 -2. 使用说明 +默认执行下载,如要播放媒体文件,加参数-p。 -> 没有解决 *7个/day* 限制 +下载的文件,保存在当前目录下。 - 下载工具默认为wget, 可用参数-a选用aria2 +关于播放操作: - 默认执行下载,如要播放媒体文件,加参数-p。 +> 在运行脚本的终端,输入1次Enter,关闭当前播放并播放下一个文件,连续输入2次Enter,关闭当前播放并退出。 - 下载的文件,保存在当前目录下。 +#### 3. 用法 - 关于播放操作: +pn 是91porn.py的马甲 (alias pn='python2 /path/to/91porn.py') -> 在运行脚本的终端,输入1次Enter,关闭当前播放并播放下一个文件,连续输入2次Enter,关闭当前播放并退出。 +#### 下载: -3. 用法 +``` +pn url # 91porn.com(或其镜像) 视频的url +``` - \# pn 是91porn.py的马甲 (alias pn='python2 /path/to/91porn.py') +#### 播放: - pn url # 91porn.com(或其镜像) 视频的url +``` +pn -p url +``` - 播放: +显示下载链接,但不下载: - pn -p url +``` +pn -u url +``` -4. 参考 +#### 4. 参考 > http://v2ex.com/t/110196 # 第16楼 diff --git a/bt.py b/bt.py index 918082f..63a74df 100755 --- a/bt.py +++ b/bt.py @@ -11,6 +11,10 @@ import urlparse import argparse +s = '\x1b[%d;%dm%s\x1b[0m' # terminual color template +letters = [i for i in '.abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' \ + + '0123456789'] + ############################################################ headers = { "Connection": "keep-alive", @@ -27,11 +31,16 @@ ss = requests.session() ss.headers.update(headers) -s = u'\x1b[%d;%dm%s\x1b[0m' # terminual color template -letters = [i for i in '.abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' \ - + '0123456789'] +def save_img(url, ext): + path = os.path.join(os.path.expanduser('~'), 'vcode.%s' % ext) + with open(path, 'w') as g: + data = requests.get(url).content + g.write(data) + print " ++ 验证码已保存至", s % (1, 97, path) + input_code = raw_input(s % (2, 92, " 输入验证码: ")) + return input_code -class bt(object): +class Bt(object): def transfer(self, string, tpath, foo=None, bar=None): self.dir_dict = {} self.sub_dir_index = 0 @@ -113,21 +122,22 @@ def _check_ext(self, ext): def get_torrent(self, hh): print s % (1, 93, '\n ++ get torrent from web') - def do(url, proxies=None, data=None, timeout=None): + def do(url, data=None, timeout=None): try: + proxies = {'http': args.proxy} if args.proxy else None r = ss.get(url, proxies=proxies, timeout=timeout) cnt = r.content if r.ok and cnt and ' ' not in cnt \ and '4:name' in cnt: - print s % (1, 92, u' √ get torrent.') + print s % (1, 92, ' √ get torrent.') return cnt else: - print s % (1, 91, u' ×ばつ not get.') + print s % (1, 91, ' ×ばつ not get.') return None except: return None - ## with xunlei + ## xunlei print s % (1, 94, '>> try:'), 'bt.box.n0808.com' url = 'http://bt.box.n0808.com/%s/%s/%s.torrent' \ % (hh[:2], hh[-2:], hh) @@ -135,37 +145,37 @@ def do(url, proxies=None, data=None, timeout=None): result = do(url) if result: return result - ## with https://torrage.com + ## https://torrage.com if ss.headers.get('Referer'): del ss.headers['Referer'] - if args.proxy: - print s % (1, 94, '>> try:'), 'torrage.com' - proxies = { - 'http': args.proxy} if args.proxy else None - url = 'http://torrage.com/torrent/%s.torrent' % hh - try: - result = do(url, proxies=proxies) - if result: return result - except: - print s % (1, 91, ' !! proxy doesn\'t work:'), args.proxy + print s % (1, 94, '>> try:'), 'torrage.com' + url = 'http://torrage.com/torrent/%s.torrent' % hh + try: + result = do(url) + if result: return result + except: + pass - ## with http://btcache.me + ## http://btcache.me if ss.headers.get('Referer'): del ss.headers['Referer'] print s % (1, 94, '>> try:'), 'btcache.me' url = 'http://btcache.me/torrent/%s' % hh r = ss.get(url) key = re.search(r'name="key" value="(.+?)"', r.content) if key: + url = 'http://btcache.me/captcha' + vcode = save_img(url, 'png') data = { - "key": key.group(1) + "key": key.group(1), + "captcha": vcode } ss.headers['Referer'] = url url = 'http://btcache.me/download' - result = do(url, data=data, proxies=proxies) + result = do(url, data=data) if result: return result else: - print s % (1, 91, u' ×ばつ not get.') + print s % (1, 91, ' ×ばつ not get.') - ## some torrent stores + ## torrent stores if ss.headers.get('Referer'): del ss.headers['Referer'] urls = [ #'http://www.sobt.org/Tool/downbt?info=%s', @@ -225,6 +235,7 @@ def trans(tpath): dd = bencode.bdecode(string) except Exception as e: print s % (1, 91, ' !! torrent is wrong:'), e + return None info = bencode.bencode(dd['info']) hh = sha1(info).hexdigest() print '# %s' % tpath @@ -274,7 +285,7 @@ def do(): tpath = os.path.join(dir_, 'change_' + i) self.transfer(string, tpath, foo=foo, bar=bar) - paths.update(ipath) + # ??? paths.update(ipath) if os.getcwd() == os.path.abspath(dir_): do() elif os.getcwd() != os.path.abspath(dir_) and \ @@ -334,7 +345,6 @@ def main(argv): p.add_argument('-i', '--import_from', type=str, nargs='*', help='import magnet from local.') p.add_argument('-p', '--proxy', action='store', - default='socks5://127.0.0.1:8883', type=str, help='proxy for torrage.com, \ eg: -p "sooks5://127.0.0.1:8883"') p.add_argument('-d', '--directory', action='store', default=None, @@ -356,18 +366,18 @@ def main(argv): if comd == 'm' or comd == 'mt': # magnet to torrent urls = xxx if not args.import_from \ else import_magnet(args.import_from) - x = bt() + x = Bt() x.magnet2torrent(urls, dir_) elif comd == 't' or comd == 'tm': # torrent ot magnet paths = xxx - x = bt() + x = Bt() x.torrent2magnet(paths) elif comd == 'c' or comd == 'ct': # change ups = xxx if not args.import_from \ else import_magnet(args.import_from) - x = bt() + x = Bt() x.change(ups, dir_, foo=None, bar=None) elif comd == 'cr' or comd == 'ctre': # change @@ -375,7 +385,7 @@ def main(argv): bar = xxx[1] ups = xxx[2:] if not args.import_from \ else import_magnet(args.import_from) - x = bt() + x = Bt() x.change(ups, dir_, foo=foo, bar=bar) else: diff --git a/flv_cmd.py b/flv_cmd.py new file mode 100755 index 0000000..48fb3d0 --- /dev/null +++ b/flv_cmd.py @@ -0,0 +1,186 @@ +#!/usr/bin/env python2 +# vim: set fileencoding=utf8 + +import re +import requests +import os +import sys +import argparse +import random +from HTMLParser import HTMLParser +import urllib +import select + +s = '\x1b[%d;%dm%s\x1b[0m' # terminual color template +parser = HTMLParser() + +############################################################ +# wget exit status +wget_es = { + 0: "No problems occurred.", + 2: "User interference.", + 1<<8: "Generic error code.", + 2<<8: "Parse error - for instance, \ + when parsing command-line optio.wgetrc or .netrc...", + 3<<8: "File I/O error.", + 4<<8: "Network failure.", + 5<<8: "SSL verification failure.", + 6<<8: "Username/password authentication failure.", + 7<<8: "Protocol errors.", + 8<<8: "Server issued an error response." +} +############################################################ + +headers = { + "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) " \ + "AppleWebKit/537.36 (KHTML, like Gecko) " \ + "Chrome/40.0.2214.91 Safari/537.36", + "Accept": "text/html,application/xhtml+xml,application/xml;" \ + "q=0.9,image/webp,*/*;q=0.8", + "Accept-Encoding": "gzip, deflate, sdch", + "Accept-Language": "en-US,en;q=0.8", + "Referer": "http://flvgo.com/download" +} + +ss = requests.session() +ss.headers.update(headers) + +def download(info): + if not os.path.exists(info['dir_']): + os.mkdir(info['dir_']) + + #else: + #if os.path.exists(info['filename']): + #return 0 + + num = random.randint(0, 7) % 8 + col = s % (2, num + 90, os.path.basename(info['filename'])) + print '\n ++ 正在下载:', '#', \ + s % (1, 97, info['n']), '/', \ + s % (1, 97, info['amount']), \ + '#', col + + print info['durl'] + cmd = 'wget -c -nv --user-agent "%s" -O "%s" "%s"' \ + % (headers['User-Agent'], info['filename'], info['durl']) + status = os.system(cmd) + + if status != 0: # other http-errors, such as 302. + wget_exit_status_info = wget_es[status] + print('\n\n ----### \x1b[1;91mERROR\x1b[0m ==> \ + \x1b[1;91m%d (%s)\x1b[0m ###--- \n\n' \ + % (status, wget_exit_status_info)) + print s % (1, 91, ' ===> '), cmd + sys.exit(1) + +def play(info): + num = random.randint(0, 7) % 8 + col = s % (2, num + 90, os.path.basename(info['filename'])) + print '\n ++ play:', '#', \ + s % (1, 97, info['n']), '/', \ + s % (1, 97, info['amount']), \ + '#', col + + cmd = 'mpv --really-quiet --cache 8140 --cache-default 8140 ' \ + '--http-header-fields "User-Agent:%s" ' \ + '"%s"' % (headers['User-Agent'], info['durl']) + #'"%s"' % parser.unescape(info['durl']) + + os.system(cmd) + timeout = 1 + ii, _, _ = select.select([sys.stdin], [], [], timeout) + if ii: + sys.exit(0) + else: + pass + +def flvxz_parser(cn): + blocks = cn.split('playerContainer')[1:] + infos = {} + title = re.search(r'class="name">(.+?)<', cn).group(1) + infos['title'] = title + infos['data'] = {} + for bc in blocks: + quality = re.search(r'视频格式:(\w+)', bc).group(1) + size = sum([float(s) for s in re.findall(r'>([\d.]+) MB<', bc)]) + durls = re.findall(r' ', bc) + infos['data'][quality] = { + 'size': size, + 'durls': durls + } + return infos + +def pickup(infos): + print s % (1, 97, infos['title']) + print s % (1, 97, ' ++ pick a quality:') + sizes = [(infos['data'][q]['size'], q) for q in infos['data']] + sizes.sort() + sizes.reverse() + for i in xrange(len(sizes)): + print s % (1, 91, ' %s' % (i+1)), \ + str(sizes[i][0]) + 'MB\t', sizes[i][1] + + p = raw_input(s % (1, 92, ' Enter') + ' (1): ') + if p == '': + return sizes[0][1] + if not p.isdigit(): + print s % (1, 91, ' !! enter error') + sys.exit() + p = int(p) + if p <= len(infos['data']): + print s % (2, 92, ' -- %s' % sizes[p-1][1]) + return sizes[p-1][1] + else: + print s % (1, 91, ' !! enter error') + sys.exit() + +def getext(durl): + if durl.find('flv'): + return '.flv' + elif durl.find('mp4'): + return '.mp4' + elif durl.find('m3u8'): + return '.m3u8' + else: + return '.flv' + +def main(purl): + apiurl = 'http://flvgo.com/download?url=%s' % urllib.quote(purl) + ss.get('http://flvgo.com') + cn = ss.get(apiurl).content + infos = flvxz_parser(cn) + title = infos['title'] + quality = pickup(infos) + durls = infos['data'][quality]['durls'] + + yes = True if len(durls)> 1 else False + dir_ = os.path.join(os.getcwd(), infos['title']) if yes else os.getcwd() + + n = args.from_ - 1 + amount = len(durls) + + for i in xrange(n, amount): + info = { + 'title': title, + 'filename': os.path.join(dir_, str(i+1) + getext(durls[i])), + 'durl': durls[i], + 'dir_': dir_, + 'amount': amount, + 'n': n + } + if args.play: + play(info) + else: + download(info) + n += 1 + +if __name__ == '__main__': + p = argparse.ArgumentParser(description='flvxz') + p.add_argument('url', help='site url') + p.add_argument('-p', '--play', action='store_true', \ + help='play with mpv') + p.add_argument('-f', '--from_', action='store', \ + default=1, type=int, \ + help='从第几个开始下载,eg: -f 42') + args = p.parse_args() + main(args.url) diff --git a/flvxz_cl.py b/flvxz_cl.py deleted file mode 100755 index 53c7efa..0000000 --- a/flvxz_cl.py +++ /dev/null @@ -1,199 +0,0 @@ -#!/usr/bin/env python2 -# vim: set fileencoding=utf8 - -import re -import base64 -import requests -import os -import sys -import argparse -import random -import json -from HTMLParser import HTMLParser -import select - -s = '\x1b[%d;%dm%s\x1b[0m' # terminual color template -parser = HTMLParser() - -############################################################ -# wget exit status -wget_es = { - 0: "No problems occurred.", - 2: "User interference.", - 1<<8: "Generic error code.", - 2<<8: "Parse error - for instance, \ - when parsing command-line optio.wgetrc or .netrc...", - 3<<8: "File I/O error.", - 4<<8: "Network failure.", - 5<<8: "SSL verification failure.", - 6<<8: "Username/password authentication failure.", - 7<<8: "Protocol errors.", - 8<<8: "Server issued an error response." -} -############################################################ - -headers = { - "Host": "www.flvxz.com", - "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) \ - AppleWebKit/537.36 (KHTML, like Gecko) \ - Chrome/40.0.2214.91 Safari/537.36", - "Accept": "*/*", - "Accept-Language": "en-US,en;q=0.5", - "Accept-Encoding": "gzip, deflate", - "Referer": "http://flvxz.com", - "Connection": "keep-alive" -} - -ss = requests.session() -ss.headers.update(headers) - -api = 'https://www.flvxz.com/getFlv.php?url=%s' - -def download(infos): - if not os.path.exists(infos['dir_']): - os.mkdir(infos['dir_']) - - #else: - #if os.path.exists(infos['filename']): - #return 0 - - num = random.randint(0, 7) % 7 - col = s % (2, num + 90, os.path.basename(infos['filename'])) - print '\n ++ 正在下载:', '#', \ - s % (1, 97, infos['n']), '/', \ - s % (1, 97, infos['amount']), \ - '#', col - - cmd = 'wget -c -nv --user-agent "%s" -O "%s" "%s"' \ - % (headers['User-Agent'], infos['filename'], - parser.unescape(infos['durl'])) - status = os.system(cmd) - - if status != 0: # other http-errors, such as 302. - wget_exit_status_info = wget_es[status] - print('\n\n ----### \x1b[1;91mERROR\x1b[0m ==> \ - \x1b[1;91m%d (%s)\x1b[0m ###--- \n\n' \ - % (status, wget_exit_status_info)) - print s % (1, 91, ' ===> '), cmd - sys.exit(1) - -def play(infos): - num = random.randint(0, 7) % 7 - col = s % (2, num + 90, os.path.basename(infos['filename'])) - print '\n ++ play:', '#', \ - s % (1, 97, infos['n']), '/', \ - s % (1, 97, infos['amount']), \ - '#', col - - cmd = 'mpv --really-quiet --cache 8140 --cache-default 8140 ' \ - '--http-header-fields "User-Agent:%s" ' \ - '"%s"' % (headers['User-Agent'], infos['durl']) - #'"%s"' % parser.unescape(infos['durl']) - - os.system(cmd) - timeout = 1 - ii, _, _ = select.select([sys.stdin], [], [], timeout) - if ii: - sys.exit(0) - else: - pass - -def decrypt(encrypted_cn): - c = encrypted_cn.split('}(') - x = re.search(r'(\[.+\])\)\);', c[1]).group(1) - y = re.search(r'(\[.+\])..\)\);if', c[2]).group(1) - - a, b = json.loads('[' + x + ']') - for i in xrange(len(b)): - b[i] = a[b[i]] - t = ''.join(b[::-1])[8:-1] - b = json.loads(t) - - a = json.loads(y) - for i in xrange(len(b)): - b[i] = a[b[i]] - decrypted_cn = ''.join(b[::-1]) - - return decrypted_cn - -def flvxz_parser(cn): - cn = decrypt(cn) - qualities = re.findall(r'"quality">\[(.+?)\]<', cn) - if not qualities: return {} - - j = {} - chucks = re.split(r'"quality">\[.+?\]<', cn)[1:] - - for i in xrange(len(qualities)): - parts = re.findall(r'data-clipboard-', chucks[i]) - t = re.findall(r'rel="noreferrer" href="/index.cgi/contrast/https://github.com/southwolf/iScript/compare/(.+?)"', chucks[i]) - urls = [ii for ii in t if 'flvxz' not in ii] - - j[qualities[i]] = zip(parts, urls) - - return j - -def pickup(j): - print s % (1, 97, ' ++ pick a quality:') - qualities = j.keys() - qualities.sort() - for i in xrange(len(qualities)): - print s % (1, 91, ' %s' % (i+1)), qualities[i] - - p = raw_input(s % (1, 92, ' Enter: ')) - if p.isdigit(): - p = int(p) - if p <= len(j): - print s % (2, 92, ' -- %s' % qualities[p-1]) - return j[qualities[p-1]] - else: - print s % (1, 91, ' !! enter error') - sys.exit() - else: - print s % (1, 91, ' !! enter error') - sys.exit() - -def main(url): - encode_url = base64.urlsafe_b64encode(url.replace('://', ':##')) - url = api % encode_url - - cn = ss.get(url).content - j = flvxz_parser(cn) - if j: - j = pickup(j) - else: - print s % (1, 91, ' !! Can\'t get videos') - sys.exit() - - title = re.search(r'media-heading">(.+?) 1 else False - dir_ = os.path.join(os.getcwd(), title) if yes else os.getcwd() - - n = args.from_ - amount = len(j) - j = j[args.from_ - 1:] - for i in j: - infos = { - 'filename': os.path.join(dir_, i[0]), - 'durl': i[1], - 'dir_': dir_, - 'amount': amount, - 'n': n - } - if args.play: - play(infos) - else: - download(infos) - n += 1 - -if __name__ == '__main__': - p = argparse.ArgumentParser(description='flvxz') - p.add_argument('url', help='site url') - p.add_argument('-p', '--play', action='store_true', \ - help='play with mpv') - p.add_argument('-f', '--from_', action='store', \ - default=1, type=int, \ - help='从第几个开始下载,eg: -f 42') - args = p.parse_args() - main(args.url) diff --git a/leetcode_problems.py b/leetcode_problems.py new file mode 100755 index 0000000..1c94063 --- /dev/null +++ b/leetcode_problems.py @@ -0,0 +1,123 @@ +#!/usr/bin/env python +# -*- coding=utf-8 -*- + +import sys +import re +import os +import argparse +import requests +from lxml import html as lxml_html + +try: + import html +except ImportError: + import HTMLParser + html = HTMLParser.HTMLParser() + +try: + import cPickle as pk +except ImportError: + import pickle as pk + +class LeetcodeProblems(object): + def get_problems_info(self): + leetcode_url = 'https://leetcode.com/problemset/algorithms' + res = requests.get(leetcode_url) + if not res.ok: + print('request error') + sys.exit() + cm = res.text + cmt = cm.split('tbody>')[-2] + indexs = re.findall(r' (\d+)', cmt) + problem_urls = ['https://leetcode.com' + url \ + for url in re.findall( + r'', html).group(1) + r'', html).group(1) j = json.loads(text) songids = [i['id'] for i in j] d = modificate_text( @@ -366,14 +365,16 @@ def display_infos(self, i): print '>>', s % (2, 92, 'http://music.163.com/song/%s' \ % i['song_id']) print '>>', s % (2, 97, 'MP3-Quality'), ':', \ - s % (1, 92, q[i['mp3_quality']]) + s % (1, 92, str(q.get(i['mp3_quality']))) print '' def play(self, amount_songs, n=None): for i in self.song_infos: - durl = i['durl'] self.display_infos(i) - os.system('mpv --really-quiet --audio-display no %s' % durl) + if not i['durl']: + continue + cmd = 'mpv --really-quiet --audio-display no %s' % i['durl'] + os.system(cmd) timeout = 1 ii, _, _ = select.select([sys.stdin], [], [], timeout) if ii: @@ -401,11 +402,9 @@ def download(self, amount_songs, n=None): else: ii += 1 continue - file_name_for_wget = file_name.replace('`', '\`') if not args.undownload: q = {'h': 'High', 'm': 'Middle', 'l': 'Low'} - mp3_quality = q[i['mp3_quality']] - durl = i['durl'] + mp3_quality = str(q.get(i['mp3_quality'])) if n == None: print(u'\n ++ 正在下载: #%s/%s# %s\n' \ u' ++ mp3_quality: %s' \ @@ -416,16 +415,20 @@ def download(self, amount_songs, n=None): u' ++ mp3_quality: %s' \ % (n, amount_songs, col, s % (1, 91, mp3_quality))) - wget = self.template_wgets \ - % (file_name_for_wget, durl) - wget = wget.encode('utf8') - status = os.system(wget) + if not i['durl']: + continue + + file_name_for_wget = file_name.replace('`', '\`') + cmd = 'wget -c -nv -U "%s" -O "%s.tmp" %s' \ + % (headers['User-Agent'], file_name_for_wget, i['durl']) + cmd = cmd.encode('utf8') + status = os.system(cmd) if status != 0: # other http-errors, such as 302. wget_exit_status_info = wget_es[status] print('\n\n ----### \x1b[1;91mERROR\x1b[0m ==> \x1b[1;91m%d ' \ '(%s)\x1b[0m ###--- \n\n' \ % (status, wget_exit_status_info)) - print s % (1, 91, ' ===> '), wget + print s % (1, 91, ' ===> '), cmd sys.exit(1) else: os.rename('%s.tmp' % file_name, file_name) diff --git a/music.baidu.com.py b/music.baidu.com.py index 0185b46..c2c9016 100755 --- a/music.baidu.com.py +++ b/music.baidu.com.py @@ -85,13 +85,10 @@ def __init__(self, url): self.download = self.play if args.play else self.download - def get_songidlist(self, id_): - html = self.opener.open(self.template_album % id_).read() + def get_songidlist(self, song_id): + html = self.opener.open(self.template_album % song_id).read() songidlist = re.findall(r'/song/(\d+)', html) - api_json = self.opener.open(self.template_api % ','.join(songidlist)).read() - api_json = json.loads(api_json) - infos = api_json['data']['songList'] - return infos + return songidlist def get_cover(self, url): i = 1 @@ -122,53 +119,40 @@ def url_parser(self): elif '/song/' in self.url: self.song_id = re.search(r'/song/(\d+)', self.url).group(1) #print(s % (2, 92, u'\n -- 正在分析歌曲信息 ...')) - self.get_song_infos() + self.get_song_infos(self.song_id) else: print(s % (2, 91, u' 请正确输入baidu网址.')) + self.download() - def get_song_infos(self): - api_json = self.opener.open(self.template_api % self.song_id).read() + def get_song_infos(self, song_id, track_number=''): + api_json = self.opener.open(self.template_api % song_id).read() j = json.loads(api_json) song_info = {} song_info['song_id'] = unicode(j['data']['songList'][0]['songId']) - song_info['track'] = u'' + song_info['track'] = unicode(track_number) song_info['song_url'] = u'http://music.baidu.com/song/' + song_info['song_id'] song_info['song_name'] = modificate_text(j['data']['songList'][0]['songName']).strip() song_info['album_name'] = modificate_text(j['data']['songList'][0]['albumName']).strip() song_info['artist_name'] = modificate_text(j['data']['songList'][0]['artistName']).strip() song_info['album_pic_url'] = j['data']['songList'][0]['songPicRadio'] + song_info['file_name'] = song_info['artist_name'] + ' - ' + song_info['song_name'] + if song_info['track']: + song_info['file_name'] = song_info['track'].zfill(2) + '.' + song_info['file_name'] if args.flac: - song_info['file_name'] = song_info['song_name'] + ' - ' + song_info['artist_name'] + '.flac' + song_info['file_name'] = song_info['file_name'] + '.flac' else: - song_info['file_name'] = song_info['song_name'] + ' - ' + song_info['artist_name'] + '.mp3' + song_info['file_name'] = song_info['file_name'] + '.mp3' song_info['durl'] = j['data']['songList'][0]['songLink'] self.song_infos.append(song_info) - self.download() def get_album_infos(self): songidlist = self.get_songidlist(self.album_id) - z = z_index(songidlist) - ii = 1 + track_number = 1 for i in songidlist: - song_info = {} - song_info['song_id'] = unicode(i['songId']) - song_info['song_url'] = u'http://music.baidu.com/song/' + song_info['song_id'] - song_info['track'] = unicode(ii) - song_info['song_name'] = modificate_text(i['songName']).strip() - song_info['artist_name'] = modificate_text(i['artistName']).strip() - song_info['album_pic_url'] = i['songPicRadio'] - if args.flac: - song_info['file_name'] = song_info['track'].zfill(z) + '.' + song_info['song_name'] + ' - ' + song_info['artist_name'] + '.flac' - else: - song_info['file_name'] = song_info['track'].zfill(z) + '.' + song_info['song_name'] + ' - ' + song_info['artist_name'] + '.mp3' - song_info['album_name'] = modificate_text(i['albumName']).strip() \ - if i['albumName'] else modificate_text(self.song_infos[0]['album_name']) - song_info['durl'] = i['songLink'] - self.song_infos.append(song_info) - ii += 1 - d = modificate_text(self.song_infos[0]['album_name'] + ' - ' + self.song_infos[0]['artist_name']) + self.get_song_infos(i, track_number) + track_number += 1 + d = modificate_text(self.song_infos[0]['artist_name'] + ' - ' + self.song_infos[0]['album_name']) self.dir_ = os.path.join(os.getcwd().decode('utf8'), d) - self.download() def display_infos(self, i): print '\n ----------------' @@ -203,6 +187,7 @@ def download(self): file_name = os.path.join(dir_, t) if os.path.exists(file_name): ## if file exists, no get_durl ii += 1 + print(u'\n 文件已存在~') continue file_name_for_wget = file_name.replace('`', '\`') if 'zhangmenshiting.baidu.com' in i['durl'] or \ diff --git a/pan.baidu.com.py b/pan.baidu.com.py index 4f13f5a..2617123 100755 --- a/pan.baidu.com.py +++ b/pan.baidu.com.py @@ -3,8 +3,10 @@ import os import sys -from getpass import getpass +import hashlib +import functools import requests +requests.packages.urllib3.disable_warnings() # disable urllib3's warnings https://urllib3.readthedocs.org/en/latest/security.html#insecurerequestwarning from requests_toolbelt import MultipartEncoder import urllib import json @@ -12,7 +14,8 @@ import re import time import argparse -import random +from random import SystemRandom +random = SystemRandom() import select import base64 import md5 @@ -38,6 +41,17 @@ MaxSlicePieces = 1024 ENoError = 0 +CIPHERS = [ + "aes-256-cfb", "aes-128-cfb", "aes-192-cfb", + "aes-256-ofb", "aes-128-ofb", "aes-192-ofb", + "aes-128-ctr", "aes-192-ctr", "aes-256-ctr", + "aes-128-cfb8", "aes-192-cfb8", "aes-256-cfb8", + "aes-128-cfb1", "aes-192-cfb1", "aes-256-cfb1", + "bf-cfb", "camellia-128-cfb", "camellia-192-cfb", + "camellia-256-cfb", "cast5-cfb", "chacha20", + "idea-cfb", "rc2-cfb", "rc4-md5", "salsa20", "seed-cfb" +] + ############################################################ # wget exit status wget_es = { @@ -56,24 +70,24 @@ ############################################################ # file extensions -mediatype = { +mediatype = [ ".wma", ".wav", ".mp3", ".aac", ".ra", ".ram", ".mp2", ".ogg", \ ".aif", ".mpega", ".amr", ".mid", ".midi", ".m4a", ".m4v", ".wmv", \ ".rmvb", ".mpeg4", ".mpeg2", ".flv", ".avi", ".3gp", ".mpga", ".qt", \ ".rm", ".wmz", ".wmd", ".wvx", ".wmx", ".wm", ".swf", ".mpg", ".mp4", \ ".mkv", ".mpeg", ".mov", ".mdf", ".iso", ".asf", ".vob" -} -imagetype = { +] +imagetype = [ ".jpg", ".jpeg", ".gif", ".bmp", ".png", ".jpe", ".cur", ".svg", \ ".svgz", ".tif", ".tiff", ".ico" -} -doctype = { +] +doctype = [ ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".vsd", ".txt", ".pdf", \ ".ods", ".ots", ".odt", ".rtf", ".dot", ".dotx", ".odm", ".pps", ".pot", \ ".xlt", ".xltx", ".csv", ".ppsx", ".potx", ".epub", ".apk", ".exe", \ ".msi", ".ipa", ".torrent", ".mobi" -} -archivetype = { +] +archivetype = [ ".7z", ".a", ".ace", ".afa", ".alz", ".android", ".apk", ".ar", \ ".arc", ".arj", ".b1", ".b1", ".ba", ".bh", ".bz2", ".cab", ".cab", \ ".cfs", ".chm", ".cpio", ".cpt", ".cqm", ".dar", ".dd", ".dgc", ".dmg", \ @@ -87,7 +101,349 @@ ".uc0", ".uc2", ".uca", ".ucn", ".ue2", ".uha", ".ur2", ".war", ".web", \ ".wim", ".x", ".xar", ".xp3", ".xz", ".yz1", ".z", ".zip", ".zipx", \ ".zoo", ".zpaq", ".zz" -} +] + +PHONE_MODEL_DATABASE = [ + "1501_M02", # 360 F4 + "1503-M02", # 360 N4 + "1505-A01", # 360 N4S + "303SH", # 夏普 Aquos Crystal Xx Mini 303SH + "304SH", # 夏普 Aquos Crystal Xx SoftBank + "305SH", # 夏普 Aquos Crystal Y + "306SH", # 夏普 Aquos Crystal 306SH + "360 Q5 Plus", # 360 Q5 Plus + "360 Q5", # 360 Q5 + "402SH", # 夏普 Aquos Crystal X + "502SH", # 夏普 Aquos Crystal Xx2 + "6607", # OPPO U3 + "A1001", # 一加手机1 + "ASUS_A001", # 华硕 ZenFone 3 Ultra + "ASUS_A001", # 华硕 ZenFone 3 Ultra + "ASUS_Z00ADB", # 华硕 ZenFone 2 + "ASUS_Z00UDB", # 华硕 Zenfone Selfie + "ASUS_Z00XSB", # 华硕 ZenFone Zoom + "ASUS_Z012DE", # 华硕 ZenFone 3 + "ASUS_Z012DE", # 华硕 ZenFone 3 + "ASUS_Z016D", # 华硕 ZenFone 3 尊爵 + "ATH-TL00H", # 华为 荣耀 7i + "Aster T", # Vertu Aster T + "BLN-AL10", # 华为 荣耀 畅玩6X + "BND-AL10", # 荣耀7X + "BTV-W09", # 华为 M3 + "CAM-UL00", # 华为 荣耀 畅玩5A + "Constellation V", # Vertu Constellation V + "D6683", # 索尼 Xperia Z3 Dual TD + "DIG-AL00", # 华为 畅享 6S + "E2312", # 索尼 Xperia M4 Aqua + "E2363 ", # 索尼 Xperia M4 Aqua Dual + "E5363", # 索尼 Xperia C4 + "E5563", # 索尼 Xperia C5 + "E5663", # 索尼 Xperia M5 + "E5823", # 索尼 Xperia Z5 Compact + "E6533", # 索尼 Xperia Z3+ + "E6683", # 索尼 Xperia Z5 + "E6883", # 索尼 Xperia Z5 Premium + "EBEN M2", # 8848 M2 + "EDI-AL10", # 华为 荣耀 Note 8 + "EVA-AL00", # 华为 P9 + "F100A", # 金立 F100 + "F103B", # 金立 F103B + "F3116", # 索尼 Xperia XA + "F3216", # 索尼 Xperia XA Ultra + "F5121 / F5122", # 索尼 Xperia X + "F5321", # 索尼 Xperia X Compact + "F8132", # 索尼 Xperia X Performance + "F8332", # 索尼 Xperia XZ + "FRD-AL00", # 华为 荣耀 8 + "FS8001", # 夏普 C1 + "FS8002", # 夏普 A1 + "G0111", # 格力手机 1 + "G0215", # 格力手机 2 + "G8142", # 索尼Xperia XZ Premium G8142 + "G8342", # 索尼Xperia XZ1 + "GIONEE S9", # 金立 S9 + "GN5001S", # 金立 金钢 + "GN5003", # 金立 大金钢 + "GN8002S", # 金立 M6 Plus + "GN8003", # 金立 M6 + "GN9011", # 金立 S8 + "GN9012", # 金立 S6 Pro + "GRA-A0", # Coolpad Cool Play 6C + "H60-L11", # 华为 荣耀 6 + "HN3-U01", # 华为 荣耀 3 + "HTC D10w", # HTC Desire 10 Pro + "HTC E9pw", # HTC One E9+ + "HTC M10u", # HTC 10 + "HTC M8St", # HTC One M8 + "HTC M9PT", # HTC One M9+ + "HTC M9e", # HTC One M9 + "HTC One A9", # HTC One A9 + "HTC U-1w", # HTC U Ultra + "HTC X9u", # HTC One X9 + "HTC_M10h", # HTC 10 国际版 + "HUAWEI CAZ-AL00", # 华为 Nova + "HUAWEI CRR-UL00", # 华为 Mate S + "HUAWEI GRA-UL10", # 华为 P8 + "HUAWEI MLA-AL10", # 华为 麦芒 5 + "HUAWEI MT7-AL00", # 华为 mate 7 + "HUAWEI MT7-TL00", # 华为 Mate 7 + "HUAWEI NXT-AL10", # 华为 Mate 8 + "HUAWEI P7-L00", # 华为 P7 + "HUAWEI RIO-AL00", # 华为 麦芒 4 + "HUAWEI TAG-AL00", # 华为 畅享 5S + "HUAWEI VNS-AL00", # 华为 G9 + "IUNI N1", # 艾优尼 N1 + "IUNI i1", # 艾优尼 i1 + "KFAPWI", # Amazon Kindle Fire HDX 8.9 + "KFSOWI", # Amazon Kindle Fire HDX 7 + "KFTHWI", # Amazon Kindle Fire HD + "KIW-TL00H", # 华为 荣耀 畅玩5X + "KNT-AL10", # 华为 荣耀 V8 + "L55t", # 索尼 Xperia Z3 + "L55u", # 索尼 Xperia Z3 + "LEX626", # 乐视 乐S3 + "LEX720", # 乐视 乐Pro3 + "LG-D858", # LG G3 + "LG-H818", # LG G4 + "LG-H848", # LG G5 SE + "LG-H868", # LG G5 + "LG-H968", # LG V10 + "LON-AL00", # 华为 Mate 9 Pro + "LON-AL00-PD", # 华为 Mate 9 Porsche Design + "LT18i", # Sony Ericsson Xperia Arc S + "LT22i", # Sony Ericsson Xperia P + "LT26i", # Sony Ericsson Xperia S + "LT26ii", # Sony Ericsson Xperia SL + "LT26w", # Sony Ericsson Xperia Acro S + "Le X520", # 乐视 乐2 + "Le X620", # 乐视 乐2Pro + "Le X820", # 乐视 乐Max2 + "Lenovo A3580", # 联想 黄金斗士 A8 畅玩 + "Lenovo A7600-m", # 联想 黄金斗士 S8 + "Lenovo A938t", # 联想 黄金斗士 Note8 + "Lenovo K10e70", # 联想 乐檬K10 + "Lenovo K30-T", # 联想 乐檬 K3 + "Lenovo K32C36", # 联想 乐檬3 + "Lenovo K50-t3s", # 联想 乐檬 K3 Note + "Lenovo K52-T38", # 联想 乐檬 K5 Note + "Lenovo K52e78", # Lenovo K5 Note + "Lenovo P2c72", # 联想 P2 + "Lenovo X3c50", # 联想 乐檬 X3 + "Lenovo Z90-3", # 联想 VIBE Shot大拍 + "M040", # 魅族 MX 2 + "M1 E", # 魅蓝 E + "M2-801w", # 华为 M2 + "M2017", # 金立 M2017 + "M3", # EBEN M3 + "M355", # 魅族 MX 3 + "MHA-AL00", # 华为 Mate 9 + "MI 4LTE", # 小米手机4 + "MI 4S", # 小米手机4S + "MI 5", # 小米手机5 + "MI 5s Plus", # 小米手机5s Plus + "MI 5s", # 小米手机5s + "MI MAX", # 小米Max + "MI Note Pro", # 小米Note顶配版 + "MI PAD 2", # 小米平板 2 + "MIX", # 小米MIX + "MLA-UL00", # 华为 G9 Plus + "MP1503", # 美图 M6 + "MP1512", # 美图 M6s + "MT27i", # Sony Ericsson Xperia Sola + "MX4 Pro", # 魅族 MX 4 Pro + "MX4", # 魅族 MX 4 + "MX5", # 魅族 MX 5 + "MX6", # 魅族 MX 6 + "Meitu V4s", # 美图 V4s + "Meizu M3 Max", # 魅蓝max + "Meizu U20", # 魅蓝U20 + "Mi 5", + "Mi 6", + "Mi A1", # MI androidone + "Mi Note 2", # 小米Note2 + "MiTV2S-48", # 小米电视2s + "Moto G (4)", # 摩托罗拉 G4 Plus + "N1", # Nokia N1 + "NCE-AL00", # 华为 畅享 6 + "NTS-AL00", # 华为 荣耀 Magic + "NWI-AL10", # nova2s + "NX508J", # 努比亚 Z9 + "NX511J", # 努比亚 小牛4 Z9 Mini + "NX512J", # 努比亚 大牛 Z9 Max + "NX513J", # 努比亚 My 布拉格 + "NX513J", # 努比亚 布拉格S + "NX523J", # 努比亚 Z11 Max + "NX529J", # 努比亚 小牛5 Z11 Mini + "NX531J", # 努比亚 Z11 + "NX549J", # 努比亚 小牛6 Z11 MiniS + "NX563J", # 努比亚Z17 + "Nexus 4", + "Nexus 5X", + "Nexus 6", + "Nexus 6P", + "Nexus 7", + "Nexus 9", + "Nokia_X", # Nokia X + "Nokia_XL_4G", # Nokia XL + "ONE A2001", # 一加手机2 + "ONE E1001", # 一加手机X + "ONEPLUS A5010", # 一加5T + "OPPO A53", # OPPO A53 + "OPPO A59M", # OPPO A59 + "OPPO A59s", # OPPO A59s + "OPPO R11", + "OPPO R7", # OPPO R7 + "OPPO R7Plus", # OPPO R7Plus + "OPPO R7S", # OPPO R7S + "OPPO R7sPlus", # OPPO R7sPlus + "OPPO R9 Plustm A", # OPPO R9Plus + "OPPO R9s Plus", # OPPO R9s Plus + "OPPO R9s", + "OPPO R9s", # OPPO R9s + "OPPO R9tm", # OPPO R9 + "PE-TL10", # 华为 荣耀 6 Plus + "PLK-TL01H", # 华为 荣耀 7 + "Pro 5", # 魅族 Pro 5 + "Pro 6", # 魅族 Pro 6 + "Pro 6s", # 魅族 Pro 6s + "RM-1010", # Nokia Lumia 638 + "RM-1018", # Nokia Lumia 530 + "RM-1087", # Nokia Lumia 930 + "RM-1090", # Nokia Lumia 535 + "RM-867", # Nokia Lumia 920 + "RM-875", # Nokia Lumia 1020 + "RM-887", # Nokia Lumia 720 + "RM-892", # Nokia Lumia 925 + "RM-927", # Nokia Lumia 929 + "RM-937", # Nokia Lumia 1520 + "RM-975", # Nokia Lumia 635 + "RM-977", # Nokia Lumia 630 + "RM-984", # Nokia Lumia 830 + "RM-996", # Nokia Lumia 1320 + "Redmi 3S", # 红米3s + "Redmi 4", # 小米 红米4 + "Redmi 4A", # 小米 红米4A + "Redmi Note 2", # 小米 红米Note2 + "Redmi Note 3", # 小米 红米Note3 + "Redmi Note 4", # 小米 红米Note4 + "Redmi Pro", # 小米 红米Pro + "S3", # 佳域S3 + "SCL-TL00H", # 华为 荣耀 4A + "SD4930UR", # Amazon Fire Phone + "SH-03G", # 夏普 Aquos Zeta SH-03G + "SH-04F", # 夏普 Aquos Zeta SH-04F + "SHV31", # 夏普 Aquos Serie Mini SHV31 + "SM-A5100", # Samsung Galaxy A5 + "SM-A7100", # Samsung Galaxy A7 + "SM-A8000", # Samsung Galaxy A8 + "SM-A9000", # Samsung Galaxy A9 + "SM-A9100", # Samsung Galaxy A9 高配版 + "SM-C5000", # Samsung Galaxy C5 + "SM-C5010", # Samsung Galaxy C5 Pro + "SM-C7000", # Samsung Galaxy C7 + "SM-C7010", # Samsung Galaxy C7 Pro + "SM-C9000", # Samsung Galaxy C9 Pro + "SM-G1600", # Samsung Galaxy Folder + "SM-G5500", # Samsung Galaxy On5 + "SM-G6000", # Samsung Galaxy On7 + "SM-G7100", # Samsung Galaxy On7(2016) + "SM-G7200", # Samsung Galasy Grand Max + "SM-G9198", # Samsung 领世旗舰III + "SM-G9208", # Samsung Galaxy S6 + "SM-G9250", # Samsung Galasy S7 Edge + "SM-G9280", # Samsung Galaxy S6 Edge+ + "SM-G9300", # Samsung Galaxy S7 + "SM-G9350", # Samsung Galaxy S7 Edge + "SM-G9500", # Samsung Galaxy S8 + "SM-G9550", # Samsung Galaxy S8+ + "SM-G9600", # Samsung Galaxy S9 + "SM-G960F", # Galaxy S9 Dual SIM + "SM-G9650", # Samsung Galaxy S9+ + "SM-G965F", # Galaxy S9+ Dual SIM + "SM-J3109", # Samsung Galaxy J3 + "SM-J3110", # Samsung Galaxy J3 Pro + "SM-J327A", # Samsung Galaxy J3 Emerge + "SM-J5008", # Samsung Galaxy J5 + "SM-J7008", # Samsung Galaxy J7 + "SM-N9108V", # Samsung Galasy Note4 + "SM-N9200", # Samsung Galaxy Note5 + "SM-N9300", # Samsung Galaxy Note 7 + "SM-N935S", # Samsung Galaxy Note Fan Edition + "SM-N9500", # Samsung Galasy Note8 + "SM-W2015", # Samsung W2015 + "SM-W2016", # Samsung W2016 + "SM-W2017", # Samsung W2017 + "SM705", # 锤子 T1 + "SM801", # 锤子 T2 + "SM901", # 锤子 M1 + "SM919", # 锤子 M1L + "ST18i", # Sony Ericsson Xperia Ray + "ST25i", # Sony Ericsson Xperia U + "STV100-1", # 黑莓Priv + "Signature Touch", # Vertu Signature Touch + "TA-1000", # Nokia 6 + "TA-1000", # HMD Nokia 6 + "TA-1041", # Nokia 7 + "VERTU Ti", # Vertu Ti + "VIE-AL10", # 华为 P9 Plus + "VIVO X20", + "VIVO X20A", + "W909", # 金立 天鉴 W909 + "X500", # 乐视 乐1S + "X608", # 乐视 乐1 + "X800", # 乐视 乐1Pro + "X900", # 乐视 乐Max + "XT1085", # 摩托罗拉 X + "XT1570", # 摩托罗拉 X Style + "XT1581", # 摩托罗拉 X 极 + "XT1585", # 摩托罗拉 Droid Turbo 2 + "XT1635", # 摩托罗拉 Z Play + "XT1635-02", # 摩托罗拉 Z Play + "XT1650", # 摩托罗拉 Z + "XT1650-05", # 摩托罗拉 Z + "XT1706", # 摩托罗拉 E3 POWER + "YD201", # YotaPhone2 + "YD206", # YotaPhone2 + "YQ60", # 锤子 坚果 + "ZTE A2015", # 中兴 AXON 天机 + "ZTE A2017", # 中兴 AXON 天机 7 + "ZTE B2015", # 中兴 AXON 天机 MINI + "ZTE BV0720", # 中兴 Blade A2 + "ZTE BV0730", # 中兴 Blade A2 Plus + "ZTE C2016", # 中兴 AXON 天机 MAX + "ZTE C2017", # 中兴 AXON 天机 7 MAX + "ZTE G720C", # 中兴 星星2号 + "ZUK Z2121", # ZUK Z2 Pro + "ZUK Z2131", # ZUK Z2 + "ZUK Z2151", # ZUK Edge + "ZUK Z2155", # ZUK Edge L + "m030", # 魅族mx + "m1 metal", # 魅蓝metal + "m1 note", # 魅蓝 Note + "m1", # 魅蓝 + "m2 note", # 魅蓝 Note 2 + "m2", # 魅蓝 2 + "m3 note", # 魅蓝 Note 3 + "m3", # 魅蓝 3 + "m3s", # 魅蓝 3S + "m9", # 魅族m9 + "marlin", # Google Pixel XL + "sailfish", # Google Pixel + "vivo V3Max", # vivo V3Max + "vivo X6D", # vivo X6 + "vivo X6PlusD", # vivo X6Plus + "vivo X6S", # vivo X6S + "vivo X6SPlus", # vivo X6SPlus + "vivo X7", # vivo X7 + "vivo X7Plus", # vivo X7Plus + "vivo X9", # vivo X9 + "vivo X9Plus", # vivo X9Plus + "vivo Xplay5A 金", # vivo Xplay5 + "vivo Xplay6", # vivo Xplay6 + "vivo Y66", # vivo Y66 + "vivo Y67", # vivo Y67 + "z1221", # ZUK Z1 +] s = '\x1b[%s;%sm%s\x1b[0m' # terminual color template @@ -96,19 +452,126 @@ save_share_path = os.path.join(os.path.expanduser('~'), '.bp.ss.pickle') headers = { - "Accept":"text/html,application/xhtml+xml,application/xml; " \ - "q=0.9,image/webp,*/*;q=0.8", - "Accept-Encoding":"text/html", + "Accept": "application/json, text/javascript, text/html, */*; q=0.01", + "Accept-Encoding":"gzip, deflate, sdch", "Accept-Language":"en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4,zh-TW;q=0.2", - "Content-Type":"application/x-www-form-urlencoded", - "Referer":"http://www.baidu.com/", - "User-Agent":"Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 "\ - "(KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36" + "Referer":"http://pan.baidu.com/disk/home", + "X-Requested-With": "XMLHttpRequest", + "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/77.0.3865.75 Safari/537.36", + "Connection": "keep-alive", } +NETDISK_UA = 'netdisk;8.12.9;;android-android;7.0;JSbridge3.0.0' + ss = requests.session() ss.headers.update(headers) +def to_md5(buff): + assert isinstance(buff, (str, unicode)) + if isinstance(buff, unicode): + buff = buff.encode('utf-8') + return hashlib.md5(buff).hexdigest() + + +def to_sha1(buff): + assert isinstance(buff, (str, unicode)) + if isinstance(buff, unicode): + buff = buff.encode('utf-8') + return hashlib.sha1(buff).hexdigest() + +# 根据key计算出imei +def sum_IMEI(key): + hs = 53202347234687234 + for k in key: + hs += (hs << 5) + ord(k) + hs %= int(1e15) + if hs < int(1e14): + hs += int(1e14) + return str(int(hs)) + +# 根据key, 从 PHONE_MODEL_DATABASE 中取出手机型号 +def get_phone_model(key): + if len(PHONE_MODEL_DATABASE) <= 0: + return "S3" + hs = 2134 + for k in key: + hs += (hs << 4) + ord(k) + hs %= len(PHONE_MODEL_DATABASE) + return PHONE_MODEL_DATABASE[hs] + +def import_shadowsocks(): + try: + global encrypt + from shadowsocks import encrypt + except ImportError: + print s % (1, 93, ' !! you don\'t install shadowsocks for python2.') + print s % (1, 97, ' install shadowsocks:') + print s % (1, 92, ' pip2 install shadowsocks') + sys.exit(1) + +def get_abspath(pt): + if '~' == pt[0]: + path = os.path.expanduser(pt) + else: + path = os.path.abspath(pt) + if os.path.exists(path): + return path + else: + print s % (1, 91, ' !! path isn\'t existed.'), pt + return None + +def make_server_path(cwd, path): + def cd(cwd, part): + if part == '..': + cwd = os.path.dirname(cwd) + elif part == '.': + pass + elif part == '': + pass + elif part == '...': + cwd = os.path.dirname(cwd) + cwd = os.path.dirname(cwd) + else: + cwd = os.path.join(cwd, part) + return cwd + + if not path or path[0] == '/': + return path + else: + parts = path.split('/') + for p in parts: + cwd = cd(cwd, p) + return cwd + +def fast_pcs_server(j): + if 'fs' not in args.type_: + return j + + do = lambda dlink: \ + re.sub(r'://[^/]+?/', '://c.pcs.baidu.com/', dlink) + #re.sub(r'://[^/]+?/', '://c.pcs.baidu.com/', dlink) + + if isinstance(j, dict) and j.get('info') and len(j['info'])> 0: + for i in xrange(len(j['info'])): + if j['info'][i].get('dlink'): + j['info'][i]['dlink'] = do(j['info'][i]['dlink']) + else: + j = do(j) + return j + +def is_wenxintishi(dlink): + while True: + try: + res = ss.head(dlink) + break + except requests.exceptions.ConnectionError: + time.sleep(2) + location = res.headers.get('location', '') + if 'wenxintishi' in location: + return True + else: + return False + # https://stackoverflow.com/questions/1094841/reusable-library-to-get-human-readable-version-of-file-size def sizeof_fmt(num): for x in ['B','KB','MB','GB']: @@ -126,28 +589,63 @@ def print_process_bar(point, total, slice_size, speed = sizeof_fmt(slice_size / (now - start_time)) + '/s' t = int(nowpoint*length) - msg = '\r' + ' '.join([pre, '[%s%s]' % ('='*t, ' '*(length - t)), \ + msg = '\r' + ' '.join([pre, '|%s%s|' % ('='*t, ' '*(length - t)), \ str(percent) + '%', speed, msg, suf]) sys.stdout.write(msg) sys.stdout.flush() return now + +def is_cookie(cookie): + return 'BDUSS=' in cookie and 'PANPSC=' in cookie and len(cookie)> 150 + + +def parse_cookies(cookie): + cookies = {} + for c in cookie.split('; '): + k, v = c.split('=', 1) + cookies[k] = v + return cookies + + class panbaiducom_HOME(object): def __init__(self): self._download_do = self._play_do if args.play else self._download_do self.ondup = 'overwrite' - self.highlights = [] self.accounts = self._check_cookie_file() + self.dsign = None + self.timestamp = None + self.user_id = None - for tail in args.tails: - self.highlights.append({'text': tail.decode('utf8', 'ignore'), - 'is_regex': 0}) - for head in args.heads: - self.highlights.append({'text': head.decode('utf8', 'ignore'), - 'is_regex': 0}) - for include in args.includes: - self.highlights.append({'text': include.decode('utf8', 'ignore'), - 'is_regex': 1}) + self.highlights = [] + if any([args.tails, args.heads, args.includes]): + for tail in args.tails: + self.highlights.append({'text': tail.decode('utf8', 'ignore'), + 'is_regex': 0}) + for head in args.heads: + self.highlights.append({'text': head.decode('utf8', 'ignore'), + 'is_regex': 0}) + for include in args.includes: + self.highlights.append({'text': include.decode('utf8', 'ignore'), + 'is_regex': 1}) + + if 'ec' in args.type_ or 'dc' in args.type_ or args.comd == 'dc': + import_shadowsocks() + + def _request(self, method, url, action, **kwargs): + i = 0 + while i < 3: + i += 1 + response = ss.request(method, url, **kwargs) + if not (response.ok is True and response.status_code == 200): + continue + else: + return response + + self.save_cookies() + + print s % (1, 91, ' ! [{}] Server error'.format(action)) + sys.exit() @staticmethod def _check_cookie_file(): @@ -175,7 +673,7 @@ def correct_do(): if type(j[i]) != type({}): del j[i] else: - if not j[i].get('cookies') and not j[i].get('on'): + if not j[i].get('cookies'): del j[i] return j @@ -185,15 +683,26 @@ def init(self): j = self.accounts u = [u for u in j if j[u]['on']] if u: - ss.cookies.update(j[u[0]]['cookies']) + user = u[0] + self.user = user + self.cwd = j[user]['cwd'] if j[user].get('cwd') else '/' + self.user_id = j[user].get('user_id') + self.bduss = j[user]['cookies']['BDUSS'] + ss.cookies.update(j[user]['cookies']) else: print s % (1, 91, ' !! no account is online, please login or userchange') sys.exit(1) if not self.check_login(): - print s % (1, 91, ' !! cookie is invalid, please login\n'), u[0] + print s % (1, 91, ' !! cookie is invalid, please login.'), u[0] + del j[u[0]] + with open(cookie_file, 'w') as g: + pk.dump(j, g) sys.exit(1) - self.save_cookies(u[0], on=1) + + if not self.user_id: + info = self._user_info(self.bduss) + self.user_id = info['user']['id'] else: print s % (1, 97, ' no account, please login') sys.exit(1) @@ -202,96 +711,153 @@ def init(self): def save_img(url, ext): path = os.path.join(os.path.expanduser('~'), 'vcode.%s' % ext) with open(path, 'w') as g: - data = urllib.urlopen(url).read() + res = ss.get(url) + data = res.content g.write(data) print " ++ 验证码已保存至", s % (1, 97, path) input_code = raw_input(s % (2, 92, " 输入验证码: ")) return input_code def check_login(self): - #print s % (1, 97, '\n -- check_login') - url = 'http://pan.baidu.com/api/quota' - j = ss.get(url).json() - if j['errno'] != 0: + # html_string = self._request('GET', 'http://pan.baidu.com/', 'check_login').content + info = self._meta(['/']) + + if info and info['errno'] == 0: + return True + else: print s % (1, 91, ' -- check_login fail\n') return False - else: #print s % (1, 92, ' -- check_login success\n') #self.get_dsign() #self.save_cookies() - return True def login(self, username, password): print s % (1, 97, '\n -- login') + if is_cookie(password): + cookies = parse_cookies(password) + ss.cookies.update(cookies) + return + + # error_message: at _check_account_exception from + # https://github.com/ly0/baidupcsapi/blob/master/baidupcsapi/api.py + login_error_msg = { + '-1': '系统错误, 请稍后重试', + '1': '输入的帐号格式不正确', + '3': '验证码不存在或已过期,请重新输入', + '4': '输入的帐号或密码有误', + '5': '请重新登录', + '6': '验证码输入错误', + '16': '帐号因安全问题已被限制登录', + '257': '需要验证码', + '100005': '系统错误, 请稍后重试', + '120016': '未知错误 120016', + '120019': '近期登录次数过多, 请先通过 passport.baidu.com 解除锁定', + '120021': '登录失败,重新登录', + '500010': '登录过于频繁,请24小时后再试', + '400031': '账号异常', + '401007': '手机号关联了其他帐号,请选择登录' + } + + self._request('GET', 'http://www.baidu.com', 'login') + # Get token - token = self._get_bdstoken() + # token = self._get_bdstoken() + resp = self._request('GET', 'https://passport.baidu.com/v2/api/?getapi&tpl=netdisk' + '&apiver=v3&tt={}&class=login&logintype=basicLogin'.format(int(time.time())), + 'login') + + _json = json.loads(resp.content.replace('\'', '"')) + if _json['errInfo']['no'] != "0": + print s % (1, 91, ' ! Can\'t get token') + sys.exit(1) + + token = _json['data']['token'] + code_string = _json['data']['codeString'] # get publickey - url = 'https://passport.baidu.com/v2/getpublickey?token=%s' % token - r = ss.get(url) - j = json.loads(r.content.replace('\'', '"')) - pubkey = j['pubkey'] - key = rsa.PublicKey.load_pkcs1_openssl_pem(pubkey) - password_encoded = base64.b64encode(rsa.encrypt(password, key)) - rsakey = j['key'] + # url = ('https://passport.baidu.com/v2/getpublickey?&token={}' + # '&tpl=netdisk&apiver=v3&tt={}').format(token, int(time.time())) + # r = ss.get(url) + # j = json.loads(r.content.replace('\'', '"')) + # pubkey = j['pubkey'] + # key = rsa.PublicKey.load_pkcs1_openssl_pem(pubkey) + # password_encoded = base64.b64encode(rsa.encrypt(password, key)) + # rsakey = j['key'] # Construct post body - data = { - "staticpage": "http://www.baidu.com/cache/user/html/v3Jump.html", - "charset": "UTF-8", - "token": token, - "tpl": "pp", - "subpro": "", - "apiver": "v3", - "tt": int(time.time()), - "codestring": "", - "safeflg": "0", - "isPhone": "", - "quick_user": "0", - "logintype": "dialogLogin", - "logLoginType": "pc_loginDialog", - "idc": "", - "loginmerge": "true", - "splogin": "rate", - "username": username, - "password": password_encoded, - "verifycode": "", - "mem_pass": "on", - "rsakey": str(rsakey), - "crypttype": "12", - "ppui_logintime": "40228", - "callback": "parent.bd__pcbs__uvwly2", - } - + verifycode = '' while True: + data = { + "staticpage": "http://pan.baidu.com/res/static/thirdparty/pass_v3_jump.html", + "charset": "utf-8", + "token": token, + "tpl": "netdisk", + "subpro": "", + "apiver": "v3", + "tt": int(time.time()), + "codestring": code_string, + "safeflg": "0", + "u": "http://pan.baidu.com/", + "isPhone": "", + "quick_user": "0", + "logintype": "basicLogin", + "logLoginType": "pc_loginBasic", + "idc": "", + "loginmerge": "true", + "username": username, + "password": password, + "verifycode": verifycode, + "mem_pass": "on", + "rsakey": "", + "crypttype": "", + "ppui_logintime": "2602", + "callback": "parent.bd__pcbs__ahhlgk", + } + # Post! # XXX : do not handle errors url = 'https://passport.baidu.com/v2/api/?login' r = ss.post(url, data=data) # Callback for verify code if we need - #codestring = r.content[r.content.index('(')+1:r.content.index(')')] - if 'err_no=0' in r.content: + #code_string = r.content[r.content.index('(')+1:r.content.index(')')] + errno = re.search(r'err_no=(\d+)', r.content).group(1) + if ss.cookies.get('BDUSS'): + # ss.get("http://pan.baidu.com/disk/home") break - else: + elif errno in ('257', '3', '6'): + print s % (1, 91, ' ! Error %s:' % errno), \ + login_error_msg[errno] t = re.search('codeString=(.+?)&', r.content) - codestring = t.group(1) if t else "" - vcurl = 'https://passport.baidu.com/cgi-bin/genimage?'+codestring - verifycode = self.save_img(vcurl, 'gif') if codestring != "" else "" - data['codestring'] = codestring + code_string = t.group(1) if t else "" + vcurl = 'https://passport.baidu.com/cgi-bin/genimage?' + code_string + verifycode = self.save_img(vcurl, 'jpg') if code_string != "" else "" + data['codestring'] = code_string data['verifycode'] = verifycode #self.save_cookies() + else: + print s % (1, 91, ' ! Error %s:' % errno), \ + login_error_msg.get(errno, "unknow, please feedback to author") + sys.exit(1) - def save_cookies(self, username, on=0): - self.username = username + def save_cookies(self, username=None, on=0, tocwd=False): + if not username: username = self.user accounts = self.accounts - accounts[username] = {} - accounts[username]['cookies'] = ss.cookies.get_dict() + accounts[username] = accounts.get(username, {}) + accounts[username]['cookies'] = \ + accounts[username].get('cookies', ss.cookies.get_dict()) accounts[username]['on'] = on + accounts[username]['user_id'] = self.user_id quota = self._get_quota() capacity = '%s/%s' % (sizeof_fmt(quota['used']), sizeof_fmt(quota['total'])) accounts[username]['capacity'] = capacity + if hasattr(self, 'cwd'): + if not accounts[username].get('cwd'): + accounts[username]['cwd'] = '/' + if tocwd: accounts[username]['cwd'] = self.cwd + else: + accounts[username]['cwd'] = '/' for u in accounts: if u != username and on: accounts[u]['on'] = 0 @@ -302,8 +868,58 @@ def _get_bdstoken(self): if hasattr(self, 'bdstoken'): return self.bdstoken - self.bdstoken = md5.new(str(time.time())).hexdigest() - return self.bdstoken + resp = self._request('GET', 'http://pan.baidu.com/disk/home', + '_get_bdstoken') + + html_string = resp.content + + mod = re.search(r'bdstoken[\'":\s]+([0-9a-f]{32})', html_string) + if mod: + self.bdstoken = mod.group(1) + return self.bdstoken + else: + print s % (1, 91, ' ! Can\'t get bdstoken') + sys.exit(1) + + # self.bdstoken = md5.new(str(time.time())).hexdigest() + + def _user_info(self, bduss): + timestamp = str(int(time.time())) + model = get_phone_model(bduss) + phoneIMEIStr = sum_IMEI(bduss) + + data = { + 'bdusstoken': bduss + '|null', + 'channel_id': '', + 'channel_uid': '', + 'stErrorNums': '0', + 'subapp_type': 'mini', + 'timestamp': timestamp + '922', + } + data['_client_type'] = '2' + data['_client_version'] = '7.0.0.0' + data['_phone_imei'] = phoneIMEIStr + data['from'] = 'mini_ad_wandoujia' + data['model'] = model + data['cuid'] = to_md5( + bduss + '_' + data['_client_version'] + '_' + data['_phone_imei'] + '_' + data['from'] + ).upper() + '|' + phoneIMEIStr[::-1] + data['sign'] = to_md5( + ''.join([k + '=' + data[k] for k in sorted(data.keys())]) + 'tiebaclient!!!' + ).upper() + + headers = { + 'Content-Type': 'application/x-www-form-urlencoded', + 'Cookie': 'ka=open', + 'net': '1', + 'User-Agent': 'bdtb for Android 6.9.2.1', + 'client_logid': timestamp + '416', + 'Connection': 'Keep-Alive', + } + + resp = requests.post('http://tieba.baidu.com/c/s/login', headers=headers, data=data) + info = resp.json() + return info #def _sift(self, fileslist, name=None, size=None, time=None, head=None, tail=None, include=None, exclude=None): def _sift(self, fileslist, **arguments): @@ -313,44 +929,39 @@ def _sift(self, fileslist, **arguments): """ # for shuffle - if 's' in args.type_.split(','): + if 's' in args.type_: random.shuffle(fileslist) return fileslist - def sort(reverse, arg, fileslist=fileslist): - tdict = {fileslist[i][arg] : i for i in xrange(len(fileslist))} - keys = tdict.keys() - keys.sort(reverse=reverse) - indexs = [tdict[i] for i in keys] - fileslist = [fileslist[i] for i in indexs] - return fileslist - # for time - if arguments.get('name'): + elif arguments.get('name'): reverse = None if arguments['name'] == 'reverse': reverse = True elif arguments['name'] == 'no_reverse': reverse = False - fileslist = sort(reverse, 'server_filename') + fileslist = sorted(fileslist, key=lambda k: k['server_filename'], + reverse=reverse) # for size - if arguments.get('size'): + elif arguments.get('size'): reverse = None if arguments['size'] == 'reverse': reverse = True elif arguments['size'] == 'no_reverse': reverse = False - fileslist = sort(reverse, 'size') + fileslist = sorted(fileslist, key=lambda k: k['size'], + reverse=reverse) # for time - if arguments.get('time'): + elif arguments.get('time'): reverse = None if arguments['time'] == 'reverse': reverse = True elif arguments['time'] == 'no_reverse': reverse = False - fileslist = sort(reverse, 'server_mtime') + fileslist = sorted(fileslist, key=lambda k: k['server_mtime'], + reverse=reverse) # for head, tail, include, exclude heads = args.heads @@ -410,7 +1021,6 @@ def sort(reverse, arg, fileslist=fileslist): dirs = [i for i in fileslist if i['isdir']] t, tt = [], [] - args.type_ = args.type_ if args.type_ else '' if 'e' in args.type_: for i in dirs: d = i['path'].encode('utf8') @@ -419,16 +1029,16 @@ def sort(reverse, arg, fileslist=fileslist): t.append(i) else: tt.append(i) - if 'e' in args.type_.split(','): dirs = t - if 'ne' in args.type_.split(','): dirs = tt + if 'e' in args.type_: dirs = t + if 'ne' in args.type_: dirs = tt files = [i for i in fileslist if not i['isdir']] if arguments.get('desc') == 1: dirs.reverse() files.reverse() - if 'f' in args.type_.split(','): + if 'f' in args.type_: fileslist = files - elif 'd' in args.type_.split(','): + elif 'd' in args.type_: fileslist = dirs else: fileslist = dirs + files @@ -439,16 +1049,18 @@ def _get_path(self, url): t = re.search(r'path=(.+?)(&|$)', url) if t: t = t.group(1) + t = urllib.unquote_plus(t) + t = urllib.unquote_plus(t) + return t else: - t = '/' - t = urllib.unquote_plus(t) - t = urllib.unquote_plus(t) - return t + return url def _get_quota(self): url = 'http://pan.baidu.com/api/quota' - r = ss.get(url) - j = r.json() + + resp = self._request('GET', url, '_get_quota') + + j = resp.json() if j['errno'] != 0: print s % (1, 92, ' !! Error at _get_quota') sys.exit(1) @@ -472,36 +1084,41 @@ def _get_file_list(self, order, desc, dir_, num, all=True): "desc": 1, ## reversely "order": order, ## sort by name, or size, time "_": int(time.time()*1000), - #"bdstoken": self._get_bdstoken(), + # "bdstoken": self._get_bdstoken(), } if not desc: del p['desc'] url = 'http://pan.baidu.com/api/list' - infos = [] + path_list = [] while True: - r = ss.get(url, params=p, headers=theaders) + r = ss.get(url, params=p) j = r.json() if j['errno'] != 0: print s % (1, 91, ' error: _get_file_list'), '--', j sys.exit(1) else: - infos += j['list'] + path_ls = j['list'] + path_list += path_ls if not all: return j - if len(infos) == num: + if len(path_ls) == num: p['page'] += 1 else: - j['list'] = infos + j['list'] = path_list return j def _get_dsign(self): + # if self.dsign is not None: + # return None + url = 'http://pan.baidu.com/disk/home' - r = ss.get(url) + r = self._request('GET', url, '_get_dsign') html = r.content - sign1 = re.search(r'sign1 = \'(.+?)\';', html).group(1) - sign3 = re.search(r'sign3 = \'(.+?)\';', html).group(1) - timestamp = re.search(r'timestamp = \'(.+?)\';', html).group(1) + + sign1 = re.search(r'"sign1":"(.+?)"', html).group(1) + sign3 = re.search(r'"sign3":"(.+?)"', html).group(1) + timestamp = re.search(r'"timestamp":(\d+)', html).group(1) # following javascript code from http://pan.baidu.com/disk/home #yunData.sign2 = function s(j, r) { @@ -564,36 +1181,76 @@ def sign2(j, r): self.dsign = sign2(sign3, sign1) self.timestamp = timestamp - def _get_dlink(self, i): - if not hasattr(self, 'dsign'): - self._get_dsign() + def _get_dlink(self, path): + bduss = self.bduss + uid = self.user_id + + timestamp = str(int(time.time() * 1000)) + devuid = '0|' + to_md5(bduss).upper() + enc = to_sha1(bduss) + rand = to_sha1( + enc + str(uid) + 'ebrcUYiuxaZv2XGu7KIYKxUrqfnOfpDF' + str(timestamp) + devuid + ) + + url = ( + 'https://pcs.baidu.com/rest/2.0/pcs/file?app_id=' + args.appid \ + + '&method=locatedownload&ver=2' \ + + '&path=' + urllib.quote(path) + '&time=' \ + + timestamp + '&rand=' + rand + '&devuid=' + devuid + ) + + headers = dict(ss.headers) + headers['User-Agent'] = 'netdisk;2.2.51.6;netdisk;10.0.63;PC;android-android' + resp = self._request('GET', url, '_get_dlink', headers=headers) + info = resp.json() + if info.get('urls'): + dlink = info['urls'][0]['url'].encode('utf8') + return dlink + else: + print s % (1, 91, ' !! Error at _get_dlink, can\'t get dlink') + sys.exit(1) + + def _get_dlink4(self, path): + # use app_id: 778750 + # reference: [3个方法解决百度网盘限速](https://www.runningcheese.com/baiduyun) + dlink = ('http://c.pcs.baidu.com/rest/2.0/pcs/file?method=download' + '&app_id={}&path={}&ver=2.0&clienttype=1').format( + args.appid, urllib.quote(path)) + + dlink = fast_pcs_server(dlink) + return dlink + + def _get_dlink3(self, fs_id): while True: + dsign, timestamp = self._get_dsign() + params = { "channel": "chunlei", "clienttype": 0, + "app_id": "250528", "web": 1, - #"bdstoken": self._get_bdstoken() - } - - data = { + # "bdstoken": self._get_bdstoken(), "sign": self.dsign, "timestamp": self.timestamp, - "fidlist": "[%s]" % i['fs_id'], - "type": "dlink" + "fidlist": '[{}]'.format(fs_id), + "type": "dlink", } url = 'http://pan.baidu.com/api/download' - r = ss.post(url, params=params, data=data) + r = ss.get(url, params=params) j = r.json() + print(j) if j['errno'] == 0: dlink = j['dlink'][0]['dlink'].encode('utf8') - dlink = re.sub(r'prisign=.+?(&|$)', r'prisign=unknow1円', dlink) - dlink = dlink.replace('chkbd=0', 'chkbd=1') - dlink = dlink.replace('chkv=0', 'chkv=1') + # dlink = re.sub(r'prisign=.+?(&|$)', r'prisign=unknow1円', dlink) + # dlink = dlink.replace('chkbd=0', 'chkbd=1') + # dlink = dlink.replace('chkv=0', 'chkv=1') + dlink = fast_pcs_server(dlink) return dlink else: - self._get_dsign() + print s % (1, 91, ' !! Error at _get_dlink, can\'t get dlink') + continue def _get_dlink2(self, i): j = self._meta([i['path'].encode('utf8')], d @@ -614,20 +1271,21 @@ def _get_m3u8(self, info): url = "https://pcs.baidu.com/rest/2.0/pcs/file" - r = ss.get(url, params=p) + r = ss.get(url, params=p, verify=VERIFY) m3u8 = r.content - if 'baidupcs.com/video' in m3u8: - return m3u8 - else: + if '#EXTM3U' not in m3u8[:7]: return None + #m3u8 = fast_pcs_server(m3u8) + return m3u8 def download(self, paths): for path in paths: path = self._get_path(path) if path[0] != '/' else path + path = make_server_path(self.cwd, path) base_dir = '' if os.path.split(path)[0] == '/' \ else os.path.split(path)[0] - meta = self._meta([path], d + meta = self._meta([path], d if meta: if meta['info'][0]['isdir']: dir_loop = [path] @@ -640,15 +1298,21 @@ def download(self, paths): if i['isdir'] else None if args.play: - j['list'] = [i for i in j['list'] \ - if not i['isdir'] and os.path.splitext(i['server_filename'])[-1].lower() in mediatype] - if 's' in args.type_.split(','): - j['list'] = self._sift(j['list']) + j['list'] = [ + i for i in j['list'] \ + if not i['isdir'] \ + and os.path.splitext( + i['server_filename'] + )[-1].lower() in mediatype] + if 's' in args.type_: + j['list'] = self._sift(j['list']) - if args.heads or args.tails or args.includes or args.excludes: + if args.heads or args.tails or args.includes \ + or args.excludes: j['list'] = self._sift(j['list']) - total_file = len([i for i in j['list'] if not i['isdir']]) + total_file = len([i for i in j['list'] \ + if not i['isdir']]) if args.from_ - 1: j['list'] = j['list'][args.from_-1:] \ @@ -661,10 +1325,9 @@ def download(self, paths): t = i['path'].encode('utf8') t = t.replace(base_dir, '') t = t[1:] if t[0] == '/' else t - t = os.path.join(os.getcwd(), t) + t = os.path.join(args.outdir, t) - if not i.has_key('dlink'): - i['dlink'] = self._get_dlink2(i) + i['dlink'] = self._get_dlink(i['path'].encode('utf8')) infos = { 'file': t, @@ -672,7 +1335,7 @@ def download(self, paths): 'dir_': os.path.split(t)[0], 'dlink': i['dlink'].encode('utf8'), 'm3u8': self._get_m3u8(i) \ - if 'm3' in args.type_.split(',') else None, + if 'm3' in args.type_ else None, 'name': i['server_filename'].encode('utf8'), 'size': i['size'], 'nn': nn, @@ -680,23 +1343,30 @@ def download(self, paths): } nn += 1 self._download_do(infos) + if 'dc' in args.type_: + self.decrypt([infos['file']]) elif not meta['info'][0]['isdir']: t = os.path.join( - os.getcwd(), meta['info'][0]['server_filename'].encode('utf8') + args.outdir, meta['info'][0]['server_filename'].encode('utf8') ) infos = { 'file': t, 'path': meta['info'][0]['path'].encode('utf8'), 'dir_': os.path.split(t)[0], - #'dlink': self._get_dlink(meta['info'][0]), + 'dlink': self._get_dlink(meta['info'][0]['path'].encode('utf8')), 'm3u8': self._get_m3u8(meta['info'][0]) \ - if 'm3' in args.type_.split(',') else None, - 'dlink': meta['info'][0]['dlink'].encode('utf8'), + if 'm3' in args.type_ else None, + # 'dlink': meta['info'][0]['dlink'].encode('utf8'), 'name': meta['info'][0]['server_filename'].encode('utf8'), 'size': meta['info'][0]['size'], } + if args.play: + if not os.path.splitext(infos['name'])[-1].lower() in mediatype: + continue self._download_do(infos) + if 'dc' in args.type_: + self.decrypt([infos['file']]) else: print s % (1, 91, ' !! path is not existed.\n'), \ @@ -711,7 +1381,7 @@ def _download_do(infos): if os.path.exists(infos['file']): return - num = random.randint(0, 7) % 7 + num = random.randint(0, 7) % 8 col = sizeof_fmt(infos['size']) + ' # ' + s % (2, num + 90, infos['path']) \ if args.view else s % (2, num + 90, infos['name']) infos['nn'] = infos['nn'] if infos.get('nn') else 1 @@ -719,39 +1389,98 @@ def _download_do(infos): print '\n ++ download: #', s % (1, 97, infos['nn']), '/', \ s % (1, 97, infos['total_file']), '#', col - if args.aria2c: + if '8s' in args.type_ and is_wenxintishi(infos['dlink']): + print s % (1, 93, ' !! 百度8秒 !!') + return + + cookie = 'Cookie: ' + '; '.join([ + k + '=' + v for k, v in ss.cookies.get_dict().items()]) + + # Netdisk user agents: + # + # "netdisk;6.7.1.9;PC;PC-Windows;10.0.17763;WindowsBaiduYunGuanJia" + # "netdisk;5.3.1.3;PC;PC-Windows;5.1.2600;WindowsBaiduYunGuanJia" + # "netdisk;7.15.1;HUAWEI+G750-T01;android-android;4.2.2" + # "netdisk;4.4.0.6;PC;PC-Windows;6.2.9200;WindowsBaiduYunGuanJia" + # "netdisk;5.3.1.3;PC;PC-Windows;5.1.2600;WindowsBaiduYunGuanJia" + # + # 'LogStatistic' + + # Recently all downloading requests using above user-agents are limited by baidu + + # user_agent = headers['User-Agent'] + user_agent = 'netdisk;2.2.51.6;netdisk;10.0.63;PC;android-android' + + if args.aget_s: + quiet = ' --quiet=true' if args.quiet else '' + cmd = 'ag ' \ + '"%s" ' \ + '-o "%s.tmp" ' \ + '-H "User-Agent: %s" ' \ + '-H "Connection: Keep-Alive" ' \ + '-H "%s" ' \ + '-s %s -k %s' \ + % (infos['dlink'], infos['file'], user_agent, cookie, args.aget_s, args.aget_k) + elif args.aria2c: quiet = ' --quiet=true' if args.quiet else '' taria2c = ' -x %s -s %s' % (args.aria2c, args.aria2c) tlimit = ' --max-download-limit %s' % args.limit if args.limit else '' cmd = 'aria2c -c%s%s%s ' \ '-o "%s.tmp" -d "%s" ' \ - '--user-agent "netdisk;4.4.0.6;PC;PC-Windows;6.2.9200;WindowsBaiduYunGuanJia" ' \ - '--header "Referer:http://pan.baidu.com/disk/home" "%s"' \ - % (quiet, taria2c, tlimit, infos['name'], \ - infos['dir_'], infos['dlink']) + '--user-agent "%s" ' \ + '--header "Connection: Keep-Alive" ' \ + '--header "Accept-Encoding: gzip" ' \ + '--header "%s" ' \ + '"%s"' \ + % (quiet, taria2c, tlimit, infos['name'], + infos['dir_'], user_agent, + cookie, infos['dlink']) else: + if infos['size']>= 100 * OneM: + print '\x1b[1;91mWarning\x1b[0m: '\ + '\x1b[1;91m%s\x1b[0m\n\n' % "File size is large, please use aget or aria2 to download\naget: https://github.com/PeterDing/aget-rs\naria2: https://github.com/aria2/aria2" + quiet = ' -q' if args.quiet else '' tlimit = ' --limit-rate %s' % args.limit if args.limit else '' cmd = 'wget -c%s%s ' \ '-O "%s.tmp" ' \ - '--user-agent "netdisk;4.4.0.6;PC;PC-Windows;6.2.9200;WindowsBaiduYunGuanJia" ' \ - '--header "Referer:http://pan.baidu.com/disk/home" "%s"' \ - % (quiet, tlimit, infos['file'], infos['dlink']) + '--header "User-Agent: %s" ' \ + '--header "Connection: Keep-Alive" ' \ + '--header "Accept-Encoding: gzip" ' \ + '--header "%s" ' \ + '"%s"' \ + % (quiet, tlimit, infos['file'], + user_agent, cookie, infos['dlink']) status = os.system(cmd) - if status != 0: # other http-errors, such as 302. - wget_exit_status_info = wget_es[status] - print('\n\n ---### \x1b[1;91mERROR\x1b[0m ==> '\ - '\x1b[1;91m%d (%s)\x1b[0m ###--- \n\n' \ - % (status, wget_exit_status_info)) + exit = True + if 'ie' in args.type_: + if status == 2 and not args.aria2c: + pass + elif status == (7 << 8) and args.aria2c: + pass + else: + exit = False + + content_length_matched = False + saved_path = '%s.tmp' % infos['file'] + if os.path.exists(saved_path): + meta = os.stat(saved_path) + if meta.st_size == infos['size']: + content_length_matched = True + + if status != 0 or not content_length_matched: # other http-errors, such as 302. + #wget_exit_status_info = wget_es[status] + print('\n\n ---### \x1b[1;91mEXIT STATUS\x1b[0m ==> '\ + '\x1b[1;91m%d\x1b[0m ###--- \n\n' % status) print s % (1, 91, ' ===> '), cmd - sys.exit(1) + if exit: sys.exit(1) else: os.rename('%s.tmp' % infos['file'], infos['file']) @staticmethod def _play_do(infos): - num = random.randint(0, 7) % 7 + num = random.randint(0, 7) % 8 col = sizeof_fmt(infos['size']) \ + ' # ' \ + s % (2, num + 90, infos['path']) \ @@ -763,30 +1492,29 @@ def _play_do(infos): % (s % (1, 92, ' m3u8') if infos.get('m3u8') else ''), \ s % (1, 97, infos['nn']), '/', \ s % (1, 97, infos['total_file']), '#', col + if is_wenxintishi(infos['dlink']): + print s % (1, 93, ' !! 百度8秒 !!') + return - if not infos.get('m3u8'): - if os.path.splitext(infos['file'])[-1].lower() == '.wmv': - quiet = ' -really-quiet' if args.quiet else '' - cmd = 'mplayer%s -cache 10000 ' \ - '-http-header-fields "user-agent:netdisk;4.4.0.6;PC;PC-Windows;6.2.9200;WindowsBaiduYunGuanJia" ' \ - '-http-header-fields "Referer:http://pan.baidu.com/disk/home" "%s"' \ - % (quiet, infos['dlink']) - else: - quiet = ' --really-quiet' if args.quiet else '' - cmd = 'mpv%s --no-ytdl --cache 10000 --cache-default 10000 ' \ - '--http-header-fields "user-agent:netdisk;4.4.0.6;PC;PC-Windows;6.2.9200;WindowsBaiduYunGuanJia" ' \ - '--http-header-fields "Referer:http://pan.baidu.com/disk/home" "%s"' \ - % (quiet, infos['dlink']) - else: + if infos.get('m3u8'): with open('/tmp/tmp_pan.baidu.com.py.m3u8', 'w') as g: g.write(infos['m3u8']) - quiet = ' --really-quiet' if args.quiet else '' - cmd = 'mpv%s --cache 10000 --cache-default 10000 ' \ - '--http-header-fields "user-agent:netdisk;4.4.0.6;PC;PC-Windows;6.2.9200;WindowsBaiduYunGuanJia" ' \ - '--http-header-fields "Referer:http://pan.baidu.com/disk/home" "%s"' \ - % (quiet, '/tmp/tmp_pan.baidu.com.py.m3u8') + infos['dlink'] = '/tmp/tmp_pan.baidu.com.py.m3u8' - status = os.system(cmd) + cookie = 'Cookie: ' + '; '.join([ + k + '=' + v for k, v in ss.cookies.get_dict().items()]) + user_agent = 'User-Agent: ' + headers['User-Agent'] + quiet = ' --really-quiet' if args.quiet else '' + cmd = 'mpv%s --no-ytdl --http-header-fields="%s","%s" ' \ + % (quiet, user_agent, cookie) + + if infos.get('m3u8'): + # https://github.com/mpv-player/mpv/issues/6928#issuecomment-532198445 + cmd += ' --stream-lavf-o-append="protocol_whitelist=file,http,https,tcp,tls,crypto,hls,applehttp" ' + + cmd += "%s" % infos['dlink'] + + os.system(cmd) timeout = 1 ii, _, _ = select.select([sys.stdin], [], [], timeout) if ii: @@ -819,26 +1547,43 @@ def _make_dir(self, dir_): return ENoError def _meta(self, file_list, dlink=0): + p = { - "channel": "chunlei", - "app_id": "250528", + # "channel": "chunlei", + # "app_id": "250528", "method": "filemetas", "dlink": dlink, "blocks": 0, # 0 or 1 - #"bdstoken": self._get_bdstoken() + # "bdstoken": self._get_bdstoken() } - data = {'target': json.dumps(file_list)} + + # ss.get('http://pan.baidu.com/disk/home') url = 'http://pan.baidu.com/api/filemetas' + i = 0 + j = {} while True: - try: - r = ss.post(url, params=p, data=data) - j = r.json() - if j['errno'] == 0: - return j - else: - return False - except Exception: - time.sleep(1) + fl = file_list[i:i+100] + if fl: + data = {'target': json.dumps(fl)} + try: + r = self._request('POST', url, '_meta', params=p, data=data) + # r = ss.post(url, params=p, data=data) + js = r.json() + if js['errno'] == 0 and i == 0: + if dlink: + js = fast_pcs_server(js) + j = js + elif js['errno'] == 0: + if dlink: + js = fast_pcs_server(js) + j['info'].append(js['info']) + else: + return False + except Exception: + time.sleep(1) + else: + return j + i += 100 ################################################################ # for upload @@ -874,8 +1619,13 @@ def _rapidupload_file(self, lpath, rpath): "content-crc32" : content_crc32, "ondup" : self.ondup } + + # WARNING: here needs netdist user-agent + theaders = dict(ss.headers) + theaders['User-Agent'] = NETDISK_UA + url = 'https://c.pcs.baidu.com/rest/2.0/pcs/file' - r = ss.post(url, params=p, data=data, verify=VERIFY) + r = ss.post(url, params=p, data=data, verify=VERIFY, headers=theaders) if r.ok: return ENoError else: @@ -889,10 +1639,22 @@ def _upload_one_file(self, lpath, rpath): "app_id": "250528", "ondup": self.ondup, "dir": rpath, - "filename": os.path.basename(lpath), "BDUSS": ss.cookies['BDUSS'], } - files = {'file': ('file', open(lpath, 'rb'), '')} + + if self.toEncrypt: + fl = self._cipherer.encrypt(open(lpath, 'rb').read()) + file = b'__' + bytes(DefaultSliceSize) + b'__' + fl + file = cStringIO.StringIO(file) + if 'np' not in args.type_: + p['filename'] = 'encrypted_' + os.path.basename(lpath) + else: + p['filename'] = os.path.basename(lpath) + else: + file = open(lpath, 'rb') + p['filename'] = os.path.basename(lpath) + + files = {'file': ('file', file, '')} data = MultipartEncoder(files) theaders = headers theaders['Content-Type'] = data.content_type @@ -910,22 +1672,32 @@ def _combine_file(self, lpath, rpath): "method": "createsuperfile", "app_id": "250528", "ondup": self.ondup, - "path": os.path.join(rpath, os.path.basename(lpath)), "BDUSS": ss.cookies['BDUSS'], } + + if self.toEncrypt and 'np' not in args.type_: + p['path'] = os.path.join(rpath, 'encrypted_' + os.path.basename(lpath)) + else: + p['path'] = os.path.join(rpath, os.path.basename(lpath)) + data = { 'param': json.dumps( {'block_list': self.upload_datas[lpath]['slice_md5s']} ) } + + # WARNING: here needs netdist user-agent + theaders = dict(ss.headers) + theaders['User-Agent'] = NETDISK_UA + url = 'https://c.pcs.baidu.com/rest/2.0/pcs/file' - r = ss.post(url, params=p, data=data, verify=VERIFY) + r = ss.post(url, params=p, data=data, verify=VERIFY, headers=theaders) if r.ok: return ENoError else: return r.json() - def _upload_slice(self): + def _upload_slice(self, piece=0, slice=DefaultSliceSize): p = { "method": "upload", "app_id": "250528", @@ -933,11 +1705,21 @@ def _upload_slice(self): "BDUSS": ss.cookies['BDUSS'], } - file = cStringIO.StringIO(self.__slice_block) - files = {'file': ('file', file, '')} + if self.toEncrypt: + __slice_block = self._cipherer.encrypt(self.__slice_block) + if piece == 0: + __slice_block = b'__' + bytes(slice) + b'__' + __slice_block + else: + __slice_block = self.__slice_block + self.__slice_md5 = md5.new(__slice_block).hexdigest() + + fl = cStringIO.StringIO(__slice_block) + files = {'file': ('file', fl, '')} data = MultipartEncoder(files) - theaders = headers + theaders = dict(headers) theaders['Content-Type'] = data.content_type + theaders['User-Agent'] = NETDISK_UA + url = 'https://c.pcs.baidu.com/rest/2.0/pcs/file' r = ss.post(url, params=p, data=data, verify=VERIFY, headers=theaders) j = r.json() @@ -968,9 +1750,9 @@ def _get_pieces_slice(self): return pieces, slice - def _get_upload_function(self, rapidupload_is_fall=False): + def _get_upload_function(self, rapidupload_is_fail=False): if self.__current_file_size> MinRapidUploadFileSize: - if not rapidupload_is_fall: + if not rapidupload_is_fail and not self.toEncrypt: return '_rapidupload_file' else: if self.__current_file_size <= DefaultSliceSize: @@ -1008,8 +1790,17 @@ def _upload_file(self, lpath, rpath): 'remotepaths': set() } - if args.type_ and 'e' in args.type_: - path = os.path.join(rpath, os.path.basename(lpath)) + if self.toEncrypt: + self._init_cipherer(toencrypt=True) + self.upload_datas[lpath]['is_over'] = False + #self.upload_datas[lpath]['remotepaths'] = set() + self.upload_datas[lpath]['slice_md5s'] = [] + + if 'e' in args.type_: + if 'ec' in args.type_ and not 'np' in args.type_: + path = os.path.join(rpath, 'encrypted_' + os.path.basename(lpath)) + else: + path = os.path.join(rpath, os.path.basename(lpath)) meta = self._meta([path]) if meta: self.upload_datas[lpath]['is_over'] = True @@ -1021,7 +1812,7 @@ def _upload_file(self, lpath, rpath): self.upload_datas[lpath]['is_over'] = False pass - if args.uploadmode == 'o': + if args.mode == 'o': self.upload_datas[lpath]['is_over'] = False self.upload_datas[lpath]['slice_md5s'] = [] @@ -1029,7 +1820,7 @@ def _upload_file(self, lpath, rpath): if not self.upload_datas[lpath]['is_over']: m = self.upload_datas[lpath]['upload_function'] if m == '_upload_file_slices': - time.sleep(2) + #time.sleep(2) print ' |-- upload_function:', s % (1, 97, '_upload_file_slices') pieces, slice = self._get_pieces_slice() f = open(lpath, 'rb') @@ -1044,9 +1835,8 @@ def _upload_file(self, lpath, rpath): for piece in xrange(current_piece_point, pieces): self.__slice_block = f.read(slice) if self.__slice_block: - self.__slice_md5 = md5.new(self.__slice_block).hexdigest() while True: - result = self._upload_slice() + result = self._upload_slice(piece=piece, slice=slice) if result == ENoError: break else: @@ -1062,6 +1852,7 @@ def _upload_file(self, lpath, rpath): pre=' ', msg='%s/%s' % (str(piece+1), \ str(pieces)) ) + f.close() if result != ENoError: self.upload_datas[lpath]['slice_md5s'] = [] @@ -1072,16 +1863,17 @@ def _upload_file(self, lpath, rpath): if result == ENoError: self.upload_datas[lpath]['is_over'] = True self.upload_datas[lpath]['remotepaths'].update([rpath]) - del self.upload_datas[lpath]['slice_md5s'] + self.upload_datas[lpath]['slice_md5s'] = [] self.save_datas(upload_datas_path, self.upload_datas) print s % (1, 92, '\n |-- success.\n') break else: print s % (1, 91, '\n !! Error at _combine_file:'), result + break #sys.exit(1) elif m == '_upload_one_file': - time.sleep(2) + #time.sleep(2) result = self._upload_one_file(lpath, rpath) if result == ENoError: self.upload_datas[lpath]['is_over'] = True @@ -1095,7 +1887,7 @@ def _upload_file(self, lpath, rpath): #sys.exit(1) elif m == '_rapidupload_file': - time.sleep(2) + #time.sleep(2) result = self._rapidupload_file(lpath, rpath) if result == ENoError: self.upload_datas[lpath]['is_over'] = True @@ -1104,12 +1896,12 @@ def _upload_file(self, lpath, rpath): print s % (1, 92, ' |-- RapidUpload: Success.\n') break else: - if args.type_ and 'r' in args.type_.split(','): # only rapidupload + if 'r' in args.type_: # only rapidupload print s % (1, 91, ' |-- can\'t be RapidUploaded\n') break print s % (1, 93, ' |-- can\'t be RapidUploaded, ' \ 'now trying normal uploading.') - upload_function = self._get_upload_function(rapidupload_is_fall=True) + upload_function = self._get_upload_function(rapidupload_is_fail=True) self.upload_datas[lpath]['upload_function'] = upload_function if upload_function == '_upload_file_slices': if not self.upload_datas[lpath].has_key('slice_md5s'): @@ -1120,27 +1912,40 @@ def _upload_file(self, lpath, rpath): break else: - if args.uploadmode == 'c': + if args.mode == 'c': if rpath in self.upload_datas[lpath]['remotepaths']: print s % (1, 92, ' |-- file was uploaded.\n') break else: self.upload_datas[lpath]['is_over'] = False - elif args.uploadmode == 'o': + elif args.mode == 'o': print s % (1, 93, ' |-- reupload.') self.upload_datas[lpath]['is_over'] = False def _upload_dir(self, lpath, rpath): base_dir = os.path.split(lpath)[0] - for a, b, c in os.walk(lpath): - for path in c: - localpath = os.path.join(a, path) + for parent, directories, files in os.walk(lpath): + for file in files: + localpath = os.path.join(parent, file) t = localpath.replace(base_dir + '/', '') t = os.path.split(t)[0] remotepath = os.path.join(rpath, t) self._upload_file(localpath, remotepath) + if not args.recursive: break + + def _init_cipherer(self, toencrypt=False): + method = args.mode + if method not in CIPHERS: + method = 'aes-256-cfb' + if not args.passwd: + print s % (1, 91, ' !! missing Password.\n') + sys.exit(1) + + self._cipherer = encrypt.Encryptor(args.passwd, method) def upload(self, localpaths, remotepath): + remotepath = make_server_path(self.cwd, remotepath) + rpath = remotepath if remotepath[0] == '/' else '/' + remotepath self.upload_datas = {} if os.path.exists(upload_datas_path): f = open(upload_datas_path, 'rb') @@ -1148,28 +1953,27 @@ def upload(self, localpaths, remotepath): if upload_datas: self.upload_datas = upload_datas + # initiate Crypter + if 'ec' in args.type_: + self.toEncrypt = True + else: + self.toEncrypt = False + for localpath in localpaths: - lpath = localpath - if localpath[0] == '~': - lpath = os.path.expanduser(localpath) - else: - lpath = os.path.abspath(localpath) - rpath = remotepath if remotepath[0] == '/' else '/' + remotepath + lpath = get_abspath(localpath) - if os.path.exists(lpath): - pass - else: + if not lpath: print s % (1, 91, ' !! Error: localpath doesn\'t exist') - print s % (1, 91, ' ==>'), lpath + print s % (1, 91, ' ==>'), localpath continue - if os.path.isdir(lpath): + if os.path.isdir(lpath) and 'f' not in args.type_: self._upload_dir(lpath, rpath) - elif os.path.isfile(lpath): + elif os.path.isfile(lpath) and 'd' not in args.type_: self._upload_file(lpath, rpath) else: - print s % (1, 91, ' !! Error: localpath ?') - sys.exit(1) + #print s % (1, 91, ' !! Error: localpath ?'), localpath + pass self.save_datas(upload_datas_path, self.upload_datas) @@ -1181,50 +1985,47 @@ def save_datas(self, path, infos): ################################################################## # for saving shares - def _share_transfer(self, info): + def _share_transfer(self, surl, info): meta = self._meta([info['remotepath'].encode('utf8')]) if not meta: self._make_dir(info['remotepath'].encode('utf8')) if not info['isdir']: remote_file_path = '/'.join( - [info['remotepath'], os.path.split(info['path'])[-1]] - ) + [info['remotepath'], os.path.split(info['path'])[-1]] ) meta = self._meta([remote_file_path]) if meta: j = {'errno': 'file has exist'} return j - theaders = headers - theaders.update( - { - 'Referer': 'http://pan.baidu.com/share/link?shareid=%s&uk=%s' \ - % (self.shareid, self.uk) - } - ) - - p = { - "app_id": 250528, - "channel": "chunlei", - "clienttype": 0, - "web": 1, - "ondup": "overwrite", - "async": 1, - "from": self.uk, - "shareid": self.shareid, - "bdstoken": self._get_bdstoken() - } - data = "path=" \ - + urllib.quote_plus(info['remotepath'].encode('utf8')) \ - + '&' \ - + "filelist=" \ - + urllib.quote_plus( - '["%s"]' % info['path'].encode('utf8') + data = ('fsidlist=' \ + + urllib.quote_plus('[%s]' % info['fs_id']) \ + + '&path=' \ + + urllib.quote_plus(info['remotepath'].encode('utf8')) ) - url = 'http://pan.baidu.com/share/transfer' - r = ss.post(url, params=p, data=data, headers=theaders) + url = ('https://pan.baidu.com/share/transfer?' + 'shareid={}&from={}&bdstoken={}&channel=chunlei' + '&clienttype=0&web=1&app_id=250528'.format( + self.shareid, + self.uk, + self._get_bdstoken())) + + theaders = { + 'Cookie': '; '.join(['{}={}'.format(k, v) for k, v in ss.cookies.get_dict().items()]), + 'Origin': 'https://pan.baidu.com', + 'Accept-Encoding': 'gzip, deflate, br', + 'Accept-Language': 'zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4', + 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.143 Safari/537.36', + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + 'Accept': '*/*', + 'Referer': surl, + 'X-Requested-With': 'XMLHttpRequest', + 'Connection': 'keep-alive', + } + r = ss.post(url, data=data, headers=theaders) j = r.json() + #if j['errno'] == 0: #return ENoError #else: @@ -1264,6 +2065,7 @@ def _get_share_list(self, info): def _get_share_infos(self, url, remotepath, infos): r = ss.get(url) + ss.cookies.update(r.cookies.get_dict()) html = r.content info = panbaiducom.get_web_fileinfo(html, url) @@ -1271,26 +2073,28 @@ def _get_share_infos(self, url, remotepath, infos): self.shareid = info['shareid'] self.bdstoken = info['bdstoken'] - fileinfo = info['fileinfo'] - j = json.loads(fileinfo) + j = info['file_list']['list'] isdirs = [x['isdir'] for x in j] paths = [x['path'] for x in j] - z = zip(isdirs, paths) + fs_ids = [x['fs_id'] for x in j] + z = zip(fs_ids, isdirs, paths) if not infos: infos = [ { - 'isdir': x, - 'path': y, + 'fs_id': a, + 'isdir': b, + 'path': c, 'remotepath': remotepath \ if remotepath[-1] != '/' else remotepath[:-1] - } for x, y in z + } for a, b, c in z ] return infos def save_share(self, url, remotepath, infos=None): + remotepath = make_server_path(self.cwd, remotepath) infos = self._get_share_infos(url, remotepath, infos) - if args.type_ == 'c': + if 'c' in args.type_: save_share_datas = {} if os.path.exists(save_share_path): f = open(save_share_path, 'rb') @@ -1303,7 +2107,7 @@ def save_share(self, url, remotepath, infos=None): while True: print s % (1, 97, ' ++ transfer:'), info['path'] - result = self._share_transfer(info) + result = self._share_transfer(url, info) if result['errno'] == 0: break elif result['errno'] == 12 or result['errno'] == -33: @@ -1312,7 +2116,7 @@ def save_share(self, url, remotepath, infos=None): infos += self._get_share_list(info) break else: - print s % (1, 91, ' !! Error: can\'t transfer file') + print s % (1, 91, ' !! Error: can\'t transfer file'), result break elif result['errno'] == 'file has exist': print s % (1, 93, ' |-- file has exist.') @@ -1321,28 +2125,54 @@ def save_share(self, url, remotepath, infos=None): print s % (1, 91, ' !! Error at save_share, errno:'), result time.sleep(5) - if args.type_ == 'c': + if 'c' in args.type_: save_share_datas[url] = infos self.save_datas(save_share_path, save_share_datas) @staticmethod def _secret_or_not(url): - ss.headers['Referer'] = 'http://pan.baidu.com' - r = ss.get(url) + surl = url.split('?')[0].split('/1')[1].strip('/') + + ss.headers['Referer'] = 'https://pan.baidu.com' + r = ss.get(url, headers=headers) + + if r.status_code != 200 and r.status_code != 302: + ss.headers['Cookie'] = ';'.join(['{}={}'.format(k, v) for k, v in ss.cookies.get_dict().items()]) + r = ss.get(url, headers=headers, cookies=r.cookies) + if 'init' in r.url: if not args.secret: secret = raw_input(s % (2, 92, " 请输入提取密码: ")) else: secret = args.secret - data = 'pwd=%s' % secret - url = "%s&t=%d" % ( - r.url.replace('init', 'verify'), \ - int(time.time()) + + data = 'pwd=%s&vcode=&vcode_str=' % secret + url = ( + 'https://pan.baidu.com/share/verify?' + + 'surl=' + surl + + '&t=' + str(int(time.time()*1000)) + + '&channel=chunlei' + + '&web=1' + + '&app_id=250528' + + '&bdstoken=null' + + '&clienttype=0' ) - r = ss.post(url, data=data) + theaders = { + 'Accept-Encoding': 'gzip, deflate', + 'Accept-Language': 'zh-CN,zh;q=0.8,en;q=0.6,zh-TW;q=0.4', + 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.71 Safari/537.36', + 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', + 'Accept': '*/*', + 'X-Requested-With': 'XMLHttpRequest', + 'Connection': 'keep-alive', + 'Sec-Fetch-Mode': 'cors', + 'Referer': 'https://pan.baidu.com/share/init?surl=' + surl + } + r = ss.post(url, data=data, headers=theaders) if r.json()['errno']: - print s % (2, 91, " !! 提取密码错误\n") + print s % (2, 91, " !! 提取密码错误, %s\n" % r.text) sys.exit(1) + ss.cookies.update(r.cookies.get_dict()) ####################################################################### # for saveing inbox shares @@ -1444,6 +2274,7 @@ def _get_share_inbox_infos(self, url, remotepath, infos): def save_inbox_share(self, url, remotepath, infos=None): ss.headers['Referer'] = 'http://pan.baidu.com' + remotepath = make_server_path(self.cwd, remotepath) remotepath = remotepath if remotepath[-1] != '/' else remotepath[:-1] infos = self._get_share_inbox_infos(url, remotepath, infos) for info in infos: @@ -1472,19 +2303,17 @@ def save_inbox_share(self, url, remotepath, infos=None): ####################################################################### # for finding files - def _search(self, keyword, directory): + def _search(self, keyword, directory, page=1, num=1000): + p = { - "channel": "chunlei", - "clienttype": 0, - "web": 1, - "dir": directory if directory else "", - "key": keyword, - #"timeStamp": "0.15937364846467972", - #"bdstoken": self._get_bdstoken(), + 'recursion': '', + 'key': keyword, + 'dir': directory, } + if args.recursive: p['recursion'] = 1 url = 'http://pan.baidu.com/api/search' - r = ss.get(url, params=p) + r = self._request('GET', url, '_search', params=p) j = r.json() if j['errno'] == 0: return j['list'] @@ -1550,19 +2379,18 @@ def _highlight_string_zones(self, string, highlights, highlight_color, \ return t def _find_display(self, info): - if 'f' in args.type_.split(','): + if 'f' in args.type_: if info['isdir']: return - elif 'd' in args.type_.split(','): + elif 'd' in args.type_: if not info['isdir']: return - else: - pass + path = info['path'] if args.ls_color == 'on': isdir = s % (1, 93, 'd') if info['isdir'] else s % (1, 97, '-') size = s % (1, 91, sizeof_fmt(info['size']).rjust(8)) - base_dir, filename = os.path.split(info['path']) + base_dir, filename = os.path.split(path) base_dir = s % (2, 95, base_dir.encode('utf8')) \ if base_dir != '/' else '/' @@ -1575,14 +2403,20 @@ def _find_display(self, info): filename, self.highlights, 93 ) - path = os.path.join(base_dir, filename) + if args.view: + path = os.path.join(base_dir, filename) + else: + path = filename elif args.ls_color == 'off': isdir = 'd' if info['isdir'] else '-' size = sizeof_fmt(info['size']).rjust(8) - path = info['path'].encode('utf8') + if args.view: + path = path.encode('utf8') + else: + path = os.path.basename(path).encode('utf8') - if args.view and info.get('md5'): + if args.view> 1 and info.get('md5'): smd5 = info['md5'].encode('utf8') template = '%s %s %s %s %s' % ( isdir, size, info['size'], smd5, path @@ -1600,9 +2434,7 @@ def do(): kw = keyword.decode('utf8', 'ignore') self.highlights.append({'text': kw, 'is_regex': 0}) infos = {i['fs_id']: i for i in infos}.values() - infos = self._sift(infos, name=arguments.get('name'), \ - size=arguments.get('size'), time=arguments.get('time'), \ - desc=arguments.get('desc')) + infos = self._sift(infos, **arguments) if not infos: return if not arguments.get('pipe'): @@ -1637,6 +2469,7 @@ def warn(comd, display=True): if not warn('play', display=True): return paths = [i['path'].encode('utf8') for i in infos] self._download_do = self._play_do + args.play = True self.download(paths) elif comd == 'rnr' or comd == 'rnre': if len(pipe) < 3: @@ -1658,11 +2491,12 @@ def warn(comd, display=True): if not warn('move', display=True): return paths = [i['path'].encode('utf8') for i in infos] remotepath = pipe[1] - self.move(paths, remotepath) + self.move(paths, remotepath, check=False) else: print s % (1, 91, ' !! command is supported by download, play, rnre, rm, mv') - if 'all' in args.type_.split(','): + if not args.view: args.view = 1 + if 'all' in args.type_: for user in self.accounts: cookie = self.accounts[user]['cookies'] ss.cookies.clear() @@ -1676,16 +2510,17 @@ def warn(comd, display=True): print '\n' + user + ':' do() - self.save_cookies(user, on=self.accounts[user]['on']) + #self.save_cookies(user, on=self.accounts[user]['on']) else: do() + sys.exit() ############################################################## # for ls def _ls_display(self, infos, dir_=None): if dir_: - print dir_ + ':' + print (dir_ + ':').encode('utf8') for info in infos: self._find_display(info) print '' @@ -1704,7 +2539,7 @@ def _ls_directory(self, order, desc, path): if args.heads or args.tails or args.includes or args.excludes \ or args.type_: tinfos = self._sift(infos) - if args.type_ != 'du': + if 'du' not in args.type_: self._ls_display(tinfos, dir_) else: sum_size += sum([i['size'] for i in tinfos]) @@ -1713,18 +2548,20 @@ def _ls_directory(self, order, desc, path): directorys[y:y] = subdirs y += 1 - if args.type_ == 'du': print 'd', s % ( - 1, 91, sizeof_fmt(sum_size) - ), sum_size, directorys[0] + if 'du' in args.type_: + print 'd', s % ( 1, 91, sizeof_fmt(sum_size)), \ + sum_size, directorys[0] def ls(self, order, desc, paths): + if not paths: paths = [self.cwd] for path in paths: + path = make_server_path(self.cwd, path) meta = self._meta([path]) if meta: if meta['info'][0]['isdir']: self._ls_directory(order, desc, path) else: - if args.type_ == 'du': args.view = True + if 'du' in args.type_: args.view = True self._ls_display(meta['info'][0]) else: print s % (1, 91, ' !! path is not existed.\n'), \ @@ -1736,7 +2573,8 @@ def ls(self, order, desc, paths): def _exist(self, list_): meta = self._meta(list_) if not meta: - print s % (1, 91, ' !! Error at _exist, some paths are not existed.') + print s % (1, 91, ' !! Error at _exist, some paths are not existed.'), \ + list_ if len(list_) <= 10 else '' sys.exit(1) def _filemanager(self, opera, data): @@ -1744,6 +2582,7 @@ def _filemanager(self, opera, data): "channel": "chunlei", "clienttype": 0, "web": 1, + "async": "2", "opera": opera, "bdstoken": self._get_bdstoken(), } @@ -1755,10 +2594,12 @@ def _filemanager(self, opera, data): elif j['errno'] == 12: print s % (1, 91, ' !! Error at filemanager:'), "部分文件已存在于目标文件夹中" else: - print s % (1, 91, ' !! Error at filemanager') + print s % (1, 91, ' !! Error at filemanager'), j - def move(self, paths, remotepath): - self._exist(paths) + def move(self, paths, remotepath, check=True): + paths = [ make_server_path(self.cwd, path) for path in paths ] + remotepath = make_server_path(self.cwd, remotepath) + if check: self._exist(paths) meta = self._meta([remotepath]) if not meta: @@ -1779,6 +2620,8 @@ def move(self, paths, remotepath): self._filemanager('move', data) def copy(self, paths, remotepath): + paths = [ make_server_path(self.cwd, path) for path in paths ] + remotepath = make_server_path(self.cwd, remotepath) self._exist(paths) t = None @@ -1827,12 +2670,15 @@ def copy(self, paths, remotepath): self._filemanager('copy', data) def remove(self, paths): + paths = [ make_server_path(self.cwd, path) for path in paths ] self._exist(paths) data = 'filelist=' + urllib.quote_plus(json.dumps(paths)) self._filemanager('delete', data) def rename(self, path, remotepath): + path = make_server_path(self.cwd, path) + remotepath = make_server_path(self.cwd, remotepath) self._exist([path]) meta = self._meta([remotepath]) @@ -1841,7 +2687,7 @@ def rename(self, path, remotepath): s % (1, 91, 'is existed.') sys.exit(1) - base_dir = os.path.split(remotepath)[0] + base_dir, newname = os.path.split(remotepath) meta = self._meta([base_dir]) if not meta: self._make_dir(base_dir) @@ -1853,7 +2699,7 @@ def rename(self, path, remotepath): t = [{ 'path': path, 'dest': base_dir, - 'newname': os.path.basename(remotepath) + 'newname': newname }] data = 'filelist=' + urllib.quote_plus(json.dumps(t)) self._filemanager('move', data) @@ -1871,7 +2717,7 @@ def _rnre_do(self, foo, bar, infos): if args.recursive and info['isdir']: continue base_dir, old_filename = os.path.split(info['path']) - if 'bd64' in args.type_.split(','): + if 'bd64' in args.type_: told_filename, ext = os.path.splitext(old_filename) if not told_filename.endswith('.base64'): continue codestr = told_filename[:-7] @@ -1931,9 +2777,9 @@ def _rnre_do(self, foo, bar, infos): print s % (1, 92, ' ++ aborted.') def _rmcre_do(self, type, infos, todir=None): - if 'd' in args.type_.split(','): + if 'd' in args.type_: infos = [i for i in infos if i['isdir']] - if 'f' in args.type_.split(','): + if 'f' in args.type_: infos = [i for i in infos if not i['isdir']] if not infos: return @@ -1965,7 +2811,7 @@ def _rmcre_do(self, type, infos, todir=None): if type == 'remove': self.remove(paths) if type == 'move': - self.move(paths, todir) + self.move(paths, todir, check=False) elif type == 'copy': self.copy(paths, todir) else: @@ -1974,6 +2820,7 @@ def _rmcre_do(self, type, infos, todir=None): def filemanager_re(self, type, dirs, todir=None, foo=None, bar=None): tinfos = [] for path in dirs: + path = make_server_path(self.cwd, path) meta = self._meta([path]) if meta: if meta['info'][0]['isdir']: @@ -2000,6 +2847,7 @@ def filemanager_re(self, type, dirs, todir=None, foo=None, bar=None): if type == 'rename': self._rnre_do(foo, bar, tinfos) else: + todir = make_server_path(self.cwd, todir) self._rmcre_do(type, tinfos, todir=todir) ############################################################## @@ -2019,7 +2867,7 @@ def _get_torrent_info(self, path): } url = 'http://pan.baidu.com/rest/2.0/services/cloud_dl' - r = ss.post(url, params=p) + r = ss.get(url, params=p) j = r.json() if j.get('error_code'): print s % (1, 91, ' !! Error at _get_torrent_info:'), j['error_msg'] @@ -2052,7 +2900,7 @@ def _get_magnet_info(self, url): return j['magnet_info'], '' def _get_selected_idx(self, infos): - types = args.type_.split(',') + types = args.type_ if not args.type_: return [] #if 'a' in types: return [str(i+1) for i in xrange(len(infos))] if 'a' in types: return [] @@ -2178,6 +3026,7 @@ def _add_task(self, url, remotepath): return def add_tasks(self, urls, remotepath): + remotepath = make_server_path(self.cwd, remotepath) + '/' for url in urls: if url.startswith('magnet:') or url.startswith('/'): if url.startswith('/'): @@ -2217,26 +3066,28 @@ def add_tasks(self, urls, remotepath): } def _task_display(self, infos): + cross_line = '—' * int(os.popen('tput cols').read()) template = '%s %s\n' \ '%s %s\n' \ '%s %s\n' \ '%s %s\n' \ '%s %s\n' \ '%s %s\n' \ - '------------------------------\n' \ - % (s % (2, 97, ' id:'), s % (1, 97, "%s"), \ - s % (1, 97, ' status:'), s % (2, "%s", "%s"), \ - s % (1, 97, ' done:'), s % (3, 93, "%s"), \ - s % (2, 97, ' name:'), "%s", \ - s % (2, 97, ' path:'), "%s", \ - s % (2, 97, ' source:'), "%s") + '%s\n' \ + % (s % (2, 97, ' id:'), s % (1, 97, "%s"), \ + s % (1, 97, 'status:'), s % (1, "%s", "%s"), \ + s % (1, 97, ' done:'), s % (2, 93, "%s"), \ + s % (2, 97, ' name:'), "%s", \ + s % (2, 97, ' path:'), "%s", \ + s % (2, 97, 'source:'), "%s", cross_line) for i in infos: if i['result'] == 0: status_color = 92 if i['status'] == '0' else 91 print template % ( i['id'].encode('utf8'), - status_color, self.jobstatus[i['status'].encode('utf8')], + status_color, + self.jobstatus.get(i['status'].encode('utf8'), '未知'), i['done'], i['name'].encode('utf8'), i['path'].encode('utf8'), @@ -2315,7 +3166,7 @@ def _list_task(self): "clienttype": 0, "channel": "chunlei", "method": "list_task", - "need_task_info": 0, + "need_task_info": 1, "status": 255, "start": 0, "limit": 1000, @@ -2325,7 +3176,7 @@ def _list_task(self): url = 'http://pan.baidu.com/rest/2.0/services/cloud_dl' r = ss.get(url, params=p) j = r.json() - if j.get('errno'): + if j.get('error_code'): print s % (1, 91, ' !! Error at _query_task:'), j sys.exit(1) @@ -2394,6 +3245,7 @@ def jobclearall(self): def mkdir(self, paths): for path in paths: + path = make_server_path(self.cwd, path) print s % (1, 97, ' ++ mkdir:'), path meta = self._meta([path]) if not meta: @@ -2403,10 +3255,143 @@ def mkdir(self, paths): else: print s % (1, 91, ' !! Error: file exists.'), path + + ############################################################ + # for share + def _share(self, paths, pwd=None): + """ + 创建一个文件的分享链接 + :param fs_ids: 要分享的文件fid列表 + :type fs_ids: list + :param pwd: 分享密码,没有则没有密码 + :type pwd: str + :return: requests.Response 对象 + .. note:: + 返回正确 + { + "errno": 0, + "request_id": 请求识别号, + "shareid": 分享识别号, + "link": "分享地址", + "shorturl": "段网址", + "ctime": 创建时间, + "premis": false + } + """ + meta = self._meta(paths) + fs_ids = [i['fs_id'] for i in meta['info']] + + params = { + 'app_id': 250528, + 'channel': 'chunlei', + 'clienttype': 0, + 'web': 1, + 'bdstoken': self._get_bdstoken(), + } + + if pwd: + data = { + 'fid_list': json.dumps(fs_ids), + 'schannel': 4, + 'channel_list': '[]', + 'pwd': pwd, + } + else: + data = { + 'fid_list': json.dumps(fs_ids), + 'schannel': 0, + 'channel_list': '[]' + } + + url = 'http://pan.baidu.com/share/set' + r = ss.post(url, params=params, data=data) + j = r.json() + + if not j.get('shorturl'): + print s % (1, 91, ' !! Error at _share'), j + sys.exit(1) + else: + print '\n 链接地址:', s % (1, 92, j.get('shorturl').encode('utf8')) + if pwd: print ' 密码:', s % (1, 91, pwd) + #if 0 == meta['info'][0]['isdir']: + #print 'MD5:%s' % (meta['info'][0]['md5']) + return ENoError + + def share(self, paths, pwd): + paths = [ make_server_path(self.cwd, path) for path in paths ] + self._share(paths, pwd) + + def decrypt(self, paths): + def init_decrypted_file(path): + open(path, 'w').close() + + def store(path, decrypted_block): + with open(path, 'ab') as g: + g.write(decrypted_block) + + def do(file): + init_decrypted_file(file + '.decrypt') + self._init_cipherer() + encrypted_file = open(file, 'rb') + block = encrypted_file.read(100) + encrypted_file.seek(0) + head = re.search(r'^__\d+__', block) + if not head: + print s % (1, 91, ' |-- file isn\'t encrypted.'), file + return + head = head.group() + head_len = len(head) + slice_size = int(re.search(r'__(\d+)__', head).group(1)) + piece = 0 + while True: + if piece == 0: + block = encrypted_file.read(head_len + slice_size) + block = block[head_len:] + piece = 1 + else: + block = encrypted_file.read(slice_size) + if block: + decrypted_block = self._cipherer.decrypt(block) + store(file + '.decrypt', decrypted_block) + else: + break + + if 'no' not in args.type_: # no overwrite + os.rename(file + '.decrypt', file) + + for pt in paths: + path = get_abspath(pt) + if not path: continue + + if os.path.isdir(path): + for parent, directories, files in os.walk(path): + for file in files: + do(os.path.join(parent, file)) + if not args.recursive: break + elif os.path.isfile(path): + do(path) + + def change_directory(self, path): + def cd_do(path): + meta = self._meta([path]) + if meta: + if meta['info'][0]['isdir']: + self.cwd = path + else: + self.cwd = os.path.dirname(path) + else: + print s % (1, 93, ' !! path isn\'t existed.'), path + + if not path: + self.cwd = '/' + else: + path = path[0] + path = make_server_path(self.cwd, path) + cd_do(path) + class panbaiducom(object): @staticmethod def get_web_fileinfo(cm, url): - info = {} if 'shareview' in url: info['uk'] = re.search(r'uk="(\d+)"', cm).group(1) info['shareid'] = re.search(r'shareid="(\d+)"', cm).group(1) @@ -2415,16 +3400,12 @@ def get_web_fileinfo(cm, url): t = t.replace('\\\\', '!@#$%^'*10) t = t.replace('\\', '') t = t.replace('!@#$%^'*10, '\\') - info['fileinfo'] = t + info['fileinfo'] = t info['timestamp'] = re.search(r'timestamp="(\d+)"', cm).group(1) - info['sign'] = re.search(r'downloadsign="(.+?)"', cm).group(1) + info['sign'] = re.search(r'downloadsign="(.+?)"', cm).group(1) else: - info['uk'] = re.search(r'yunData\.MYUK = "(\d+)"', cm).group(1) - info['shareid'] = re.search(r'yunData\.SHARE_ID = "(\d+)"', cm).group(1) - info['bdstoken'] = re.search(r'yunData\.MYBDSTOKEN = "(.*?)"', cm).group(1) - info['fileinfo'] = re.search(r'yunData.FILEINFO = (.+)', cm).group(1)[:-2] - info['timestamp'] = re.search(r'yunData.TIMESTAMP = "(.+?)"', cm).group(1) - info['sign'] = re.search(r'yunData.SIGN = "(.+?)"', cm).group(1) + info_str = re.search(r'yunData.setData\((.+?)\);', cm).group(1) + info = json.loads(info_str) return info @@ -2433,55 +3414,86 @@ def get_params(self, path): html = r.content info = self.get_web_fileinfo(html, path) - uk = info['uk'] - shareid = info['shareid'] - timestamp = info['timestamp'] - sign = info['sign'] + self.uk = str(info['uk']) + self.shareid = str(info['shareid']) + self.timestamp = str(info['timestamp']) + self.sign = info['sign'] + self.bdstoken = info['bdstoken'] self.params = { - #"bdstoken": bdstoken, - "uk": uk, - "shareid": shareid, - "timestamp": timestamp, - "sign": sign, - "channel": "chunlei", - "clienttype": 0, - "web": 1, + "bdstoken": self.bdstoken, + "uk": self.uk, + "shareid": self.shareid, + "timestamp": self.timestamp, + "sign": self.sign, "channel": "chunlei", "clienttype": 0, "web": 1 } - fileinfo = info['fileinfo'] - j = json.loads(fileinfo) + j = info['file_list']['list'] self.infos.update({ 'name': j[0]['server_filename'].encode('utf8'), 'file': os.path.join( - os.getcwd(), j[0]['server_filename'].encode('utf8') + args.outdir, j[0]['server_filename'].encode('utf8') ), - 'dir_': os.getcwd(), + 'dir_': args.outdir, 'fs_id': j[0]['fs_id'] }) + def get_vcode(self): + url = ( + 'https://pan.baidu.com/api/getvcode' + '?prod=pan' + '&t={}' + '&channel=chunlei' + '&web=1' + '&app_id=250528' + '&bdstoken={}' + ).format(random.random(), self.bdstoken) + + r = ss.get(url) + j = r.json() + return j + def get_infos(self): - url = 'http://pan.baidu.com/share/download' - data = 'fid_list=["%s"]' % self.infos['fs_id'] + url = ('https://pan.baidu.com/api/sharedownload?' + 'sign={}×tamp={}&bdstoken={}' + '&channel=chunlei&clienttype=0&web=1').format( + self.sign, self.timestamp, self.bdstoken) + + data = { + 'encrypt': '0', + 'product': 'share', + 'uk': self.uk, + 'primaryid': self.shareid, + 'fid_list': urllib.quote_plus('[%s]' % self.infos['fs_id']), + 'path_list': '', + 'vip': '0', + } while True: - r = ss.post(url, data=data, params=self.params) + data_str = '&'.join(['{}={}'.format(k, v) for k, v in data.items()]) + r = ss.post(url, data=data_str) j = r.json() - if not j['errno']: - self.infos['dlink'] = j['dlink'].encode('utf8') + errno = j['errno'] + if errno == 0: + dlink = fast_pcs_server(j['list'][0]['dlink'].encode('utf8')) + self.infos['dlink'] = dlink if args.play: panbaiducom_HOME._play_do(self.infos) else: panbaiducom_HOME._download_do(self.infos) break + elif errno == 118: + print s % (1, 91, ' !! 没有下载权限!, 请转存网盘后,从网盘地址下载') + sys.exit(1) else: + j = self.get_vcode() vcode = j['vcode'] input_code = panbaiducom_HOME.save_img(j['img'], 'jpg') - self.params.update({'input': input_code, 'vcode': vcode}) + data.update({'vcode_input': input_code, 'vcode_str': vcode}) def get_infos2(self, path): while True: @@ -2492,9 +3504,9 @@ def get_infos2(self, path): if dlink: self.infos = { 'name': name, - 'file': os.path.join(os.getcwd(), name), - 'dir_': os.getcwd(), - 'dlink': dlink.group(1) + 'file': os.path.join(args.outdir, name), + 'dir_': args.outdir, + 'dlink': fast_pcs_server(dlink.group(1)) } if args.play: panbaiducom_HOME._play_do(self.infos) @@ -2502,7 +3514,7 @@ def get_infos2(self, path): panbaiducom_HOME._download_do(self.infos) break else: - print s % (1, ' !! Error at get_infos2, can\'t get dlink') + print s % (1, 91, ' !! Error at get_infos2, can\'t get dlink') def do(self, paths): for path in paths: @@ -2523,9 +3535,9 @@ def do4(self, paths): name = urllib.unquote_plus(t) self.infos = { 'name': name, - 'file': os.path.join(os.getcwd(), name), - 'dir_': os.getcwd(), - 'dlink': path + 'file': os.path.join(args.outdir, name), + 'dir_': args.outdir, + 'dlink': fast_pcs_server(path) } if args.play: @@ -2534,6 +3546,11 @@ def do4(self, paths): panbaiducom_HOME._download_do(self.infos) break +def assert_download_tools(): + for tool in ('wget', 'aget', 'aria2c'): + if ' ' in os.popen('which %s' % tool).read(): + print s % (1, 91, ' !!! aria2 is not installed') + def sighandler(signum, frame): print s % (1, 91, " !! Signal:"), signum if args.comd in ('u', 'upload'): @@ -2542,11 +3559,11 @@ def sighandler(signum, frame): #print s % (1, 91, " !! Frame: %s" % frame) sys.exit(1) -def main(argv): +def handle_signal(): signal.signal(signal.SIGBUS, sighandler) signal.signal(signal.SIGHUP, sighandler) - # https://stackoverflow.com/questions/108183/how-to-prevent-sigpipes-or-handle-them-properly - signal.signal(signal.SIGPIPE, signal.SIG_IGN) + # http://stackoverflow.com/questions/14207708/ioerror-errno-32-broken-pipe-python + signal.signal(signal.SIGPIPE, signal.SIG_DFL) signal.signal(signal.SIGQUIT, sighandler) signal.signal(signal.SIGSYS, sighandler) @@ -2557,20 +3574,23 @@ def main(argv): signal.signal(signal.SIGSEGV, sighandler) signal.signal(signal.SIGTERM, sighandler) - usage = "usage: https://github.com/PeterDing/iScript#pan.baidu.com.py" - if len(argv) <= 1: - print usage - sys.exit() - - ###################################################### +def handle_args(argv): # for argparse p = argparse.ArgumentParser(description='about pan.baidu.com.' \ ' 用法见 https://github.com/PeterDing/iScript') p.add_argument('xxx', type=str, nargs='*', help='命令对象.') p.add_argument('-a', '--aria2c', action='store', default=None, \ - type=int, help='aria2c分段下载数量') + type=int, help='aria2c 分段下载数量') + p.add_argument('-g', '--aget_s', action='store', default=None, \ + type=int, help='aget 分段下载数量') + p.add_argument('-k', '--aget_k', action='store', default='200K', \ + type=str, help='aget 分段大小') + p.add_argument('--appid', action='store', default='778750', type=str, \ + help='设置 app-id. 如果无法下载或下载慢, 尝试设置为 778750') + p.add_argument('-o', '--outdir', action='store', default=os.getcwd(), \ + type=str, help='保存目录') p.add_argument('-p', '--play', action='store_true', help='play with mpv') - p.add_argument('-v', '--view', action='store_true', help='view details') + p.add_argument('-v', '--view', action='count', help='view details') p.add_argument('-V', '--VERIFY', action='store_true', help='verify') p.add_argument('-y', '--yes', action='store_true', help='yes') p.add_argument('-q', '--quiet', action='store_true', help='quiet for download and play') @@ -2583,9 +3603,11 @@ def main(argv): help='类型参数,eg: ls -t f (文件(f)、文件夹(d))') p.add_argument('-l', '--limit', action='store', \ default=None, type=str, help='下载速度限制,eg: -l 100k') + p.add_argument('-P', '--passwd', action='store', \ + default=None, type=str, help='设置密码,eg: -P pawd') # for upload - p.add_argument('-m', '--uploadmode', action='store', \ - default='c', type=str, choices=['o', 'c'], \ + p.add_argument('-m', '--mode', action='/index.cgi/contrast/https://github.com/southwolf/iScript/compare/store', \ + default='c', type=str, choices=['o', 'c'] + CIPHERS, \ help='上传模式: o --> 重传. c --> 续传 .') # for recurse, head, tail, include, exclude p.add_argument('-R', '--recursive', action='store_true', help='递归 ls') @@ -2603,8 +3625,12 @@ def main(argv): global VERIFY comd = argv[1] args = p.parse_args(argv[2:]) + if args.type_: + args.type_ = args.type_.split(',') + else: + args.type_ = [] VERIFY = args.VERIFY - if (comd == 'rnr' or comd == 'rnre') and 'bd64' not in args.type_.split(','): + if (comd == 'rnr' or comd == 'rnre') and 'bd64' not in args.type_: if len(argv[2:]) < 3: print s % (1, 91, " !! 参数错误\n rnr foo bar /path/to") sys.exit(1) @@ -2612,27 +3638,44 @@ def main(argv): args = p.parse_args(argv[4:]) xxx = args.xxx args.comd = comd - ####################################################### + return comd, xxx + +def enter_password(): + if not args.passwd: + from getpass import getpass + if 'ec' in args.type_: + while True: + pwd1 = getpass(s % (2, 97, 'Password: ')) + pwd2 = getpass(s % (2, 97, 'verify Password: ')) + if pwd1 == pwd2: + args.passwd = pwd1 + break + else: + print s % (2, 91, '! Passwords do not match.') + elif 'dc' in args.type_: + args.passwd = getpass(s % (2, 97, 'Password: ')) +def handle_command(comd, xxx): if comd == 'login' or comd == 'g': - x = panbaiducom_HOME() + from getpass import getpass + xh = panbaiducom_HOME() if len(xxx) < 1: - username = raw_input(s % (1, 97, ' username: ')) - password = getpass(s % (1, 97, ' password: ')) + username = raw_input(s % (1, 97, ' username: ')) + password = getpass(s % (1, 97, ' password / cookie: ')) elif len(xxx) == 1: username = xxx[0] - password = getpass(s % (1, 97, ' password: ')) + password = getpass(s % (1, 97, ' password / cookie: ')) elif len(xxx) == 2: username = xxx[0] password = xxx[1] else: print s % (1, 91, ' login\n login username\n login username password') - x.login(username, password) - result = x.check_login() + xh.login(username, password) + result = xh.check_login() if result: - x.save_cookies(username, on=1) + xh.save_cookies(username, on=1) print s % (1, 92, ' ++ login succeeds.') else: print s % (1, 91, ' login failes') @@ -2642,10 +3685,10 @@ def main(argv): comd == 'user': accounts = panbaiducom_HOME._check_cookie_file() if accounts: - cu = zip(range(len(accounts)), [u for u in accounts]) + cu = zip(range(len(accounts)), sorted([u for u in accounts])) for i, u in cu: print s % (1, 92, i+1) if accounts[u]['on'] else s % (1, 91, i+1), \ - accounts[u]['capacity'], \ + accounts[u]['capacity'].ljust(15), \ s % (2, 92, u) if accounts[u]['on'] else s % (2, 97, u) if comd == 'userdelete' or comd == 'ud': print s % (2, 97, 0), s % (2, 91, 'ALL') elif comd == 'user': sys.exit() @@ -2662,7 +3705,7 @@ def main(argv): if comd == 'userdelete' or comd == 'ud': if u != 'ALL': if accounts[u]['on'] and len(accounts)> 1: - print s % (1, 91, ' !! %s is online. To delete the account, firstly changing another account' % u) + print s % (1, 91, ' !! %s is online. To delete the account, firstly switching to other account' % u) sys.exit() del accounts[u] else: @@ -2690,18 +3733,55 @@ def main(argv): ' u localpath1 localpath2 .. remotepath') sys.exit(1) global px + + enter_password() + px = panbaiducom_HOME() px.init() px.upload(xxx[:-1], xxx[-1]) + elif comd == 'S' or comd == 'share': + if len(xxx) < 1: + print s % (1,91, ' !! S path1 path2 \n share path1 path2 \n ..') + sys.exit(1) + + pwd = args.passwd + if pwd: + if not re.match(r'^[a-z0-9]{4}$', pwd): + from string import lowercase, digits + print s % (1, 91, ' !! passwd is wrong and will be randomly choiced.' \ + '\n passwd is 4 symbols and choiced from %s%s' \ + % (lowercase, digits)) + pwd = ''.join(random.sample(lowercase + digits, 4)) + + paths = xxx + paths1 = [] + for path in paths: + if path[0] == '/': + paths1.append(path) + else: + print s % (2, 91, ' !!! url 路径不正确.'), path + if paths1: + x = panbaiducom_HOME() + x.init() + x.share(paths1, pwd) + elif comd == 'd' or comd == 'download' \ or comd == 'p' or comd == 'play': if len(xxx) < 1: - print s % (1, 91, ' !! 参数错误\n download url1 url2 ..\n' \ + print s % (1, 91, ' !! 参数错误\n download path1 .. url1 ..\n' \ ' d url1 url2 ..') sys.exit(1) - if comd == 'p' or comd == 'play': args.play = True + # login session + panbaiducom_HOME().init() + + if comd == 'p' or comd == 'play': + args.play = True + else: + assert_download_tools() + + enter_password() paths = xxx paths1 = [] @@ -2710,9 +3790,7 @@ def main(argv): paths4 = [] for path in paths: - if path[0] == '/': - paths1.append(path) - elif '/disk/home' in path: + if '/disk/home' in path: paths1.append(path) elif 'baidu.com/pcloud/album/file' in path: paths2.append(path) @@ -2722,7 +3800,7 @@ def main(argv): elif 'pcs.baidu.com' in path: paths4.append(path) else: - print s % (2, 91, ' !!! url 地址不正确.'), path + paths1.append(path) if paths1: x = panbaiducom_HOME() @@ -2730,16 +3808,16 @@ def main(argv): x.download(paths1) if paths2: - x = panbaiducom() - x.do2(paths2) + xw = panbaiducom() + xw.do2(paths2) if paths3: - x = panbaiducom() - x.do(paths3) + xw = panbaiducom() + xw.do(paths3) if paths4: - x = panbaiducom() - x.do4(paths4) + xw = panbaiducom() + xw.do4(paths4) elif comd == 's' or comd == 'save': if len(xxx) != 2: @@ -2751,7 +3829,7 @@ def main(argv): path = x._get_path(xxx[0]) remotepath = xxx[1].decode('utf8', 'ignore') infos = [] - if path != '/': + if path != '/' and path[0] == '/': infos.append( { 'isdir': 1, @@ -2763,11 +3841,12 @@ def main(argv): ) else: infos = None + if '/inbox/' in xxx[0]: url = xxx[0] x.save_inbox_share(url, remotepath, infos=infos) else: - url = re.search(r'(http://.+?.baidu.com/.+?)(#|$)', xxx[0]).group(1) + url = re.search(r'(https?://.+?.baidu.com/.+?)(#|$)', xxx[0]).group(1) url = url.replace('wap/link', 'share/link') x._secret_or_not(url) x.save_share(url, remotepath, infos=infos) @@ -2786,12 +3865,12 @@ def main(argv): iii = xxx.index('|') if '|' in xxx else -1 fxxx = xxx[:iii] if iii != -1 else xxx pxxx = xxx[iii+1:] if iii != -1 else None - directory = None if fxxx[-1][0] == '/': keywords = fxxx[:-1] directory = fxxx[-1] else: keywords = fxxx + directory = x.cwd if comd == 'f' or comd == 'find': x.find(keywords, directory=directory, pipe=pxxx) @@ -2819,7 +3898,7 @@ def main(argv): or comd == 'ln' or comd == 'lnn'\ or comd == 'ls' or comd == 'lss' \ or comd == 'lt' or comd == 'ltt': - if len(xxx) < 1: + if len(xxx) < 1 and (comd[0] != 'l'): print s % (1, 91, ' !! 参数错误\n move path1 path2 .. /path/to/directory\n' \ ' mv path1 path2 .. /path/to/directory\n' \ ' remove path1 path2 ..\n' \ @@ -2832,10 +3911,10 @@ def main(argv): ' l path1 path2 ..\n' \ ' ls path1 path2 ..\n') sys.exit(1) - e = True if 'f' in ['f' for i in xxx if i[0] != '/'] else False - if e: - print s % (1, 91, ' !! path is incorrect.') - sys.exit(1) + #e = True if 'f' in ['f' for i in xxx if i[0] != '/'] else False + #if e and (comd[0] != 'l'): + #print s % (1, 91, ' !! path is incorrect.') + #sys.exit(1) x = panbaiducom_HOME() x.init() if comd == 'mv' or comd == 'move': @@ -2849,7 +3928,7 @@ def main(argv): elif comd == 'l' or comd == 'ln': x.ls('name', None, xxx) elif comd == 'du': - args.type_ = 'du' + args.type_.append('du') x.ls('name', None, xxx) elif comd == 'll' or comd == 'lnn': x.ls('name', 1, xxx) @@ -2874,8 +3953,7 @@ def main(argv): sys.exit(1) if args.recursive \ - and (not 'f' in args.type_.split(',') \ - and not 'd' in args.type_.split(',')): + and (not 'f' in args.type_ and not 'd' in args.type_): print s % (1, 91, ' !! you don\'t choose "-t f" or "-t d", it will delete all files and directorys matched.') ipt = raw_input(s % (1, 93, ' are your sure? [y/N] ')) if ipt != 'y': @@ -2883,7 +3961,7 @@ def main(argv): sys.exit() if comd == 'rnr' or comd == 'rnre': - if 'bd64' in args.type_.split(','): + if 'bd64' in args.type_: foo, bar = '', '' dirs = xxx else: @@ -2891,10 +3969,10 @@ def main(argv): bar = argv[3] dirs = xxx - e = True if 'f' in ['f' for i in dirs if i[0] != '/'] else False - if e: - print s % (1, 91, ' !! path is incorrect.') - sys.exit(1) + #e = True if 'f' in ['f' for i in dirs if i[0] != '/'] else False + #if e: + #print s % (1, 91, ' !! path is incorrect.') + #sys.exit(1) x = panbaiducom_HOME() x.init() @@ -2906,10 +3984,10 @@ def main(argv): sys.exit(1) dirs = xxx - e = True if 'f' in ['f' for i in dirs if i[0] != '/'] else False - if e: - print s % (1, 91, ' !! path is incorrect.') - sys.exit(1) + #e = True if 'f' in ['f' for i in dirs if i[0] != '/'] else False + #if e: + #print s % (1, 91, ' !! path is incorrect.') + #sys.exit(1) x = panbaiducom_HOME() x.init() @@ -2921,10 +3999,10 @@ def main(argv): sys.exit(1) dirs = xxx - e = True if 'f' in ['f' for i in dirs if i[0] != '/'] else False - if e: - print s % (1, 91, ' !! path is incorrect.') - sys.exit(1) + #e = True if 'f' in ['f' for i in dirs if i[0] != '/'] else False + #if e: + #print s % (1, 91, ' !! path is incorrect.') + #sys.exit(1) x = panbaiducom_HOME() x.init() @@ -2936,10 +4014,10 @@ def main(argv): sys.exit(1) dirs = xxx - e = True if 'f' in ['f' for i in dirs if i[0] != '/'] else False - if e: - print s % (1, 91, ' !! path is incorrect.') - sys.exit(1) + #e = True if 'f' in ['f' for i in dirs if i[0] != '/'] else False + #if e: + #print s % (1, 91, ' !! path is incorrect.') + #sys.exit(1) x = panbaiducom_HOME() x.init() @@ -2952,15 +4030,23 @@ def main(argv): ' a url1 url2 .. [directory] [-t {m,d,p,a}]') sys.exit(1) - args.type_ = 'm' if not args.type_ else args.type_ # default args.type_ + if not args.type_: args.type_.append('m') # default is mediatype - if xxx[-1].startswith('/') and not xxx[0].startswith('/'): - remotepath = xxx[-1] if xxx[-1][-1] == '/' else xxx[-1] + '/' + if len(xxx[-1]) < 4 \ + or xxx[-1][:4] not in ['magn', 'http', 'ed2k', 'ftp:']: + remotepath = xxx[-1] urls = xxx[:-1] else: remotepath = '/' urls = xxx + localtorrents = [i for i in xxx \ + if i[:4] not in ['magn', 'http', 'ed2k', 'ftp:'] \ + and i[-8:] == '.torrent'] + if localtorrents: + remotepath = '/' + urls = localtorrents + x = panbaiducom_HOME() x.init() x.add_tasks(urls, remotepath) @@ -2971,10 +4057,10 @@ def main(argv): ' md path1 path2 ..') sys.exit(1) paths = xxx - e = True if 'f' in ['f' for i in xxx if i[0] != '/'] else False - if e: - print s % (1, 91, ' !! some path is wrong') - sys.exit(1) + #e = True if 'f' in ['f' for i in xxx if i[0] != '/'] else False + #if e: + #print s % (1, 91, ' !! some path is wrong') + #sys.exit(1) x = panbaiducom_HOME() x.init() x.mkdir(paths) @@ -3008,9 +4094,50 @@ def main(argv): elif comd == 'jca' or comd == 'jobclearall': x.jobclearall() + elif comd == 'dc' or comd == 'decrypt': + if 'dc' not in args.type_: args.type_.append('dc') + enter_password() + + x = panbaiducom_HOME() + x.init() + x.decrypt(xxx) + + elif comd == 'cd': + x = panbaiducom_HOME() + x.init() + + if len(xxx)> 1: + print s % (1, 91, ' !! 参数错误\n cd path') + sys.exit(1) + + x.change_directory(xxx) + + elif comd == 'cwd': + xd = panbaiducom_HOME() + xd.init() + print xd.cwd + else: print s % (2, 91, ' !! 命令错误\n') + if 'x' in locals(): + x.save_cookies(on=1, tocwd=True) + elif 'px' in globals(): + px.save_cookies(on=1, tocwd=True) + + + +def main(argv): + handle_signal() + + usage = "usage: https://github.com/PeterDing/iScript#pan.baidu.com.py" + if len(argv) <= 1: + print usage + sys.exit() + + comd, xxx = handle_args(argv) + handle_command(comd, xxx) + if __name__ == '__main__': argv = sys.argv main(argv) diff --git a/tumblr.py b/tumblr.py index 54579ba..4329c63 100755 --- a/tumblr.py +++ b/tumblr.py @@ -1,17 +1,33 @@ #!/usr/bin/env python2 # vim: set fileencoding=utf8 +from __future__ import unicode_literals + import os import sys import re import json +import collections +import multiprocessing import requests +requests.packages.urllib3.disable_warnings() import argparse import random -import subprocess import time +import select +import signal + +API_KEY = 'fuiKNFp9vQFvjLNvx4sUwti4Yb5yGutBN4Xh10LXZhhRKjWlV4' -api_key = 'fuiKNFp9vQFvjLNvx4sUwti4Yb5yGutBN4Xh10LXZhhRKjWlV4' +PID_PATH = '/tmp/tumblr.py.pid' + +# statistic parameters +NET_ERRORS = multiprocessing.Value('i', 0) +UNCOMPLETION = multiprocessing.Value('i', 0) +DOWNLOAD_ERRORS = multiprocessing.Value('i', 0) +DOWNLOADS = multiprocessing.Value('i', 0) +COMPLETION = multiprocessing.Value('i', 0) +OFFSET = multiprocessing.Value('i', 0) ############################################################ # wget exit status @@ -39,258 +55,585 @@ "Accept-Language":"en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4,zh-TW;q=0.2", "Content-Type":"application/x-www-form-urlencoded", "Referer":"https://api.tumblr.com/console//calls/blog/posts", - "User-Agent":"Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 \ - (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36" + "User-Agent":"Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 " \ + "(KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36" } ss = requests.session() ss.headers.update(headers) -def check_queue(queue, cb): - for f in queue: - st = f[0].poll() - if st is not None: - if st == 0: cb(f[1]) - queue.remove(f) - return queue - -def sleep(size, num): - t = float(size) / num - time.sleep(t) - -def async(tasks, queue, run=None, cb=None, num=10): - queue = check_queue(queue, cb) - sleep(len(queue), num) - nsize = num - len(queue) - for i in xrange(nsize): - try: - task = tasks.pop(0) - except IndexError: - break - f = run(task) - if f: queue.append(f) - return tasks, queue - -class tumblr(object): - def __init__(self): - self.queue = [] - self.tasks = [] +PROXY = None - def save_json(self): - with open(self.json_path, 'w') as g: - g.write(json.dumps( - {'key': self.key}, indent=4, sort_keys=True)) +class Error(Exception): + def __init__(self, msg): + self.msg = msg + def __str__(self): + return self.msg - def get_site_infos(self, postid=None): - self.infos['photos'] = [] - self.url = 'http://api.tumblr.com/v2/blog/%s/posts/photo' \ - % self.infos['host'] - params = { - "offset": self.key if not postid else "", - "limit": 20 if not postid else "", - "type": "photo", - "filter": "text", - "tag": args.tag, - "id": postid if postid else "", - "api_key": api_key - } +def reset_statistic_params(): + NET_ERRORS.value = 0 + UNCOMPLETION.value = 0 + DOWNLOAD_ERRORS.value = 0 + DOWNLOADS.value = 0 + COMPLETION.value = 0 + OFFSET.value = 0 - r = None +def play(urls, args): + for url in urls: + tumblr = Tumblr(args, url) + while True: + items = tumblr.get_item_generator() + if not items: + break + play_do(items, args.quiet) + +def play_do(items, quiet): + for item in items: + num = random.randint(0, 7) % 8 + col = s % (2, num + 90, item['durl']) + print ' ++ play:', col + quiet = ' --really-quiet' if quiet else '' + cmd = 'mpv%s --no-ytdl --cache-default 20480 --cache-secs 120 ' \ + '--http-header-fields "User-Agent:%s" ' \ + '"%s"' \ + % (quiet, headers['User-Agent'], item['durl']) + + os.system(cmd) + timeout = 1 + ii, _, _ = select.select([sys.stdin], [], [], timeout) + if ii: + sys.exit(0) + else: + pass + +def remove_downloaded_items(items): + N = len(items) + for i in range(N): + item = items.pop() + filepath = os.path.join(item['dir_'], item['subdir'], item['filename']) + if not os.path.exists(filepath): + items.appendleft(item) + +def download_run(item): + filepath = os.path.join(item['dir_'], item['subdir'], item['filename']) + # if os.path.exists(filepath): + # return None + # num = random.randint(0, 7) % 8 + # col = s % (1, num + 90, filepath) + # print ' ++ download: %s' % col + + if PROXY: + cmd = ' '.join([ + 'curl', '-s', '-x', '"%s"' % PROXY, '-o', '"%s.tmp"' % filepath, + '-H', '"User-Agent: %s"' % headers['User-Agent'], + '"%s"' % item['durl'] + ]) + else: + cmd = ' '.join([ + 'curl', '-s', '-o', '"%s.tmp"' % filepath, + '-H', '"User-Agent: %s"' % headers['User-Agent'], + '"%s"' % item['durl'] + ]) + status = os.system(cmd) + return status, filepath + +def callback(filepath): + os.rename('%s.tmp' % filepath, filepath) + +class Downloader(multiprocessing.Process): + def __init__(self, queue, lock): + super(Downloader, self).__init__() + self.queue = queue + self.daemon = True + self.lock = lock + + def run(self): + while True: + item = self.queue.get() + self.queue.task_done() + if not item: + break + status = download_run(item) + if not status: # file was downloaded. + continue + status, filepath = status + if status != 0: + # print s % (1, 93, '[Error %s] at wget' % status), wget_es[status] + self.lock.acquire() + UNCOMPLETION.value += 1 + DOWNLOAD_ERRORS.value += 1 + self.lock.release() + else: + self.lock.acquire() + DOWNLOADS.value += 1 + self.lock.release() + callback(filepath) + +class TumblrAPI(object): + def _request(self, base_hostname, target, type, params): + api_url = '/'.join(['https://api.tumblr.com/v2/blog', + base_hostname, target, type]) + params['api_key'] = API_KEY + if PROXY: + proxies = {'http': PROXY, 'https': PROXY} + else: + proxies = None while True: try: - r = ss.get(self.url, params=params) + res = ss.get(api_url, params=params, proxies=proxies, timeout=10) + json_data = res.json() break + except KeyboardInterrupt: + sys.exit() except Exception as e: - print s % (1, 91, ' !! Error at get_infos'), e + NET_ERRORS.value += 1 # count errors + print s % (1, 93, '[Error at requests]:'), e, '\n' time.sleep(5) - if r.ok: - j = r.json() - if j['response']['posts']: - for i in j['response']['posts']: - index = 1 + if json_data['meta']['msg'].lower() != 'ok': + raise Error(s % (1, 91, json_data['meta']['msg'])) + + return json_data['response'] + + def _info(self, base_hostname): + return self._request(base_hostname, 'info', '', None) + + def _photo(self, base_hostname, offset='', tag='', post_id='', to_items=True): + def make_items(raw_data): + items = collections.deque() + for i in raw_data['posts']: + index = 1 + if i.get('photos'): for ii in i['photos']: - durl = ii['original_size']['url'].encode('utf8') - filepath = os.path.join(self.infos['dir_'], '%s_%s.%s' \ - % (i['id'], index, durl.split('.')[-1])) - filename = os.path.split(filepath)[-1] + durl = ii['original_size']['url'].replace('http:', 'https:') + filename = os.path.join( + '%s_%s.%s' % (i['id'], index, durl.split('.')[-1])) t = { - 'filepath': filepath, 'durl': durl, - 'filename': filename + 'filename': filename, + 'key': i['timestamp'], + 'subdir': 'photos', } index += 1 - self.infos['photos'].append(t) - else: - print s % (1, 92, '\n --- job over ---') - sys.exit(0) - else: - print s % (1, 91, '\n !! Error, get_infos') - print r.status_code, r.content - sys.exit(1) + items.append(t) + return items - def get_tag_infos(self): - self.infos['photos'] = [] - self.url = 'http://api.tumblr.com/v2/tagged' params = { - "limit": 20, - "type": "photo", - "tag": self.infos['tag'], - "before": self.key, - "api_key": api_key + 'offset': offset, + 'before': offset if tag else '', + 'tag': tag, + 'id': post_id, + 'limit': 20 if not tag and not post_id else '', + 'filter': 'text' } + raw_data = self._request(base_hostname, 'posts', 'photo', params) + if to_items: + return make_items(raw_data) + else: + return raw_data - r = None - while True: - try: - r = ss.get(self.url, params=params) - break - except Exception as e: - print s % (1, 91, ' !! Error at get_infos'), e - time.sleep(5) - if r.ok: - j = r.json() - if j['response']: - for i in j['response']: - index = 1 - if i.get('photos'): - for ii in i['photos']: - durl = ii['original_size']['url'].encode('utf8') - filepath = os.path.join( - self.infos['dir_'], '%s_%s.%s' \ - % (i['id'], index, durl.split('.')[-1])) - filename = os.path.split(filepath)[-1] - t = { - 'filepath': filepath, - 'durl': durl, - 'filename': filename, - 'key': i['timestamp'] - } - index += 1 - self.infos['photos'].append(t) - else: - print s % (1, 92, '\n --- job over ---') - sys.exit(0) + def _audio(self, base_hostname, offset='', tag='', post_id='', to_items=True): + def make_items(raw_data): + items = collections.deque() + for i in raw_data['posts']: + durl = i['audio_url'].replace('http:', 'https:') + filename = os.path.join( + '%s_%s.%s' % (i['id'], i['track_name'], durl.split('.')[-1])) + t = { + 'durl': durl, + 'filename': filename, + 'timestamp': i['timestamp'] if tag else '', + 'subdir': 'audios' + } + items.append(t) + return items + + params = { + 'offset': offset, + 'before': offset if tag else '', + 'tag': tag, + 'id': post_id, + 'limit': 20 if not tag and not post_id else '', + 'filter': 'text' + } + raw_data = self._request(base_hostname, 'posts', 'audio', params) + if to_items: + return make_items(raw_data) else: - print s % (1, 91, '\n !! Error, get_infos') - print r.status_code, r.content - sys.exit(1) + return raw_data - def download(self): - def run(i): - if os.path.exists(i['filepath']): - return - num = random.randint(0, 7) % 7 - col = s % (1, num + 90, i['filepath']) - print ' ++ download: %s' % col - cmd = [ - 'wget', '-c', '-q', - '-O', '%s.tmp' % i['filepath'], - '--user-agent', '"%s"' % headers['User-Agent'], - '%s' % i['durl'] - ] - f = subprocess.Popen(cmd) - return f, i['filepath'] - - def callback(filepath): - os.rename('%s.tmp' % filepath, filepath) - - tasks = self.infos['photos'] + self.tasks - self.tasks = [] - while True: - tasks, self.queue = async( - tasks, self.queue, run=run, - cb=callback, num=self.processes) - if len(tasks) <= self.processes: - self.tasks = tasks - break + def _video(self, base_hostname, offset='', tag='', post_id='', to_items=True): + def make_items(raw_data): + items = collections.deque() + for i in raw_data['posts']: + if not i.get('video_url'): + continue + durl = i['video_url'].replace('http:', 'https:') + filename = os.path.join( + '%s.%s' % (i['id'], durl.split('.')[-1])) + t = { + 'durl': durl, + 'filename': filename, + 'timestamp': i['timestamp'] if tag else '', + 'subdir': 'videos' + } + items.append(t) + return items - def download_site(self, url): - self.infos = { - 'host': re.search(r'http(s|)://(.+?)($|/)', url).group(2)} - self.infos['dir_'] = os.path.join(os.getcwd(), self.infos['host']) - self.processes = int(args.processes) - - if not os.path.exists(self.infos['dir_']): - os.makedirs(self.infos['dir_']) - self.json_path = os.path.join(self.infos['dir_'], 'json.json') - self.key = 0 - print s % (1, 92, '\n ## begin'), 'key = %s' % self.key + params = { + 'offset': offset, + 'before': offset if tag else '', + 'tag': tag, + 'id': post_id, + 'limit': 20 if not tag and not post_id else '', + 'filter': 'text' + } + raw_data = self._request(base_hostname, 'posts', 'video', params) + if to_items: + return make_items(raw_data) else: - self.json_path = os.path.join(self.infos['dir_'], 'json.json') - if os.path.exists(self.json_path): - self.key = json.loads(open(self.json_path).read())['key'] - 20 - print s % (1, 92, '\n ## begin'), 'key = %s' % self.key + return raw_data + +class Tumblr(TumblrAPI): + def __init__(self, args, url): + self.args = args + self.offset = self.args.offset + self.make_items = self.parse_urls(url) + + def save_json(self): + with open(self.json_path, 'w') as g: + g.write(json.dumps( + {'offset': self.offset}, indent=4, sort_keys=True)) + + def init_infos(self, base_hostname, target_type, tag=''): + self.infos = {'host': base_hostname} + if not tag: + dir_ = os.path.join(os.getcwd(), self.infos['host']) + json_path = os.path.join(dir_, 'json.json') + + if not os.path.exists(dir_): + if not self.args.play: + os.makedirs(dir_) else: - self.key = 0 - - if args.check: - t = os.listdir(self.infos['dir_']) - t = [i[:i.find('_')] for i in t if i.endswith('.tmp')] - ltmp = list(set(t)) - for postid in ltmp: - self.get_site_infos(postid) - self.download() + if os.path.exists(json_path): + self.offset = json.load(open(json_path))['offset'] - 60 \ + if not self.args.update else self.args.offset + if self.offset < 0: self.offset = 0 else: - while True: - self.get_site_infos() - self.key += 20 + dir_ = os.path.join(os.getcwd(), 'tumblr-%s' % tag) + json_path = os.path.join(dir_, 'json.json') + + if not os.path.exists(dir_): + if not self.args.play: + os.makedirs(dir_) + self.offset = int(time.time()) + else: + if os.path.exists(json_path): + self.offset = json.load(open(json_path))['offset'] \ + if not self.args.update else int(time.time()) + + self.infos['dir_'] = dir_ + self.json_path = json_path + subdir = os.path.join(dir_, target_type) + if not os.path.exists(subdir) and not self.args.play: + os.makedirs(subdir) + + if not self.args.play: + for fl in os.listdir(subdir): + if not fl.endswith('.tmp'): + COMPLETION.value += 1 + else: + UNCOMPLETION.value += 1 + + if self.args.offset: + self.offset = self.args.offset + + print s % (1, 92, '## begin:'), 'offset = %s,' % self.offset, base_hostname + print s % (1, 97, 'INFO:\n') + \ + 'D = Downloads, R = Repair_Need\n' + \ + 'C = Completion, NE = Net_Errors, O = Offset' + + def download_photos_by_offset(self, base_hostname, post_id): + self.init_infos(base_hostname, 'photos') + + def do(): + items = self._photo( + base_hostname, offset=self.offset if not post_id else '', post_id=post_id) + if not items: + return [] + self.offset += 20 + self.save_json() + return items + return do + + def download_photos_by_tag(self, base_hostname, tag): + self.init_infos(base_hostname, 'photos', tag=tag) + + def do(): + items = self._photo(base_hostname, tag=tag, before=self.offset) + if not items: + return [] + self.offset = items[-1]['timestamp'] + self.save_json() + return items + return do + + def download_videos_by_offset(self, base_hostname, post_id): + self.init_infos(base_hostname, 'videos') + + def do(): + items = self._video( + base_hostname, offset=self.offset, post_id=post_id) + if not items: + return [] + self.offset += 20 + if not self.args.play: + self.save_json() + return items + return do + + def download_videos_by_tag(self, base_hostname, tag): + self.init_infos(base_hostname, 'videos', tag) + + def do(): + items = self._video( + base_hostname, before=self.offset, tag=tag) + if not items: + return [] + self.offset = items[-1]['timestamp'] + if not self.args.play: + self.save_json() + return items + return do + + def download_audios_by_offset(self, base_hostname, post_id): + self.init_infos(base_hostname, 'audios') + + def do(): + items = self._audio( + base_hostname, offset=self.offset if not post_id else '', post_id=post_id) + if not items: + return [] + self.offset += 20 + if not self.args.play: self.save_json() - self.download() - - def download_tag(self, tag): - self.infos = {'tag': tag} - self.infos['dir_'] = os.path.join( - os.getcwd(), 'tumblr-%s' % self.infos['tag']) - self.processes = int(args.processes) - - if not os.path.exists(self.infos['dir_']): - os.makedirs(self.infos['dir_']) - self.json_path = os.path.join(self.infos['dir_'], 'json.json') - self.key = int(time.time()) - print s % (1, 92, '\n ## begin'), 'key = %s' % self.key + return items + return do + + def download_audios_by_tag(self, base_hostname, tag): + self.init_infos(base_hostname, 'audios', tag) + + def do(): + items = self._audio( + base_hostname, before=self.offset, tag=tag) + if not self.infos['items']: + return [] + self.offset = self.infos['items'][-1]['timestamp'] + if not self.args.play: + self.save_json() + return items + return do + + def download_photos(self, base_hostname, post_id='', tag=''): + if tag: + return self.download_photos_by_tag(base_hostname, tag) + else: + return self.download_photos_by_offset(base_hostname, post_id=post_id) + + def download_videos(self, base_hostname, post_id='', tag=''): + if tag: + return self.download_videos_by_tag(base_hostname, tag) + else: + return self.download_videos_by_offset(base_hostname, post_id=post_id) + + def download_audios(self, base_hostname, post_id='', tag=''): + if tag: + return self.download_audios_by_tag(base_hostname, tag) else: - self.json_path = os.path.join(self.infos['dir_'], 'json.json') - if os.path.exists(self.json_path): - self.key = json.loads(open(self.json_path).read())['key'] - print s % (1, 92, '\n ## begin'), 'key = %s' % self.key + return self.download_audios_by_offset(base_hostname, post_id=post_id) + + def fix_photos(self, base_hostname): + self.init_infos(base_hostname, 'photos') + + t = os.listdir(os.path.join(self.infos['dir_'], 'photos')) + t = [i[:i.find('_')] for i in t if i.endswith('.tmp')] + self.post_ids = list(set(t)) + + def do(): + if len(self.post_ids): + post_id = self.post_ids.pop() + return self._photo(base_hostname, post_id=post_id) else: - self.key = int(time.time()) - - if args.check: - t = os.listdir(self.infos['dir_']) - t = [i[:i.find('_')] for i in t if i.endswith('.tmp')] - ltmp = list(set(t)) - for postid in ltmp: - self.get_site_infos(postid) - self.download() + return [] + return do + + def parse_urls(self, url): + _mod = re.search(r'(http://|https://|)(?P.+\.tumblr.com)', url) + if not _mod: + print s % (1, 91, '[Error]:'), 'url is illegal.', '\n' + url.decode('utf8', 'ignore') + return lambda: [] + base_hostname = _mod.group('hostname') + if self.args.check: + return self.fix_photos(base_hostname) + + if re.search(r'post/(\d+)', url): + post_id = re.search(r'post/(\d+)', url).group(1) else: - while True: - self.get_tag_infos() - self.key = self.infos['photos'][-1]['key'] - self.save_json() - self.download() + post_id = '' -def main(argv): + if self.args.video: + return self.download_videos(base_hostname, post_id=post_id, tag=self.args.tag) + elif self.args.audio: + return self.download_audios(base_hostname, post_id=post_id, tag=self.args.tag) + else: + return self.download_photos(base_hostname, post_id=post_id, tag=self.args.tag) + + def get_item_generator(self): + OFFSET.value = self.offset + items = self.make_items() + for item in items: + item['dir_'] = self.infos['dir_'] + return items + +def args_handler(argv): p = argparse.ArgumentParser( description='download from tumblr.com') - p.add_argument('xxx', help='xxx') - p.add_argument('-p', '--processes', action='store', default=10, - help='指定多进程数,默认为10个,最多为20个 eg: -p 20') + p.add_argument('xxx', type=str, nargs='*', help='命令对象.') + p.add_argument('-p', '--processes', action='store', type=int, default=10, + help='指定多进程数,默认为10个,最多为20个 eg: -p 20') + p.add_argument('-f', '--offset', action='store', type=int, default=0, + help='offset') + p.add_argument('-q', '--quiet', action='store_true', + help='quiet') p.add_argument('-c', '--check', action='store_true', - help='尝试修复未下载成功的图片') + help='尝试修复未下载成功的图片') + p.add_argument('-P', '--play', action='store_true', + help='play with mpv') + p.add_argument('-V', '--video', action='store_true', + help='download videos') + p.add_argument('-A', '--audio', action='store_true', + help='download audios') p.add_argument('-t', '--tag', action='store', default=None, type=str, help='下载特定tag的图片, eg: -t beautiful') - global args + p.add_argument('--update', action='store_true', + help='update new things') + p.add_argument('--redownload', action='store_true', + help='redownload all things') + p.add_argument('-x', '--proxy', type=str, + help='redownload all things') args = p.parse_args(argv[1:]) xxx = args.xxx - if 'http:' in xxx: - x = tumblr() - x.download_site(xxx) - else: - x = tumblr() - x.download_tag(xxx) + if args.proxy: + if args.proxy[:4] not in ('http', 'sock'): + print s % (1, 91, '[Error]:'), 'proxy must have a protocol:// prefix' + sys.exit(1) + else: + global PROXY + PROXY = args.proxy + + if args.redownload: args.update = True + return args, xxx + +def print_msg(check): + time.sleep(2) # initial interval + + while True: + msg = "\r%s, %s, %s, %s, %s " % \ + ( + 'D: ' + s % (1, 92, DOWNLOADS.value), + 'R: ' + s % (1, 93, UNCOMPLETION.value \ + if not check \ + else UNCOMPLETION.value - DOWNLOAD_ERRORS.value - DOWNLOADS.value), + 'C: ' + s % (1, 97, COMPLETION.value + DOWNLOADS.value), + 'NE: ' + s % (1, 91, NET_ERRORS.value), + 'O: %s' % OFFSET.value + ) + sys.stdout.write(msg) + sys.stdout.flush() + time.sleep(2) + +def sighandler(signum, frame): + # print s % (1, 91, "\n !! Signal:"), signum + # print s % (1, 91, " !! Frame: %s" % frame) + sys.exit() + +def handle_signal(): + signal.signal(signal.SIGBUS, sighandler) + signal.signal(signal.SIGHUP, sighandler) + # http://stackoverflow.com/questions/14207708/ioerror-errno-32-broken-pipe-python + signal.signal(signal.SIGPIPE, signal.SIG_DFL) + signal.signal(signal.SIGQUIT, sighandler) + signal.signal(signal.SIGSYS, sighandler) + + signal.signal(signal.SIGABRT, sighandler) + signal.signal(signal.SIGFPE, sighandler) + signal.signal(signal.SIGILL, sighandler) + signal.signal(signal.SIGINT, sighandler) + signal.signal(signal.SIGSEGV, sighandler) + signal.signal(signal.SIGTERM, sighandler) + +def main(argv): + handle_signal() + args, xxx = args_handler(argv) + + if args.play: + play(xxx, args) + + lock = multiprocessing.Lock() + queue = multiprocessing.JoinableQueue(maxsize=args.processes) + thrs = [] + for i in range(args.processes): + thr = Downloader(queue, lock) + thr.start() + thrs.append(thr) + + # massage thread + msg_thr = multiprocessing.Process(target=print_msg, args=(args.check,)) + msg_thr.daemon = True + msg_thr.start() + + for url in xxx: + reset_statistic_params() + tumblr = Tumblr(args, url) + not_add = 0 + while True: + items = tumblr.get_item_generator() + if not items: + break + + # Check the downloaded items. + # It will be exited, if there is no new item to download + # in 5 loops, unless with --redownload + remove_downloaded_items(items) + if not args.redownload: + if not items: + not_add += 1 + if not_add> 5: + print s % (1, 93, '\n[Warning]:'), \ + 'There is nothing new to download in 5 loops.\n', \ + 'If you want to scan all resources, using --redownload\n' \ + 'or running the script again to next 5 loops.' + break + continue + else: + not_add = 0 + + for item in items: + queue.put(item) + + while not queue.empty(): + time.sleep(2) + + for i in range(args.processes): + queue.put(None) + + queue.join() + + for thr in thrs: + thr.join() + + msg_thr.terminate() if __name__ == '__main__': argv = sys.argv diff --git a/xiami.py b/xiami.py index 4a6a23e..7709f82 100755 --- a/xiami.py +++ b/xiami.py @@ -5,21 +5,24 @@ import sys from getpass import getpass import os +import copy import random import time +import datetime import json import argparse import requests import urllib +import hashlib import select from mutagen.id3 import ID3,TRCK,TIT2,TALB,TPE1,APIC,TDRC,COMM,TPOS,USLT from HTMLParser import HTMLParser url_song = "http://www.xiami.com/song/%s" url_album = "http://www.xiami.com/album/%s" -url_collect = "http://www.xiami.com/collect/%s" +url_collect = "http://www.xiami.com/collect/ajax-get-list" url_artist_albums = "http://www.xiami.com/artist/album/id/%s/page/%s" -url_artist_top_song = "http://www.xiami.com/artist/top/id/%s" +url_artist_top_song = "http://www.xiami.com/artist/top-%s" url_lib_songs = "http://www.xiami.com/space/lib-song/u/%s/page/%s" url_recent = "http://www.xiami.com/space/charts-recent/u/%s/page/%s" @@ -57,8 +60,19 @@ "Accept-Language":"en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4,zh-TW;q=0.2", "Content-Type":"application/x-www-form-urlencoded", "Referer":"http://www.xiami.com/", - "User-Agent":"Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 "\ - "(KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36" + "User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36"\ +} + +HEADERS2 = { + 'pragma': 'no-cache', + 'accept-encoding': 'gzip, deflate, br', + 'accept-language': 'zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7', + 'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36', + 'accept': 'text/javascript, application/javascript, application/ecmascript, application/x-ecmascript, */*; q=0.01', + 'cache-control': 'no-cache', + 'authority': 'www.xiami.com', + 'x-requested-with': 'XMLHttpRequest', + 'referer': 'https://www.xiami.com/play?ids=/song/playlist/id/', } ss = requests.session() @@ -112,10 +126,339 @@ def z_index(song_infos): ######################################################## +class Song(object): + + def __init__(self): + self.__sure() + self.track = 0 + self.year = 0 + self.cd_serial = 0 + self.disc_description = '' + + # z = len(str(album_size)) + self.z = 1 + + def __sure(self): + __dict__ = self.__dict__ + if '__keys' not in __dict__: + __dict__['__keys'] = {} + + def __getattr__(self, name): + __dict__ = self.__dict__ + return __dict__['__keys'].get(name) + + def __setattr__(self, name, value): + __dict__ = self.__dict__ + __dict__['__keys'][name] = value + + def __getitem__(self, key): + return getattr(self, key) + + def __setitem__(self, key, value): + return setattr(self, key, value) + + def feed(self, **kwargs): + for name, value in kwargs.items(): + setattr(self, name, value) + + +class XiamiH5API(object): + + URL = 'http://api.xiami.com/web' + PARAMS = { + 'v': '2.0', + 'app_key': '1', + } + + def __init__(self): + self.cookies = { + 'user_from': '2', + 'XMPLAYER_addSongsToggler': '0', + 'XMPLAYER_isOpen': '0', + '_xiamitoken': hashlib.md5(str(time.time())).hexdigest() + } + self.sess = requests.session() + self.sess.cookies.update(self.cookies) + + def _request(self, url, method='GET', **kwargs): + try: + resp = self.sess.request(method, url, **kwargs) + except Exception, err: + print 'Error:', err + sys.exit() + + return resp + + def _make_params(self, **kwargs): + params = copy.deepcopy(self.PARAMS) + params.update(kwargs) + return params + + def song(self, song_id): + params = self._make_params(id=song_id, r='song/detail') + url = self.URL + resp = self._request(url, params=params, headers=headers) + + info = resp.json()['data']['song'] + pic_url = re.sub('_\d+\.', '.', info['logo']) + song = Song() + song.feed( + song_id=info['song_id'], + song_name=info['song_name'], + album_id=info['album_id'], + album_name=info['album_name'], + artist_id=info['artist_id'], + artist_name=info['artist_name'], + singers=info['singers'], + album_pic_url=pic_url, + comment='http://www.xiami.com/song/' + str(info['song_id']) + ) + return song + + def album(self, album_id): + url = self.URL + params = self._make_params(id=album_id, r='album/detail') + resp = self._request(url, params=params, headers=headers) + + info = resp.json()['data'] + songs = [] + album_id=info['album_id'], + album_name=info['album_name'], + artist_id = info['artist_id'] + artist_name = info['artist_name'] + pic_url = re.sub('_\d+\.', '.', info['album_logo']) + for track, info_n in enumerate(info['songs'], 1): + song = Song() + song.feed( + song_id=info_n['song_id'], + song_name=info_n['song_name'], + album_id=album_id, + album_name=album_name, + artist_id=artist_id, + artist_name=artist_name, + singers=info_n['singers'], + album_pic_url=pic_url, + track=track, + comment='http://www.xiami.com/song/' + str(info_n['song_id']) + ) + songs.append(song) + return songs + + def collect(self, collect_id): + url = self.URL + params = self._make_params(id=collect_id, r='collect/detail') + resp = self._request(url, params=params, headers=headers) + + info = resp.json()['data'] + collect_name = info['collect_name'] + collect_id = info['list_id'] + songs = [] + for info_n in info['songs']: + pic_url = re.sub('_\d+\.', '.', info['album_logo']) + song = Song() + song.feed( + song_id=info_n['song_id'], + song_name=info_n['song_name'], + album_id=info_n['album_id'], + album_name=info_n['album_name'], + artist_id=info_n['artist_id'], + artist_name=info_n['artist_name'], + singers=info_n['singers'], + album_pic_url=pic_url, + comment='http://www.xiami.com/song/' + str(info_n['song_id']) + ) + songs.append(song) + return collect_id, collect_name, songs + + def artist_top_songs(self, artist_id, page=1, limit=20): + url = self.URL + params = self._make_params(id=artist_id, page=page, limit=limit, r='artist/hot-songs') + resp = self._request(url, params=params, headers=headers) + + info = resp.json()['data'] + for info_n in info['songs']: + song_id = info_n['song_id'] + yield self.song(song_id) + + def search_songs(self, keywords, page=1, limit=20): + url = self.URL + params = self._make_params(key=keywords, page=page, limit=limit, r='search/songs') + resp = self._request(url, params=params, headers=headers) + + info = resp.json()['data'] + for info_n in info['songs']: + pic_url = re.sub('_\d+\.', '.', info['album_logo']) + song = Song() + song.feed( + song_id=info_n['song_id'], + song_name=info_n['song_name'], + album_id=info_n['album_id'], + album_name=info_n['album_name'], + artist_id=info_n['artist_id'], + artist_name=info_n['artist_name'], + singers=info_n['singer'], + album_pic_url=pic_url, + comment='http://www.xiami.com/song/' + str(info_n['song_id']) + ) + yield song + + def get_song_id(self, *song_sids): + song_ids = [] + for song_sid in song_sids: + if isinstance(song_sid, int) or song_sid.isdigit(): + song_ids.append(int(song_sid)) + + url = 'https://www.xiami.com/song/playlist/id/{}/cat/json'.format(song_sid) + resp = self._request(url, headers=headers) + info = resp.json() + song_id = int(str(info['data']['trackList'][0]['song_id'])) + song_ids.append(song_id) + return song_ids + + +class XiamiWebAPI(object): + + URL = 'https://www.xiami.com/song/playlist/' + + def __init__(self): + self.sess = requests.session() + + def _request(self, url, method='GET', **kwargs): + try: + resp = self.sess.request(method, url, **kwargs) + except Exception, err: + print 'Error:', err + sys.exit() + + return resp + + def _make_song(self, info): + song = Song() + + location=info['location'] + row = location[0] + encryed_url = location[1:] + durl = decry(row, encryed_url) + + song.feed( + song_id=info['song_id'], + song_sub_title=info['song_sub_title'], + songwriters=info['songwriters'], + singers=info['singers'], + song_name=parser.unescape(info['name']), + + album_id=info['album_id'], + album_name=info['album_name'], + + artist_id=info['artist_id'], + artist_name=info['artist_name'], + + composer=info['composer'], + lyric_url='http:' + info['lyric_url'], + + track=info['track'], + cd_serial=info['cd_serial'], + album_pic_url='http:' + info['album_pic'], + comment='http://www.xiami.com/song/' + str(info['song_id']), + + length=info['length'], + play_count=info['playCount'], + + location=info['location'], + location_url=durl + ) + return song + + def _find_z(self, album): + zs = [] + song = album[0] + + for i, song in enumerate(album[:-1]): + next_song = album[i+1] + + cd_serial = song.cd_serial + next_cd_serial = next_song.cd_serial + + if cd_serial != next_cd_serial: + z = len(str(song.track)) + zs.append(z) + + z = len(str(song.track)) + zs.append(z) + + for song in album: + song.z = zs[song.cd_serial - 1] + + def song(self, song_id): + url = self.URL + 'id/%s/cat/json' % song_id + resp = self._request(url, headers=HEADERS2) + + # there is no song + if not resp.json().get('data'): + return None + + info = resp.json()['data']['trackList'][0] + song = self._make_song(info) + return song + + def songs(self, *song_ids): + url = self.URL + 'id/%s/cat/json' % '%2C'.join(song_ids) + resp = self._request(url, headers=HEADERS2) + + # there is no song + if not resp.json().get('data'): + return None + + info = resp.json()['data'] + songs = [] + for info_n in info['trackList']: + song = self._make_song(info_n) + songs.append(song) + return songs + + def album(self, album_id): + url = self.URL + 'id/%s/type/1/cat/json' % album_id + resp = self._request(url, headers=HEADERS2) + + # there is no album + if not resp.json().get('data'): + return None + + info = resp.json()['data'] + songs = [] + for info_n in info['trackList']: + song = self._make_song(info_n) + songs.append(song) + + self._find_z(songs) + return songs + + def collect(self, collect_id): + url = self.URL + 'id/%s/type/3/cat/json' % collect_id + resp = self._request(url, headers=HEADERS2) + + info = resp.json()['data'] + songs = [] + for info_n in info['trackList']: + song = self._make_song(info_n) + songs.append(song) + return songs + + def search_songs(self, keywords): + url = 'https://www.xiami.com/search?key=%s&_=%s' % ( + urllib.quote(keywords), int(time.time() * 1000)) + resp = self._request(url, headers=headers) + + html = resp.content + song_ids = re.findall(r'song/(\w+)"', html) + songs = self.songs(*song_ids) + return songs + + class xiami(object): def __init__(self): - self.dir_ = os.getcwd().decode('utf8') - self.template_record = 'http://www.xiami.com/count/playrecord?sid=%s' + self.dir_ = os.getcwdu() + self.template_record = 'https://www.xiami.com/count/playrecord?sid={song_id}&ishq=1&t={time}&object_id={song_id}&object_name=default&start_point=120&_xiamitoken={token}' self.collect_id = '' self.album_id = '' @@ -129,18 +472,20 @@ def __init__(self): self.disc_description_archives = {} self.download = self.play if args.play else self.download + self._is_play = bool(args.play) + + self._api = XiamiWebAPI() def init(self): if os.path.exists(cookie_file): try: - t = json.loads(open(cookie_file).read()) - ss.cookies.update(t.get('cookies', t)) + cookies = json.load(open(cookie_file)) + ss.cookies.update(cookies.get('cookies', cookies)) if not self.check_login(): print s % (1, 91, ' !! cookie is invalid, please login\n') sys.exit(1) except: - g = open(cookie_file, 'w') - g.close() + open(cookie_file, 'w').close() print s % (1, 97, ' please login') sys.exit(1) else: @@ -150,15 +495,42 @@ def init(self): def check_login(self): #print s % (1, 97, '\n -- check_login') url = 'http://www.xiami.com/task/signin' - r = ss.get(url) + r = self._request(url) if r.content: #print s % (1, 92, ' -- check_login success\n') - self.save_cookies() + # self.save_cookies() return True else: print s % (1, 91, ' -- login fail, please check email and password\n') return False + def _request(self, url, headers=None, params=None, data=None, method='GET', timeout=30, retry=2): + for _ in range(retry): + try: + headers = headers or ss.headers + resp = ss.request(method, url, headers=headers, params=params, data=data, timeout=timeout) + except Exception, err: + continue + + if not resp.ok: + raise Exception("response is not ok, status_code = %s" % resp.status_code) + + # save cookies + self.save_cookies() + + return resp + raise err + + # manually, add cookies + # you must know how to get the cookie + def add_cookies(self, cookies): + _cookies = {} + for item in cookies.strip('; ').split('; '): + k, v = item.split('=', 1) + _cookies[k] = v + self.save_cookies(_cookies) + ss.cookies.update(_cookies) + def login(self, email, password): print s % (1, 97, '\n -- login') @@ -171,9 +543,33 @@ def login(self, email, password): 'LoginButton': '登录' } - url = 'http://www.xiami.com/web/login' - ss.post(url, data=data) - self.save_cookies() + hds = { + 'Origin': 'http://www.xiami.com', + 'Accept-Encoding': 'gzip, deflate', + 'Accept-Language': 'en-US,en;q=0.8', + 'Upgrade-Insecure-Requests': '1', + 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36', + 'Content-Type': 'application/x-www-form-urlencoded', + 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', + 'Cache-Control': 'max-age=1', + 'Referer': 'http://www.xiami.com/web/login', + 'Connection': 'keep-alive', + '_xiamitoken': hashlib.md5(str(time.time())).hexdigest() + } + + url = 'https://login.xiami.com/web/login' + + for i in xrange(2): + res = self._request(url, headers=hds, data=data) + if ss.cookies.get('member_auth'): + return True + else: + if 'checkcode' not in res.content: + return False + validate = self.get_validate(res.content) + data['validate'] = validate + + return False # {{{ code from https://github.com/ly0/xiami-tools/blob/master/xiami.py def login_taobao(self, username, password): @@ -190,8 +586,8 @@ def login_taobao(self, username, password): "notKeepLogin": "", "appName": "xiami", "appEntrance": "taobao", - "cssLink": "https://h.alipayobjects.com/static/applogin/\ - assets/login/mini-login-form-min.css", + "cssLink": "https://h.alipayobjects.com/static/applogin/" \ + "assets/login/mini-login-form-min.css", "styleType": "vertical", "bizParams": "", "notLoadSsoView": "true", @@ -226,9 +622,9 @@ def login_taobao(self, username, password): if 'titleMsg' not in j['content']['data']: continue err_msg = j['content']['data']['titleMsg'] if err_msg == u'请输入验证码' or err_msg == u'验证码错误,请重新输入': - captcha_url = 'http://pin.aliyun.com/get_img?\ - identity=passport.alipay.com&sessionID=%s' % data['cid'] - tr = ss.get(captcha_url, headers=theaders) + captcha_url = 'http://pin.aliyun.com/get_img?' \ + 'identity=passport.alipay.com&sessionID=%s' % data['cid'] + tr = self._request(captcha_url, headers=theaders) path = os.path.join(os.path.expanduser('~'), 'vcode.jpg') with open(path, 'w') as g: img = tr.content @@ -245,55 +641,57 @@ def login_taobao(self, username, password): url = 'http://www.xiami.com/accounts/back?st=%s' \ % j['content']['data']['st'] - ss.get(url, headers=theaders) + self._request(url, headers=theaders) self.save_cookies() return # }}} - def get_validate(self): - url = 'https://login.xiami.com/coop/checkcode?forlogin=1&%s' \ - % int(time.time()) + def get_validate(self, cn): + #url = 'https://login.xiami.com/coop/checkcode?forlogin=1&%s' \ + #% int(time.time()) + url = re.search(r'src="(http.+checkcode.+?)"', cn).group(1) path = os.path.join(os.path.expanduser('~'), 'vcode.png') with open(path, 'w') as g: - data = ss.get(url).content + data = self._request(url).content g.write(data) print " ++ 验证码已经保存至", s % (2, 91, path) - print s % (2, 92, u' 请输入验证码:') - validate = raw_input() + validate = raw_input(s % (2, 92, ' 请输入验证码: ')) return validate - def save_cookies(self): + def save_cookies(self, cookies=None): + if not cookies: + cookies = ss.cookies.get_dict() with open(cookie_file, 'w') as g: - c = { - 'cookies': { - 'member_auth': ss.cookies.get_dict()['member_auth'] - } - } - g.write(json.dumps(c, indent=4, sort_keys=True)) + json.dump(cookies, g) def get_durl(self, id_): while True: try: if not args.low: url = 'http://www.xiami.com/song/gethqsong/sid/%s' - j = ss.get(url % id_).json() + j = self._request(url % id_).json() t = j['location'] else: url = 'http://www.xiami.com/song/playlist/id/%s' - cn = ss.get(url % id_).content + cn = self._request(url % id_).text t = re.search(r'location>(.+?)(http.+?)', xml) if not t: return None lyric_url = t.group(1) - data = ss.get(lyric_url).content.replace('\r\n', '\n') + data = self._request(lyric_url).content.replace('\r\n', '\n') data = lyric_parser(data) if data: return data.decode('utf8', 'ignore') @@ -351,9 +749,9 @@ def lyric_parser(data): def get_disc_description(self, album_url, info): if not self.html: - self.html = ss.get(album_url).content + self.html = self._request(album_url).text t = re.findall(re_disc_description, self.html) - t = dict([(a, modificate_text(parser.unescape(b.decode('utf8')))) \ + t = dict([(a, modificate_text(parser.unescape(b))) \ for a, b in t]) self.disc_description_archives = dict(t) if self.disc_description_archives.has_key(info['cd_serial']): @@ -364,12 +762,12 @@ def get_disc_description(self, album_url, info): def modified_id3(self, file_name, info): id3 = ID3() - id3.add(TRCK(encoding=3, text=info['track'])) - id3.add(TDRC(encoding=3, text=info['year'])) + id3.add(TRCK(encoding=3, text=str(info['track']))) + id3.add(TDRC(encoding=3, text=str(info['year']))) id3.add(TIT2(encoding=3, text=info['song_name'])) id3.add(TALB(encoding=3, text=info['album_name'])) id3.add(TPE1(encoding=3, text=info['artist_name'])) - id3.add(TPOS(encoding=3, text=info['cd_serial'])) + id3.add(TPOS(encoding=3, text=str(info['cd_serial']))) lyric_data = self.get_lyric(info) id3.add(USLT(encoding=3, text=lyric_data)) if lyric_data else None #id3.add(TCOM(encoding=3, text=info['composer'])) @@ -386,22 +784,22 @@ def modified_id3(self, file_name, info): def url_parser(self, urls): for url in urls: if '/collect/' in url: - self.collect_id = re.search(r'/collect/(\d+)', url).group(1) + self.collect_id = re.search(r'/collect/(\w+)', url).group(1) #print(s % (2, 92, u'\n -- 正在分析精选集信息 ...')) self.download_collect() elif '/album/' in url: - self.album_id = re.search(r'/album/(\d+)', url).group(1) + self.album_id = re.search(r'/album/(\w+)', url).group(1) #print(s % (2, 92, u'\n -- 正在分析专辑信息 ...')) self.download_album() elif '/artist/' in url or 'i.xiami.com' in url: def get_artist_id(url): - html = ss.get(url).content - artist_id = re.search(r'artist_id = \'(\d+)\'', html).group(1) + html = self._request(url).text + artist_id = re.search(r'artist_id = \'(\w+)\'', html).group(1) return artist_id - self.artist_id = re.search(r'/artist/(\d+)', url).group(1) \ + self.artist_id = re.search(r'/artist/(\w+)', url).group(1) \ if '/artist/' in url else get_artist_id(url) code = raw_input('>> a # 艺术家所有专辑.\n' \ '>> r # 艺术家 radio\n' \ @@ -418,17 +816,20 @@ def get_artist_id(url): print(s % (1, 92, u' --> Over')) elif '/song/' in url: - self.song_id = re.search(r'/song/(\d+)', url).group(1) + self.song_id = re.search(r'/song/(\w+)', url).group(1) #print(s % (2, 92, u'\n -- 正在分析歌曲信息 ...')) self.download_song() elif '/u/' in url: - self.user_id = re.search(r'/u/(\d+)', url).group(1) - code = raw_input('>> m # 该用户歌曲库.\n' \ - '>> c # 最近在听\n' \ + self.user_id = re.search(r'/u/(\w+)', url).group(1) + code = raw_input( + '>> m # 该用户歌曲库.\n' + '>> c # 最近在听\n' '>> s # 分享的音乐\n' - '>> rm # 私人电台:来源于"收藏的歌曲","收藏的专辑",\ - "喜欢的艺人","收藏的精选集"\n' + '>> r # 歌曲试听排行 - 一周\n' + '>> rt # 歌曲试听排行 - 全部 \n' + '>> rm # 私人电台:来源于"收藏的歌曲","收藏的专辑",' + ' "喜欢的艺人","收藏的精选集"\n' '>> rc # 虾米猜:基于试听行为所建立的个性电台\n>> ') if code == 'm': #print(s % (2, 92, u'\n -- 正在分析用户歌曲库信息 ...')) @@ -436,9 +837,15 @@ def get_artist_id(url): elif code == 'c': self.download_user_songs(url_recent, u'最近在听的歌曲') elif code == 's': - url_shares = 'http://www.xiami.com\ - /space/feed/u/%s/type/3/page/%s' % (self.user_id, '%s') + url_shares = 'http://www.xiami.com' \ + '/space/feed/u/%s/type/3/page/%s' % (self.user_id, '%s') self.download_user_shares(url_shares) + elif code == 'r': + url = 'http://www.xiami.com/space/charts/u/%s/c/song/t/week' % self.user_id + self.download_ranking_songs(url, 'week') + elif code == 'rt': + url = 'http://www.xiami.com/space/charts/u/%s/c/song/t/all' % self.user_id + self.download_ranking_songs(url, 'all') elif code == 'rm': #print(s % (2, 92, u'\n -- 正在分析该用户的虾米推荐 ...')) url_rndsongs = url_radio_my @@ -459,12 +866,12 @@ def get_artist_id(url): elif '/genre/' in url: if '/gid/' in url: self.genre_id = re.search(r'/gid/(\d+)', url).group(1) - url_genre = 'http://www.xiami.com\ - /genre/songs/gid/%s/page/%s' + url_genre = 'http://www.xiami.com' \ + '/genre/songs/gid/%s/page/%s' elif '/sid/' in url: self.genre_id = re.search(r'/sid/(\d+)', url).group(1) - url_genre = 'http://www.xiami.com\ - /genre/songs/sid/%s/page/%s' + url_genre = 'http://www.xiami.com' \ + '/genre/songs/sid/%s/page/%s' else: print s % (1, 91, ' !! Error: missing genre id at url') sys.exit(1) @@ -476,124 +883,55 @@ def get_artist_id(url): elif code == 'r': self.download_genre_radio(url_genre) + elif 'luoo.net' in url: + self.hack_luoo(url) + + elif 'sid=' in url: + _mod = re.search(r'sid=([\w+,]+\w)', url) + if _mod: + song_ids = _mod.group(1).split(',') + self.download_songs(song_ids) + else: - print(s % (2, 91, u' 请正确输入虾米网址.')) + print s % (2, 91, u' 请正确输入虾米网址.') + + def make_file_name(self, song, cd_serial_auth=False): + z = song['z'] + file_name = str(song['track']).zfill(z) + '.' \ + + song['song_name'] \ + + ' - ' + song['artist_name'] + '.mp3' + if cd_serial_auth: + song['file_name'] = ''.join([ + '[Disc-', + str(song['cd_serial']), + ' # ' + song['disc_description'] \ + if song['disc_description'] else '', '] ', + file_name]) + else: + song['file_name'] = file_name def get_songs(self, album_id, song_id=None): - html = ss.get(url_album % album_id).content - html = html.split('
(.+?)<', html1 - ).group(1).decode('utf8', 'ignore') - album_name = modificate_text(t) - - t = re.search( - r'"/artist/\d+.+?>(.+?)<', html1 - ).group(1).decode('utf8', 'ignore') - artist_name = modificate_text(t) - - t = re.findall(r'(\d+)年(\d+)月(\d+)', html1)[0] - year = '-'.join(t).decode('utf8', 'ignore') - - album_description = '' - t = re.search( - r'专辑介绍:(.+?)
', html2, re.DOTALL) - if t: - t = t.group(1) - t = re.sub(r'<.+?>', '', t).decode('utf8', 'ignore') - t = parser.unescape(t) - t = parser.unescape(t) - t = re.sub(r'\s\s+', u'\n', t).strip() - t = re.sub(r'<.+?(http://.+?)".+?>', r'1円', t) - t = re.sub(r'<.+?>([^\n])', r'1円', t) - t = re.sub(r'<.+?>(\r\n|)', u'\n', t) - album_description = t - - t = re.search(r'href="(.+?)" id="albumCover"', html1).group(1) - tt = t.rfind('.') - t = '%s_4%s' % (t[:tt], t[tt:]) - album_pic_url = t.decode('utf8', 'ignore') + songs = self._api.album(album_id) - songs = [] - for c in html2.split('class="trackname"')[1:]: - disc = re.search( - r'>disc (\d+)', c).group(1).decode('utf8', 'ignore') - - t = re.search(r'>disc .+?\[(.+?)\]', c) - disc_description = modificate_text( - t.group(1).decode('utf8', 'ignore')) if t else '' - - t = re.findall(r'"trackid">(\d+)', c) - tracks = [i.lstrip('0').decode('utf8', 'ignore') for i in t] - z = len(str(len(tracks))) - - t = re.findall(r'(.+?) 1 - for i in xrange(len(songs)): - z = songs[i]['z'] - file_name = songs[i]['track'].zfill(z) + '.' \ - + songs[i]['song_name'] \ - + ' - ' + songs[i]['artist_name'] + '.mp3' - if cd_serial_auth: - songs[i]['file_name'] = ''.join([ - '[Disc-', - songs[i]['cd_serial'], - ' # ' + songs[i]['disc_description'] \ - if songs[i]['disc_description'] else '', '] ', - file_name]) - else: - songs[i]['file_name'] = file_name - - t = [i for i in songs if i['song_id'] == song_id] \ - if song_id else songs - songs = t + for song in songs: + self.make_file_name(song, cd_serial_auth=cd_serial_auth) + songs = [i for i in songs if i['song_id'] == song_id] \ + if song_id else songs return songs def get_song(self, song_id): - html = ss.get(url_song % song_id).content - html = html.split('
(.+?)<', html).group(1).decode('utf8') + collect_name = re.search(r'(.+?)<', html).group(1) d = collect_name - dir_ = os.path.join(os.getcwd().decode('utf8'), d) + dir_ = os.path.join(os.getcwdu(), d) self.dir_ = modificate_file_name_for_wget(dir_) - song_ids = re.findall('/song/(\d+)" title', html) amount_songs = unicode(len(song_ids)) song_ids = song_ids[args.from_ - 1:] print(s % (2, 97, u'\n>> ' + amount_songs + u' 首歌曲将要下载.')) \ @@ -641,9 +1002,9 @@ def download_artist_albums(self): ii = 1 album_ids = [] while True: - html = ss.get( - url_artist_albums % (self.artist_id, str(ii))).content - t = re.findall(r'/album/(\d+)"', html) + html = self._request( + url_artist_albums % (self.artist_id, str(ii))).text + t = re.findall(r'/album/(\w+)"', html) if album_ids == t: break album_ids = t if album_ids: @@ -658,13 +1019,12 @@ def download_artist_albums(self): ii += 1 def download_artist_top_20_songs(self): - html = ss.get(url_artist_top_song % self.artist_id).content - song_ids = re.findall(r'/song/(.+?)" title', html) + html = self._request(url_artist_top_song % self.artist_id).text + song_ids = re.findall(r'/music/send/id/(\d+)', html) artist_name = re.search( - r'<p><a href="/index.cgi/contrast/https://github.com/artist/\d+">(.+?)<', html - ).group(1).decode('utf8', 'ignore') + r'<p><a href="/index.cgi/contrast/https://github.com/artist/\w+">(.+?)<', html).group(1) d = modificate_text(artist_name + u' - top 20') - dir_ = os.path.join(os.getcwd().decode('utf8'), d) + dir_ = os.path.join(os.getcwdu(), d) self.dir_ = modificate_file_name_for_wget(dir_) amount_songs = unicode(len(song_ids)) print(s % (2, 97, u'\n>> ' + amount_songs + u' 首歌曲将要下载.')) \ @@ -678,20 +1038,18 @@ def download_artist_top_20_songs(self): n += 1 def download_artist_radio(self): - html = ss.get(url_artist_top_song % self.artist_id).content + html = self._request(url_artist_top_song % self.artist_id).text artist_name = re.search( - r'<p><a href="/index.cgi/contrast/https://github.com/artist/\d+">(.+?)<', html - ).group(1).decode('utf8', 'ignore') - + r'<p><a href="/index.cgi/contrast/https://github.com/artist/\w+">(.+?)<', html).group(1) d = modificate_text(artist_name + u' - radio') - dir_ = os.path.join(os.getcwd().decode('utf8'), d) + dir_ = os.path.join(os.getcwdu(), d) self.dir_ = modificate_file_name_for_wget(dir_) url_artist_radio = "http://www.xiami.com/radio/xml/type/5/id/%s" \ % self.artist_id n = 1 while True: - xml = ss.get(url_artist_radio).content + xml = self._request(url_artist_radio).text song_ids = re.findall(r'<song_id>(\d+)', xml) for i in song_ids: songs = self.get_song(i) @@ -701,13 +1059,13 @@ def download_artist_radio(self): n += 1 def download_user_songs(self, url, desc): - dir_ = os.path.join(os.getcwd().decode('utf8'), + dir_ = os.path.join(os.getcwdu(), u'虾米用户 %s %s' % (self.user_id, desc)) self.dir_ = modificate_file_name_for_wget(dir_) ii = 1 n = 1 while True: - html = ss.get(url % (self.user_id, str(ii))).content + html = self._request(url % (self.user_id, str(ii))).text song_ids = re.findall(r'/song/(.+?)"', html) if song_ids: for i in song_ids: @@ -722,11 +1080,11 @@ def download_user_songs(self, url, desc): def download_user_shares(self, url_shares): d = modificate_text(u'%s 的分享' % self.user_id) - dir_ = os.path.join(os.getcwd().decode('utf8'), d) + dir_ = os.path.join(os.getcwdu(), d) self.dir_ = modificate_file_name_for_wget(dir_) page = 1 while True: - html = ss.get(url_shares % page).content + html = self._request(url_shares % page).text shares = re.findall(r'play.*\(\'\d+\'\)', html) for share in shares: if 'album' in share: @@ -738,13 +1096,32 @@ def download_user_shares(self, url_shares): if not shares: break page += 1 + def download_ranking_songs(self, url, tp): + d = modificate_text(u'%s 的试听排行 - %s' % (self.user_id, tp)) + dir_ = os.path.join(os.getcwdu(), d) + self.dir_ = modificate_file_name_for_wget(dir_) + page = 1 + n = 1 + while True: + html = self._request(url + '/page/' + str(page)).text + song_ids = re.findall(r"play\('(\d+)'", html) + if not song_ids: + break + for song_id in song_ids: + songs = self.get_song(song_id) + self.download(songs, n=n) + self.html = '' + self.disc_description_archives = {} + n += 1 + page += 1 + def download_user_radio(self, url_rndsongs): d = modificate_text(u'%s 的虾米推荐' % self.user_id) - dir_ = os.path.join(os.getcwd().decode('utf8'), d) + dir_ = os.path.join(os.getcwdu(), d) self.dir_ = modificate_file_name_for_wget(dir_) n = 1 while True: - xml = ss.get(url_rndsongs % self.user_id).content + xml = self._request(url_rndsongs % self.user_id).text song_ids = re.findall(r'<song_id>(\d+)', xml) for i in song_ids: songs = self.get_song(i) @@ -754,19 +1131,16 @@ def download_user_radio(self, url_rndsongs): n += 1 def download_chart(self, type_): - html = ss.get('http://www.xiami.com/chart/index/c/%s' \ - % self.chart_id).content - title = re.search( - r'<title>(.+?)', html - ).group(1).decode('utf8', 'ignore') + html = self._request('http://www.xiami.com/chart/index/c/%s' \ + % self.chart_id).text + title = re.search(r'(.+?)', html).group(1) d = modificate_text(title) - dir_ = os.path.join(os.getcwd().decode('utf8'), d) + dir_ = os.path.join(os.getcwdu(), d) self.dir_ = modificate_file_name_for_wget(dir_) - html = ss.get( + html = self._request( 'http://www.xiami.com/chart/data?c=%s&limit=200&type=%s' \ - % (self.chart_id, type_) - ).content + % (self.chart_id, type_)).text song_ids = re.findall(r'/song/(\d+)', html) n = 1 for i in song_ids: @@ -777,19 +1151,17 @@ def download_chart(self, type_): n += 1 def download_genre(self, url_genre): - html = ss.get(url_genre % (self.genre_id, 1)).content + html = self._request(url_genre % (self.genre_id, 1)).text if '/gid/' in url_genre: t = re.search( r'/genre/detail/gid/%s".+?title="(.+?)"' \ - % self.genre_id, html - ).group(1).decode('utf8', 'ignore') + % self.genre_id, html).group(1) elif '/sid/' in url_genre: t = re.search( r'/genre/detail/sid/%s" title="(.+?)"' \ - % self.genre_id, html - ).group(1).decode('utf8', 'ignore') + % self.genre_id, html).group(1) d = modificate_text(u'%s - 代表曲目 - xiami' % t) - dir_ = os.path.join(os.getcwd().decode('utf8'), d) + dir_ = os.path.join(os.getcwdu(), d) self.dir_ = modificate_file_name_for_wget(dir_) n = 1 @@ -803,32 +1175,30 @@ def download_genre(self, url_genre): self.html = '' self.disc_description_archives = {} n += 1 - html = ss.get(url_genre % (self.chart_id, page)).content + html = self._request(url_genre % (self.chart_id, page)).text page += 1 def download_genre_radio(self, url_genre): - html = ss.get(url_genre % (self.genre_id, 1)).content + html = self._request(url_genre % (self.genre_id, 1)).text if '/gid/' in url_genre: t = re.search( r'/genre/detail/gid/%s".+?title="(.+?)"' \ - % self.genre_id, html - ).group(1).decode('utf8', 'ignore') + % self.genre_id, html).group(1) url_genre_radio = "http://www.xiami.com/radio/xml/type/12/id/%s" \ % self.genre_id elif '/sid/' in url_genre: t = re.search( r'/genre/detail/sid/%s" title="(.+?)"' \ - % self.genre_id, html - ).group(1).decode('utf8', 'ignore') + % self.genre_id, html).group(1) url_genre_radio = "http://www.xiami.com/radio/xml/type/13/id/%s" \ % self.genre_id d = modificate_text(u'%s - radio - xiami' % t) - dir_ = os.path.join(os.getcwd().decode('utf8'), d) + dir_ = os.path.join(os.getcwdu(), d) self.dir_ = modificate_file_name_for_wget(dir_) n = 1 while True: - xml = ss.get(url_genre_radio).content + xml = self._request(url_genre_radio).text song_ids = re.findall(r'(\d+)', xml) for i in song_ids: songs = self.get_song(i) @@ -837,44 +1207,80 @@ def download_genre_radio(self, url_genre): self.disc_description_archives = {} n += 1 - def display_infos(self, i, nn, n): - print '\n ----------------' - print '>>', n, '/', nn - print '>>', s % (2, 94, i['file_name']) - print '>>', s % (2, 95, i['album_name']) - print '>>', 'http://www.xiami.com/song/%s' % i['song_id'] - print '>>', 'http://www.xiami.com/album/%s' % i['album_id'] + def hack_luoo(self, url): + # parse luoo.net + theaders = headers + theaders.pop('Referer') + r = requests.get(url) + if not r.ok: + return None + cn = r.content + songs_info = re.findall(r'

(.+?)

\s+' + r'

(?:Artist:|艺人:)(.+?)

\s+' + r'

(?:Album:|专辑:)(.+?)

', cn) + + # search song at xiami + for name, artist, album in songs_info: + name = name.strip() + artist = artist.strip() + album = album.strip() + + songs = self._api.search_songs(name + ' ' + artist) + if not songs: + print s % (1, 93, ' !! no find:'), ' - '.join([name, artist, album]) + continue + + self.make_file_name(songs[0]) + self.download(songs[:1], n=1) + + def display_infos(self, i, nn, n, durl): + length = datetime.datetime.fromtimestamp(i['length']).strftime('%M:%S') + print n, '/', nn + print s % (2, 94, i['file_name']) + print s % (2, 95, i['album_name']) + print s % (2, 93, length) + print 'http://www.xiami.com/song/%s' % i['song_id'] + print 'http://www.xiami.com/album/%s' % i['album_id'] + print durl if i['durl_is_H'] == 'h': - print '>>', s % (1, 97, 'MP3-Quality:'), s % (1, 91, 'High') + print s % (1, 97, 'MP3-Quality:'), s % (1, 92, 'High') else: - print '>>', s % (1, 97, 'MP3-Quality:'), s % (1, 91, 'Low') - print '' + print s % (1, 97, 'MP3-Quality:'), s % (1, 91, 'Low') + print '—' * int(os.popen('tput cols').read()) def get_mp3_quality(self, durl): - if 'm3.file.xiami.com' in durl or 'm6.file.xiami.com' in durl: + if 'm3.file.xiami.com' in durl \ + or 'm6.file.xiami.com' in durl \ + or '_h.mp3' in durl \ + or 'm320.xiami.net' in durl: return 'h' else: return 'l' def play(self, songs, nn=u'1', n=1): + if args.play == 2: + songs = sorted(songs, key=lambda k: k['play_count'], reverse=True) + for i in songs: - self.record(i['song_id']) + self.record(i['song_id'], i['album_id']) durl = self.get_durl(i['song_id']) if not durl: print s % (2, 91, ' !! Error: can\'t get durl'), i['song_name'] continue + cookies = '; '.join(['%s=%s' % (k, v) for k, v in ss.cookies.items()]) mp3_quality = self.get_mp3_quality(durl) i['durl_is_H'] = mp3_quality - self.display_infos(i, nn, n) + self.display_infos(i, nn, n, durl) n = int(n) + 1 cmd = 'mpv --really-quiet ' \ '--cache 8146 ' \ '--user-agent "%s" ' \ - '--http-header-fields="Referer:http://img.xiami.com\ - /static/swf/seiya/1.4/player.swf?v=%s" ' \ + '--http-header-fields "Referer: http://img.xiami.com' \ + '/static/swf/seiya/1.4/player.swf?v=%s",' \ + '"Cookie: %s" ' \ '"%s"' \ - % (headers['User-Agent'], int(time.time()*1000), durl) + % (headers['User-Agent'], int(time.time()*1000), cookies, durl) os.system(cmd) timeout = 1 ii, _, _ = select.select([sys.stdin], [], [], timeout) @@ -883,16 +1289,17 @@ def play(self, songs, nn=u'1', n=1): else: pass - def download(self, songs, amount_songs=u'1', n=None): + def download(self, songs, amount_songs=u'1', n=1): dir_ = modificate_file_name_for_wget(self.dir_) - cwd = os.getcwd().decode('utf8') + cwd = os.getcwd() if dir_ != cwd: if not os.path.exists(dir_): os.mkdir(dir_) + ii = 1 for i in songs: - num = random.randint(0, 100) % 7 + num = random.randint(0, 100) % 8 col = s % (2, num + 90, i['file_name']) t = modificate_file_name_for_wget(i['file_name']) file_name = os.path.join(dir_, t) @@ -927,14 +1334,16 @@ def download(self, songs, amount_songs=u'1', n=None): else: print ' |--', s % (1, 97, 'MP3-Quality:'), s % (1, 91, 'Low') + cookies = '; '.join(['%s=%s' % (k, v) for k, v in ss.cookies.items()]) file_name_for_wget = file_name.replace('`', '\`') - cmd = 'wget -c -T 5 -nv ' \ + quiet = ' -q' if args.quiet else ' -nv' + cmd = 'wget -c%s ' \ '-U "%s" ' \ - '--header "Referer:http://img.xiami.com\ - /static/swf/seiya/1.4/player.swf?v=%s" ' \ + '--header "Referer:http://img.xiami.com' \ + '/static/swf/seiya/1.4/player.swf?v=%s" ' \ + '--header "Cookie: member_auth=%s" ' \ '-O "%s.tmp" %s' \ - % (headers['User-Agent'], int(time.time()*1000), - file_name_for_wget, durl) + % (quiet, headers['User-Agent'], int(time.time()*1000), cookies, file_name_for_wget, durl) cmd = cmd.encode('utf8') status = os.system(cmd) if status != 0: # other http-errors, such as 302. @@ -961,8 +1370,8 @@ def _save_do(self, id_, type, tags): "shareTo": "all", "_xiamitoken": ss.cookies['_xiamitoken'], } - url = 'http://www.xiami.com/ajax/addtag' - r = ss.post(url, data=data) + url = 'https://www.xiami.com/ajax/addtag' + r = self._request(url, data=data, method='POST') j = r.json() if j['status'] == 'ok': return 0 @@ -973,30 +1382,41 @@ def save(self, urls): tags = args.tags for url in urls: if '/collect/' in url: - collect_id = re.search(r'/collect/(\d+)', url).group(1) + collect_id = re.search(r'/collect/(\w+)', url).group(1) print s % (1, 97, u'\n ++ save collect:'), \ 'http://www.xiami.com/song/collect/' + collect_id result = self._save_do(collect_id, 4, tags) elif '/album/' in url: - album_id = re.search(r'/album/(\d+)', url).group(1) + album_id = re.search(r'/album/(\w+)', url).group(1) + album = self._api.album(album_id) + album_id = album[0].album_id print s % (1, 97, u'\n ++ save album:'), \ - 'http://www.xiami.com/album/' + album_id + 'http://www.xiami.com/album/' + str(album_id) result = self._save_do(album_id, 5, tags) elif '/artist/' in url: - artist_id = re.search(r'/artist/(\d+)', url).group(1) + artist_id = re.search(r'/artist/(\w+)', url).group(1) print s % (1, 97, u'\n ++ save artist:'), \ 'http://www.xiami.com/artist/' + artist_id result = self._save_do(artist_id, 6, tags) elif '/song/' in url: - song_id = re.search(r'/song/(\d+)', url).group(1) + song_id = re.search(r'/song/(\w+)', url).group(1) + song = self._api.song(song_id) + song_id = song.song_id print s % (1, 97, u'\n ++ save song:'), \ - 'http://www.xiami.com/song/' + song_id + 'http://www.xiami.com/song/' + str(song_id) result = self._save_do(song_id, 3, tags) + elif '/u/' in url: + user_id = re.search(r'/u/(\d+)', url).group(1) + print s % (1, 97, u'\n ++ save user:'), \ + 'http://www.xiami.com/u/' + user_id + result = self._save_do(user_id, 1, tags) + else: + result = -1 print(s % (2, 91, u' 请正确输入虾米网址.')) if result == 0: @@ -1013,10 +1433,12 @@ def main(argv): p = argparse.ArgumentParser(description='downloading any xiami.com') p.add_argument('xxx', type=str, nargs='*', \ help='命令对象.') - p.add_argument('-p', '--play', action='store_true', \ + p.add_argument('-p', '--play', action='count', \ help='play with mpv') p.add_argument('-l', '--low', action='store_true', \ help='low mp3') + p.add_argument('-q', '--quiet', action='store_true', \ + help='quiet for download') p.add_argument('-f', '--from_', action='store', \ default=1, type=int, \ help='从第几个开始下载,eg: -f 42') @@ -1031,32 +1453,34 @@ def main(argv): comd = argv[1] xxx = args.xxx - if comd == 'login' or comd == 'g' \ - or comd == 'logintaobao' or comd == 'gt': + if comd == 'login' or comd == 'g': + # or comd == 'logintaobao' or comd == 'gt': + # taobao has updated login algorithms which is hard to hack + # so remove it. if len(xxx) < 1: email = raw_input(s % (1, 97, ' username: ') \ if comd == 'logintaobao' or comd == 'gt' \ else s % (1, 97, ' email: ')) - password = getpass(s % (1, 97, ' password: ')) + cookies = getpass(s % (1, 97, ' cookies: ')) elif len(xxx) == 1: - email = xxx[0] - password = getpass(s % (1, 97, ' password: ')) + # for add_member_auth + if '; ' in xxx[0]: + email = None + cookies = xxx[0] + else: + email = xxx[0] + cookies = getpass(s % (1, 97, ' cookies: ')) elif len(xxx) == 2: email = xxx[0] - password = xxx[1] + cookies = xxx[1] else: - print s % (1, 91, - ' login\n login email\n \ - login email password\n \ - logintaobao\n \ - logintaobao username\n \ - logintaobao username password') + msg = ('login: \n' + 'login cookies') + print s % (1, 91, msg) + return x = xiami() - if comd == 'logintaobao' or comd == 'gt': - x.login_taobao(email, password) - else: - x.login(email, password) + x.add_cookies(cookies) is_signin = x.check_login() if is_signin: print s % (1, 92, ' ++ login succeeds.') @@ -1067,9 +1491,14 @@ def main(argv): g = open(cookie_file, 'w') g.close() - elif comd == 'd' or comd == 'download' \ - or comd == 'p' or comd == 'play': - if comd == 'p' or comd == 'play': args.play = True + elif comd == 'd' or comd == 'download': + urls = xxx + x = xiami() + x.init() + x.url_parser(urls) + + elif comd == 'p' or comd == 'play': + if not args.play: args.play = 1 urls = xxx x = xiami() x.init() diff --git a/yunpan.360.cn.py b/yunpan.360.cn.py index 55ede34..807cdc4 100755 --- a/yunpan.360.cn.py +++ b/yunpan.360.cn.py @@ -228,7 +228,7 @@ def download(infos): if os.path.exists(infos['file']): return 0 - num = random.randint(0, 7) % 7 + num = random.randint(0, 7) % 8 col = s % (2, num + 90, infos['file']) infos['nn'] = infos['nn'] if infos.get('nn') else 1 infos['total_file'] = infos['total_file'] if infos.get('total_file') else 1

AltStyle によって変換されたページ (->オリジナル) /