├── get115cookies.py ├── .gitignore ├── LICENSE ├── unzip.py ├── ed2k_search.py ├── leetcode_problems.py ├── flv_cmd.py ├── 91porn.py ├── f115api.py ├── music.baidu.com.py ├── yunpan.360.cn.py ├── bt.py ├── music.163.com.py ├── 115.py ├── tumblr.py ├── README.md └── xiami.py /get115cookies.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding:utf-8 -*- 3 | import requests 4 | import browsercookie 5 | import re 6 | import sys 7 | reload(sys).setdefaultencoding("utf-8") 8 | cj = browsercookie.chrome() 9 | dict115 = cj._cookies[u'.115.com'][u'/'] 10 | cid = dict115['CID'].value 11 | oefl = dict115['OOFL'].value 12 | seid = dict115['SEID'].value 13 | uid = dict115['UID'].value 14 | data = {'cookies': {'CID': cid, 'OEFL': oefl, 'SEID': seid, 'UID': uid}} 15 | import json 16 | with open('/Users/kim/.115.cookies', 'w') as f: 17 | json.dump(data, f, ensure_ascii=False) 18 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | 5 | # C extensions 6 | *.so 7 | 8 | # Distribution / packaging 9 | .Python 10 | env/ 11 | bin/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | eggs/ 16 | lib/ 17 | lib64/ 18 | parts/ 19 | sdist/ 20 | var/ 21 | *.egg-info/ 22 | .installed.cfg 23 | *.egg 24 | 25 | # Installer logs 26 | pip-log.txt 27 | pip-delete-this-directory.txt 28 | 29 | # Unit test / coverage reports 30 | htmlcov/ 31 | .tox/ 32 | .coverage 33 | .cache 34 | nosetests.xml 35 | coverage.xml 36 | 37 | # Translations 38 | *.mo 39 | 40 | # Mr Developer 41 | .mr.developer.cfg 42 | .project 43 | .pydevproject 44 | 45 | # Rope 46 | .ropeproject 47 | 48 | # Django stuff: 49 | *.log 50 | *.pot 51 | 52 | # Sphinx documentation 53 | docs/_build/ 54 | 55 | # other 56 | *.txt 57 | *.jpg 58 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 PeterDing 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /unzip.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import sys 6 | import zipfile 7 | import argparse 8 | 9 | s = '\x1b[%d;%dm%s\x1b[0m' # terminual color template 10 | 11 | def unzip(path): 12 | 13 | file = zipfile.ZipFile(path,"r") 14 | if args.secret: 15 | file.setpassword(args.secret) 16 | 17 | for name in file.namelist(): 18 | try: 19 | utf8name=name.decode('gbk') 20 | pathname = os.path.dirname(utf8name) 21 | except: 22 | utf8name=name 23 | pathname = os.path.dirname(utf8name) 24 | 25 | #print s % (1, 92, ' >> extracting:'), utf8name 26 | #pathname = os.path.dirname(utf8name) 27 | if not os.path.exists(pathname) and pathname != "": 28 | os.makedirs(pathname) 29 | data = file.read(name) 30 | if not os.path.exists(utf8name): 31 | try: 32 | fo = open(utf8name, "w") 33 | fo.write(data) 34 | fo.close 35 | except: 36 | pass 37 | file.close() 38 | 39 | def main(argv): 40 | ###################################################### 41 | # for argparse 42 | p = argparse.ArgumentParser(description='解决unzip乱码') 43 | p.add_argument('xxx', type=str, nargs='*', \ 44 | help='命令对象.') 45 | p.add_argument('-s', '--secret', action='store', \ 46 | default=None, help='密码') 47 | global args 48 | args = p.parse_args(argv[1:]) 49 | xxx = args.xxx 50 | 51 | for path in xxx: 52 | if path.endswith('.zip'): 53 | if os.path.exists(path): 54 | print s % (1, 97, ' ++ unzip:'), path 55 | unzip(path) 56 | else: 57 | print s % (1, 91, ' !! file doesn\'t exist.'), path 58 | else: 59 | print s % (1, 91, ' !! file isn\'t a zip file.'), path 60 | 61 | if __name__ == '__main__': 62 | argv = sys.argv 63 | main(argv) 64 | -------------------------------------------------------------------------------- /ed2k_search.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # vim: set fileencoding=utf8 3 | 4 | import sys 5 | import urllib 6 | import re 7 | import argparse 8 | 9 | s = '\x1b[%d;%dm%s\x1b[0m' # terminual color template 10 | 11 | opener = urllib.urlopen 12 | 13 | class ed2k_search(object): 14 | def __init__(self, keyword=''): 15 | self.url = "http://donkey4u.com/search/%s?page=%s&mode=list" \ 16 | % (keyword, '%s') 17 | print '' 18 | 19 | def get_infos(self, url): 20 | r = opener(url) 21 | assert r 22 | self.html = r.read() 23 | html = re.search(r'.+?
', 24 | self.html, re.DOTALL).group() 25 | 26 | sizes = re.findall(r'(.+)', html) 27 | seeds = re.findall(r'(.+)', html) 28 | links = re.findall(r'ed2k://.+?/', html) 29 | 30 | infos = zip(sizes, seeds, links) 31 | 32 | if infos: 33 | self.display(infos) 34 | else: 35 | print s % (1, 91, ' !! You are not Lucky, geting nothing.') 36 | sys.exit(1) 37 | 38 | def display(self, infos): 39 | template = ' size: ' + s % (1, 97, '%s') \ 40 | + ' seed: ' + s % (1, 91, '%s') \ 41 | + '\n ----------------------------' \ 42 | + '\n ' + s % (2, 92, '%s') \ 43 | + '\n ----------------------------\n' 44 | 45 | for i in infos: 46 | t = template % i 47 | print t 48 | 49 | def do(self): 50 | page = 1 51 | while True: 52 | url = self.url % page 53 | self.get_infos(url) 54 | nx = raw_input(s % (1, 93, ' next page?') + ' (N/y): ') 55 | if nx in ('Y', 'y'): 56 | page += 1 57 | print '' 58 | else: 59 | sys.exit(1) 60 | 61 | 62 | def main(xxx): 63 | keyword = ' '.join(xxx) 64 | x = ed2k_search(keyword) 65 | x.do() 66 | 67 | if __name__ == '__main__': 68 | p = argparse.ArgumentParser( 69 | description='searching ed2k at donkey4u.com') 70 | p.add_argument('xxx', type=str, nargs='*', help='keyword') 71 | args = p.parse_args() 72 | main(args.xxx) 73 | -------------------------------------------------------------------------------- /leetcode_problems.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding=utf-8 -*- 3 | 4 | import sys 5 | import re 6 | import os 7 | import argparse 8 | import requests 9 | from lxml import html as lxml_html 10 | 11 | try: 12 | import html 13 | except ImportError: 14 | import HTMLParser 15 | html = HTMLParser.HTMLParser() 16 | 17 | try: 18 | import cPickle as pk 19 | except ImportError: 20 | import pickle as pk 21 | 22 | class LeetcodeProblems(object): 23 | def get_problems_info(self): 24 | leetcode_url = 'https://leetcode.com/problemset/algorithms' 25 | res = requests.get(leetcode_url) 26 | if not res.ok: 27 | print('request error') 28 | sys.exit() 29 | cm = res.text 30 | cmt = cm.split('tbody>')[-2] 31 | indexs = re.findall(r'(\d+)', cmt) 32 | problem_urls = ['https://leetcode.com' + url \ 33 | for url in re.findall( 34 | r'(.+?)", cmt) 36 | tinfos = zip(indexs, levels, problem_urls) 37 | assert (len(indexs) == len(problem_urls) == len(levels)) 38 | infos = [] 39 | for info in tinfos: 40 | res = requests.get(info[-1]) 41 | if not res.ok: 42 | print('request error') 43 | sys.exit() 44 | tree = lxml_html.fromstring(res.text) 45 | title = tree.xpath('//meta[@property="og:title"]/@content')[0] 46 | description = tree.xpath('//meta[@property="description"]/@content') 47 | if not description: 48 | description = tree.xpath('//meta[@property="og:description"]/@content')[0] 49 | else: 50 | description = description[0] 51 | description = html.unescape(description.strip()) 52 | tags = tree.xpath('//div[@id="tags"]/following::a[@class="btn btn-xs btn-primary"]/text()') 53 | infos.append( 54 | { 55 | 'title': title, 56 | 'level': info[1], 57 | 'index': int(info[0]), 58 | 'description': description, 59 | 'tags': tags 60 | } 61 | ) 62 | 63 | with open('leecode_problems.pk', 'wb') as g: 64 | pk.dump(infos, g) 65 | return infos 66 | 67 | def to_text(self, pm_infos): 68 | if self.args.index: 69 | key = 'index' 70 | elif self.args.title: 71 | key = 'title' 72 | elif self.args.tag: 73 | key = 'tags' 74 | elif self.args.level: 75 | key = 'level' 76 | else: 77 | key = 'index' 78 | 79 | infos = sorted(pm_infos, key=lambda i: i[key]) 80 | 81 | text_template = '## {index} - {title}\n' \ 82 | '~{level}~ {tags}\n' \ 83 | '{description}\n' + '\n' * self.args.line 84 | text = '' 85 | for info in infos: 86 | if self.args.rm_blank: 87 | info['description'] = re.sub(r'[\n\r]+', r'\n', info['description']) 88 | text += text_template.format(**info) 89 | 90 | with open('leecode problems.txt', 'w') as g: 91 | g.write(text) 92 | 93 | def run(self): 94 | if os.path.exists('leecode_problems.pk') and not self.args.redownload: 95 | with open('leecode_problems.pk', 'rb') as f: 96 | pm_infos = pk.load(f) 97 | else: 98 | pm_infos = self.get_problems_info() 99 | 100 | print('find %s problems.' % len(pm_infos)) 101 | self.to_text(pm_infos) 102 | 103 | def handle_args(argv): 104 | p = argparse.ArgumentParser(description='extract all leecode problems to location') 105 | p.add_argument('--index', action='store_true', help='sort by index') 106 | p.add_argument('--level', action='store_true', help='sort by level') 107 | p.add_argument('--tag', action='store_true', help='sort by tag') 108 | p.add_argument('--title', action='store_true', help='sort by title') 109 | p.add_argument('--rm_blank', action='store_true', help='remove blank') 110 | p.add_argument('--line', action='store', type=int, default=10, help='blank of two problems') 111 | p.add_argument('-r', '--redownload', action='store_true', help='redownload data') 112 | args = p.parse_args(argv[1:]) 113 | return args 114 | 115 | def main(argv): 116 | args = handle_args(argv) 117 | x = LeetcodeProblems() 118 | x.args = args 119 | x.run() 120 | 121 | if __name__ == '__main__': 122 | argv = sys.argv 123 | main(argv) 124 | -------------------------------------------------------------------------------- /flv_cmd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # vim: set fileencoding=utf8 3 | 4 | import re 5 | import requests 6 | import os 7 | import sys 8 | import argparse 9 | import random 10 | from HTMLParser import HTMLParser 11 | import urllib 12 | import select 13 | 14 | s = '\x1b[%d;%dm%s\x1b[0m' # terminual color template 15 | parser = HTMLParser() 16 | 17 | ############################################################ 18 | # wget exit status 19 | wget_es = { 20 | 0: "No problems occurred.", 21 | 2: "User interference.", 22 | 1<<8: "Generic error code.", 23 | 2<<8: "Parse error - for instance, \ 24 | when parsing command-line optio.wgetrc or .netrc...", 25 | 3<<8: "File I/O error.", 26 | 4<<8: "Network failure.", 27 | 5<<8: "SSL verification failure.", 28 | 6<<8: "Username/password authentication failure.", 29 | 7<<8: "Protocol errors.", 30 | 8<<8: "Server issued an error response." 31 | } 32 | ############################################################ 33 | 34 | headers = { 35 | "User-Agent": "Mozilla/5.0 (X11; Linux x86_64) " \ 36 | "AppleWebKit/537.36 (KHTML, like Gecko) " \ 37 | "Chrome/40.0.2214.91 Safari/537.36", 38 | "Accept": "text/html,application/xhtml+xml,application/xml;" \ 39 | "q=0.9,image/webp,*/*;q=0.8", 40 | "Accept-Encoding": "gzip, deflate, sdch", 41 | "Accept-Language": "en-US,en;q=0.8", 42 | "Referer": "http://flvgo.com/download" 43 | } 44 | 45 | ss = requests.session() 46 | ss.headers.update(headers) 47 | 48 | def download(info): 49 | if not os.path.exists(info['dir_']): 50 | os.mkdir(info['dir_']) 51 | 52 | #else: 53 | #if os.path.exists(info['filename']): 54 | #return 0 55 | 56 | num = random.randint(0, 7) % 8 57 | col = s % (2, num + 90, os.path.basename(info['filename'])) 58 | print '\n ++ 正在下载:', '#', \ 59 | s % (1, 97, info['n']), '/', \ 60 | s % (1, 97, info['amount']), \ 61 | '#', col 62 | 63 | print info['durl'] 64 | cmd = 'wget -c -nv --user-agent "%s" -O "%s" "%s"' \ 65 | % (headers['User-Agent'], info['filename'], info['durl']) 66 | status = os.system(cmd) 67 | 68 | if status != 0: # other http-errors, such as 302. 69 | wget_exit_status_info = wget_es[status] 70 | print('\n\n ----### \x1b[1;91mERROR\x1b[0m ==> \ 71 | \x1b[1;91m%d (%s)\x1b[0m ###--- \n\n' \ 72 | % (status, wget_exit_status_info)) 73 | print s % (1, 91, ' ===> '), cmd 74 | sys.exit(1) 75 | 76 | def play(info): 77 | num = random.randint(0, 7) % 8 78 | col = s % (2, num + 90, os.path.basename(info['filename'])) 79 | print '\n ++ play:', '#', \ 80 | s % (1, 97, info['n']), '/', \ 81 | s % (1, 97, info['amount']), \ 82 | '#', col 83 | 84 | cmd = 'mpv --really-quiet --cache 8140 --cache-default 8140 ' \ 85 | '--http-header-fields "User-Agent:%s" ' \ 86 | '"%s"' % (headers['User-Agent'], info['durl']) 87 | #'"%s"' % parser.unescape(info['durl']) 88 | 89 | os.system(cmd) 90 | timeout = 1 91 | ii, _, _ = select.select([sys.stdin], [], [], timeout) 92 | if ii: 93 | sys.exit(0) 94 | else: 95 | pass 96 | 97 | def flvxz_parser(cn): 98 | blocks = cn.split('playerContainer')[1:] 99 | infos = {} 100 | title = re.search(r'class="name">(.+?)<', cn).group(1) 101 | infos['title'] = title 102 | infos['data'] = {} 103 | for bc in blocks: 104 | quality = re.search(r'视频格式:(\w+)', bc).group(1) 105 | size = sum([float(s) for s in re.findall(r'>([\d.]+) MB<', bc)]) 106 | durls = re.findall(r'', bc) 107 | infos['data'][quality] = { 108 | 'size': size, 109 | 'durls': durls 110 | } 111 | return infos 112 | 113 | def pickup(infos): 114 | print s % (1, 97, infos['title']) 115 | print s % (1, 97, ' ++ pick a quality:') 116 | sizes = [(infos['data'][q]['size'], q) for q in infos['data']] 117 | sizes.sort() 118 | sizes.reverse() 119 | for i in xrange(len(sizes)): 120 | print s % (1, 91, ' %s' % (i+1)), \ 121 | str(sizes[i][0]) + 'MB\t', sizes[i][1] 122 | 123 | p = raw_input(s % (1, 92, ' Enter') + ' (1): ') 124 | if p == '': 125 | return sizes[0][1] 126 | if not p.isdigit(): 127 | print s % (1, 91, ' !! enter error') 128 | sys.exit() 129 | p = int(p) 130 | if p <= len(infos['data']): 131 | print s % (2, 92, ' -- %s' % sizes[p-1][1]) 132 | return sizes[p-1][1] 133 | else: 134 | print s % (1, 91, ' !! enter error') 135 | sys.exit() 136 | 137 | def getext(durl): 138 | if durl.find('flv'): 139 | return '.flv' 140 | elif durl.find('mp4'): 141 | return '.mp4' 142 | elif durl.find('m3u8'): 143 | return '.m3u8' 144 | else: 145 | return '.flv' 146 | 147 | def main(purl): 148 | apiurl = 'http://flvgo.com/download?url=%s' % urllib.quote(purl) 149 | ss.get('http://flvgo.com') 150 | cn = ss.get(apiurl).content 151 | infos = flvxz_parser(cn) 152 | title = infos['title'] 153 | quality = pickup(infos) 154 | durls = infos['data'][quality]['durls'] 155 | 156 | yes = True if len(durls) > 1 else False 157 | dir_ = os.path.join(os.getcwd(), infos['title']) if yes else os.getcwd() 158 | 159 | n = args.from_ - 1 160 | amount = len(durls) 161 | 162 | for i in xrange(n, amount): 163 | info = { 164 | 'title': title, 165 | 'filename': os.path.join(dir_, str(i+1) + getext(durls[i])), 166 | 'durl': durls[i], 167 | 'dir_': dir_, 168 | 'amount': amount, 169 | 'n': n 170 | } 171 | if args.play: 172 | play(info) 173 | else: 174 | download(info) 175 | n += 1 176 | 177 | if __name__ == '__main__': 178 | p = argparse.ArgumentParser(description='flvxz') 179 | p.add_argument('url', help='site url') 180 | p.add_argument('-p', '--play', action='store_true', \ 181 | help='play with mpv') 182 | p.add_argument('-f', '--from_', action='store', \ 183 | default=1, type=int, \ 184 | help='从第几个开始下载,eg: -f 42') 185 | args = p.parse_args() 186 | main(args.url) 187 | -------------------------------------------------------------------------------- /91porn.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # vim: set fileencoding=utf8 3 | 4 | import os 5 | import sys 6 | import requests 7 | import urlparse 8 | import re 9 | import argparse 10 | import random 11 | import select 12 | import urllib2 13 | 14 | ############################################################ 15 | # wget exit status 16 | wget_es = { 17 | 0: "No problems occurred.", 18 | 2: "User interference.", 19 | 1<<8: "Generic error code.", 20 | 2<<8: "Parse error - for instance, when parsing command-line " \ 21 | "optio.wgetrc or .netrc...", 22 | 3<<8: "File I/O error.", 23 | 4<<8: "Network failure.", 24 | 5<<8: "SSL verification failure.", 25 | 6<<8: "Username/password authentication failure.", 26 | 7<<8: "Protocol errors.", 27 | 8<<8: "Server issued an error response." 28 | } 29 | ############################################################ 30 | 31 | s = '\x1b[%d;%dm%s\x1b[0m' # terminual color template 32 | 33 | headers = { 34 | "Accept":"text/html,application/xhtml+xml,application/xml; " \ 35 | "q=0.9,image/webp,*/*;q=0.8", 36 | "Accept-Encoding":"text/html", 37 | "Accept-Language":"en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4,zh-TW;q=0.2", 38 | "Content-Type":"application/x-www-form-urlencoded", 39 | "User-Agent":"Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 " \ 40 | "(KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36" 41 | } 42 | 43 | ss = requests.session() 44 | ss.headers.update(headers) 45 | 46 | class nrop19(object): 47 | def __init__(self, url=None): 48 | self.url = url 49 | self.download = self.play if args.play else self.download 50 | 51 | def get_infos(self): 52 | r = ss.get(self.url) 53 | if r.ok: 54 | n1 = re.search(r'so.addVariable\(\'file\',\'(\d+)\'', r.content) 55 | n2 = re.search(r'so.addVariable\(\'seccode\',\'(.+?)\'', r.content) 56 | n3 = re.search(r'so.addVariable\(\'max_vid\',\'(\d+)\'', r.content) 57 | 58 | if n1 and n2 and n3: 59 | apiurl = 'http://%s/getfile.php' \ 60 | % urlparse.urlparse(self.url).hostname 61 | 62 | params = { 63 | 'VID': n1.group(1), 64 | 'mp4': '1', 65 | 'seccode': n2.group(1), 66 | 'max_vid': n3.group(1), 67 | } 68 | 69 | #tapiurl = apiurl + '?' + \ 70 | #'&'.join(['='.join(item) for item in params.items()]) 71 | #print tapiurl 72 | 73 | r = requests.get(apiurl, params=params) 74 | if r.ok: 75 | dlink = re.search( 76 | r'file=(http.+?)&', r.content).group(1) 77 | dlink = urllib2.unquote(dlink) 78 | name = re.search( 79 | r'viewkey=([\d\w]+)', self.url).group(1) 80 | infos = { 81 | 'name': '%s.mp4' % name, 82 | 'file': os.path.join(os.getcwd(), '%s.mp4' % name), 83 | 'dir_': os.getcwd(), 84 | 'dlink': dlink, 85 | } 86 | if not args.get_url: 87 | self.download(infos) 88 | else: 89 | print dlink 90 | else: 91 | print s % (1, 91, ' Error at get(apiurl)') 92 | else: 93 | print s % (1, 91, ' You are blocked') 94 | 95 | def download(self, infos): 96 | num = random.randint(0, 7) % 7 97 | col = s % (2, num + 90, infos['file']) 98 | print '\n ++ 正在下载: %s' % col 99 | 100 | cookies = '; '.join( 101 | ['%s=%s' % (i, ii) for i, ii in ss.cookies.items()]) 102 | if args.aria2c: 103 | cmd = 'aria2c -c -x10 -s10 ' \ 104 | '-o "%s.tmp" -d "%s" --header "User-Agent: %s" ' \ 105 | '--header "Cookie: %s" "%s"' \ 106 | % (infos['name'], infos['dir_'], \ 107 | headers['User-Agent'], cookies, infos['dlink']) 108 | else: 109 | cmd = 'wget -c -O "%s.tmp" --header "User-Agent: %s" ' \ 110 | '--header "Cookie: %s" "%s"' \ 111 | % (infos['file'], headers['User-Agent'], cookies, infos['dlink']) 112 | 113 | status = os.system(cmd) 114 | if status != 0: # other http-errors, such as 302. 115 | wget_exit_status_info = wget_es[status] 116 | print('\n\n ----### \x1b[1;91mERROR\x1b[0m ==> '\ 117 | '\x1b[1;91m%d (%s)\x1b[0m ###--- \n\n' \ 118 | % (status, wget_exit_status_info)) 119 | print s % (1, 91, ' ===> '), cmd 120 | sys.exit(1) 121 | else: 122 | os.rename('%s.tmp' % infos['file'], infos['file']) 123 | 124 | def play(self, infos): 125 | num = random.randint(0, 7) % 7 126 | col = s % (2, num + 90, infos['name']) 127 | print '\n ++ play: %s' % col 128 | 129 | cmd = 'mpv --really-quiet --cache 8140 --cache-default 8140 ' \ 130 | '--http-header-fields "user-agent:%s" "%s"' \ 131 | % (headers['User-Agent'], infos['dlink']) 132 | 133 | os.system(cmd) 134 | timeout = 1 135 | ii, _, _ = select.select([sys.stdin], [], [], timeout) 136 | if ii: 137 | sys.exit(0) 138 | else: 139 | pass 140 | 141 | def do(self): 142 | self.get_infos() 143 | 144 | def main(url): 145 | if args.proxy: 146 | ss.proxies = { 147 | 'http': args.proxy, 148 | 'https': args.proxy 149 | } 150 | x = nrop19(url) 151 | x.do() 152 | 153 | if __name__ == '__main__': 154 | p = argparse.ArgumentParser( 155 | description='download from 91porn.com') 156 | p.add_argument('url', help='url of 91porn.com') 157 | p.add_argument('-a', '--aria2c', action='store_true', \ 158 | help='download with aria2c') 159 | p.add_argument('-p', '--play', action='store_true', \ 160 | help='play with mpv') 161 | p.add_argument('-u', '--get_url', action='store_true', \ 162 | help='print download_url without download') 163 | p.add_argument('--proxy', action='store', type=str, default=None, \ 164 | help='print download_url without download') 165 | args = p.parse_args() 166 | main(args.url) 167 | -------------------------------------------------------------------------------- /f115api.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | import requests,ssl,json,time,os 4 | import threading 5 | import sys 6 | import re 7 | import os.path 8 | import os 9 | from pyquery import PyQuery as pq 10 | QRImagePath = os.path.join(os.getcwd(), 'qrcode.jpg') 11 | 12 | 13 | 14 | def getInfos(mySession): 15 | global uid,uidTime,sign,session_id 16 | url = 'http://passport.115.com' 17 | params = { 18 | 'ct': 'login', 19 | 'ac': 'qrcode_token', 20 | 'is_ssl': '1', 21 | } 22 | 23 | r= mySession.get(url=url, params=params) 24 | r.encoding = 'utf-8' 25 | data = r.text 26 | try: 27 | uid = json.loads(data)['uid'] 28 | uidTime = json.loads(data)['time'] 29 | sign = json.loads(data)['sign'] 30 | except Exception, e: 31 | return False 32 | url = 'http://msg.115.com/proapi/anonymous.php' 33 | params = { 34 | 'ac':'signin', 35 | 'user_id':uid, 36 | 'sign':sign, 37 | 'time':uidTime, 38 | '_': str(int(time.time()*1000)), 39 | } 40 | r = mySession.get(url=url, params=params) 41 | session_id = json.loads(r.text)['session_id'] 42 | return True 43 | 44 | 45 | 46 | 47 | def getQrcode(mySession): 48 | global QrcodeUrl 49 | url = 'http://qrcode.115.com/api/qrcode.php' 50 | params = { 51 | 'qrfrom':'1', 52 | 'uid':uid, 53 | '_'+str(uidTime):'', 54 | '_t':str(int(time.time()*1000)), 55 | } 56 | 57 | r = mySession.get(url=url, params=params) 58 | QrcodeUrl = r.url 59 | r.encoding = 'utf-8' 60 | f = open(QRImagePath, 'wb') 61 | f.write(r.content) 62 | f.close() 63 | print(u"使用115手机客户端扫码登录") 64 | time.sleep(1) 65 | 66 | 67 | def getUserinfo(): 68 | global userid 69 | url = 'http://passport.115.com/' 70 | params = { 71 | 'ct':'ajax', 72 | 'ac':'islogin', 73 | 'is_ssl':'1', 74 | '_'+str(int(time.time()*1000)):'', 75 | } 76 | uinfos = json.loads(mySession.get(url=url, params=params).text) 77 | userid = uinfos['data']['USER_ID'] 78 | 79 | print("====================") 80 | print(u"用户ID:"+userid) 81 | print(u"用户名:"+uinfos['data']['USER_NAME']) 82 | if uinfos['data']['IS_VIP'] == 1: 83 | print(u"会员") 84 | url = 'http://115.com/web/lixian/?ct=lixian&ac=task_lists' 85 | data = { 86 | 'page':'1', 87 | 'uid':userid, 88 | 'sign': tsign, 89 | 'time': ttime, 90 | } 91 | quota = json.loads(mySession.post(url=url, data=data).text)['quota'] 92 | total = json.loads(mySession.post(url=url, data=data).text)['total'] 93 | print(u"本月离线配额:"+str(quota)+u"个,总共"+str(total)+u"个。") 94 | else: 95 | print(u"非会员") 96 | print("===================") 97 | 98 | 99 | 100 | def keepLogin(mySession): 101 | while True: 102 | url = 'http://im37.115.com/chat/r' 103 | params = { 104 | 'VER':'2', 105 | 'c':'b0', 106 | 's':session_id, 107 | '_t':str(int(time.time()*1000)), 108 | } 109 | r = mySession.get(url=url, params=params) 110 | time.sleep(60) 111 | 112 | 113 | def waitLogin(mySession): 114 | while True: 115 | url = 'http://im37.115.com/chat/r' 116 | params = { 117 | 'VER':'2', 118 | 'c':'b0', 119 | 's':session_id, 120 | '_t':str(int(time.time()*1000)), 121 | } 122 | r = mySession.get(url=url, params=params) 123 | try: 124 | status = json.loads(r.text)[0]['p'][0]['status'] 125 | if status == 1001: 126 | print(u"请点击登录") 127 | elif status == 1002: 128 | print(u"登录成功") 129 | return 130 | else: 131 | return 132 | except Exception, e: 133 | print(u"超时,请重试") 134 | sys.exit(0) 135 | 136 | def login(mySession): # 触发登陆 137 | url = 'http://passport.115.com/' 138 | params = { 139 | 'ct':'login', 140 | 'ac':'qrcode', 141 | 'key':uid, 142 | 'v':'android', 143 | 'goto':'http%3A%2F%2Fwww.J3n5en.com' 144 | } 145 | r = mySession.get(url=url, params=params) 146 | 147 | 148 | def getTasksign(mySession): # 获取登陆后的sign 149 | global tsign,ttime 150 | url = 'http://115.com/' 151 | params = { 152 | 'ct':'offline', 153 | 'ac':'space', 154 | '_':str(int(time.time()*1000)), 155 | } 156 | r = mySession.get(url=url, params=params) 157 | tsign = json.loads(r.text)['sign'] 158 | ttime = json.loads(r.text)['time'] 159 | 160 | def addLinktask(mySession,link): 161 | url = "http://115.com/web/lixian/?ct=lixian&ac=add_task_url" 162 | data = { 163 | 'url':link, 164 | 'uid':userid, 165 | 'sign':tsign, 166 | 'time':ttime 167 | } 168 | linkinfo = json.loads(mySession.post(url,data=data).content) 169 | try: 170 | print(linkinfo['error_msg']) 171 | except Exception, e: 172 | print(linkinfo['name']) 173 | 174 | def addLinktasks(linklist): 175 | if len(linklist) > 15: 176 | for i in range(0,len(linklist),15): 177 | newlist = linklist[i:i+15] 178 | addLinktasks(newlist) 179 | else: 180 | url = "http://115.com/web/lixian/?ct=lixian&ac=add_task_urls" 181 | data = { 182 | 'uid':userid, 183 | 'sign':tsign, 184 | 'time':ttime 185 | } 186 | for i in range(len(linklist)): 187 | data['url['+str(i)+']'] = linklist[i] 188 | linksinfo = json.loads(mySession.post(url,data=data).text) 189 | # print linksinfo['result'] 190 | for linkinfo in linksinfo['result']: 191 | try: 192 | print(linkinfo['error_msg']) 193 | except Exception, e: 194 | print(linkinfo['name']) 195 | 196 | def get_bt_upload_info(mySession): 197 | global cid,upload_url 198 | # getTasksign() 199 | url = 'http://115.com/' 200 | params = { 201 | 'ct':'lixian', 202 | 'ac':'get_id', 203 | 'torrent': '1', 204 | '_':str(int(time.time()*1000)), 205 | } 206 | cid = json.loads(mySession.post(url,params=params).text)['cid'] 207 | req = mySession.get('http://115.com/?tab=offline&mode=wangpa').content 208 | reg = re.compile('upload\?(\S+?)"') 209 | ids = re.findall(reg, req) 210 | upload_url = ids[0] 211 | 212 | 213 | def upload_torrent(mySession, filename,filedir): 214 | url = 'http://upload.115.com/upload?' + upload_url 215 | files = { 216 | 'Filename':('', 'torrent.torrent', ''), 217 | 'target': ('', 'U_1_'+str(cid), ''), 218 | 'Filedata':('torrent.torrent',open(filedir,'rb'),'application/octet-stream'), 219 | 'Upload':('', 'Submit Query', ''), 220 | } 221 | # mySession.get('http://upload.115.com/crossdomain.xml') 222 | req = mySession.post(url = url, files = files) 223 | req = json.loads(req.content) 224 | if req['state'] is False: 225 | print("上传种子出错了1") 226 | return False 227 | data = {'file_id': req['data']['file_id']} 228 | post_url = 'http://115.com/lixian/?ct=lixian&ac=torrent' 229 | data = { 230 | 'pickcode': req['data']['pick_code'], 231 | 'sha1': req['data']['sha1'], 232 | 'uid':userid, 233 | 'sign': tsign, 234 | 'time': ttime, 235 | } 236 | resp = mySession.post(url=post_url,data=data) 237 | resp = json.loads(resp.content) 238 | if resp['state'] is False: 239 | print("上传种子出错2") 240 | return False 241 | wanted = None 242 | idx = 0 243 | for item in resp['torrent_filelist_web']: 244 | if item['wanted'] != -1: 245 | if wanted is None: 246 | wanted = str(idx) 247 | else: 248 | wanted = wanted + ',' + str(idx) 249 | idx += 1 250 | post_url = 'http://115.com/lixian/?ct=lixian&ac=add_task_bt' 251 | data = { 252 | 'info_hash': resp['info_hash'], 253 | 'wanted': wanted, 254 | 'savepath': resp['torrent_name'].replace('\'', ''), 255 | 'uid':userid, 256 | 'sign': tsign, 257 | 'time': ttime, 258 | } 259 | resp = mySession.post(post_url,data).content 260 | ret = json.loads(resp) 261 | print ret['name'] 262 | if 'error_msg' in ret: 263 | print(ret['error_msg']) 264 | 265 | def add_many_bt(): 266 | get_bt_upload_info() 267 | for parent,dirnames,filenames in os.walk("torrents"): 268 | for filename in filenames: 269 | filedir = os.path.join(parent,filename) 270 | # time.sleep(1) 271 | upload_torrent(filename,filedir) 272 | # print open(qq,'rb') 273 | 274 | # def main(): 275 | # # global mySession 276 | # ssl._create_default_https_context = ssl._create_unverified_context 277 | # headers = {'User-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/51.0.2663.0 Safari/537.36'} 278 | # mySession = requests.Session() 279 | # mySession.headers.update(headers) 280 | # if not getInfos(): 281 | # print(u'获取信息失败') 282 | # return 283 | # getQrcode() # 取二维码 284 | # waitLogin() # 等待手机确认登录 285 | # login() # 触发登陆 286 | # print('开启心跳线程') 287 | # threading.Thread(target=keepLogin) # 开启心跳,防止掉线 288 | # getTasksign() # 获取操作task所需信息 289 | # getUserinfo() # 获取登陆用户信息 290 | # addLinktask("magnet:?xt=urn:btih:690ba0361597ffb2007ad717bd805447f2acc624") 291 | # addLinktasks([link]) 传入一个list 292 | # print tsign 293 | # print "fuck" 294 | # get_bt_upload_info() 295 | # upload_torrent() 296 | # add_many_bt() 297 | 298 | 299 | # if __name__ == '__main__': 300 | # main() 301 | # print cid 302 | # print requests.get("http://j3n5en.com", proxies=proxies).text 303 | -------------------------------------------------------------------------------- /music.baidu.com.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # vim: set fileencoding=utf8 3 | 4 | import re 5 | import sys 6 | import os 7 | import random 8 | import time 9 | import json 10 | import urllib2 11 | import argparse 12 | import select 13 | 14 | from mutagen.id3 import ID3,TRCK,TIT2,TALB,TPE1,APIC,TDRC,COMM,TCOM,TCON,TSST,WXXX,TSRC 15 | from HTMLParser import HTMLParser 16 | parser = HTMLParser() 17 | s = u'\x1b[%d;%dm%s\x1b[0m' # terminual color template 18 | 19 | headers = { 20 | "Accept":"text/html,application/xhtml+xml,application/xml; \ 21 | q=0.9,image/webp,*/*;q=0.8", 22 | "Accept-Encoding":"text/html", 23 | "Accept-Language":"en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4,zh-TW;q=0.2", 24 | "Content-Type":"application/x-www-form-urlencoded", 25 | "Referer":"http://www.baidu.com/", 26 | "User-Agent":"Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 \ 27 | (KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36" 28 | } 29 | 30 | ############################################################ 31 | # wget exit status 32 | wget_es = { 33 | 0:"No problems occurred.", 34 | 2:"User interference.", 35 | 1<<8:"Generic error code.", 36 | 2<<8:"Parse error - for instance, when parsing command-line \ 37 | optio.wgetrc or .netrc...", 38 | 3<<8:"File I/O error.", 39 | 4<<8:"Network failure.", 40 | 5<<8:"SSL verification failure.", 41 | 6<<8:"Username/password authentication failure.", 42 | 7<<8:"Protocol errors.", 43 | 8<<8:"Server issued an error response." 44 | } 45 | ############################################################ 46 | 47 | def modificate_text(text): 48 | text = parser.unescape(text) 49 | text = re.sub(r'//*', '-', text) 50 | text = text.replace('/', '-') 51 | text = text.replace('\\', '-') 52 | text = re.sub(r'\s\s+', ' ', text) 53 | return text 54 | 55 | def modificate_file_name_for_wget(file_name): 56 | file_name = re.sub(r'\s*:\s*', u' - ', file_name) # for FAT file system 57 | file_name = file_name.replace('?', '') # for FAT file system 58 | file_name = file_name.replace('"', '\'') # for FAT file system 59 | return file_name 60 | 61 | def z_index(song_infos): 62 | size = len(song_infos) 63 | z = len(str(size)) 64 | return z 65 | 66 | class baidu_music(object): 67 | def __init__(self, url): 68 | self.url = url 69 | self.song_infos = [] 70 | self.json_url = '' 71 | self.dir_ = os.getcwd().decode('utf8') 72 | self.template_wgets = 'wget -nv -U "%s" -O "%s.tmp" %s' % (headers['User-Agent'], '%s', '%s') 73 | self.template_album = 'http://music.baidu.com/album/%s' 74 | if args.flac: 75 | self.template_api = 'http://music.baidu.com/data/music/fmlink?songIds=%s&type=flac' 76 | elif args.low: 77 | self.template_api = 'http://music.baidu.com/data/music/fmlink?songIds=%s&type=mp3' 78 | elif args.high: 79 | self.template_api = 'http://music.baidu.com/data/music/fmlink?songIds=%s&type=mp3&rate=320' 80 | else: 81 | self.template_api = 'http://music.baidu.com/data/music/fmlink?songIds=%s&type=mp3&rate=320' 82 | 83 | self.album_id = '' 84 | self.song_id = '' 85 | 86 | self.download = self.play if args.play else self.download 87 | 88 | def get_songidlist(self, id_): 89 | html = self.opener.open(self.template_album % id_).read() 90 | songidlist = re.findall(r'/song/(\d+)', html) 91 | api_json = self.opener.open(self.template_api % ','.join(songidlist)).read() 92 | api_json = json.loads(api_json) 93 | infos = api_json['data']['songList'] 94 | return infos 95 | 96 | def get_cover(self, url): 97 | i = 1 98 | while True: 99 | cover_data = self.opener.open(url).read() 100 | if cover_data[:5] != '= 10: 103 | print s % (1, 91, " |--> Error: can't get cover image") 104 | sys.exit(0) 105 | i += 1 106 | 107 | def modified_id3(self, file_name, info): 108 | id3 = ID3() 109 | id3.add(TRCK(encoding=3, text=info['track'])) 110 | id3.add(TIT2(encoding=3, text=info['song_name'])) 111 | id3.add(TALB(encoding=3, text=info['album_name'])) 112 | id3.add(TPE1(encoding=3, text=info['artist_name'])) 113 | id3.add(COMM(encoding=3, desc=u'Comment', text=info['song_url'])) 114 | id3.add(APIC(encoding=3, mime=u'image/jpg', type=3, desc=u'Cover', data=self.get_cover(info['album_pic_url']))) 115 | id3.save(file_name) 116 | 117 | def url_parser(self): 118 | if '/album/' in self.url: 119 | self.album_id = re.search(r'/album/(\d+)', self.url).group(1) 120 | #print(s % (2, 92, u'\n -- 正在分析专辑信息 ...')) 121 | self.get_album_infos() 122 | elif '/song/' in self.url: 123 | self.song_id = re.search(r'/song/(\d+)', self.url).group(1) 124 | #print(s % (2, 92, u'\n -- 正在分析歌曲信息 ...')) 125 | self.get_song_infos() 126 | else: 127 | print(s % (2, 91, u' 请正确输入baidu网址.')) 128 | 129 | def get_song_infos(self): 130 | api_json = self.opener.open(self.template_api % self.song_id).read() 131 | j = json.loads(api_json) 132 | song_info = {} 133 | song_info['song_id'] = unicode(j['data']['songList'][0]['songId']) 134 | song_info['track'] = u'' 135 | song_info['song_url'] = u'http://music.baidu.com/song/' + song_info['song_id'] 136 | song_info['song_name'] = modificate_text(j['data']['songList'][0]['songName']).strip() 137 | song_info['album_name'] = modificate_text(j['data']['songList'][0]['albumName']).strip() 138 | song_info['artist_name'] = modificate_text(j['data']['songList'][0]['artistName']).strip() 139 | song_info['album_pic_url'] = j['data']['songList'][0]['songPicRadio'] 140 | if args.flac: 141 | song_info['file_name'] = song_info['song_name'] + ' - ' + song_info['artist_name'] + '.flac' 142 | else: 143 | song_info['file_name'] = song_info['song_name'] + ' - ' + song_info['artist_name'] + '.mp3' 144 | song_info['durl'] = j['data']['songList'][0]['songLink'] 145 | self.song_infos.append(song_info) 146 | self.download() 147 | 148 | def get_album_infos(self): 149 | songidlist = self.get_songidlist(self.album_id) 150 | z = z_index(songidlist) 151 | ii = 1 152 | for i in songidlist: 153 | song_info = {} 154 | song_info['song_id'] = unicode(i['songId']) 155 | song_info['song_url'] = u'http://music.baidu.com/song/' + song_info['song_id'] 156 | song_info['track'] = unicode(ii) 157 | song_info['song_name'] = modificate_text(i['songName']).strip() 158 | song_info['artist_name'] = modificate_text(i['artistName']).strip() 159 | song_info['album_pic_url'] = i['songPicRadio'] 160 | if args.flac: 161 | song_info['file_name'] = song_info['track'].zfill(z) + '.' + song_info['song_name'] + ' - ' + song_info['artist_name'] + '.flac' 162 | else: 163 | song_info['file_name'] = song_info['track'].zfill(z) + '.' + song_info['song_name'] + ' - ' + song_info['artist_name'] + '.mp3' 164 | song_info['album_name'] = modificate_text(i['albumName']).strip() \ 165 | if i['albumName'] else modificate_text(self.song_infos[0]['album_name']) 166 | song_info['durl'] = i['songLink'] 167 | self.song_infos.append(song_info) 168 | ii += 1 169 | d = modificate_text(self.song_infos[0]['album_name'] + ' - ' + self.song_infos[0]['artist_name']) 170 | self.dir_ = os.path.join(os.getcwd().decode('utf8'), d) 171 | self.download() 172 | 173 | def display_infos(self, i): 174 | print '\n ----------------' 175 | print ' >>', s % (2, 94, i['file_name']) 176 | print ' >>', s % (2, 95, i['album_name']) 177 | print ' >>', s % (2, 92, 'http://music.baidu.com/song/%s' % i['song_id']) 178 | print '' 179 | 180 | def play(self): 181 | for i in self.song_infos: 182 | durl = i['durl'] 183 | self.display_infos(i) 184 | os.system('mpv --really-quiet %s' % durl) 185 | timeout = 1 186 | ii, _, _ = select.select([sys.stdin], [], [], timeout) 187 | if ii: 188 | sys.exit(0) 189 | else: 190 | pass 191 | 192 | def download(self): 193 | dir_ = modificate_file_name_for_wget(self.dir_) 194 | cwd = os.getcwd().decode('utf8') 195 | csongs = len(self.song_infos) 196 | if dir_ != cwd: 197 | if not os.path.exists(dir_): 198 | os.mkdir(dir_) 199 | print(s % (2, 97, u'\n >> ' + str(csongs) + u' 首歌曲将要下载.')) 200 | ii = 1 201 | for i in self.song_infos: 202 | t = modificate_file_name_for_wget(i['file_name']) 203 | file_name = os.path.join(dir_, t) 204 | if os.path.exists(file_name): ## if file exists, no get_durl 205 | ii += 1 206 | continue 207 | file_name_for_wget = file_name.replace('`', '\`') 208 | if 'zhangmenshiting.baidu.com' in i['durl'] or \ 209 | 'yinyueshiting.baidu.com' in i['durl']: 210 | num = random.randint(0,100) % 7 211 | col = s % (2, num + 90, i['file_name']) 212 | print(u'\n ++ 正在下载: %s' % col) 213 | wget = self.template_wgets % (file_name_for_wget, i['durl']) 214 | wget = wget.encode('utf8') 215 | status = os.system(wget) 216 | if status != 0: # other http-errors, such as 302. 217 | wget_exit_status_info = wget_es[status] 218 | print('\n\n ----### \x1b[1;91mERROR\x1b[0m ==> \x1b[1;91m%d (%s)\x1b[0m ###--- \n\n' % (status, wget_exit_status_info)) 219 | print(' ===> ' + wget) 220 | break 221 | else: 222 | os.rename('%s.tmp' % file_name, file_name) 223 | 224 | self.modified_id3(file_name, i) 225 | ii += 1 226 | #time.sleep(10) 227 | else: 228 | print s % (1, 91, ' !! Oops, you are unlucky, the song is not from zhangmenshiting.baidu.com') 229 | print i['durl'] 230 | 231 | def main(url): 232 | x = baidu_music(url) 233 | opener = urllib2.build_opener() 234 | opener.addheaders = headers.items() 235 | x.opener = opener 236 | x.url_parser() 237 | 238 | if __name__ == '__main__': 239 | p = argparse.ArgumentParser(description='downloading any music.baidu.com') 240 | p.add_argument('url', help='any url of music.baidu.com') 241 | p.add_argument('-f', '--flac', action='store_true', help='download flac') 242 | p.add_argument('-i', '--high', action='store_true', help='download 320') 243 | p.add_argument('-l', '--low', action='store_true', help='download 128') 244 | p.add_argument('-p', '--play', action='store_true', \ 245 | help='play with mpv') 246 | args = p.parse_args() 247 | main(args.url) 248 | -------------------------------------------------------------------------------- /yunpan.360.cn.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # vim: set fileencoding=utf8 3 | 4 | import os 5 | import sys 6 | from getpass import getpass 7 | import requests 8 | import urllib 9 | import json 10 | import re 11 | import time 12 | import argparse 13 | import random 14 | import md5 15 | 16 | ############################################################ 17 | # wget exit status 18 | wget_es = { 19 | 0: "No problems occurred.", 20 | 2: "User interference.", 21 | 1<<8: "Generic error code.", 22 | 2<<8: "Parse error - for instance, when parsing command-line " \ 23 | "optio.wgetrc or .netrc...", 24 | 3<<8: "File I/O error.", 25 | 4<<8: "Network failure.", 26 | 5<<8: "SSL verification failure.", 27 | 6<<8: "Username/password authentication failure.", 28 | 7<<8: "Protocol errors.", 29 | 8<<8: "Server issued an error response." 30 | } 31 | ############################################################ 32 | 33 | s = '\x1b[%d;%dm%s\x1b[0m' # terminual color template 34 | 35 | cookie_file = os.path.join(os.path.expanduser('~'), '.360.cookies') 36 | 37 | headers = { 38 | "Accept":"text/html,application/xhtml+xml,application/xml; " \ 39 | "q=0.9,image/webp,*/*;q=0.8", 40 | "Accept-Encoding":"text/html", 41 | "Accept-Language":"en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4,zh-TW;q=0.2", 42 | "Content-Type":"application/x-www-form-urlencoded", 43 | "Referer":"http://yunpan.360.cn/", 44 | "X-Requested-With":"XMLHttpRequest", 45 | "User-Agent":"Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 "\ 46 | "(KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36" 47 | } 48 | 49 | ss = requests.session() 50 | ss.headers.update(headers) 51 | 52 | class yunpan360(object): 53 | def init(self): 54 | if os.path.exists(cookie_file): 55 | try: 56 | t = json.loads(open(cookie_file).read()) 57 | ss.cookies.update(t.get('cookies', t)) 58 | if not self.check_login(): 59 | print s % (1, 91, ' !! cookie is invalid, please login\n') 60 | sys.exit(1) 61 | except: 62 | g = open(cookie_file, 'w') 63 | g.close() 64 | print s % (1, 97, ' please login') 65 | sys.exit(1) 66 | else: 67 | print s % (1, 91, ' !! cookie_file is missing, please login') 68 | sys.exit(1) 69 | 70 | def get_path(self, url): 71 | url = urllib.unquote_plus(url) 72 | f = re.search(r'#(.+?)(&|$)', url) 73 | if f: 74 | return f.group(1) 75 | else: 76 | return '/' 77 | 78 | def check_login(self): 79 | #print s % (1, 97, '\n -- check_login') 80 | url = 'http://yunpan.360.cn/user/login?st=774' 81 | r = ss.get(url) 82 | self.save_cookies() 83 | 84 | if r.ok: 85 | #print s % (1, 92, ' -- check_login success\n') 86 | 87 | # get apihost 88 | self.apihost = re.search(r'http://(.+?)/', r.url).group(1).encode('utf8') 89 | self.save_cookies() 90 | return True 91 | else: 92 | print s % (1, 91, ' -- check_login fail\n') 93 | return False 94 | 95 | def login(self, username, password): 96 | print s % (1, 97, '\n -- login') 97 | 98 | # get token 99 | params = { 100 | "o": "sso", 101 | "m": "getToken", 102 | "func": "QHPass.loginUtils.tokenCallback", 103 | "userName": username, 104 | "rand": random.random() 105 | } 106 | url = 'https://login.360.cn' 107 | r = ss.get(url, params=params) 108 | token = re.search(r'token":"(.+?)"', r.content).group(1) 109 | 110 | # now loin 111 | params = { 112 | "o": "sso", 113 | "m": "login", 114 | "requestScema": "http", 115 | "from": "pcw_cloud", 116 | "rtype": "data", 117 | "func": "QHPass.loginUtils.loginCallback", 118 | "userName": username, 119 | "pwdmethod": 1, 120 | "isKeepAlive": 0, 121 | "token": token, 122 | "captFlag": 1, 123 | "captId": "i360", 124 | "captCode": "", 125 | "lm": 0, 126 | "validatelm": 0, 127 | "password": md5.new(password).hexdigest(), 128 | "r": int(time.time()*1000) 129 | } 130 | url = 'https://login.360.cn' 131 | ss.get(url, params=params) 132 | self.save_cookies() 133 | 134 | def save_cookies(self): 135 | with open(cookie_file, 'w') as g: 136 | c = {'cookies': ss.cookies.get_dict()} 137 | g.write(json.dumps(c, indent=4, sort_keys=True)) 138 | 139 | def get_dlink(self, i): 140 | data = 'nid=%s&fname=%s&' % (i['nid'].encode('utf8'), \ 141 | urllib.quote_plus(i['path'].encode('utf8'))) 142 | apiurl = 'http://%s/file/download' % self.apihost 143 | r = ss.post(apiurl, data=data) 144 | j = r.json() 145 | if j['errno'] == 0: 146 | dlink = j['data']['download_url'].encode('utf8') 147 | return dlink 148 | 149 | def fix_json(self, ori): 150 | # 万恶的 360,返回的json尽然不合法。 151 | jdata = re.search(r'data:\s*\[.+?\]', ori).group() 152 | jlist = re.split(r'\}\s*,\s*\{', jdata) 153 | jlist = [l for l in jlist if l.strip()] 154 | j = [] 155 | for item in jlist: 156 | nid = re.search(r',nid: \'(\d+)\'', item) 157 | path = re.search(r',path: \'(.+?)\',nid', item) 158 | name = re.search(r'oriName: \'(.+?)\',path', item) 159 | isdir = 'isDir: ' in item 160 | if nid: 161 | t = { 162 | 'nid': nid.group(1), 163 | 'path': path.group(1).replace("\\'", "'"), 164 | 'name': name.group(1).replace("\\'", "'"), 165 | 'isdir': 1 if isdir else 0 166 | } 167 | j.append(t) 168 | return j 169 | 170 | def get_infos(self): 171 | apiurl = 'http://%s/file/list' % self.apihost 172 | data = "type" + "=2" + "&" \ 173 | "t" + "=%s" % random.random() + "&" \ 174 | "order" + "=asc" + "&" \ 175 | "field" + "=file_name" + "&" \ 176 | "path" + "=%s" + "&" \ 177 | "page" + "=0" + "&" \ 178 | "page_size" + "=10000" + "&" \ 179 | "ajax" + "=1" 180 | 181 | dir_loop = [self.path] 182 | base_dir = os.path.split(self.path[:-1])[0] if self.path[-1] == '/' \ 183 | and self.path != '/' else os.path.split(self.path)[0] 184 | for d in dir_loop: 185 | data = data % urllib.quote_plus(d) 186 | r = ss.post(apiurl, data=data) 187 | j = self.fix_json(r.text.strip()) 188 | if j: 189 | if args.type_: 190 | j = [x for x in j if x['isdir'] \ 191 | or x['name'][-len(args.type_):] \ 192 | == unicode(args.type_)] 193 | total_file = len([i for i in j if not i['isdir']]) 194 | if args.from_ - 1: 195 | j = j[args.from_-1:] if args.from_ else j 196 | nn = args.from_ 197 | for i in j: 198 | if i['isdir']: 199 | dir_loop.append(i['path'].encode('utf8')) 200 | else: 201 | t = i['path'].encode('utf8') 202 | t = t.replace(base_dir, '') 203 | t = t[1:] if t[0] == '/' else t 204 | t = os.path.join(os.getcwd(), t) 205 | infos = { 206 | 'file': t, 207 | 'dir_': os.path.split(t)[0], 208 | 'dlink': self.get_dlink(i), 209 | 'name': i['name'].encode('utf8'), 210 | 'apihost': self.apihost, 211 | 'nn': nn, 212 | 'total_file': total_file 213 | } 214 | nn += 1 215 | self.download(infos) 216 | else: 217 | print s % (1, 91, ' error: get_infos') 218 | sys.exit(0) 219 | 220 | @staticmethod 221 | def download(infos): 222 | #### !!!! 注意:360不支持断点续传 223 | 224 | ## make dirs 225 | if not os.path.exists(infos['dir_']): 226 | os.makedirs(infos['dir_']) 227 | else: 228 | if os.path.exists(infos['file']): 229 | return 0 230 | 231 | num = random.randint(0, 7) % 8 232 | col = s % (2, num + 90, infos['file']) 233 | infos['nn'] = infos['nn'] if infos.get('nn') else 1 234 | infos['total_file'] = infos['total_file'] if infos.get('total_file') else 1 235 | print '\n ++ 正在下载: #', s % (1, 97, infos['nn']), '/', s % (1, 97, infos['total_file']), '#', col 236 | 237 | cookie = '; '.join(['%s=%s' % (x, y) for x, y in ss.cookies.items()]).encode('utf8') 238 | if args.aria2c: 239 | if args.limit: 240 | cmd = 'aria2c -c -s10 -x10 ' \ 241 | '--max-download-limit %s ' \ 242 | '-o "%s.tmp" -d "%s" ' \ 243 | '--user-agent "%s" ' \ 244 | '--header "Cookie:%s" ' \ 245 | '--header "Referer:http://%s/" "%s"' \ 246 | % (args.limit, infos['name'], infos['dir_'],\ 247 | headers['User-Agent'], cookie, infos['apihost'], infos['dlink']) 248 | else: 249 | cmd = 'aria2c -c -s10 -x10 ' \ 250 | '-o "%s.tmp" -d "%s" --user-agent "%s" ' \ 251 | '--header "Cookie:%s" ' \ 252 | '--header "Referer:http://%s/" "%s"' \ 253 | % (infos['name'], infos['dir_'], headers['User-Agent'], \ 254 | cookie, infos['apihost'], infos['dlink']) 255 | else: 256 | if args.limit: 257 | cmd = 'wget -c --limit-rate %s ' \ 258 | '-O "%s.tmp" --user-agent "%s" ' \ 259 | '--header "Cookie:%s" ' \ 260 | '--header "Referer:http://%s/" "%s"' \ 261 | % (args.limit, infos['file'], headers['User-Agent'], \ 262 | cookie, infos['apihost'], infos['dlink']) 263 | else: 264 | cmd = 'wget -c -O "%s.tmp" --user-agent "%s" ' \ 265 | '--header "Cookie:%s" ' \ 266 | '--header "Referer:http://%s/" "%s"' \ 267 | % (infos['file'], headers['User-Agent'], \ 268 | cookie, infos['apihost'], infos['dlink']) 269 | 270 | status = os.system(cmd) 271 | if status != 0: # other http-errors, such as 302. 272 | wget_exit_status_info = wget_es[status] 273 | print('\n\n ---### \x1b[1;91mERROR\x1b[0m ==> '\ 274 | '\x1b[1;91m%d (%s)\x1b[0m ###--- \n\n' \ 275 | % (status, wget_exit_status_info)) 276 | print s % (1, 91, ' ===> '), cmd 277 | sys.exit(1) 278 | else: 279 | os.rename('%s.tmp' % infos['file'], infos['file']) 280 | 281 | def exists(self, filepath): 282 | pass 283 | 284 | def upload(self, path, dir_): 285 | pass 286 | 287 | def addtask(self): 288 | pass 289 | 290 | def do(self): 291 | self.get_infos() 292 | 293 | def main(argv): 294 | if len(argv) <= 1: 295 | sys.exit() 296 | 297 | ###################################################### 298 | # for argparse 299 | p = argparse.ArgumentParser(description='download from yunpan.360.com') 300 | p.add_argument('xxx', type=str, nargs='*', \ 301 | help='命令对象.') 302 | p.add_argument('-a', '--aria2c', action='store_true', \ 303 | help='download with aria2c') 304 | p.add_argument('-p', '--play', action='store_true', \ 305 | help='play with mpv') 306 | p.add_argument('-f', '--from_', action='store', \ 307 | default=1, type=int, \ 308 | help='从第几个开始下载,eg: -f 42') 309 | p.add_argument('-t', '--type_', action='store', \ 310 | default=None, type=str, \ 311 | help='要下载的文件的后缀,eg: -t mp3') 312 | p.add_argument('-l', '--limit', action='store', \ 313 | default=None, type=str, help='下载速度限制,eg: -l 100k') 314 | global args 315 | args = p.parse_args(argv[1:]) 316 | xxx = args.xxx 317 | 318 | if xxx[0] == 'login' or xxx[0] == 'g': 319 | if len(xxx[1:]) < 1: 320 | username = raw_input(s % (1, 97, ' username: ')) 321 | password = getpass(s % (1, 97, ' password: ')) 322 | elif len(xxx[1:]) == 1: 323 | username = xxx[1] 324 | password = getpass(s % (1, 97, ' password: ')) 325 | elif len(xxx[1:]) == 2: 326 | username = xxx[1] 327 | password = xxx[2] 328 | else: 329 | print s % (1, 91, ' login\n login username\n login username password') 330 | 331 | x = yunpan360() 332 | x.login(username, password) 333 | is_signin = x.check_login() 334 | if is_signin: 335 | print s % (1, 92, ' ++ login succeeds.') 336 | else: 337 | print s % (1, 91, ' login failes') 338 | 339 | elif xxx[0] == 'signout': 340 | g = open(cookie_file, 'w') 341 | g.close() 342 | 343 | else: 344 | urls = xxx 345 | x = yunpan360() 346 | x.init() 347 | for url in urls: 348 | x.path = x.get_path(url) 349 | x.do() 350 | 351 | if __name__ == '__main__': 352 | argv = sys.argv 353 | main(argv) 354 | -------------------------------------------------------------------------------- /bt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # vim: set fileencoding=utf8 3 | 4 | import bencode 5 | import os 6 | import sys 7 | import re 8 | from hashlib import sha1 9 | import base64 10 | import requests 11 | import urlparse 12 | import argparse 13 | 14 | s = '\x1b[%d;%dm%s\x1b[0m' # terminual color template 15 | letters = [i for i in '.abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ' \ 16 | + '0123456789'] 17 | 18 | ############################################################ 19 | headers = { 20 | "Connection": "keep-alive", 21 | "Accept":"text/html,application/xhtml+xml,\ 22 | application/xml;q=0.9,image/webp,*/*;q=0.8", 23 | "Accept-Encoding":"gzip,deflate,sdch", 24 | "Accept-Language":"en-US,en;q=0.8,zh-CN;\ 25 | q=0.6,zh;q=0.4,zh-TW;q=0.2", 26 | "User-Agent":"Mozilla/5.0 (X11; Linux x86_64) \ 27 | AppleWebKit/537.36 (KHTML, like Gecko) \ 28 | Chrome/40.0.2214.91 Safari/537.36" 29 | } 30 | 31 | ss = requests.session() 32 | ss.headers.update(headers) 33 | 34 | def save_img(url, ext): 35 | path = os.path.join(os.path.expanduser('~'), 'vcode.%s' % ext) 36 | with open(path, 'w') as g: 37 | data = requests.get(url).content 38 | g.write(data) 39 | print " ++ 验证码已保存至", s % (1, 97, path) 40 | input_code = raw_input(s % (2, 92, " 输入验证码: ")) 41 | return input_code 42 | 43 | class Bt(object): 44 | def transfer(self, string, tpath, foo=None, bar=None): 45 | self.dir_dict = {} 46 | self.sub_dir_index = 0 47 | 48 | dstring = bencode.bdecode(string) 49 | files = [] 50 | file_index = 0 51 | 52 | ## change files' name 53 | if dstring['info'].get('files'): 54 | for fl in dstring['info']['files']: 55 | filename = fl['path'][-1] 56 | if args.type_ == 'n': 57 | newfilename = re.sub(foo, bar, filename, re.I) \ 58 | if foo and bar else filename 59 | if filename != newfilename: 60 | print filename, s % (1, 92, '==>'), newfilename 61 | path = [self._get_sub_dir_index(i) \ 62 | for i in fl['path'][:-1]] + [newfilename] 63 | else: 64 | ext = os.path.splitext(filename)[-1] 65 | ext = self._check_ext(ext) 66 | path = [self._get_sub_dir_index(i) \ 67 | for i in fl['path'][:-1]] \ 68 | + ['%s%s' % (file_index, ext)] 69 | file_index += 1 70 | fl['path'] = path 71 | 72 | elif args.type_ == 'be64': 73 | fn, ext = os.path.splitext(filename) 74 | ext = self._check_ext(ext) 75 | tfn = '/'.join(fl['path'][:-1] + [fn]) 76 | e_fn = base64.urlsafe_b64encode(tfn) 77 | fl['path'] = [e_fn + '.base64' + ext] 78 | 79 | for item in fl.keys(): 80 | #if item not in ['path', 'length', 'filehash', 'ed2k']: 81 | if item not in ['path', 'length', 'filehash']: 82 | del fl[item] 83 | 84 | files.append(fl) 85 | dstring['info']['files'] = files 86 | 87 | ## change top directory 88 | for i in dstring['info'].keys(): 89 | if i not in ['files', 'piece length', 'pieces', 'name', 'length']: 90 | del dstring['info'][i] 91 | elif 'name' in i: 92 | if args.name: 93 | dstring['info'][i] = args.name 94 | 95 | ## delete comment and creator 96 | for i in dstring.keys(): 97 | if i not in ['creation date', 'announce', 'info', 'encoding']: 98 | del dstring[i] 99 | 100 | c = bencode.bencode(dstring) 101 | with open(tpath, 'w') as g: 102 | g.write(c) 103 | 104 | def _get_sub_dir_index(self, dir_): 105 | if not self.dir_dict.get(dir_): 106 | self.dir_dict[dir_] = str(self.sub_dir_index) 107 | self.sub_dir_index += 1 108 | return self.dir_dict[dir_] 109 | else: 110 | return self.dir_dict[dir_] 111 | 112 | def _check_ext(self, ext): 113 | if len(ext) > 4: 114 | return '' 115 | 116 | for e in ext: 117 | if e not in letters: 118 | return '' 119 | 120 | return ext 121 | 122 | def get_torrent(self, hh): 123 | print s % (1, 93, '\n ++ get torrent from web') 124 | 125 | def do(url, data=None, timeout=None): 126 | try: 127 | proxies = {'http': args.proxy} if args.proxy else None 128 | r = ss.get(url, proxies=proxies, timeout=timeout) 129 | cnt = r.content 130 | if r.ok and cnt and '' not in cnt \ 131 | and '4:name' in cnt: 132 | print s % (1, 92, ' √ get torrent.') 133 | return cnt 134 | else: 135 | print s % (1, 91, ' × not get.') 136 | return None 137 | except: 138 | return None 139 | 140 | ## xunlei 141 | print s % (1, 94, ' >> try:'), 'bt.box.n0808.com' 142 | url = 'http://bt.box.n0808.com/%s/%s/%s.torrent' \ 143 | % (hh[:2], hh[-2:], hh) 144 | ss.headers['Referer'] = 'http://bt.box.n0808.com' 145 | result = do(url) 146 | if result: return result 147 | 148 | ## https://torrage.com 149 | if ss.headers.get('Referer'): del ss.headers['Referer'] 150 | print s % (1, 94, ' >> try:'), 'torrage.com' 151 | url = 'http://torrage.com/torrent/%s.torrent' % hh 152 | try: 153 | result = do(url) 154 | if result: return result 155 | except: 156 | pass 157 | 158 | ## http://btcache.me 159 | if ss.headers.get('Referer'): del ss.headers['Referer'] 160 | print s % (1, 94, ' >> try:'), 'btcache.me' 161 | url = 'http://btcache.me/torrent/%s' % hh 162 | r = ss.get(url) 163 | key = re.search(r'name="key" value="(.+?)"', r.content) 164 | if key: 165 | url = 'http://btcache.me/captcha' 166 | vcode = save_img(url, 'png') 167 | data = { 168 | "key": key.group(1), 169 | "captcha": vcode 170 | } 171 | ss.headers['Referer'] = url 172 | url = 'http://btcache.me/download' 173 | result = do(url, data=data) 174 | if result: return result 175 | else: 176 | print s % (1, 91, ' × not get.') 177 | 178 | ## torrent stores 179 | if ss.headers.get('Referer'): del ss.headers['Referer'] 180 | urls = [ 181 | #'http://www.sobt.org/Tool/downbt?info=%s', 182 | 'http://www.win8down.com/url.php?hash=%s&name=name', 183 | #'http://www.31bt.com/Torrent/%s', 184 | 'http://178.73.198.210/torrent/%s', 185 | 'http://zoink.it/torrent/%s.torrent', 186 | 'http://torcache.net/torrent/%s.torrent', 187 | 'http://torrentproject.se/torrent/%s.torrent', 188 | 'http://istoretor.com/fdown.php?hash=%s', 189 | 'http://torrentbox.sx/torrent/%s', 190 | 'http://www.torrenthound.com/torrent/%s', 191 | 'http://www.silvertorrent.org/download.php?id=%s', 192 | ] 193 | for url in urls: 194 | print s % (1, 94, ' >> try:'), urlparse.urlparse(url).hostname 195 | url = url % hh 196 | try: 197 | result = do(url) 198 | if result: return result 199 | except: 200 | print s % (1, 91, ' !! Error at connection') 201 | 202 | ## with Vuze 203 | #print s % (1, 94, ' >> try:'), 'magnet.vuze.com' 204 | #if ss.headers.get('Referer'): del ss.headers['Referer'] 205 | #chh = base64.b32encode(binascii.unhexlify(hh)) 206 | #url = 'http://magnet.vuze.com/magnetLookup?hash=%s' % chh 207 | #result = do(url) 208 | #if result: return result 209 | 210 | return False 211 | 212 | def magnet2torrent(self, urls, dir_): 213 | for url in urls: 214 | hh = re.search(r'urn:btih:(\w+)', url) 215 | if hh: 216 | hh = hh.group(1).upper() 217 | else: 218 | print s % (1, 91, ' !! magnet is wrong.'), url 219 | continue 220 | string = self.get_torrent(hh) 221 | if string: 222 | tpath = os.path.join(dir_, hh + '.torrent') 223 | print s % (1, 97, ' ++ magnet to torrent:'), \ 224 | 'magnet:?xt=urn:btih:%s' % hh 225 | with open(tpath, 'w') as g: 226 | g.write(string) 227 | else: 228 | print s % (1, 91, ' !! Can\'t get torrent from web.'), url 229 | 230 | def torrent2magnet(self, paths): 231 | def trans(tpath): 232 | if tpath.lower().endswith('torrent'): 233 | string = open(tpath).read() 234 | try: 235 | dd = bencode.bdecode(string) 236 | except Exception as e: 237 | print s % (1, 91, ' !! torrent is wrong:'), e 238 | info = bencode.bencode(dd['info']) 239 | hh = sha1(info).hexdigest() 240 | print '# %s' % tpath 241 | print 'magnet:?xt=urn:btih:%s' % hh, '\n' 242 | 243 | for path in paths: 244 | if os.path.exists(path): 245 | if os.path.isdir(path): 246 | for a, b, c in os.walk(path): 247 | for i in c: 248 | tpath = os.path.join(a, i) 249 | trans(tpath) 250 | elif os.path.isfile(path): 251 | tpath = path 252 | trans(tpath) 253 | else: 254 | print s % (1, 91, ' !! file doesn\'t existed'), \ 255 | s % (1, 93, '--'), path 256 | 257 | def change(self, ups, dir_, foo=None, bar=None): 258 | for up in ups: 259 | path = up 260 | if path.startswith('magnet:'): 261 | hh = re.search(r'urn:btih:(\w+)', path) 262 | if hh: 263 | hh = hh.group(1).upper() 264 | else: 265 | print s % (1, 91, ' !! magnet is wrong.'), path 266 | string = self.get_torrent(hh) 267 | if string: 268 | tpath = os.path.join(dir_, hh + '.torrent') 269 | print s % (1, 97, ' ++ transfer:'), \ 270 | 'magnet:?xt=urn:btih:%s' % hh 271 | self.transfer(string, tpath, foo=foo, bar=bar) 272 | else: 273 | print s % (1, 91, ' !! Can\'t get torrent from web.'), path 274 | 275 | elif os.path.exists(path): 276 | if os.path.isdir(path): 277 | for a, b, c in os.walk(path): 278 | for i in c: 279 | ipath = os.path.join(a, i) 280 | if i.lower().endswith('torrent'): 281 | def do(): 282 | print s % (1, 97, ' ++ transfer:'), ipath 283 | string = open(ipath).read() 284 | tpath = os.path.join(dir_, 'change_' + i) 285 | self.transfer(string, tpath, foo=foo, 286 | bar=bar) 287 | # ??? paths.update(ipath) 288 | if os.getcwd() == os.path.abspath(dir_): 289 | do() 290 | elif os.getcwd() != os.path.abspath(dir_) and \ 291 | os.path.abspath(a) != os.path.abspath(dir_): 292 | do() 293 | elif os.path.isfile(path): 294 | if path.lower().endswith('torrent'): 295 | print s % (1, 97, ' ++ transfer:'), path 296 | string = open(path).read() 297 | tpath = os.path.join(dir_, 298 | 'change_' + os.path.basename(path)) 299 | self.transfer(string, tpath, foo=foo, bar=bar) 300 | else: 301 | print s % (1, 91, ' !! file doesn\'t existed'), \ 302 | s % (1, 93, '--'), path 303 | 304 | def import_magnet(froms): 305 | ml = [] 306 | m_re = re.compile(r'btih:([a-zA-Z0-9]{40})') 307 | 308 | def get_magnet(cm): 309 | ls = m_re.findall(cm) 310 | ls = ['magnet:?xt=urn:btih:' + i for i in ls] 311 | return ls 312 | 313 | for path in froms: 314 | if path[0] == '~': 315 | path = os.path.expanduser(path) 316 | else: 317 | path = os.path.abspath(path) 318 | 319 | if os.path.isfile(path): 320 | cm = open(path).read() 321 | ls = get_magnet(cm) 322 | ml += ls 323 | elif os.path.isdir(path): 324 | for a, b, c in os.walk(path): 325 | for i in c: 326 | p = os.path.join(a, i) 327 | cm = open(p).read() 328 | ls = get_magnet(cm) 329 | ml += ls 330 | else: 331 | print s % (1, 91, ' !! path is wrong:'), path 332 | 333 | t = set(ml) 334 | return list(t) 335 | 336 | def main(argv): 337 | ###################################################### 338 | # for argparse 339 | p = argparse.ArgumentParser( 340 | description='magnet torrent 互转,数字命名bt内容文件名' \ 341 | ' 用法见 https://github.com/PeterDing/iScript') 342 | p.add_argument('xxx', type=str, nargs='*', 343 | help='命令对象.') 344 | p.add_argument('-i', '--import_from', type=str, nargs='*', 345 | help='import magnet from local.') 346 | p.add_argument('-p', '--proxy', action='store', 347 | type=str, help='proxy for torrage.com, \ 348 | eg: -p "sooks5://127.0.0.1:8883"') 349 | p.add_argument('-d', '--directory', action='store', default=None, 350 | type=str, help='torrents保存的路径, eg: -d /path/to/save') 351 | p.add_argument('-n', '--name', action='store', default=None, 352 | type=str, help='顶级文件夹名称, eg: -n thistopdirectory') 353 | p.add_argument('-t', '--type_', action='store', 354 | default='n', type=str, 355 | help='类型参数,eg: ') 356 | global args 357 | args = p.parse_args(argv[2:]) 358 | comd = argv[1] 359 | xxx = args.xxx 360 | 361 | dir_ = os.getcwd() if not args.directory \ 362 | else args.directory 363 | if not os.path.exists(dir_): 364 | os.mkdir(dir_) 365 | if comd == 'm' or comd == 'mt': # magnet to torrent 366 | urls = xxx if not args.import_from \ 367 | else import_magnet(args.import_from) 368 | x = Bt() 369 | x.magnet2torrent(urls, dir_) 370 | 371 | elif comd == 't' or comd == 'tm': # torrent ot magnet 372 | paths = xxx 373 | x = Bt() 374 | x.torrent2magnet(paths) 375 | 376 | elif comd == 'c' or comd == 'ct': # change 377 | ups = xxx if not args.import_from \ 378 | else import_magnet(args.import_from) 379 | x = Bt() 380 | x.change(ups, dir_, foo=None, bar=None) 381 | 382 | elif comd == 'cr' or comd == 'ctre': # change 383 | foo = xxx[0] 384 | bar = xxx[1] 385 | ups = xxx[2:] if not args.import_from \ 386 | else import_magnet(args.import_from) 387 | x = Bt() 388 | x.change(ups, dir_, foo=foo, bar=bar) 389 | 390 | else: 391 | print s % (2, 91, ' !! 命令错误\n') 392 | 393 | if __name__ == '__main__': 394 | argv = sys.argv 395 | main(argv) 396 | -------------------------------------------------------------------------------- /music.163.com.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # vim: set fileencoding=utf8 3 | 4 | import re 5 | import sys 6 | import os 7 | import random 8 | import time 9 | import json 10 | import argparse 11 | import urllib 12 | import requests 13 | import select 14 | import md5 15 | from mutagen.id3 import ID3,TRCK,TIT2,TALB,TPE1,APIC,TDRC,COMM,TPOS,USLT 16 | from HTMLParser import HTMLParser 17 | 18 | parser = HTMLParser() 19 | s = u'\x1b[%d;%dm%s\x1b[0m' # terminual color template 20 | 21 | ############################################################ 22 | # music.163.com api 23 | # {{{ 24 | url_song = "http://music.163.com/api/song/detail?id=%s&ids=%s" 25 | url_album = "http://music.163.com/api/album/%s" 26 | url_playlist = "http://music.163.com/api/playlist/detail?id=%s&ids=%s" 27 | url_dj = "http://music.163.com/api/dj/program/detail?id=%s&ids=%s" 28 | url_artist_albums = "http://music.163.com\ 29 | /api/artist/albums/%s?offset=0&limit=1000" 30 | url_artist_top_50_songs = "http://music.163.com/artist?id=%s" 31 | # }}} 32 | ############################################################ 33 | 34 | ############################################################ 35 | # wget exit status 36 | wget_es = { 37 | 0:"No problems occurred.", 38 | 2:"User interference.", 39 | 1<<8:"Generic error code.", 40 | 2<<8:"Parse error - for instance, when parsing command-line ' \ 41 | 'optio.wgetrc or .netrc...", 42 | 3<<8:"File I/O error.", 43 | 4<<8:"Network failure.", 44 | 5<<8:"SSL verification failure.", 45 | 6<<8:"Username/password authentication failure.", 46 | 7<<8:"Protocol errors.", 47 | 8<<8:"Server issued an error response." 48 | } 49 | ############################################################ 50 | 51 | headers = { 52 | "Accept":"text/html,application/xhtml+xml,application/xml; " \ 53 | "q=0.9,image/webp,*/*;q=0.8", 54 | "Accept-Encoding":"text/html", 55 | "Accept-Language":"en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4,zh-TW;q=0.2", 56 | "Content-Type":"application/x-www-form-urlencoded", 57 | "Referer":"http://music.163.com/", 58 | "User-Agent":"Mozilla/5.0 (X11; Linux x86_64) " \ 59 | "AppleWebKit/537.36 (KHTML, like Gecko) " \ 60 | "Chrome/40.0.2214.91 Safari/537.36" 61 | } 62 | 63 | ss = requests.session() 64 | ss.headers.update(headers) 65 | 66 | def encrypted_id(id): 67 | byte1 = bytearray('3go8&$8*3*3h0k(2)2') 68 | byte2 = bytearray(id) 69 | byte1_len = len(byte1) 70 | for i in xrange(len(byte2)): 71 | byte2[i] = byte2[i]^byte1[i%byte1_len] 72 | m = md5.new() 73 | m.update(byte2) 74 | result = m.digest().encode('base64')[:-1] 75 | result = result.replace('/', '_') 76 | result = result.replace('+', '-') 77 | return result 78 | 79 | def modificate_text(text): 80 | text = parser.unescape(text) 81 | text = re.sub(r'//*', '-', text) 82 | text = text.replace('/', '-') 83 | text = text.replace('\\', '-') 84 | text = re.sub(r'\s\s+', ' ', text) 85 | return text 86 | 87 | # for FAT file system 88 | def modificate_file_name_for_wget(file_name): 89 | file_name = re.sub(r'\s*:\s*', u' - ', file_name) 90 | file_name = file_name.replace('?', '') 91 | file_name = file_name.replace('"', '\'') 92 | return file_name 93 | 94 | def z_index(size): 95 | z = len(str(size)) 96 | return z 97 | 98 | ######################################################## 99 | 100 | class neteaseMusic(object): 101 | def __init__(self, url): 102 | self.url = url 103 | self.song_infos = [] 104 | self.dir_ = os.getcwd().decode('utf8') 105 | 106 | self.playlist_id = '' 107 | self.dj_id = '' 108 | self.album_id = '' 109 | self.artist_id = '' 110 | self.song_id = '' 111 | self.cover_id = '' 112 | self.cover_data = '' 113 | self.amount_songs = u'1' 114 | 115 | self.download = self.play if args.play else self.download 116 | 117 | def get_durl(self, i): 118 | for q in ('hMusic', 'mMusic', 'lMusic'): 119 | if i[q]: 120 | dfsId = str(i[q]['dfsId']) 121 | edfsId = encrypted_id(dfsId) 122 | durl = u'http://m2.music.126.net/%s/%s.mp3' \ 123 | % (edfsId, dfsId) 124 | return durl, q[0] 125 | 126 | def get_cover(self, info): 127 | if info['album_name'] == self.cover_id: 128 | return self.cover_data 129 | else: 130 | self.cover_id = info['album_name'] 131 | while True: 132 | url = info['album_pic_url'] 133 | try: 134 | self.cover_data = requests.get(url).content 135 | if self.cover_data[:5] != '> 输入 a 下载该艺术家所有专辑.\n' \ 183 | ' >> 输入 t 下载该艺术家 Top 50 歌曲.\n >> ') 184 | if code == 'a': 185 | print(s % (2, 92, u'\n -- 正在分析艺术家专辑信息 ...')) 186 | self.download_artist_albums() 187 | elif code == 't': 188 | print(s % (2, 92, u'\n -- 正在分析艺术家 Top 50 信息 ...')) 189 | self.download_artist_top_50_songs() 190 | else: 191 | print(s % (1, 92, u' --> Over')) 192 | elif 'song' in self.url: 193 | self.song_id = re.search( 194 | r'song.+?(\d+)', self.url).group(1) 195 | print(s % (2, 92, u'\n -- 正在分析歌曲信息 ...')) 196 | self.download_song() 197 | elif 'djradio' in self.url: 198 | self.djradio_id = re.search( 199 | r'id=(\d+)', self.url).group(1) 200 | print(s % (2, 92, u'\n -- 正在分析DJ节目信息 ...')) 201 | self.download_djradio() 202 | elif 'program' in self.url: 203 | self.dj_id = re.search( 204 | r'id=(\d+)', self.url).group(1) 205 | print(s % (2, 92, u'\n -- 正在分析DJ节目信息 ...')) 206 | self.download_dj() 207 | else: 208 | print(s % (2, 91, u' 请正确输入music.163.com网址.')) 209 | 210 | def get_song_info(self, i): 211 | z = z_index(i['album']['size']) \ 212 | if i['album'].get('size') else 1 213 | song_info = {} 214 | song_info['song_id'] = i['id'] 215 | song_info['song_url'] = u'http://music.163.com/song/%s' \ 216 | % i['id'] 217 | song_info['track'] = str(i['position']) 218 | song_info['durl'], song_info['mp3_quality'] = self.get_durl(i) 219 | #song_info['album_description'] = album_description 220 | #song_info['lyric_url'] = i['lyric'] 221 | #song_info['sub_title'] = i['sub_title'] 222 | #song_info['composer'] = i['composer'] 223 | #song_info['disc_code'] = i['disc_code'] 224 | #if not song_info['sub_title']: song_info['sub_title'] = u'' 225 | #if not song_info['composer']: song_info['composer'] = u'' 226 | #if not song_info['disc_code']: song_info['disc_code'] = u'' 227 | t = time.gmtime(int(i['album']['publishTime'])*0.001) 228 | #song_info['year'] = unicode('-'.join([str(t.tm_year), \ 229 | #str(t.tm_mon), str(t.tm_mday)])) 230 | song_info['year'] = unicode('-'.join( 231 | [str(t.tm_year), str(t.tm_mon), str(t.tm_mday)] 232 | )) 233 | song_info['song_name'] = modificate_text(i['name']).strip() 234 | song_info['artist_name'] = modificate_text(i['artists'][0]['name']) 235 | song_info['album_pic_url'] = i['album']['picUrl'] 236 | song_info['cd_serial'] = u'1' 237 | song_info['album_name'] = modificate_text(i['album']['name']) 238 | file_name = song_info[ 'track'].zfill(z) \ 239 | + '.' + song_info['song_name'] \ 240 | + ' - ' + song_info['artist_name'] \ 241 | + '.mp3' 242 | song_info['file_name'] = file_name 243 | # song_info['low_mp3'] = i['mp3Url'] 244 | return song_info 245 | 246 | def get_song_infos(self, songs): 247 | for i in songs: 248 | song_info = self.get_song_info(i) 249 | self.song_infos.append(song_info) 250 | 251 | def download_song(self, noprint=False, n=1): 252 | j = ss.get( 253 | url_song % ( 254 | self.song_id, urllib.quote('[%s]' % self.song_id) 255 | ) 256 | ).json() 257 | songs = j['songs'] 258 | if not noprint: 259 | print(s % (2, 97, u'\n >> ' + u'1 首歌曲将要下载.')) \ 260 | if not args.play else '' 261 | self.get_song_infos(songs) 262 | self.download(self.amount_songs, n) 263 | 264 | def download_album(self): 265 | j = ss.get(url_album % (self.album_id)).json() 266 | songs = j['album']['songs'] 267 | d = modificate_text( 268 | j['album']['name'] \ 269 | + ' - ' + j['album']['artist']['name']) 270 | dir_ = os.path.join(os.getcwd().decode('utf8'), d) 271 | self.dir_ = modificate_file_name_for_wget(dir_) 272 | self.amount_songs = unicode(len(songs)) 273 | print(s % (2, 97, \ 274 | u'\n >> ' + self.amount_songs + u' 首歌曲将要下载.')) \ 275 | if not args.play else '' 276 | self.get_song_infos(songs) 277 | self.download(self.amount_songs) 278 | 279 | def download_playlist(self): 280 | j = ss.get( 281 | url_playlist % ( 282 | self.playlist_id, urllib.quote('[%s]' % self.playlist_id) 283 | ) 284 | ).json() 285 | songs = j['result']['tracks'] 286 | d = modificate_text( 287 | j['result']['name'] + ' - ' \ 288 | + j['result']['creator']['nickname']) 289 | dir_ = os.path.join(os.getcwd().decode('utf8'), d) 290 | self.dir_ = modificate_file_name_for_wget(dir_) 291 | self.amount_songs = unicode(len(songs)) 292 | print(s % (2, 97, u'\n >> ' \ 293 | + self.amount_songs + u' 首歌曲将要下载.')) \ 294 | if not args.play else '' 295 | self.get_song_infos(songs) 296 | self.download(self.amount_songs) 297 | 298 | def download_djradio(self): 299 | html = ss.get( 300 | 'http://music.163.com/djradio?id=%s' \ 301 | % self.djradio_id).content 302 | dj_ids = re.findall(r'/program\?id=(\d+)', html) 303 | 304 | for dj_id in dj_ids: 305 | self.dj_id = dj_id 306 | self.download_dj() 307 | self.song_infos = [] 308 | 309 | def download_dj(self): 310 | j = ss.get( 311 | url_dj % ( 312 | self.dj_id, urllib.quote('[%s]' % self.dj_id) 313 | ) 314 | ).json() 315 | songs = j['program']['songs'] 316 | d = modificate_text( 317 | j['program']['name'] + ' - ' \ 318 | + j['program']['dj']['nickname']) 319 | dir_ = os.path.join(os.getcwd().decode('utf8'), d) 320 | self.dir_ = modificate_file_name_for_wget(dir_) 321 | self.amount_songs = unicode(len(songs)) 322 | print(s % (2, 97, u'\n >> \ 323 | ' + self.amount_songs + u' 首歌曲将要下载.')) \ 324 | if not args.play else None 325 | self.get_song_infos(songs) 326 | self.download(self.amount_songs) 327 | 328 | 329 | def download_artist_albums(self): 330 | ss.cookies.update({'appver': '1.5.2'}) 331 | j = ss.get( 332 | url_artist_albums % self.artist_id).json() 333 | for albuminfo in j['hotAlbums']: 334 | self.album_id = albuminfo['id'] 335 | self.download_album() 336 | 337 | def download_artist_top_50_songs(self): 338 | html = ss.get( 339 | url_artist_top_50_songs % self.artist_id).content 340 | text = re.search( 341 | r'', html).group(1) 342 | j = json.loads(text) 343 | songids = [i['id'] for i in j] 344 | d = modificate_text( 345 | j[0]['artists'][0]['name'] + ' - ' + 'Top 50') 346 | dir_ = os.path.join(os.getcwd().decode('utf8'), d) 347 | self.dir_ = modificate_file_name_for_wget(dir_) 348 | self.amount_songs = unicode(len(songids)) 349 | print(s % (2, 97, u'\n >> \ 350 | ' + self.amount_songs + u' 首歌曲将要下载.')) \ 351 | if not args.play else '' 352 | n = 1 353 | for sid in songids: 354 | self.song_id = sid 355 | self.song_infos = [] 356 | self.download_song(noprint=True, n=n) 357 | n += 1 358 | 359 | def display_infos(self, i): 360 | q = {'h': 'High', 'm': 'Middle', 'l': 'Low'} 361 | print '\n ----------------' 362 | print ' >>', s % (2, 94, i['file_name']) 363 | print ' >>', s % (2, 95, i['album_name']) 364 | print ' >>', s % (2, 92, 'http://music.163.com/song/%s' \ 365 | % i['song_id']) 366 | print ' >>', s % (2, 97, 'MP3-Quality'), ':', \ 367 | s % (1, 92, q[i['mp3_quality']]) 368 | print '' 369 | 370 | def play(self, amount_songs, n=None): 371 | for i in self.song_infos: 372 | self.display_infos(i) 373 | cmd = 'mpv --really-quiet --audio-display no %s' % i['durl'] 374 | os.system(cmd) 375 | timeout = 1 376 | ii, _, _ = select.select([sys.stdin], [], [], timeout) 377 | if ii: 378 | sys.exit(0) 379 | else: 380 | pass 381 | 382 | def download(self, amount_songs, n=None): 383 | dir_ = modificate_file_name_for_wget(self.dir_) 384 | cwd = os.getcwd().decode('utf8') 385 | if dir_ != cwd: 386 | if not os.path.exists(dir_): 387 | os.mkdir(dir_) 388 | ii = 1 389 | for i in self.song_infos: 390 | num = random.randint(0, 100) % 7 391 | col = s % (2, num + 90, i['file_name']) 392 | t = modificate_file_name_for_wget(i['file_name']) 393 | file_name = os.path.join(dir_, t) 394 | if os.path.exists(file_name): # if file exists, no get_durl 395 | if args.undownload: 396 | self.modified_id3(file_name, i) 397 | ii += 1 398 | continue 399 | else: 400 | ii += 1 401 | continue 402 | if not args.undownload: 403 | q = {'h': 'High', 'm': 'Middle', 'l': 'Low'} 404 | mp3_quality = q[i['mp3_quality']] 405 | if n == None: 406 | print(u'\n ++ 正在下载: #%s/%s# %s\n' \ 407 | u' ++ mp3_quality: %s' \ 408 | % (ii, amount_songs, col, 409 | s % (1, 91, mp3_quality))) 410 | else: 411 | print(u'\n ++ 正在下载: #%s/%s# %s\n' \ 412 | u' ++ mp3_quality: %s' \ 413 | % (n, amount_songs, col, 414 | s % (1, 91, mp3_quality))) 415 | file_name_for_wget = file_name.replace('`', '\`') 416 | cmd = 'wget -c -nv -U "%s" -O "%s.tmp" %s' \ 417 | % (headers['User-Agent'], file_name_for_wget, i['durl']) 418 | cmd = cmd.encode('utf8') 419 | status = os.system(cmd) 420 | if status != 0: # other http-errors, such as 302. 421 | wget_exit_status_info = wget_es[status] 422 | print('\n\n ----### \x1b[1;91mERROR\x1b[0m ==> \x1b[1;91m%d ' \ 423 | '(%s)\x1b[0m ###--- \n\n' \ 424 | % (status, wget_exit_status_info)) 425 | print s % (1, 91, ' ===> '), cmd 426 | sys.exit(1) 427 | else: 428 | os.rename('%s.tmp' % file_name, file_name) 429 | 430 | self.modified_id3(file_name, i) 431 | ii += 1 432 | time.sleep(0) 433 | 434 | def main(url): 435 | x = neteaseMusic(url) 436 | x.url_parser() 437 | 438 | if __name__ == '__main__': 439 | p = argparse.ArgumentParser( 440 | description='downloading any music.163.com') 441 | p.add_argument('url', help='any url of music.163.com') 442 | p.add_argument('-p', '--play', action='store_true', \ 443 | help='play with mpv') 444 | p.add_argument('-c', '--undownload', action='store_true', \ 445 | help='no download, using to renew id3 tags') 446 | args = p.parse_args() 447 | main(args.url) 448 | -------------------------------------------------------------------------------- /115.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding:utf-8 -*- 3 | #!/usr/bin/env python2 4 | # vim: set fileencoding=utf8 5 | 6 | import os 7 | import sys 8 | from getpass import getpass 9 | import requests 10 | import urllib 11 | import json 12 | import re 13 | import time 14 | import argparse 15 | import random 16 | import sha 17 | import select 18 | import f115api 19 | import threading 20 | reload(sys).setdefaultencoding("utf-8") 21 | ############################################################ 22 | # wget exit status 23 | wget_es = { 24 | 0: "No problems occurred.", 25 | 2: "User interference.", 26 | 1<<8: "Generic error code.", 27 | 2<<8: "Parse error - for instance, when parsing command-line " \ 28 | "optio.wgetrc or .netrc...", 29 | 3<<8: "File I/O error.", 30 | 4<<8: "Network failure.", 31 | 5<<8: "SSL verification failure.", 32 | 6<<8: "Username/password authentication failure.", 33 | 7<<8: "Protocol errors.", 34 | 8<<8: "Server issued an error response." 35 | } 36 | ############################################################ 37 | 38 | # file extensions 39 | mediatype = [ 40 | ".wma", ".wav", ".mp3", ".aac", ".ra", ".ram", ".mp2", ".ogg", ".aif", 41 | ".mpega", ".amr", ".mid", ".midi", ".m4a", ".m4v", ".wmv", ".rmvb", 42 | ".mpeg4", ".mpeg2", ".flv", ".avi", ".3gp", ".mpga", ".qt", ".rm", 43 | ".wmz", ".wmd", ".wvx", ".wmx", ".wm", ".swf", ".mpg", ".mp4", ".mkv", 44 | ".mpeg", ".mov", ".mdf", ".iso", ".asf" 45 | ] 46 | 47 | s = '\x1b[%d;%dm%s\x1b[0m' # terminual color template 48 | 49 | cookie_file = os.path.join(os.path.expanduser('~'), '.115.cookies') 50 | 51 | # headers = { 52 | # "Accept":"Accept: application/json, text/javascript, */*; q=0.01", 53 | # "Accept-Encoding":"text/html", 54 | # "Accept-Language":"en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4,zh-TW;q=0.2", 55 | # "Content-Type":"application/x-www-form-urlencoded; charset=UTF-8", 56 | # "Referer":"http://m.115.com/", 57 | # "X-Requested-With": "XMLHttpRequest", 58 | # "User-Agent":"Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 "\ 59 | # "(KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36" 60 | # } 61 | 62 | headers = { 63 | "Accept":"Accept: application/json, text/javascript, */*; q=0.01", 64 | "Accept-Encoding":"text/html", 65 | "Accept-Language":"en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4,zh-TW;q=0.2", 66 | "Content-Type":"application/x-www-form-urlencoded; charset=UTF-8", 67 | "Referer":"http://m.115.com/", 68 | "X-Requested-With": "XMLHttpRequest", 69 | "User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) \ Chrome/49.0.2623.75 Safari/537.36 115Browser/5.0.0" 70 | } 71 | 72 | ss = requests.session() 73 | ss.headers.update(headers) 74 | 75 | class pan115(object): 76 | def __init__(self): 77 | self.download = self.play if args.play else self.download 78 | 79 | def init(self): 80 | if os.path.exists(cookie_file): 81 | try: 82 | t = json.loads(open(cookie_file).read()) 83 | ss.cookies.update(t.get('cookies', t)) 84 | if not self.check_login(): 85 | print s % (1, 91, ' !! cookie is invalid, please login\n') 86 | sys.exit(1) 87 | self.check_vip() 88 | except: 89 | g = open(cookie_file, 'w') 90 | g.close() 91 | print s % (1, 97, ' please login') 92 | sys.exit(1) 93 | else: 94 | print s % (1, 91, ' !! cookie_file is missing, please login') 95 | sys.exit(1) 96 | 97 | def check_vip(self): 98 | url = 'http://vip.115.com/?ac=mycouponcount' 99 | r = ss.get(url).content 100 | 101 | if '"vip":0' in r: 102 | self.is_vip = False 103 | else: 104 | self.is_vip = True 105 | 106 | def check_login(self): 107 | #print s % (1, 97, '\n -- check_login') 108 | url = 'http://msg.115.com/?ac=unread' 109 | j = ss.get(url) 110 | if '"code"' not in j.text: 111 | #print s % (1, 92, ' -- check_login success\n') 112 | self.save_cookies() 113 | return True 114 | else: 115 | # print j 116 | print s % (1, 91, ' -- check_login fail\n') 117 | return False 118 | 119 | def login1(self, account, password): 120 | print s % (1, 97, '\n -- login') 121 | 122 | def get_ssopw(ssoext): 123 | p = sha.new(password).hexdigest() 124 | a = sha.new(account).hexdigest() 125 | t = sha.new(p + a).hexdigest() 126 | ssopw = sha.new(t + ssoext.upper()).hexdigest() 127 | return ssopw 128 | 129 | ssoext = str(int(time.time()*1000)) 130 | ssopw = get_ssopw(ssoext) 131 | 132 | quote = urllib.quote 133 | data = quote("login[ssoent]")+"=B1&" + \ 134 | quote("login[version]")+"=2.0&" + \ 135 | quote("login[ssoext]")+"=%s&" % ssoext + \ 136 | quote("login[ssoln]")+"=%s&" % quote(account) + \ 137 | quote("login[ssopw]")+"=%s&" % ssopw + \ 138 | quote("login[ssovcode]")+"=%s&" % ssoext + \ 139 | quote("login[safe]")+"=1&" + \ 140 | quote("login[time]")+"=1&" + \ 141 | quote("login[safe_login]")+"=1&" + \ 142 | "goto=http://m.115.com/?ac=home" 143 | 144 | theaders = headers 145 | theaders["Referer"] = "http://passport.115.com\ 146 | /static/reg_login_130418/bridge.html?ajax_cb_key=bridge_%s" \ 147 | % int(time.time()*1000) 148 | 149 | # Post! 150 | # XXX : do not handle errors 151 | params = { 152 | 'ct': 'login', 153 | 'ac': 'ajax', 154 | 'is_ssl': 1 155 | } 156 | url = 'http://passport.115.com' 157 | ss.post(url, params=params, data=data, headers=theaders) 158 | self.save_cookies() 159 | 160 | def login(self): 161 | if not f115api.getInfos(ss): 162 | print(u'获取信息失败') 163 | return 164 | f115api.getQrcode(ss) # 取二维码 165 | f115api.waitLogin(ss) # 等待手机确认登录 166 | f115api.login(ss) # 触发登陆 167 | # print('开启心跳线程') 168 | # threading.Thread(target=keepLogin) # 开启心跳,防止掉线 169 | # getTasksign() # 获取操作task所需信息 170 | # getUserinfo() # 获取登陆用户信息 171 | self.save_cookies() 172 | 173 | def save_cookies(self): 174 | with open(cookie_file, 'w') as g: 175 | c = {'cookies': ss.cookies.get_dict()} 176 | g.write(json.dumps(c, indent=4, sort_keys=True)) 177 | 178 | def get_dlink(self, pc): 179 | params = { 180 | "pickcode": pc.encode('utf8'), 181 | "_": int(time.time()*1000), 182 | } 183 | url = 'http://web.api.115.com/files/download' 184 | r = ss.get(url, params=params) 185 | j = r.json() 186 | dlink = j['file_url'].encode('utf8') 187 | return dlink 188 | 189 | def _get_play_purl(self, pickcode): 190 | url = 'http://115.com/api/video/m3u8/%s.m3u8' % pickcode 191 | r = ss.get(url) 192 | c = r.content.strip() 193 | 194 | if c: 195 | purl = c.split()[-1] 196 | if 'http' not in purl: 197 | return None 198 | else: 199 | return purl 200 | else: 201 | return None 202 | 203 | def get_infos(self, cid): 204 | params = { 205 | "cid": cid, 206 | "offset": 0, 207 | "type": "", 208 | "limit": 10000, 209 | "format": "json", 210 | "aid": 1, 211 | "o": "file_name", 212 | "asc": 0, 213 | "show_dir": 1 214 | } 215 | 216 | url = 'http://web.api.115.com/files' 217 | j = ss.get(url, params=params).json() 218 | 219 | dir_loop1 = [{'dir': j['path'][-1]['name'], 'cid': j['cid']}] 220 | dir_loop2 = [] 221 | #base_dir = os.getcwd() 222 | 223 | while dir_loop1: 224 | for d in dir_loop1: 225 | params['cid'] = d['cid'] 226 | j = ss.get(url, params=params).json() 227 | if j['errNo'] == 0 and j['data']: 228 | if args.type_: 229 | j['data'] = [ 230 | x for x in j['data'] \ 231 | if x.get('ns') \ 232 | or x['ico'].lower() == unicode(args.type_.lower()) 233 | ] 234 | 235 | for i in j['data']: 236 | if i.get('ns'): 237 | item = { 238 | 'dir': os.path.join(d['dir'], i['ns']), 239 | 'cid': i['cid'] 240 | } 241 | dir_loop2.append(item) 242 | 243 | if args.play: 244 | j['data'] = [ 245 | i for i in j['data'] \ 246 | if i.get('sha') \ 247 | and os.path.splitext(i['n'])[-1].lower() \ 248 | in mediatype 249 | ] 250 | 251 | total_file = len([i for i in j['data'] if not i.get('ns')]) 252 | if args.from_ - 1: 253 | j['data'] = j['data'][args.from_-1:] if args.from_ \ 254 | else j['data'] 255 | nn = args.from_ 256 | for i in j['data']: 257 | if not i.get('ns'): 258 | t = i['n'] 259 | t = os.path.join(d['dir'], t).encode('utf8') 260 | t = os.path.join(os.getcwd(), t) 261 | infos = { 262 | # 'file': t, 263 | # 'dir_': os.path.split(t)[0], 264 | # 'dlink': self.get_dlink(i['pc']), 265 | # 'name': i['n'].encode('utf8'), 266 | 'name': i['n'].encode('utf-8'), 267 | 'code': 'pickcode='+i['pc'] 268 | #'purl': self._get_play_purl( 269 | # i['pc'].encode('utf8')) \ 270 | # if args.play and self.is_vip else None, 271 | # 'purl': self._get_play_purl( 272 | # i['pc'].encode('utf8')) \ 273 | # if args.play else None, 274 | # 'nn': nn, 275 | # 'total_file': total_file 276 | } 277 | nn += 1 278 | self.download(infos) 279 | # if infos['name'].endswith('mp4') or infos['name'].endswith('rmvb') or infos['name'].endswith('mkv') or infos['name'].endswith('mpg') or infos['name'].endswith('wmv'): 280 | # print i['n'], 'pickcode='+i['pc'] 281 | # # print type(i['n']) 282 | # # print infos 283 | # with open('info1.txt', 'a') as f: 284 | # # f.write(str(infos)) 285 | # f.write('name: ' + str(i['n'].encode('utf-8'))+ ', code: pickcode=' + i['pc'] + '\n') 286 | else: 287 | print s % (1, 91, ' error: get_infos') 288 | sys.exit(0) 289 | dir_loop1 = dir_loop2 290 | dir_loop2 = [] 291 | 292 | 293 | @staticmethod 294 | def download(infos): 295 | ## make dirs 296 | if not os.path.exists(infos['dir_']): 297 | os.makedirs(infos['dir_']) 298 | else: 299 | if os.path.exists(infos['file']): 300 | return 0 301 | 302 | num = random.randint(0, 7) % 8 303 | col = s % (2, num + 90, infos['file']) 304 | infos['nn'] = infos['nn'] if infos.get('nn') else 1 305 | infos['total_file'] = infos['total_file'] \ 306 | if infos.get('total_file') else 1 307 | print '\n ++ 正在下载: #', \ 308 | s % (1, 97, infos['nn']), \ 309 | '/', s % (1, 97, infos['total_file']), \ 310 | '#', col 311 | 312 | if args.aria2c: 313 | # 115 普通用户只能有4下载通道。 314 | quiet = ' --quiet=true' if args.quiet else '' 315 | taria2c = ' -x %s -s %s' % (args.aria2c, args.aria2c) 316 | tlimit = ' --max-download-limit %s' \ 317 | % args.limit if args.limit else '' 318 | cmd = 'aria2c -c%s%s%s ' \ 319 | '-m 0 ' \ 320 | '-o "%s.tmp" -d "%s" ' \ 321 | '--user-agent "%s" ' \ 322 | '--header "Referer:http://m.115.com/" "%s"' \ 323 | % (quiet, taria2c, tlimit, infos['name'], infos['dir_'],\ 324 | headers['User-Agent'], infos['dlink']) 325 | else: 326 | tlimit = ' --limit-rate %s' % args.limit if args.limit else '' 327 | cmd = 'wget -c%s ' \ 328 | '-O "%s.tmp" --user-agent "%s" ' \ 329 | '--header "Referer:http://m.115.com/" "%s"' \ 330 | % (tlimit, infos['file'], headers['User-Agent'], 331 | infos['dlink']) 332 | 333 | status = os.system(cmd) 334 | if status != 0: # other http-errors, such as 302. 335 | wget_exit_status_info = wget_es[status] 336 | print('\n\n ---### \x1b[1;91mERROR\x1b[0m ==> '\ 337 | '\x1b[1;91m%d (%s)\x1b[0m ###--- \n\n' \ 338 | % (status, wget_exit_status_info)) 339 | print s % (1, 91, ' ===> '), cmd 340 | sys.exit(1) 341 | else: 342 | os.rename('%s.tmp' % infos['file'], infos['file']) 343 | 344 | @staticmethod 345 | def play(infos): 346 | num = random.randint(0, 7) % 8 347 | col = s % (2, num + 90, infos['name']) 348 | infos['nn'] = infos['nn'] if infos.get('nn') else 1 349 | infos['total_file'] = infos['total_file'] \ 350 | if infos.get('total_file') else 1 351 | print '\n ++ play: #', \ 352 | s % (1, 97, infos['nn']), '/', \ 353 | s % (1, 97, infos['total_file']), \ 354 | '#', col 355 | 356 | if not infos['purl']: 357 | print s % (1, 91, ' |-- m3u8 is not ready, using dlink') 358 | infos['purl'] = infos['dlink'] 359 | 360 | cmd = 'mpv --really-quiet --cache 8140 --cache-default 8140 ' \ 361 | '--http-header-fields "user-agent:%s" '\ 362 | '--http-header-fields "Referer:http://m.115.com" "%s"' \ 363 | % (headers['User-Agent'], infos['purl']) 364 | 365 | status = os.system(cmd) 366 | timeout = 1 367 | ii, _, _ = select.select([sys.stdin], [], [], timeout) 368 | if ii: 369 | sys.exit(0) 370 | else: 371 | pass 372 | 373 | # TODO 374 | def exists(self, filepath): 375 | pass 376 | 377 | # TODO 378 | def upload(self, path, dir_): 379 | pass 380 | 381 | def addtask(self, u): 382 | # get uid 383 | url = 'http://my.115.com/?ct=ajax&ac=get_user_aq' 384 | r = ss.get(url) 385 | j = r.json() 386 | uid = j['data']['uid'] 387 | 388 | # get sign, time 389 | url = 'http://115.com/?ct=offline&ac=space' 390 | r = ss.get(url) 391 | j = r.json() 392 | sign = j['sign'] 393 | tm = j['time'] 394 | 395 | # now, add task 396 | data = { 397 | 'url': urllib.quote_plus(u), 398 | 'uid': uid, 399 | 'sign': sign, 400 | 'time': str(tm) 401 | } 402 | url = 'http://115.com/lixian/?ct=lixian&ac=add_task_url' 403 | r = ss.post(url, data=data) 404 | if not r.ok: 405 | print s % (1, 91, ' !! Error at addtask') 406 | print r.content 407 | sys.exit(1) 408 | 409 | j = r.json() 410 | if j['info_hash']: 411 | print s % (1, 92, ' ++ add task success.') 412 | else: 413 | print s % (2, 91, ' !! Error: %s' % j['error_msg']) 414 | sys.exit() 415 | 416 | data = { 417 | 'page': 1, 418 | 'uid': uid, 419 | 'sign': sign, 420 | 'time': str(tm) 421 | } 422 | url = 'http://115.com/lixian/?ct=lixian&ac=task_lists' 423 | r = ss.post(url, data=data) 424 | j = r.json() 425 | percentDone = j['tasks'][0]['percentDone'] 426 | print s % (1, 97, ' ++ %s' % j['tasks'][0]['name']) 427 | print s % (1, 92, ' %s%s Done' % (percentDone, '%')) 428 | 429 | def do(self, pc): 430 | dlink = self.get_dlink(pc) 431 | name = re.search(r'/([^/]+?)\?', dlink).group(1) 432 | name = urllib.unquote_plus(name) 433 | t = os.path.join(os.getcwd(), name) 434 | infos = { 435 | 'file': t, 436 | 'dir_': os.path.split(t)[0], 437 | 'dlink': dlink, 438 | #'purl': self._get_play_purl(pc) \ 439 | # if args.play and self.is_vip else None, 440 | 'purl': self._get_play_purl(pc) if args.play else None, 441 | 'name': name, 442 | 'nn': 1, 443 | 'total_file': 1 444 | } 445 | self.download(infos) 446 | 447 | def main(argv): 448 | if len(argv) <= 1: 449 | sys.exit() 450 | 451 | ###################################################### 452 | # for argparse 453 | p = argparse.ArgumentParser( 454 | description='download from 115.com reversely') 455 | p.add_argument('xxx', type=str, nargs='*', \ 456 | help='命令对象.') 457 | p.add_argument('-a', '--aria2c', action='store', default=None, \ 458 | type=int, help='aria2c分段下载数量') 459 | p.add_argument('-p', '--play', action='store_true', \ 460 | help='play with mpv') 461 | p.add_argument('-q', '--quiet', action='store_true', \ 462 | help='quiet for download and play') 463 | p.add_argument('-f', '--from_', action='store', \ 464 | default=1, type=int, \ 465 | help='从第几个开始下载,eg: -f 42') 466 | p.add_argument('-t', '--type_', action='store', \ 467 | default=None, type=str, \ 468 | help='要下载的文件的后缀,eg: -t mp3') 469 | p.add_argument('-l', '--limit', action='store', \ 470 | default=None, type=str, help='下载速度限制,eg: -l 100k') 471 | p.add_argument('-d', '--addtask', action='store_true', \ 472 | help='加离线下载任务') 473 | global args 474 | args = p.parse_args(argv[1:]) 475 | xxx = args.xxx 476 | 477 | if xxx[0] == 'login' or xxx[0] == 'g': 478 | # if len(xxx[1:]) < 1: 479 | # account = raw_input(s % (1, 97, ' account: ')) 480 | # password = getpass(s % (1, 97, 'password: ')) 481 | # elif len(xxx[1:]) == 1: 482 | # account = xxx[1] 483 | # password = getpass(s % (1, 97, ' password: ')) 484 | # elif len(xxx[1:]) == 2: 485 | # account = xxx[1] 486 | # password = xxx[2] 487 | # else: 488 | # print s % (1, 91, ' login\n login account\n \ 489 | # login account password') 490 | 491 | x = pan115() 492 | # x.login(account, password) 493 | x.login() 494 | is_signin = x.check_login() 495 | if is_signin: 496 | print s % (1, 92, ' ++ login succeeds.') 497 | else: 498 | print s % (1, 91, ' login failes') 499 | 500 | elif xxx[0] == 'signout': 501 | g = open(cookie_file, 'w') 502 | g.close() 503 | 504 | else: 505 | x = pan115() 506 | x.init() 507 | for url in xxx: 508 | if 'pickcode' in url: 509 | pc = re.search(r'pickcode=([\d\w]+)', url) 510 | if pc: 511 | pc = pc.group(1) 512 | x.do(pc) 513 | else: 514 | print s % (1, 91, ' can\'t find pickcode.') 515 | elif 'cid=' in url: 516 | cid = re.search(r'cid=(\d+)', url) 517 | cid = cid.group(1) if cid else '0' 518 | x.get_infos(cid) 519 | elif args.addtask: 520 | x.addtask(url) 521 | else: 522 | print s % (2, 91, ' 请正确输入自己的115地址。') 523 | 524 | if __name__ == '__main__': 525 | argv = sys.argv 526 | main(argv) 527 | 528 | -------------------------------------------------------------------------------- /tumblr.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # vim: set fileencoding=utf8 3 | 4 | from __future__ import unicode_literals 5 | 6 | import os 7 | import sys 8 | import re 9 | import json 10 | import collections 11 | import multiprocessing 12 | import requests 13 | requests.packages.urllib3.disable_warnings() 14 | import argparse 15 | import random 16 | import time 17 | import select 18 | import signal 19 | 20 | API_KEY = 'fuiKNFp9vQFvjLNvx4sUwti4Yb5yGutBN4Xh10LXZhhRKjWlV4' 21 | 22 | PID_PATH = '/tmp/tumblr.py.pid' 23 | 24 | # statistic parameters 25 | NET_ERRORS = multiprocessing.Value('i', 0) 26 | UNCOMPLETION = multiprocessing.Value('i', 0) 27 | DOWNLOAD_ERRORS = multiprocessing.Value('i', 0) 28 | DOWNLOADS = multiprocessing.Value('i', 0) 29 | COMPLETION = multiprocessing.Value('i', 0) 30 | OFFSET = multiprocessing.Value('i', 0) 31 | 32 | ############################################################ 33 | # wget exit status 34 | wget_es = { 35 | 0: "No problems occurred.", 36 | 2: "User interference.", 37 | 1<<8: "Generic error code.", 38 | 2<<8: "Parse error - for instance, when parsing command-line " \ 39 | "optio.wgetrc or .netrc...", 40 | 3<<8: "File I/O error.", 41 | 4<<8: "Network failure.", 42 | 5<<8: "SSL verification failure.", 43 | 6<<8: "Username/password authentication failure.", 44 | 7<<8: "Protocol errors.", 45 | 8<<8: "Server issued an error response." 46 | } 47 | ############################################################ 48 | 49 | s = '\x1b[%d;%dm%s\x1b[0m' # terminual color template 50 | 51 | headers = { 52 | "Accept":"text/html,application/xhtml+xml,application/xml; " \ 53 | "q=0.9,image/webp,*/*;q=0.8", 54 | "Accept-Encoding":"text/html", 55 | "Accept-Language":"en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4,zh-TW;q=0.2", 56 | "Content-Type":"application/x-www-form-urlencoded", 57 | "Referer":"https://api.tumblr.com/console//calls/blog/posts", 58 | "User-Agent":"Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 " \ 59 | "(KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36" 60 | } 61 | 62 | ss = requests.session() 63 | ss.headers.update(headers) 64 | 65 | class Error(Exception): 66 | def __init__(self, msg): 67 | self.msg = msg 68 | def __str__(self): 69 | return self.msg 70 | 71 | def reset_statistic_params(): 72 | NET_ERRORS.value = 0 73 | UNCOMPLETION.value = 0 74 | DOWNLOAD_ERRORS.value = 0 75 | DOWNLOADS.value = 0 76 | COMPLETION.value = 0 77 | OFFSET.value = 0 78 | 79 | def play(urls, args): 80 | for url in urls: 81 | tumblr = Tumblr(args, url) 82 | while True: 83 | items = tumblr.get_item_generator() 84 | if not items: 85 | break 86 | play_do(items, args.quiet) 87 | 88 | def play_do(items, quiet): 89 | for item in items: 90 | num = random.randint(0, 7) % 8 91 | col = s % (2, num + 90, item['durl']) 92 | print ' ++ play:', col 93 | quiet = ' --really-quiet' if quiet else '' 94 | cmd = 'mpv%s --no-ytdl --cache-default 20480 --cache-secs 120 ' \ 95 | '--http-header-fields "User-Agent:%s" ' \ 96 | '"%s"' \ 97 | % (quiet, headers['User-Agent'], item['durl']) 98 | 99 | os.system(cmd) 100 | timeout = 1 101 | ii, _, _ = select.select([sys.stdin], [], [], timeout) 102 | if ii: 103 | sys.exit(0) 104 | else: 105 | pass 106 | 107 | def remove_downloaded_items(items): 108 | N = len(items) 109 | for i in range(N): 110 | item = items.pop() 111 | filepath = os.path.join(item['dir_'], item['subdir'], item['filename']) 112 | if not os.path.exists(filepath): 113 | items.appendleft(item) 114 | 115 | def download_run(item): 116 | filepath = os.path.join(item['dir_'], item['subdir'], item['filename']) 117 | # if os.path.exists(filepath): 118 | # return None 119 | # num = random.randint(0, 7) % 8 120 | # col = s % (1, num + 90, filepath) 121 | # print ' ++ download: %s' % col 122 | cmd = ' '.join([ 123 | 'wget', '-c', '-q', '-T', '10', 124 | '-O', '"%s.tmp"' % filepath, 125 | '--user-agent', '"%s"' % headers['User-Agent'], 126 | '"%s"' % item['durl'].replace('http:', 'https:') 127 | ]) 128 | status = os.system(cmd) 129 | return status, filepath 130 | 131 | def callback(filepath): 132 | os.rename('%s.tmp' % filepath, filepath) 133 | 134 | class Downloader(multiprocessing.Process): 135 | def __init__(self, queue, lock): 136 | super(Downloader, self).__init__() 137 | self.queue = queue 138 | self.daemon = True 139 | self.lock = lock 140 | 141 | def run(self): 142 | while True: 143 | item = self.queue.get() 144 | self.queue.task_done() 145 | if not item: 146 | break 147 | status = download_run(item) 148 | if not status: # file was downloaded. 149 | continue 150 | status, filepath = status 151 | if status != 0: 152 | # print s % (1, 93, '[Error %s] at wget' % status), wget_es[status] 153 | self.lock.acquire() 154 | UNCOMPLETION.value += 1 155 | DOWNLOAD_ERRORS.value += 1 156 | self.lock.release() 157 | else: 158 | self.lock.acquire() 159 | DOWNLOADS.value += 1 160 | self.lock.release() 161 | callback(filepath) 162 | 163 | class TumblrAPI(object): 164 | def _request(self, base_hostname, target, type, params): 165 | api_url = '/'.join(['https://api.tumblr.com/v2/blog', 166 | base_hostname, target, type]) 167 | params['api_key'] = API_KEY 168 | while True: 169 | try: 170 | res = ss.get(api_url, params=params, timeout=10) 171 | json_data = res.json() 172 | break 173 | except KeyboardInterrupt: 174 | sys.exit() 175 | except Exception as e: 176 | NET_ERRORS.value += 1 # count errors 177 | # print s % (1, 93, '[Error at requests]:'), e 178 | time.sleep(5) 179 | if json_data['meta']['msg'].lower() != 'ok': 180 | raise Error(s % (1, 91, json_data['meta']['msg'])) 181 | 182 | return json_data['response'] 183 | 184 | def _info(self, base_hostname): 185 | return self._request(base_hostname, 'info', '', None) 186 | 187 | def _photo(self, base_hostname, offset='', tag='', post_id='', to_items=True): 188 | def make_items(raw_data): 189 | items = collections.deque() 190 | for i in raw_data['posts']: 191 | index = 1 192 | if i.get('photos'): 193 | for ii in i['photos']: 194 | durl = ii['original_size']['url'].replace('http:', 'https:') 195 | filename = os.path.join( 196 | '%s_%s.%s' % (i['id'], index, durl.split('.')[-1])) 197 | t = { 198 | 'durl': durl, 199 | 'filename': filename, 200 | 'key': i['timestamp'], 201 | 'subdir': 'photos', 202 | } 203 | index += 1 204 | items.append(t) 205 | return items 206 | 207 | params = { 208 | 'offset': offset, 209 | 'before': offset if tag else '', 210 | 'tag': tag, 211 | 'id': post_id, 212 | 'limit': 20 if not tag and not post_id else '', 213 | 'filter': 'text' 214 | } 215 | raw_data = self._request(base_hostname, 'posts', 'photo', params) 216 | if to_items: 217 | return make_items(raw_data) 218 | else: 219 | return raw_data 220 | 221 | def _audio(self, base_hostname, offset='', tag='', post_id='', to_items=True): 222 | def make_items(raw_data): 223 | items = collections.deque() 224 | for i in raw_data['posts']: 225 | durl = i['audio_url'].replace('http:', 'https:') 226 | filename = os.path.join( 227 | '%s_%s.%s' % (i['id'], i['track_name'], durl.split('.')[-1])) 228 | t = { 229 | 'durl': durl, 230 | 'filename': filename, 231 | 'timestamp': i['timestamp'] if tag else '', 232 | 'subdir': 'audios' 233 | } 234 | items.append(t) 235 | return items 236 | 237 | params = { 238 | 'offset': offset, 239 | 'before': offset if tag else '', 240 | 'tag': tag, 241 | 'id': post_id, 242 | 'limit': 20 if not tag and not post_id else '', 243 | 'filter': 'text' 244 | } 245 | raw_data = self._request(base_hostname, 'posts', 'audio', params) 246 | if to_items: 247 | return make_items(raw_data) 248 | else: 249 | return raw_data 250 | 251 | def _video(self, base_hostname, offset='', tag='', post_id='', to_items=True): 252 | def make_items(raw_data): 253 | items = collections.deque() 254 | for i in raw_data['posts']: 255 | if not i.get('video_url'): 256 | continue 257 | durl = i['video_url'].replace('http:', 'https:') 258 | filename = os.path.join( 259 | '%s.%s' % (i['id'], durl.split('.')[-1])) 260 | t = { 261 | 'durl': durl, 262 | 'filename': filename, 263 | 'timestamp': i['timestamp'] if tag else '', 264 | 'subdir': 'videos' 265 | } 266 | items.append(t) 267 | return items 268 | 269 | params = { 270 | 'offset': offset, 271 | 'before': offset if tag else '', 272 | 'tag': tag, 273 | 'id': post_id, 274 | 'limit': 20 if not tag and not post_id else '', 275 | 'filter': 'text' 276 | } 277 | raw_data = self._request(base_hostname, 'posts', 'video', params) 278 | if to_items: 279 | return make_items(raw_data) 280 | else: 281 | return raw_data 282 | 283 | class Tumblr(TumblrAPI): 284 | def __init__(self, args, url): 285 | self.args = args 286 | self.offset = self.args.offset 287 | self.make_items = self.parse_urls(url) 288 | 289 | def save_json(self): 290 | with open(self.json_path, 'w') as g: 291 | g.write(json.dumps( 292 | {'offset': self.offset}, indent=4, sort_keys=True)) 293 | 294 | def init_infos(self, base_hostname, target_type, tag=''): 295 | self.infos = {'host': base_hostname} 296 | if not tag: 297 | dir_ = os.path.join(os.getcwd(), self.infos['host']) 298 | json_path = os.path.join(dir_, 'json.json') 299 | 300 | if not os.path.exists(dir_): 301 | if not self.args.play: 302 | os.makedirs(dir_) 303 | else: 304 | if os.path.exists(json_path): 305 | self.offset = json.load(open(json_path))['offset'] - 60 \ 306 | if not self.args.update else self.args.offset 307 | if self.offset < 0: self.offset = 0 308 | else: 309 | dir_ = os.path.join(os.getcwd(), 'tumblr-%s' % tag) 310 | json_path = os.path.join(dir_, 'json.json') 311 | 312 | if not os.path.exists(dir_): 313 | if not self.args.play: 314 | os.makedirs(dir_) 315 | self.offset = int(time.time()) 316 | else: 317 | if os.path.exists(json_path): 318 | self.offset = json.load(open(json_path))['offset'] \ 319 | if not self.args.update else int(time.time()) 320 | 321 | self.infos['dir_'] = dir_ 322 | self.json_path = json_path 323 | subdir = os.path.join(dir_, target_type) 324 | if not os.path.exists(subdir) and not self.args.play: 325 | os.makedirs(subdir) 326 | 327 | if not self.args.play: 328 | for fl in os.listdir(subdir): 329 | if not fl.endswith('.tmp'): 330 | COMPLETION.value += 1 331 | else: 332 | UNCOMPLETION.value += 1 333 | 334 | if self.args.offset: 335 | self.offset = self.args.offset 336 | 337 | print s % (1, 92, '## begin:'), 'offset = %s,' % self.offset, base_hostname 338 | print s % (1, 97, 'INFO:\n') + \ 339 | 'D = Downloads, R = Repair_Need\n' + \ 340 | 'C = Completion, NE = Net_Errors, O = Offset' 341 | 342 | def download_photos_by_offset(self, base_hostname, post_id): 343 | self.init_infos(base_hostname, 'photos') 344 | 345 | def do(): 346 | items = self._photo( 347 | base_hostname, offset=self.offset if not post_id else '', post_id=post_id) 348 | if not items: 349 | return [] 350 | self.offset += 20 351 | self.save_json() 352 | return items 353 | return do 354 | 355 | def download_photos_by_tag(self, base_hostname, tag): 356 | self.init_infos(base_hostname, 'photos', tag=tag) 357 | 358 | def do(): 359 | items = self._photo(base_hostname, tag=tag, before=self.offset) 360 | if not items: 361 | return [] 362 | self.offset = items[-1]['timestamp'] 363 | self.save_json() 364 | return items 365 | return do 366 | 367 | def download_videos_by_offset(self, base_hostname, post_id): 368 | self.init_infos(base_hostname, 'videos') 369 | 370 | def do(): 371 | items = self._video( 372 | base_hostname, offset=self.offset, post_id=post_id) 373 | if not items: 374 | return [] 375 | self.offset += 20 376 | if not self.args.play: 377 | self.save_json() 378 | return items 379 | return do 380 | 381 | def download_videos_by_tag(self, base_hostname, tag): 382 | self.init_infos(base_hostname, 'videos', tag) 383 | 384 | def do(): 385 | items = self._video( 386 | base_hostname, before=self.offset, tag=tag) 387 | if not items: 388 | return [] 389 | self.offset = items[-1]['timestamp'] 390 | if not self.args.play: 391 | self.save_json() 392 | return items 393 | return do 394 | 395 | def download_audios_by_offset(self, base_hostname, post_id): 396 | self.init_infos(base_hostname, 'audios') 397 | 398 | def do(): 399 | items = self._audio( 400 | base_hostname, offset=self.offset if not post_id else '', post_id=post_id) 401 | if not items: 402 | return [] 403 | self.offset += 20 404 | if not self.args.play: 405 | self.save_json() 406 | return items 407 | return do 408 | 409 | def download_audios_by_tag(self, base_hostname, tag): 410 | self.init_infos(base_hostname, 'audios', tag) 411 | 412 | def do(): 413 | items = self._audio( 414 | base_hostname, before=self.offset, tag=tag) 415 | if not self.infos['items']: 416 | return [] 417 | self.offset = self.infos['items'][-1]['timestamp'] 418 | if not self.args.play: 419 | self.save_json() 420 | return items 421 | return do 422 | 423 | def download_photos(self, base_hostname, post_id='', tag=''): 424 | if tag: 425 | return self.download_photos_by_tag(base_hostname, tag) 426 | else: 427 | return self.download_photos_by_offset(base_hostname, post_id=post_id) 428 | 429 | def download_videos(self, base_hostname, post_id='', tag=''): 430 | if tag: 431 | return self.download_videos_by_tag(base_hostname, tag) 432 | else: 433 | return self.download_videos_by_offset(base_hostname, post_id=post_id) 434 | 435 | def download_audios(self, base_hostname, post_id='', tag=''): 436 | if tag: 437 | return self.download_audios_by_tag(base_hostname, tag) 438 | else: 439 | return self.download_audios_by_offset(base_hostname, post_id=post_id) 440 | 441 | def fix_photos(self, base_hostname): 442 | self.init_infos(base_hostname, 'photos') 443 | 444 | t = os.listdir(os.path.join(self.infos['dir_'], 'photos')) 445 | t = [i[:i.find('_')] for i in t if i.endswith('.tmp')] 446 | self.post_ids = list(set(t)) 447 | 448 | def do(): 449 | if len(self.post_ids): 450 | post_id = self.post_ids.pop() 451 | return self._photo(base_hostname, post_id=post_id) 452 | else: 453 | return [] 454 | return do 455 | 456 | def parse_urls(self, url): 457 | _mod = re.search(r'(http://|https://|)(?P.+\.tumblr.com)', url) 458 | if not _mod: 459 | print s % (1, 91, '[Error]:'), 'url is illegal.', '\n' + url.decode('utf8', 'ignore') 460 | return lambda: [] 461 | base_hostname = _mod.group('hostname') 462 | if self.args.check: 463 | return self.fix_photos(base_hostname) 464 | 465 | if re.search(r'post/(\d+)', url): 466 | post_id = re.search(r'post/(\d+)', url).group(1) 467 | else: 468 | post_id = '' 469 | 470 | if self.args.video: 471 | return self.download_videos(base_hostname, post_id=post_id, tag=self.args.tag) 472 | elif self.args.audio: 473 | return self.download_audios(base_hostname, post_id=post_id, tag=self.args.tag) 474 | else: 475 | return self.download_photos(base_hostname, post_id=post_id, tag=self.args.tag) 476 | 477 | def get_item_generator(self): 478 | OFFSET.value = self.offset 479 | items = self.make_items() 480 | for item in items: 481 | item['dir_'] = self.infos['dir_'] 482 | return items 483 | 484 | def args_handler(argv): 485 | p = argparse.ArgumentParser( 486 | description='download from tumblr.com') 487 | p.add_argument('xxx', type=str, nargs='*', help='命令对象.') 488 | p.add_argument('-p', '--processes', action='store', type=int, default=10, 489 | help='指定多进程数,默认为10个,最多为20个 eg: -p 20') 490 | p.add_argument('-f', '--offset', action='store', type=int, default=0, 491 | help='offset') 492 | p.add_argument('-q', '--quiet', action='store_true', 493 | help='quiet') 494 | p.add_argument('-c', '--check', action='store_true', 495 | help='尝试修复未下载成功的图片') 496 | p.add_argument('-P', '--play', action='store_true', 497 | help='play with mpv') 498 | p.add_argument('-V', '--video', action='store_true', 499 | help='download videos') 500 | p.add_argument('-A', '--audio', action='store_true', 501 | help='download audios') 502 | p.add_argument('-t', '--tag', action='store', 503 | default=None, type=str, 504 | help='下载特定tag的图片, eg: -t beautiful') 505 | p.add_argument('--update', action='store_true', 506 | help='update new things') 507 | p.add_argument('--redownload', action='store_true', 508 | help='redownload all things') 509 | args = p.parse_args(argv[1:]) 510 | xxx = args.xxx 511 | 512 | if args.redownload: args.update = True 513 | return args, xxx 514 | 515 | def print_msg(check): 516 | time.sleep(2) # initial interval 517 | 518 | while True: 519 | msg = "\r%s, %s, %s, %s, %s " % \ 520 | ( 521 | 'D: ' + s % (1, 92, DOWNLOADS.value), 522 | 'R: ' + s % (1, 93, UNCOMPLETION.value \ 523 | if not check \ 524 | else UNCOMPLETION.value - DOWNLOAD_ERRORS.value - DOWNLOADS.value), 525 | 'C: ' + s % (1, 97, COMPLETION.value + DOWNLOADS.value), 526 | 'NE: ' + s % (1, 91, NET_ERRORS.value), 527 | 'O: %s' % OFFSET.value 528 | ) 529 | sys.stdout.write(msg) 530 | sys.stdout.flush() 531 | time.sleep(2) 532 | 533 | def sighandler(signum, frame): 534 | # print s % (1, 91, "\n !! Signal:"), signum 535 | # print s % (1, 91, " !! Frame: %s" % frame) 536 | sys.exit() 537 | 538 | def handle_signal(): 539 | signal.signal(signal.SIGBUS, sighandler) 540 | signal.signal(signal.SIGHUP, sighandler) 541 | # http://stackoverflow.com/questions/14207708/ioerror-errno-32-broken-pipe-python 542 | signal.signal(signal.SIGPIPE, signal.SIG_DFL) 543 | signal.signal(signal.SIGQUIT, sighandler) 544 | signal.signal(signal.SIGSYS, sighandler) 545 | 546 | signal.signal(signal.SIGABRT, sighandler) 547 | signal.signal(signal.SIGFPE, sighandler) 548 | signal.signal(signal.SIGILL, sighandler) 549 | signal.signal(signal.SIGINT, sighandler) 550 | signal.signal(signal.SIGSEGV, sighandler) 551 | signal.signal(signal.SIGTERM, sighandler) 552 | 553 | def main(argv): 554 | handle_signal() 555 | args, xxx = args_handler(argv) 556 | 557 | if args.play: 558 | play(xxx, args) 559 | 560 | lock = multiprocessing.Lock() 561 | queue = multiprocessing.JoinableQueue(maxsize=args.processes) 562 | thrs = [] 563 | for i in range(args.processes): 564 | thr = Downloader(queue, lock) 565 | thr.start() 566 | thrs.append(thr) 567 | 568 | # massage thread 569 | msg_thr = multiprocessing.Process(target=print_msg, args=(args.check,)) 570 | msg_thr.daemon = True 571 | msg_thr.start() 572 | 573 | for url in xxx: 574 | reset_statistic_params() 575 | tumblr = Tumblr(args, url) 576 | not_add = 0 577 | while True: 578 | items = tumblr.get_item_generator() 579 | if not items: 580 | break 581 | 582 | # Check the downloaded items. 583 | # It will be exited, if there is no new item to download 584 | # in 5 loops, unless with --redownload 585 | remove_downloaded_items(items) 586 | if not args.redownload: 587 | if not items: 588 | not_add += 1 589 | if not_add > 5: 590 | print s % (1, 93, '\n[Warning]:'), \ 591 | 'There is nothing new to download in 5 loops.\n', \ 592 | 'If you want to scan all resources, using --redownload\n' \ 593 | 'or running the script again to next 5 loops.' 594 | break 595 | continue 596 | else: 597 | not_add = 0 598 | 599 | for item in items: 600 | queue.put(item) 601 | 602 | while not queue.empty(): 603 | time.sleep(2) 604 | 605 | for i in range(args.processes): 606 | queue.put(None) 607 | 608 | queue.join() 609 | 610 | for thr in thrs: 611 | thr.join() 612 | 613 | msg_thr.terminate() 614 | 615 | if __name__ == '__main__': 616 | argv = sys.argv 617 | main(argv) 618 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## iScript 2 | 3 | > *[L]* *[W]* *[LW]* 分别表示,在linux, windows, linux和windows 下通过测试。 4 | 5 | > ***windows用户可在babun (https://github.com/babun/babun) 下运行。*** 6 | 7 | | | | 8 | --------|---------|---------| 9 | *[L]* | [leetcode_problems.py](#leetcode_problems.py) | 下载Leetcode的算法题 | 10 | *[L]* | [xiami.py](#xiami.py) | 下载或播放高品质虾米音乐(xiami.com) | 11 | *[L]* | [pan.baidu.com.py](#pan.baidu.com.py) | 百度网盘的下载、离线下载、上传、播放、转存、文件操作 | 12 | *[L]* | [bt.py](#bt.py) | magnet torrent 互转、及 过滤敏.感.词 | 13 | *[L]* | [115.py](#115.py) | 115网盘的下载和播放 | 14 | *[L]* | [yunpan.360.cn.py](#yunpan.360.cn.py) | 360网盘的下载 | 15 | *[L]* | [music.baidu.com.py](#music.baidu.com.py) | 下载或播放高品质百度音乐(music.baidu.com) | 16 | *[L]* | [music.163.com.py](#music.163.com.py) | 下载或播放高品质网易音乐(music.163.com) | 17 | *[L]* | [flv_cmd.py](#flv_cmd.py) | 基于在线服务的视频解析 client - 支持下载、播放 | 18 | *[L]* | [tumblr.py](#tumblr.py) | 下载某个tumblr.com的所有图片、视频、音频 | 19 | *[L]* | [unzip.py](#unzip.py) | 解决linux下unzip乱码的问题 | 20 | *[L]* | [ed2k_search.py](#ed2k_search.py) | 基于 donkey4u.com 的emule搜索 | 21 | *[L]* | [91porn.py](#91porn.py) | 下载或播放91porn | 22 | *[L]* | [ThunderLixianExporter.user.js](#ThunderLixianExporter.user.js) | A fork of https://github.com/binux/ThunderLixianExporter - 增加了mpv和mplayer的导出。 | 23 | | 待续 | | 24 | 25 | --- 26 | 27 | --- 28 | 29 | 30 | ### leetcode_problems.py - 下载Leetcode的算法题 31 | 32 | #### 依赖 33 | 34 | ``` 35 | python2-requests (https://github.com/kennethreitz/requests) 36 | 37 | python2-lxml 38 | 39 | ``` 40 | 41 | #### 参数: 42 | 43 | ``` 44 | --index sort by index 45 | --level sort by level 46 | --tag sort by tag 47 | --title sort by title 48 | --rm_blank 移除题中的空行 49 | --line LINE 两题之间的空行 50 | -r, --redownload 重新下载数据 51 | ``` 52 | 53 | 下载的数据保持在 ./leecode_problems.pk 54 | 转成的txt在 './leecode problems.txt' 55 | 56 | --- 57 | 58 | 59 | ### xiami.py - 下载或播放高品质虾米音乐(xiami.com) 60 | 61 | #### 1. 依赖 62 | 63 | ``` 64 | wget 65 | 66 | python2-requests (https://github.com/kennethreitz/requests) 67 | 68 | python2-mutagen (https://code.google.com/p/mutagen/) 69 | 70 | mpv (http://mpv.io) 71 | ``` 72 | 73 | #### 2. 使用说明 74 | 75 | xiami.py 是一个虾米音乐的命令行(CLI)客户端。提供登录、下载、播放、收藏的功能。 76 | 77 | **提供对[落网 luoo.net](http://www.luoo.net)的分析** 78 | 79 | 初次使用需要登录 xm login (原xiami账号) 80 | 81 | ~~**支持淘宝账户** xm logintaobao~~ 82 | 83 | ~~**对于淘宝账户,登录后只保存有关虾米的cookies,删除了有关淘宝的cookies**~~ 84 | 85 | **淘宝登录加密算法无法破解,需要手动获取cookies (方法见下 手动添加cookie登录)** 86 | 87 | **vip账户**支持高品质音乐的下载和播放。 88 | 89 | **原虾米vip用户如果不能获得高品质音乐,请用关联的淘宝帐号登录。** 90 | 91 | 下载的MP3默认添加id3 tags,保存在当前目录下。 92 | 93 | cookies保存在 ~/.Xiami.cookies。 94 | 95 | 关于播放操作: 96 | 97 | > 在运行脚本的终端,输入1次Enter,关闭当前播放并播放下一个文件,连续输入2次Enter,关闭当前播放并退出。 98 | 99 | #### 命令: 100 | 101 | ``` 102 | # 虾米账号登录 103 | g 104 | login 105 | login username 106 | login username password 107 | 108 | signout # 退出登录 109 | 110 | d 或 download url1 url2 # 下载 111 | p 或 play url1 url2 # 播放 112 | s 或 save url1 url2 # 收藏 113 | ``` 114 | 115 | #### 参数: 116 | 117 | ``` 118 | -p, --play 按顺序播放 119 | -pp 按歌曲被播放的次数,从高到低播放 120 | -l, --low 低品质mp3 121 | -d, --undescription 不加入disk的描述 122 | -f num, --from_ num 从第num个开始 123 | -t TAGS, --tags TAGS 收藏用的tags,用英文逗号分开, eg: -t piano,cello,guitar 124 | -n, --undownload 不下载,用于修改已存在的MP3的id3 tags 125 | ``` 126 | 127 | #### 3. 用法 128 | 129 | xm 是xiami.py的马甲 (alias xm='python2 /path/to/xiami.py') 130 | 131 | ``` 132 | # 登录 133 | xm g 134 | xm login 135 | xm login username 136 | xm login username password 137 | 138 | # 手动添加cookie登录 139 | 1. 用浏览器登录后,按F12,然后访问 http://xiami.com/vip 140 | 2. 选择‘网络’或network,找到 xiami.com/vip,在其中找到 Cookie: memthod_auth=value 141 | 3. value填入 xm g value,再执行。 142 | 143 | # 退出登录 144 | xm signout 145 | 146 | # 下载专辑 147 | xm d http://www.xiami.com/album/168709?spm=a1z1s.6928801.1561534521.114.ShN6mD 148 | 149 | # 下载单曲 150 | xm d http://www.xiami.com/song/2082998?spm=a1z1s.6659513.0.0.DT2j7T 151 | 152 | # 下载精选集 153 | xm d http://www.xiami.com/song/showcollect/id/30374035?spm=a1z1s.3061701.6856305.16.fvh75t 154 | 155 | # 下载该艺术家所有专辑, Top 20 歌曲, radio 156 | xm d http://www.xiami.com/artist/23460?spm=a1z1s.6928801.1561534521.115.ShW08b 157 | 158 | # 下载用户的收藏, 虾米推荐, radio, 推荐 159 | xm d http://www.xiami.com/u/141825?spm=a1z1s.3521917.0.0.zI0APP 160 | 161 | # 下载排行榜 162 | xm d http://www.xiami.com/chart/index/c/2?spm=a1z1s.2943549.6827465.6.VrEAoY 163 | 164 | # 下载 风格 genre, radio 165 | xm d http://www.xiami.com/genre/detail/gid/2?spm=a1z1s.3057857.6850221.1.g9ySan 166 | xm d http://www.xiami.com/genre/detail/sid/2970?spm=a1z1s.3057857.6850221.4.pkepgt 167 | 168 | # 下载 widget (虾米播播) 169 | 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 170 | 171 | # 下载落网期刊 172 | # 分析落网期刊的音乐后,在虾米上搜索并下载 173 | xm d http://www.luoo.net/music/706 174 | ``` 175 | 176 | #### 播放: 177 | 178 | ``` 179 | # url 是上面的 180 | xm p url 181 | ``` 182 | 183 | #### 收藏: 184 | 185 | ``` 186 | xm s http://www.xiami.com/album/168709?spm=a1z1s.6928801.1561534521.114.ShN6mD 187 | xm s -t 'tag1,tag 2,tag 3' http://www.xiami.com/song/2082998?spm=a1z1s.6659513.0.0.DT2j7T 188 | xm s http://www.xiami.com/song/showcollect/id/30374035?spm=a1z1s.3061701.6856305.16.fvh75t 189 | xm s http://www.xiami.com/artist/23460?spm=a1z1s.6928801.1561534521.115.ShW08b 190 | ``` 191 | 192 | #### 4. 参考: 193 | 194 | > http://kanoha.org/2011/08/30/xiami-absolute-address/ 195 | 196 | > http://www.blackglory.me/xiami-vip-audition-with-no-quality-difference-between-downloading/ 197 | 198 | > https://gist.github.com/lepture/1014329 199 | 200 | > 淘宝登录代码: https://github.com/ly0/xiami-tools 201 | 202 | --- 203 | 204 | 205 | ### pan.baidu.com.py - 百度网盘的下载、离线下载、上传、播放、转存、文件操作 206 | 207 | #### 1. 依赖 208 | 209 | ``` 210 | wget 211 | 212 | aria2 (~ 1.18) 213 | 214 | python2-rsa 215 | 216 | python2-pyasn1 217 | 218 | python2-requests (https://github.com/kennethreitz/requests) 219 | 220 | requests-toolbelt (https://github.com/sigmavirus24/requests-toolbelt) 221 | 222 | mpv (http://mpv.io) 223 | 224 | # 可选依赖 225 | shadowsocks # 用于加密上传。 226 | # 用 python2 的 pip 安装 227 | 228 | # 除了用pip安装包,还可以手动: 229 | 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 230 | ``` 231 | 232 | #### other 233 | 234 | [尝试解决百度网盘下载速度问题](https://github.com/PeterDing/iScript/wiki/解决百度网盘下载速度问题) 235 | 236 | #### 2. 使用说明 237 | 238 | pan.baidu.com.py 是一个百度网盘的命令行客户端。 239 | 240 | 初次使用需要登录 bp login 241 | 242 | **支持多帐号登录** 243 | 244 | **支持加密上传**, 需要 shadowsocks 245 | 246 | **cd, ls 功能完全支持** 247 | 248 | **所有路径可以是 相对路径 或 绝对路径** 249 | 250 | 他人分享的网盘连接,只支持单个的下载。 251 | 252 | 下载工具默认为wget, 可用参数-a num选用aria2 253 | 254 | 下载的文件,保存在当前目录下。 255 | 256 | 下载默认为非递归,递归下载加 -R 257 | 258 | 搜索时,默认在 cwd 259 | 260 | 搜索支持高亮 261 | 262 | 上传模式默认是 c (续传)。 263 | 264 | **开启证实(verification) 用参数 -V** 265 | 266 | 理论上,上传的单个文件最大支持 2T 267 | 268 | cookies保存在 ~/.bp.cookies 269 | 270 | 上传数据保存在 ~/.bp.pickle 271 | 272 | 关于播放操作: 273 | 274 | > 在运行脚本的终端,输入1次Enter,关闭当前播放并播放下一个文件,连续输入2次Enter,关闭当前播放并退出。 275 | 276 | 277 | #### 命令: 278 | 279 | **!!注意:** 280 | **命令参数中,所有网盘的路径和本地路径可以是 相对路径 或 绝对路径** 281 | 282 | ``` 283 | # 登录 284 | g 285 | login 286 | login username 287 | login username password 288 | 289 | # 删除帐号 290 | userdelete 或 ud 291 | 292 | # 切换帐号 293 | userchange 或 uc 294 | 295 | # 帐号信息 296 | user 297 | 298 | # 显示当前工作目录 299 | cwd 300 | 301 | # 切换当前工作目录 302 | cd path # 支持 ./../... 303 | 304 | # 播放 305 | p 或 play url1 url2 path1 path2 306 | 307 | # 上传 308 | u 或 upload localpath remotepath 309 | 310 | # 加密上传 311 | u localpath remotepath [-P password] -t ec -R 312 | 313 | # 转存 314 | s 或 save url remotepath [-s secret] 315 | 316 | # 下载 317 | d 或 download url1 url2 path1 path2 非递归下载 到当前本地目录 318 | d 或 download url1 url2 path1 path2 -R 递归下载 到当前本地目录 319 | # !! 注意: 320 | # d /path/to/download -R 递归下载 *download文件夹* 到当前本地目录 321 | # d /path/to/download/ -R 递归下载 *download文件夹中的文件* 到当前本地目录 322 | 323 | # 下载并解密 324 | d /path/to/download -R -t dc [-P password] [-m aes-256-cfb] 325 | 326 | # 解密已下载的文件 327 | dc path1 path2 -R [-P password] [-m aes-256-cfb] 328 | 329 | # 文件操作 330 | md 或 mkdir path1 path2 创建文件夹 331 | rn 或 rename path new_path 重命名 332 | rm 或 remove path1 path2 删除 333 | mv 或 move path1 path2 /path/to/directory 移动 334 | cp 或 copy path /path/to/directory_or_file 复制 335 | cp 或 copy path1 path2 /path/to/directory 复制 336 | 337 | # 使用正则表达式进行文件操作 338 | rnr 或 rnre foo bar dir1 dir2 -I re1 re2 重命名文件夹中的文件名 339 | rmr 或 rmre dir1 dir2 -E re1 re2 删除文件夹下匹配到的文件 340 | mvr 或 mvre dir1 dir2 /path/to/dir -H head1 head2 移动文件夹下匹配到的文件 341 | cpr 或 cpre dir1 dir2 /path/to/dir -T tail1 tail2 复制文件夹下匹配到的文件 342 | # 递归加 -R 343 | # rmr, mvr, cpr 中 -t, -I, -E, -H, -T 至少要有一个,放在命令行末尾 344 | # -I, -E, -H, -T 后可跟多个匹配式 345 | # 可以用 -t 指定操作的文件类型 346 | -t f # 文件 347 | -t d # 文件夹 348 | # rnr 中 foo bar 都是 regex 349 | # -y, --yes # 不显示警示,直接进行。 !!注意,除非你知道你做什么,否则请不要使用。 350 | rmr / -I '.*' -y # !! 删除网盘中的所有文件 351 | 352 | # 回复用bt.py做base64加密的文件 353 | rnr /path/to/decode1 /path/to/decode2 -t f,bd64 354 | 355 | # 搜索 356 | # directory 必须是绝对路径, 默认是 cwd 357 | f 或 find keyword1 keyword2 [directory] 非递归搜索 358 | ff keyword1 keyword2 [directory] 非递归搜索 反序 359 | ft keyword1 keyword2 [directory] 非递归搜索 by time 360 | ftt keyword1 keyword2 [directory] 非递归搜索 by time 反序 361 | fs keyword1 keyword2 [directory] 非递归搜索 by size 362 | fss keyword1 keyword2 [directory] 非递归搜索 by size 反序 363 | fn keyword1 keyword2 [directory] 非递归搜索 by name 364 | fnn keyword1 keyword2 [directory] 非递归搜索 by name 反序 365 | # 递归搜索加 -R 366 | f 'ice and fire' /doc -R 367 | # 搜索所有的账户加 -t all 368 | f keyword1 keyword2 [directory] -t all -R 369 | f keyword1 keyword2 [directory] -t f,all -R 370 | # directory 默认为 / 371 | # 关于-H, -T, -I, -E 372 | # -I, -E, -H, -T 后可跟多个匹配式, 需要放在命令行末尾 373 | f keyword1 keyword2 [directory] -H head -T tail -I "re(gul.*) ex(p|g)ress$" 374 | f keyword1 keyword2 [directory] -H head -T tail -E "re(gul.*) ex(p|g)ress$" 375 | # 搜索 加 通道(只支持 donwload, play, rnre, rm, mv) 376 | f keyword1 keyword2 [directory] \| d -R 递归搜索后递归下载 377 | ftt keyword1 keyword2 [directory] \| p -R 递归搜索(by time 反序)后递归播放 378 | f keyword1 keyword2 [directory] \| rnr foo bar -R 递归搜索后rename by regex 379 | f keyword1 keyword2 [directory] \| rm -R -T tail 递归搜索后删除 380 | f keyword1 keyword2 [directory] \| mv /path/to -R 递归搜索后移动 381 | 382 | # 列出文件 383 | l path1 path2 ls by name 384 | ll path1 path2 ls by name 反序 385 | ln path1 path2 ls by name 386 | lnn path1 path2 ls by name 反序 387 | lt path1 path2 ls by time 388 | ltt path1 path2 ls by time 反序 389 | ls path1 path2 ls by size 390 | lss path1 path2 ls by size 反序 391 | l /doc/books /videos 392 | # 以下是只列出文件或文件夹 393 | l path1 path2 -t f ls files 394 | l path1 path2 -t d ls directorys 395 | # 关于-H, -T, -I, -E 396 | # -I, -E, -H, -T 后可跟多个匹配式, 需要放在命令行末尾 397 | l path1 path2 -H head -T tail -I "^re(gul.*) ex(p|g)ress$" 398 | l path1 path2 -H head -T tail -E "^re(gul.*) ex(p|g)ress$" 399 | # 显示绝对路径 400 | l path1 path2 -v 401 | # 显示文件size, md5 402 | l path1 path2 -vv 403 | # 空文件夹 404 | l path1 path2 -t e,d 405 | # 非空文件夹 406 | l path1 path2 -t ne,d 407 | 408 | # 分享文件 409 | S 或 share path1 path2 为每个提供的文件路劲创建分享链接 410 | S 或 share [-P pawd 或 --passwd pawd] path1 path2 为每个提供的路径创建加密的分享链接 411 | 412 | # 查看文件占用空间 413 | du path1 path2 文件夹下所有*文件(不包含下层文件夹)*总大小 414 | du path1 path2 -R 文件夹下所有*文件(包含下层文件夹)*总大小 415 | 如果下层文件多,会花一些时间 416 | # 相当于 l path1 path2 -t du [-R] 417 | # eg: 418 | du /doc /videos -R 419 | 420 | # 离线下载 421 | a 或 add http https ftp ed2k remotepath 422 | a 或 add magnet remotepath [-t {m,i,d,p}] 423 | a 或 add remote_torrent [-t {m,i,d,p}] # 使用网盘中torrent 424 | 425 | # 离线任务操作 426 | j 或 job # 列出离线下载任务 427 | jd 或 jobdump # 清除全部 *非正在下载中的任务* 428 | jc 或 jobclear taskid1 taskid2 # 清除 *正在下载中的任务* 429 | jca 或 jobclearall # 清除 *全部任务* 430 | ``` 431 | 432 | #### 参数: 433 | 434 | ``` 435 | -a num, --aria2c num aria2c分段下载数量: eg: -a 10 436 | -p, --play play with mpv 437 | -P password, --passwd password 分享密码,加密密码 438 | -y, --yes yes # 用于 rmre, mvre, cpre, rnre !!慎用 439 | -q, --quiet 无输出模式, 用于 download, play 440 | -V, --VERIFY verification 441 | -v, --view view detail 442 | eg: 443 | l -v # 显示绝对路径 444 | a magnet /path -v # 离线下载并显示下载的文件 445 | d -p url1 url2 -v # 显示播放文件的完整路径 446 | l path1 path2 -vv # 显示文件的size, md5 447 | -s SECRET, --secret SECRET 提取密码 448 | -f number, --from_ number 从第几个开始(用于download, play),eg: p /video -f 42 449 | -t ext, --type_ ext 类型参数, 用 “,” 分隔 450 | eg: 451 | -t fs # 换用下载服务器,用于下载、播放 452 | # 如果wiki中的速度解决方法不管用,可以试试加该参数 453 | d -t dc # 下载并解密,覆盖加密文件(默认) 454 | d -t dc,no # 下载并解密,不覆盖加密文件 455 | dc -t no # 解密,不覆盖加密文件 456 | d -t ie # ignore error, 忽略除Ctrl-C以外的下载错误 457 | d -t 8s # 检测文件是否是“百度8秒”,如果是则不下载 458 | p -t m3 # 播放流媒体(m3u8) 459 | s -t c # 连续转存 (如果转存出错,再次运行命令 460 | # 可以从出错的地方开始,用于转存大量文件时) 461 | l -t f # 文件 462 | l -t d # 文件夹 463 | l -t du # 查看文件占用空间 464 | l -t e,d # 空文件夹 465 | f -t all # 搜索所有账户 466 | a -t m,d,p,a 467 | u -t ec # encrypt, 加密上传, 默认加前缀 468 | u -t ec,np # encrypt, 加密上传, 不加前缀 469 | u -t r # 只进行 rapidupload 470 | u -t e # 如果云端已经存在则不上传(不比对md5) 471 | u -t r,e 472 | -t s # shuffle,乱序 473 | -l amount, --limit amount 下载速度限制,eg: -l 100k 474 | -m {o,c}, --mode {o,c} 模式: o # 重新上传. c # 连续上传. 475 | 加密方法: https://github.com/shadowsocks/shadowsocks/wiki/Encryption 476 | -R, --recursive 递归, 用于download, play, upload, ls, find, rmre, rnre, rmre, cpre 477 | -H HEADS, --head HEADS 匹配开头的字符,eg: -H Head1 Head2 478 | -T TAILS, --tail TAILS 匹配结尾的字符,eg: -T Tail1 Tail2 479 | -I INCLUDES, --include INCLUDES 不排除匹配到表达的文件名, 可以是正则表达式,eg: -I ".*.mp3" ".*.avi" 480 | -E EXCLUDES, --exclude EXCLUDES 排除匹配到表达的文件名, 可以是正则表达式,eg: -E ".*.html" ".*.jpg" 481 | -c {on, off}, --ls_color {on, off} ls 颜色,默认是on 482 | 483 | # -t, -H, -T, -I, -E 都能用于 download, play, ls, find, rnre, rmre, cpre, mvre 484 | ``` 485 | 486 | #### 3. 用法 487 | 488 | bp 是pan.baidu.com.py的马甲 (alias bp='python2 /path/to/pan.baidu.com.py') 489 | 490 | #### 登录: 491 | 492 | ``` 493 | bp g 494 | bp login 495 | bp login username 496 | bp login username password 497 | 498 | # 多帐号登录 499 | # 一直用 bp login 即可 500 | ``` 501 | 502 | #### 删除帐号: 503 | 504 | ``` 505 | bp ud 506 | ``` 507 | 508 | #### 切换帐号: 509 | 510 | ``` 511 | bp uc 512 | ``` 513 | 514 | #### 帐号信息: 515 | 516 | ``` 517 | bp user 518 | ``` 519 | 520 | #### 显示当前工作目录 521 | 522 | ``` 523 | bp cwd 524 | ``` 525 | 526 | #### 切换当前工作目录 527 | 528 | ``` 529 | bp cd # 切换到 / 530 | bp cd path # 支持 ./../... 531 | bp cd .. 532 | bp cd ../../Music 533 | bp cd ... 534 | ``` 535 | 536 | #### 下载: 537 | 538 | ``` 539 | ## 下载、播放速度慢? 540 | 如果wiki中的速度解决方法不管用,可以试试加该参数 -t fs 541 | 542 | # 下载当前工作目录 (递归) 543 | bp d . -R 544 | 545 | # 下载自己网盘中的*单个或多个文件* 546 | bp d http://pan.baidu.com/disk/home#dir/path=/path/to/filename1 http://pan.baidu.com/disk/home#dir/path=/path/to/filename2 547 | # or 548 | bp d /path/to/filename1 /path/to/filename2 549 | 550 | # 递归下载自己网盘中的*单个或多个文件夹* 551 | 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 552 | # or 553 | bp d -R /path/to/directory1 /path/to/directory2 554 | # 递归下载后缀为 .mp3 的文件 555 | bp d -R /path/to/directory1 /path/to/directory2 -T .mp3 556 | 557 | # 非递归下载 558 | bp d relative_path/to/directory1 /path/to/directory2 559 | 560 | # 下载别人分享的*单个文件* 561 | bp d http://pan.baidu.com/s/1o6psfnxx 562 | bp d 'http://pan.baidu.com/share/link?shareid=1622654699&uk=1026372002&fid=2112674284' 563 | 564 | # 下载别人加密分享的*单个文件*,密码参数-s 565 | bp d http://pan.baidu.com/s/1i3FVlw5 -s vuej 566 | 567 | # 用aria2下载 568 | bp d http://pan.baidu.com/s/1i3FVlw5 -s vuej -a 5 569 | bp d /movie/her.mkv -a 4 570 | bp d url -s [secret] -a 10 571 | 572 | # 下载并解码 573 | ## 默认加密方法为 aes-256-cfb 574 | bp d /path/to/encrypted_file -t dc [-P password] # 覆盖加密文件 (默认) 575 | bp d /path/to/encrypted_file -t dc,no [-P password] # 不覆盖加密文件 576 | ## 设置加密方法 577 | bp d /path/to/encrypted_file -t dc [-P password] -m 'rc4-md5' 578 | bp d /path/to/directory -t dc [-P password] -m 'rc4-md5' 579 | ``` 580 | 581 | #### 解码已下载的加密文件: 582 | 583 | ``` 584 | bp dc /local/to/encrypted_file [-P password] -m 'aes-256-cfb' 585 | bp dc /local/to/encrypted_file [-P password] 586 | bp dc /local/to/directory [-P password] 587 | ``` 588 | 589 | #### 播放: 590 | 591 | ``` 592 | bp p /movie/her.mkv 593 | bp p http://pan.baidu.com/s/xxxxxxxxx -s [secret] 594 | 595 | bp cd /movie 596 | bp p movie -R # 递归播放 /movie 中所有媒体文件 597 | 598 | # 播放流媒体(m3u8) 599 | 上面的命令后加 -t m3 600 | 清晰度与在浏览器上播放的一样. 601 | 如果源文件是高清的(720P,1280P),那么流媒体会自动转为480P. 602 | ``` 603 | 604 | #### 离线下载: 605 | 606 | ``` 607 | bp a http://mirrors.kernel.org/archlinux/iso/latest/archlinux-2014.06.01-dual.iso /path/to/save 608 | bp a https://github.com/PeterDing/iScript/archive/master.zip /path/to/save 609 | bp a ftp://ftp.netscape.com/testfile /path/to/save 610 | 611 | bp a 'magnet:?xt=urn:btih:64b7700828fd44b37c0c045091939a2c0258ddc2' /path/to/save -v -t a 612 | bp a 'ed2k://|file|[美]徐中約《中国近代史》第六版原版PDF.rar|547821118|D09FC5F70DEA63E585A74FBDFBD7598F|/' /path/to/save 613 | 614 | bp a /path/to/a.torrent -v -t m,i # 使用网盘中torrent,下载到/path/to 615 | # 注意 ------------------ 616 | ↓ 617 | 网盘中的torrent 618 | ``` 619 | 620 | #### magnet离线下载 -- 文件选择: 621 | 622 | ``` 623 | -t m # 视频文件 (默认), 如: mkv, avi ..etc 624 | -t i # 图像文件, 如: jpg, png ..etc 625 | -t d # 文档文件, 如: pdf, doc, docx, epub, mobi ..etc 626 | -t p # 压缩文件, 如: rar, zip ..etc 627 | -t a # 所有文件 628 | m, i, d, p, a 可以任意组合(用,分隔), 如: -t m,i,d -t d,p -t i,p 629 | remotepath 默认为 / 630 | 631 | bp a 'magnet:?xt=urn:btih:64b7700828fd44b37c0c045091939a2c0258ddc2' /path/to/save -v -t p,d 632 | bp a /download/a.torrent -v -t m,i,d # 使用网盘中torrent,下载到/download 633 | ``` 634 | 635 | #### 离线任务操作: 636 | 637 | ``` 638 | bp j 639 | bp j 3482938 8302833 640 | bp jd 641 | bp jc taskid1 taskid2 642 | bp jc 1208382 58239221 643 | bp jca 644 | ``` 645 | 646 | #### 上传: (默认为非递归,递归加 -R) 647 | 648 | ``` 649 | # 支持文件类型选择 650 | bp u ~/Documents/* # 默认上传所以文件 651 | bp u ~/Documents/* -t f # 不上传文件夹 652 | bp u ~/Documents/* -t d # 不上传文件 653 | bp u ~/Documents/* -t f,d # 不上传文件和文件夹 654 | 655 | bp u ~/Documents/reading/三体\ by\ 刘慈欣.mobi /doc -m o 656 | # 上传模式: 657 | # -m o --> 重传 658 | # -m c --> 续传 (默认) 659 | # 递归加-R 660 | 661 | bp u ~/Videos/*.mkv /videos -t r 662 | # 只进行rapidupload 663 | 664 | bp u ~/Documents ~/Videos ~/Documents /backup -t e -R 665 | # 如果云端已经存在则不上传(不比对md5) 666 | # 用 -t e 时, -m o 无效 667 | 668 | bp u ~/Documents ~/Videos ~/Documents /backup -t r,e # 以上两种模式 669 | ``` 670 | 671 | #### 加密上传: (默认为非递归,递归加 -R) 672 | 673 | ``` 674 | bp u ~/{p1,p2,p3} -t ec [-P password] # 默认加密方法 'aes-256-cfb' 675 | bp u ~/{p1,p2,p3} -t ec [-P password] -m 'rc4-md5' 676 | 677 | # 注意: 678 | # 上传后的文件名会默认加上前缀 encrypted_ 679 | # 不加前缀用 -t ec,np 680 | ``` 681 | 682 | #### 转存: 683 | 684 | ``` 685 | bp s url remotepath [-s secret] 686 | # url是他人分享的连接, 如: http://pan.baidu.com/share/link?shareid=xxxxxxx&uk=xxxxxxx, http://pan.baidu.com/s/xxxxxxxx 687 | bp s 'http://pan.baidu.com/share/link?shareid=xxxxxxx&uk=xxxxxxx' /path/to/save 688 | bp s http://pan.baidu.com/s/xxxxxxxx /path/to/save 689 | bp s http://pan.baidu.com/s/xxxxxxxx /path/to/save -s xxxx 690 | bp s http://pan.baidu.com/s/xxxxxxxx#dir/path=/path/to/anything /path/to/save -s xxxx 691 | 692 | bp s http://pan.baidu.com/inbox/i/xxxxxxxx /path/to/save 693 | 694 | # -t c 连续转存 (如果转存出错,再次运行命令可以从出错的地方开始,用于转存大量文件时) 695 | bp s 'http://pan.baidu.com/share/link?shareid=2705944270&uk=708312363' /path/to/save -t c 696 | # 注意:再次运行时,命令要一样。 697 | ``` 698 | 699 | #### 搜索: 700 | 701 | ``` 702 | # 默认搜索当前服务器工作目录 cwd 703 | bp f keyword1 keyword2 704 | bp f "this is one keyword" "this is another keyword" /path/to/search 705 | 706 | bp f ooxx -R 707 | bp f 三体 /doc/fiction -R 708 | bp f 晓波 /doc -R 709 | 710 | bp ff keyword1 keyword2 /path/to/music 非递归搜索 反序 711 | bp ft keyword1 keyword2 /path/to/doc 非递归搜索 by time 712 | bp ftt keyword1 keyword2 /path/to/other 非递归搜索 by time 反序 713 | bp fs keyword1 keyword2 非递归搜索 by size 714 | bp fss keyword1 keyword2 非递归搜索 by size 反序 715 | bp fn keyword1 keyword2 非递归搜索 by name 716 | bp fnn keyword1 keyword2 非递归搜索 by name 反序 717 | 718 | # 递归搜索加 -R 719 | # 关于-H, -T, -I, -E 720 | bp f mp3 /path/to/search -H "[" "01" -T ".tmp" -I ".*-.*" -R 721 | 722 | # 搜索所有的账户 723 | bp f iDoNotKnow [directory] -t all -R 724 | bp f archlinux ubuntu [directory] -t f,all -T .iso -R 725 | 726 | # 搜索 加 通道(只支持 donwload, play, rnre, rm, mv) 727 | bp f bioloy \| d -R 递归搜索后递归下载 728 | bp ftt ooxx \| p -R -t f 递归搜索(by time 反序)后递归播放 729 | bp f sound \| rnr mp3 mp4 -R 递归搜索后rename by regex 730 | bp f ccav \| rm -R -T avi 递归搜索后删除 731 | bp f 新闻联播(大结局) \| mv /Favor -R 递归搜索后移动 732 | ``` 733 | 734 | #### 恢复用bt.py做base64加密的文件: 735 | 736 | ``` 737 | rnr /ooxx -t f,bd64 738 | !! 注意: /ooxx 中的所有文件都必须是被base64加密的,且加密段要有.base64后缀 739 | # 可以参考 by.py 的用法 740 | ``` 741 | 742 | ls、重命名、移动、删除、复制、使用正则表达式进行文件操作: 743 | 744 | 见[命令](#cmd) 745 | 746 | #### 4. 参考: 747 | 748 | > https://gist.github.com/HououinRedflag/6191023 749 | 750 | > https://github.com/banbanchs/pan-baidu-download/blob/master/bddown_core.py 751 | 752 | > https://github.com/houtianze/bypy 753 | 754 | --- 755 | 756 | 757 | ### bt.py - magnet torrent 互转、及 过滤敏.感.词 758 | 759 | #### 1. 依赖 760 | 761 | ``` 762 | python2-requests (https://github.com/kennethreitz/requests) 763 | bencode (https://github.com/bittorrent/bencode) 764 | ``` 765 | 766 | #### 2. 使用说明 767 | 768 | magnet 和 torrent 的相互转换 769 | 770 | 过滤敏.感.词功能用于净网时期的 baidu, xunlei 771 | 772 | 在中国大陆使用代理可能有更好的效果: 773 | 使用代理有两种方法: 774 | 1. shadowsocks + proxychains 775 | 2. -p protocol://ip:port 776 | 777 | ~~8.30日后,无法使用。 见 http://tieba.baidu.com/p/3265467666~~ 778 | 779 | [**百度云疑似解封,百度网盘内八秒视频部分恢复**](http://fuli.ba/baiduyunhuifuguankan.html) 780 | 781 | **!! 注意:过滤后生成的torrent在百度网盘只能用一次,如果需要再次使用,则需用 -n 改顶层目录名** 782 | 783 | 磁力连接转种子,用的是 784 | 785 | ``` 786 | http://bt.box.n0808.com 787 | http://btcache.me 788 | http://www.sobt.org # 302 --> http://www.win8down.com/url.php?hash= 789 | http://www.31bt.com 790 | http://178.73.198.210 791 | http://www.btspread.com # link to http://btcache.me 792 | http://torcache.net 793 | http://zoink.it 794 | http://torrage.com # 用torrage.com需要设置代理, eg: -p 127.0.0.1:8087 795 | http://torrentproject.se 796 | http://istoretor.com 797 | http://torrentbox.sx 798 | http://www.torrenthound.com 799 | http://www.silvertorrent.org 800 | http://magnet.vuze.com 801 | ``` 802 | 803 | 如果有更好的种子库,请提交issue 804 | 805 | > 对于baidu, 加入离线任务后,需等待一段时间才会下载完成。 806 | 807 | #### 命令: 808 | 809 | ``` 810 | # magnet 2 torrent 811 | m 或 mt magnet_link1 magnet_link2 [-d /path/to/save] 812 | m -i /there/are/files -d new 813 | 814 | # torrent 2 magnet, 输出magnet 815 | t 或 tm path1 path2 816 | 817 | # 过滤敏.感.词 818 | # 有2种模式 819 | # -t n (默认) 用数字替换文件名 820 | # -t be64 用base64加密文件名,torrent用百度下载后,可用 pan.baidu.com.py rnr /path -t f,bd64 改回原名字 821 | c 或 ct magnet_link1 magnet_link2 /path/to/torrent1 /path/to/torrent2 [-d /path/to/save] 822 | c -i /there/are/files and_other_dir -d new # 从文件或文件夹中寻找 magnet,再过滤 823 | # 过滤敏.感.词 - 将magnet或torrent转成不敏感的 torrent 824 | # /path/to/save 默认为 . 825 | 826 | # 用base64加密的文件名: 827 | c magnet_link1 magnet_link2 /path/to/torrent1 /path/to/torrent2 [-d /path/to/save] -t be64 828 | 829 | # 使用正则表达式过滤敏.感.词 830 | cr 或 ctre foo bar magnet_link1 /path/to/torrent1 [-d /path/to/save] 831 | # foo bar 都是 regex 832 | ``` 833 | 834 | #### 参数: 835 | 836 | ``` 837 | -p PROXY, --proxy PROXY proxy for torrage.com, eg: -p "sooks5://127.0.0.1:8883" 838 | -t TYPE_, --type_ TYPE_ 类型参数: 839 | -t n (默认) 用数字替换文件名 840 | -t be64 用base64加密文件名,torrent用百度下载后,可用 pan.baidu.com.py rnr /path -t f,bd64 改回原名字 841 | -d DIRECTORY, --directory DIRECTORY 指定torrents的保存路径, eg: -d /path/to/save 842 | -n NAME, --name NAME 顶级文件夹名称, eg: -m thistopdirectory 843 | -i localpath1 localpath2, --import_from localpath1 localpath2 从本地文本文件导入magnet (用正则表达式匹配) 844 | ``` 845 | 846 | #### 3. 用法 847 | 848 | bt 是bt.py的马甲 (alias bt='python2 /path/to/bt.py') 849 | 850 | ``` 851 | bt mt magnet_link1 magnet_link2 [-d /path/to/save] 852 | bt tm path1 path2 853 | bt ct magnet_link1 path1 [-d /path/to/save] 854 | 855 | bt m magnet_link1 magnet_link2 [-d /path/to/save] 856 | bt t path1 path2 857 | bt c magnet_link1 path1 [-d /path/to/save] 858 | 859 | # 用torrage.com 860 | bt m magnet_link1 path1 -p 127.0.0.1:8087 861 | bt c magnet_link1 path1 -p 127.0.0.1:8087 862 | 863 | # 从文件或文件夹中寻找 magnet,再过滤 864 | bt c -i ~/Downloads -d new 865 | 866 | # 使用正则表达式过滤敏.感.词 867 | bt cr '.*(old).*' '\1' magnet_link 868 | bt cr 'old.iso' 'new.iso' /path/to/torrent 869 | 870 | # 用base64加密的文件名: 871 | bt c magnet_link -t be64 872 | ``` 873 | 874 | #### 4. 参考: 875 | 876 | > http://blog.chinaunix.net/uid-28450123-id-4051635.html 877 | 878 | > http://en.wikipedia.org/wiki/Torrent_file 879 | 880 | --- 881 | 882 | 883 | ### 115.py - 115网盘的下载和播放 884 | 885 | #### 1. 依赖 886 | 887 | ``` 888 | wget 889 | 890 | aria2 (~ 1.18) 891 | 892 | python2-requests (https://github.com/kennethreitz/requests) 893 | 894 | mpv (http://mpv.io) 895 | 896 | mplayer # 我的linux上mpv播放wmv出错,换用mplayer 897 | ``` 898 | 899 | #### 2. 使用说明 900 | 901 | 初次使用需要登录 pan115 login 902 | 903 | **脚本是用于下载自己的115网盘文件,不支持他人分享文件。** 904 | 905 | 下载工具默认为wget, 可用参数-a选用aria2。 906 | 907 | **现在vip和非vip用户下载只能有1个通道,用aria2下载已经无意义。** 908 | 909 | 对所有文件,默认执行下载(用wget),如要播放媒体文件,加参数-p。 910 | 911 | **非vip用户下载太慢,已经不支持播放。 vip播放正常** 912 | 913 | 下载的文件,保存在当前目录下。 914 | 915 | cookies保存在 ~/.115.cookies 916 | 917 | 关于播放操作: 918 | 919 | > 在运行脚本的终端,输入1次Enter,关闭当前播放并播放下一个文件,连续输入2次Enter,关闭当前播放并退出。 920 | 921 | #### 参数: 922 | 923 | ``` 924 | -a, --aria2c download with aria2c 925 | -p, --play play with mpv 926 | -f number, --from_ number 从第几个开始下载,eg: -f 42 927 | -t ext, --type_ ext 要下载的文件的后缀,eg: -t mp3 928 | -l amount, --limit amount 下载速度限制,eg: -l 100k 929 | -d "url" 增加离线下载 "http/ftp/magnet/ed2k" 930 | ``` 931 | 932 | #### 3. 用法 933 | 934 | pan115 是115.py的马甲 (alias pan115='python2 /path/to/115.py') 935 | 936 | ``` 937 | # 登录 938 | pan115 g 939 | pan115 login 940 | pan115 login username 941 | pan115 login username password 942 | 943 | # 退出登录 944 | pan115 signout 945 | 946 | # 递归下载自己网盘中的*文件夹* 947 | pan115 http://115.com/?cid=xxxxxxxxxxxx&offset=0&mode=wangpan 948 | 949 | # 下载自己网盘中的*单个文件* -- 只能是115上可单独打开的文件,如pdf,视频 950 | pan115 http://wenku.115.com/preview/?pickcode=xxxxxxxxxxxx 951 | 952 | # 下载用aria2, url 是上面的 953 | pan115 -a url 954 | 955 | # 增加离线下载 956 | pan115 -d "magnet:?xt=urn:btih:757fc565c56462b28b4f9c86b21ac753500eb2a7&dn=archlinux-2014.04.01-dual.iso" 957 | ``` 958 | 959 | #### 播放 960 | 961 | ``` 962 | # url 是上面的 963 | pan115 -p url 964 | ``` 965 | 966 | #### 4. 参考: 967 | 968 | > http://passport.115.com/static/wap/js/common.js?v=1.6.39 969 | 970 | --- 971 | 972 | 973 | ### yunpan.360.cn.py - 360网盘的下载 974 | 975 | #### 1. 依赖 976 | 977 | ``` 978 | wget 979 | 980 | aria2 (~ 1.18) 981 | 982 | python2-requests (https://github.com/kennethreitz/requests) 983 | ``` 984 | 985 | #### 2. 使用说明 986 | 987 | 初次使用需要登录 yp login 988 | 989 | **!!!!!! 万恶的360不支持断点续传 !!!!!!** 990 | 991 | 由于上面的原因,不能播放媒体文件。 992 | 993 | 只支持自己的\*文件夹\*的递归下载。 994 | 995 | 下载工具默认为wget, 可用参数-a选用aria2 996 | 997 | 下载的文件,保存在当前目录下。 998 | 999 | cookies保存在 ~/.360.cookies 1000 | 1001 | #### 参数: 1002 | 1003 | ``` 1004 | -a, --aria2c download with aria2c 1005 | -f number, --from_ number 从第几个开始下载,eg: -f 42 1006 | -t ext, --type_ ext 要下载的文件的后缀,eg: -t mp3 1007 | -l amount, --limit amount 下载速度限制,eg: -l 100k 1008 | ``` 1009 | 1010 | #### 3. 用法 1011 | 1012 | yp 是yunpan.360.cn.py的马甲 (alias yp='python2 /path/to/yunpan.360.cn.py') 1013 | 1014 | ``` 1015 | # 登录 1016 | yp g 1017 | yp login 1018 | yp login username 1019 | yp login username password 1020 | 1021 | # 退出登录 1022 | yp signout 1023 | 1024 | # 递归下载自己网盘中的*文件夹* 1025 | yp http://c17.yunpan.360.cn/my/?sid=#/path/to/directory 1026 | yp http://c17.yunpan.360.cn/my/?sid=#%2Fpath%3D%2Fpath%2Fto%2Fdirectory 1027 | # or 1028 | yp sid=/path/to/directory 1029 | yp sid%3D%2Fpath%2Fto%2Fdirectory 1030 | 1031 | # 下载用aria2, url 是上面的 1032 | yp -a url 1033 | ``` 1034 | 1035 | #### 4. 参考: 1036 | 1037 | > https://github.com/Shu-Ji/gorthon/blob/master/_3rdapp/CloudDisk360/main.py 1038 | 1039 | --- 1040 | 1041 | 1042 | ### music.baidu.com.py - 下载或播放高品质百度音乐(music.baidu.com) 1043 | 1044 | #### 1. 依赖 1045 | 1046 | ``` 1047 | wget 1048 | 1049 | python2-mutagen (https://code.google.com/p/mutagen/) 1050 | 1051 | mpv (http://mpv.io) 1052 | ``` 1053 | 1054 | #### 2. 使用说明 1055 | 1056 | 默认执行下载,如要播放,加参数-p。 1057 | 1058 | #### 参数: 1059 | 1060 | ``` 1061 | -f, --flac download flac 1062 | -i, --high download 320, default 1063 | -l, --low download 128 1064 | -p, --play play with mpv 1065 | ``` 1066 | 1067 | 下载的MP3默认添加id3 tags,保存在当前目录下。 1068 | 1069 | 关于播放操作: 1070 | 1071 | > 在运行脚本的终端,输入1次Enter,关闭当前播放并播放下一个文件,连续输入2次Enter,关闭当前播放并退出。 1072 | 1073 | #### 3. 用法 1074 | 1075 | bm 是music.baidu.com.py的马甲 (alias bm='python2 /path/to/music.baidu.com.py') 1076 | 1077 | ``` 1078 | # 下载专辑 1079 | bm http://music.baidu.com/album/115032005 1080 | 1081 | # 下载单曲 1082 | bm http://music.baidu.com/song/117948039 1083 | ``` 1084 | 1085 | #### 播放: 1086 | 1087 | ``` 1088 | # url 是上面的 1089 | bm -p url 1090 | ``` 1091 | 1092 | #### 4. 参考: 1093 | 1094 | > http://v2ex.com/t/77685 # 第9楼 1095 | 1096 | --- 1097 | 1098 | 1099 | ### music.163.com.py - 下载或播放高品质网易音乐(music.163.com) 1100 | 1101 | #### 1. 依赖 1102 | 1103 | ``` 1104 | wget 1105 | 1106 | python2-requests (https://github.com/kennethreitz/requests) 1107 | 1108 | python2-mutagen (https://code.google.com/p/mutagen/) 1109 | 1110 | mpv (http://mpv.io) 1111 | ``` 1112 | 1113 | #### 2. 使用说明 1114 | 1115 | **默认下载和播放高品质音乐,如果服务器没有高品质音乐则转到低品质音乐。** 1116 | 1117 | 默认执行下载,如要播放,加参数-p。 1118 | 1119 | 下载的MP3默认添加id3 tags,保存在当前目录下。 1120 | 1121 | 关于播放操作: 1122 | 1123 | > 在运行脚本的终端,输入1次Enter,关闭当前播放并播放下一个文件,连续输入2次Enter,关闭当前播放并退出。 1124 | 1125 | #### 3. 用法 1126 | 1127 | nm 是music.163.com.py的马甲 (alias nm='python2 /path/to/music.163.com.py') 1128 | 1129 | ``` 1130 | # 下载专辑 1131 | nm http://music.163.com/#/album?id=18915 1132 | 1133 | # 下载单曲 1134 | nm http://music.163.com/#/song?id=186114 1135 | 1136 | # 下载歌单 1137 | nm http://music.163.com/#/playlist?id=12214308 1138 | 1139 | # 下载该艺术家所有专辑或 Top 50 歌曲 1140 | nm http://music.163.com/#/artist?id=6452 1141 | 1142 | # 下载DJ节目 1143 | nm http://music.163.com/#/dj?id=675051 1144 | 1145 | # 下载排行榜 1146 | nm http://music.163.com/#/discover/toplist?id=11641012 1147 | ``` 1148 | 1149 | #### 播放: 1150 | 1151 | ``` 1152 | # url 是上面的 1153 | nm -p url 1154 | ``` 1155 | 1156 | #### 4. 参考: 1157 | 1158 | > 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 1159 | 1160 | > http://s3.music.126.net/s/2/core.js 1161 | 1162 | --- 1163 | 1164 | 1165 | ### flv_cmd.py - 基于在线服务的视频解析 client - 支持下载、播放 1166 | 1167 | #### 1. 依赖 1168 | 1169 | ``` 1170 | wget 1171 | 1172 | python2-requests (https://github.com/kennethreitz/requests) 1173 | 1174 | mpv (http://mpv.io) 1175 | ``` 1176 | 1177 | #### 2. 使用说明 1178 | 1179 | ~~flvxz.com 视频解析~~ 不能用。 1180 | 1181 | flvgo.com 视频解析 1182 | 1183 | **不提供视频合并操作** 1184 | 1185 | #### 支持的网站: 1186 | 1187 | http://flvgo.com/sites 1188 | 1189 | 关于播放操作: 1190 | 1191 | > 在运行脚本的终端,输入1次Enter,关闭当前播放并播放下一个文件,连续输入2次Enter,关闭当前播放并退出。 1192 | 1193 | #### 3. 用法 1194 | 1195 | fl是flv_cmd.py的马甲 (alias fl='python2 /path/to/flv_cmd.py') 1196 | 1197 | #### 下载: 1198 | 1199 | ``` 1200 | fl http://v.youku.com/v_show/id_XNTI2Mzg4NjAw.html 1201 | fl http://www.tudou.com/albumplay/Lqfme5hSolM/tJ_Gl3POz7Y.html 1202 | ``` 1203 | 1204 | #### 播放: 1205 | 1206 | ``` 1207 | # url 是上面的 1208 | fl url -p 1209 | ``` 1210 | 1211 | #### 4. 相关脚本: 1212 | 1213 | > https://github.com/soimort/you-get 1214 | 1215 | > https://github.com/iambus/youku-lixian 1216 | 1217 | > https://github.com/rg3/youtube-dl 1218 | 1219 | --- 1220 | 1221 | 1222 | ### tumblr.py - 下载某个tumblr.com的所有图片、视频、音频 1223 | 1224 | #### 1. 依赖 1225 | 1226 | ``` 1227 | wget 1228 | 1229 | mpv (http://mpv.io) 1230 | 1231 | python2-requests (https://github.com/kennethreitz/requests) 1232 | ``` 1233 | 1234 | #### 2. 使用说明 1235 | 1236 | * 使用前需用在 http://www.tumblr.com/oauth/apps 加入一个app,证实后得到api_key,再在源码中填入,完成后则可使用。 1237 | 1238 | * 或者用 http://www.tumblr.com/docs/en/api/v2 提供的api_key ( fuiKNFp9vQFvjLNvx4sUwti4Yb5yGutBN4Xh10LXZhhRKjWlV4 ) 1239 | 1240 | 默认开10个进程,如需改变用参数-p [num]。 1241 | 1242 | 下载的文件,保存在当前目录下。 1243 | 1244 | 默认下载图片(原图)。 1245 | 1246 | 支持连续下载,下载进度储存在下载文件夹内的 json.json。 1247 | 1248 | **正确退出程序使用 Ctrl-C** 1249 | **下载 更新的图片或其他 用 tumblr --update URL, 或 删除 json.json** 1250 | 1251 | #### 参数: 1252 | 1253 | ``` 1254 | -p PROCESSES, --processes PROCESSES 指定多进程数,默认为10个,最多为20个 eg: -p 20 1255 | -c, --check 尝试修复未下载成功的图片 1256 | -t TAG, --tag TAG 下载特定tag的图片, eg: -t beautiful 1257 | 1258 | -P, --play play with mpv 1259 | -A, --audio download audios 1260 | -V, --video download videos 1261 | -q, --quiet quiet 1262 | 1263 | --update 下载新发布的东西 1264 | --redownload 重新遍历所有的东西,如果有漏掉的东西则下载 1265 | 1266 | -f OFFSET, --offset OFFSET 从第offset个开始,只对 -V 有用。 1267 | ``` 1268 | 1269 | #### 3. 用法 1270 | 1271 | tm是tumblr.py的马甲 (alias tm='python2 /path/to/tumblr.py') 1272 | 1273 | ``` 1274 | # 下载图片 1275 | tm http://sosuperawesome.tumblr.com 1276 | tm http://sosuperawesome.tumblr.com -t beautiful 1277 | 1278 | # 下载单张图片 1279 | tm http://sosuperawesome.tumblr.com/post/121467716523/murosvur-on-etsy 1280 | 1281 | # 下载视频 1282 | tm url -V 1283 | tm url -V -f 42 1284 | tm url -V -t tag 1285 | 1286 | # 下载单个视频 1287 | tm url/post/1234567890 -V 1288 | 1289 | # 播放视频 1290 | tm url -VP 1291 | tm url -VP -f 42 1292 | 1293 | # 下载音频 1294 | tm url -A 1295 | tm url -A -f 42 1296 | tm url -A -t tag 1297 | 1298 | # 下载单个音频 1299 | tm url/post/1234567890 -A 1300 | 1301 | # 播放音频 1302 | tm url -AP 1303 | tm url -AP -f 42 1304 | 1305 | # 播放音频(quiet) 1306 | tm url -APq 1307 | 1308 | ``` 1309 | 1310 | --- 1311 | 1312 | 1313 | ### unzip.py - 解决linux下unzip乱码的问题 1314 | 1315 | #### 用法 1316 | 1317 | ``` 1318 | python2 unzip.py azipfile1.zip azipfile2.zip 1319 | python2 unzip.py azipfile.zip -s secret 1320 | # -s 密码 1321 | ``` 1322 | 1323 | 代码来自以下连接,我改了一点。 1324 | 1325 | > http://wangqige.com/the-solution-of-unzip-files-which-zip-under-windows/解决在Linux环境下解压zip的乱码问题 1326 | 1327 | --- 1328 | 1329 | 1330 | ### ed2k_search.py - 基于 donkey4u.com 的emule搜索 1331 | 1332 | #### 1. 依赖 1333 | 1334 | ``` 1335 | python2 1336 | ``` 1337 | 1338 | #### 2. 用法 1339 | 1340 | ed 是ed2k_search.py的马甲 (alias ed='python2 /path/to/ed2k_search.py') 1341 | 1342 | ``` 1343 | ed this is a keyword 1344 | or 1345 | ed "this is a keyword" 1346 | ``` 1347 | 1348 | --- 1349 | 1350 | 1351 | ### 91porn.py - 下载或播放91porn 1352 | 1353 | **警告: 18岁以下者,请自觉远离。** 1354 | 1355 | #### 1. 依赖 1356 | 1357 | ``` 1358 | wget 1359 | 1360 | aria2 (~ 1.18) 1361 | 1362 | python2-requests (https://github.com/kennethreitz/requests) 1363 | 1364 | mpv (http://mpv.io) 1365 | ``` 1366 | 1367 | #### 2. 使用说明 1368 | 1369 | > youtube-dl 已支持91porn 1370 | 1371 | 没有解决每个ip *10个/day* 限制 1372 | 1373 | 下载工具默认为wget, 可用参数-a选用aria2 1374 | 1375 | 默认执行下载,如要播放媒体文件,加参数-p。 1376 | 1377 | 下载的文件,保存在当前目录下。 1378 | 1379 | 关于播放操作: 1380 | 1381 | > 在运行脚本的终端,输入1次Enter,关闭当前播放并播放下一个文件,连续输入2次Enter,关闭当前播放并退出。 1382 | 1383 | #### 3. 用法 1384 | 1385 | pn 是91porn.py的马甲 (alias pn='python2 /path/to/91porn.py') 1386 | 1387 | #### 下载: 1388 | 1389 | ``` 1390 | pn url # 91porn.com(或其镜像) 视频的url 1391 | ``` 1392 | 1393 | #### 播放: 1394 | 1395 | ``` 1396 | pn -p url 1397 | ``` 1398 | 1399 | 显示下载链接,但不下载: 1400 | 1401 | ``` 1402 | pn -u url 1403 | ``` 1404 | 1405 | #### 4. 参考 1406 | 1407 | > http://v2ex.com/t/110196 # 第16楼 1408 | 1409 | --- 1410 | 1411 | 1412 | ### ThunderLixianExporter.user.js - A fork of https://github.com/binux/ThunderLixianExporter 1413 | 1414 | **一个github.com/binux的迅雷离线导出脚本的fork。** 1415 | 1416 | 增加了mpv和mplayer的导出。 1417 | 1418 | 用法见: https://github.com/binux/ThunderLixianExporter 1419 | -------------------------------------------------------------------------------- /xiami.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | # -*- coding: utf-8 -*- 3 | 4 | import re 5 | import sys 6 | from getpass import getpass 7 | import os 8 | import random 9 | import time 10 | import json 11 | import argparse 12 | import requests 13 | import urllib 14 | import hashlib 15 | import select 16 | from mutagen.id3 import ID3,TRCK,TIT2,TALB,TPE1,APIC,TDRC,COMM,TPOS,USLT 17 | from HTMLParser import HTMLParser 18 | 19 | url_song = "http://www.xiami.com/song/%s" 20 | url_album = "http://www.xiami.com/album/%s" 21 | url_collect = "http://www.xiami.com/collect/%s" 22 | url_artist_albums = "http://www.xiami.com/artist/album/id/%s/page/%s" 23 | url_artist_top_song = "http://www.xiami.com/artist/top/id/%s" 24 | url_lib_songs = "http://www.xiami.com/space/lib-song/u/%s/page/%s" 25 | url_recent = "http://www.xiami.com/space/charts-recent/u/%s/page/%s" 26 | 27 | # 电台来源:来源于"收藏的歌曲","收藏的专辑","喜欢的艺人","我收藏的精选集" 28 | url_radio_my = "http://www.xiami.com/radio/xml/type/4/id/%s" 29 | # 虾米猜, 基于你的虾米试听行为所建立的个性电台 30 | url_radio_c = "http://www.xiami.com/radio/xml/type/8/id/%s" 31 | 32 | ############################################################ 33 | # wget exit status 34 | wget_es = { 35 | 0:"No problems occurred.", 36 | 2:"User interference.", 37 | 1<<8:"Generic error code.", 38 | 2<<8:"Parse error - for instance, when parsing command-line ' \ 39 | 'optio.wgetrc or .netrc...", 40 | 3<<8:"File I/O error.", 41 | 4<<8:"Network failure.", 42 | 5<<8:"SSL verification failure.", 43 | 6<<8:"Username/password authentication failure.", 44 | 7<<8:"Protocol errors.", 45 | 8<<8:"Server issued an error response." 46 | } 47 | ############################################################ 48 | 49 | parser = HTMLParser() 50 | s = '\x1b[%d;%dm%s\x1b[0m' # terminual color template 51 | 52 | cookie_file = os.path.join(os.path.expanduser('~'), '.Xiami.cookies') 53 | 54 | headers = { 55 | "Accept":"text/html,application/xhtml+xml,application/xml; " \ 56 | "q=0.9,image/webp,*/*;q=0.8", 57 | "Accept-Encoding":"text/html", 58 | "Accept-Language":"en-US,en;q=0.8,zh-CN;q=0.6,zh;q=0.4,zh-TW;q=0.2", 59 | "Content-Type":"application/x-www-form-urlencoded", 60 | "Referer":"http://www.xiami.com/", 61 | "User-Agent":"Mozilla/5.0 (X11; Linux i686) AppleWebKit/537.36 "\ 62 | "(KHTML, like Gecko) Chrome/32.0.1700.77 Safari/537.36" 63 | } 64 | 65 | ss = requests.session() 66 | ss.headers.update(headers) 67 | 68 | ############################################################ 69 | # Regular Expression Templates 70 | re_disc_description = r'disc (\d+) \[(.+?)\]' 71 | ############################################################ 72 | 73 | def decry(row, encryed_url): 74 | url = encryed_url 75 | urllen = len(url) 76 | rows = int(row) 77 | 78 | cols_base = urllen / rows # basic column count 79 | rows_ex = urllen % rows # count of rows that have 1 more column 80 | 81 | matrix = [] 82 | for r in xrange(rows): 83 | length = cols_base + 1 if r < rows_ex else cols_base 84 | matrix.append(url[:length]) 85 | url = url[length:] 86 | 87 | url = '' 88 | for i in xrange(urllen): 89 | url += matrix[i % rows][i / rows] 90 | 91 | return urllib.unquote(url).replace('^', '0') 92 | 93 | def modificate_text(text): 94 | text = parser.unescape(text) 95 | text = re.sub(r'//*', '-', text) 96 | text = text.replace('/', '-') 97 | text = text.replace('\\', '-') 98 | text = re.sub(r'\s\s+', ' ', text) 99 | text = text.strip() 100 | return text 101 | 102 | def modificate_file_name_for_wget(file_name): 103 | file_name = re.sub(r'\s*:\s*', u' - ', file_name) # for FAT file system 104 | file_name = file_name.replace('?', '') # for FAT file system 105 | file_name = file_name.replace('"', '\'') # for FAT file system 106 | file_name = file_name.replace('$', '\\$') # for command, see issue #7 107 | return file_name 108 | 109 | def z_index(song_infos): 110 | size = len(song_infos) 111 | z = len(str(size)) 112 | return z 113 | 114 | ######################################################## 115 | 116 | class xiami(object): 117 | def __init__(self): 118 | self.dir_ = os.getcwdu() 119 | self.template_record = 'http://www.xiami.com/count/playrecord?sid=%s' 120 | 121 | self.collect_id = '' 122 | self.album_id = '' 123 | self.artist_id = '' 124 | self.song_id = '' 125 | self.user_id = '' 126 | self.cover_id = '' 127 | self.cover_data = '' 128 | 129 | self.html = '' 130 | self.disc_description_archives = {} 131 | 132 | self.download = self.play if args.play else self.download 133 | 134 | def init(self): 135 | if os.path.exists(cookie_file): 136 | try: 137 | cookies = json.load(open(cookie_file)) 138 | ss.cookies.update(cookies.get('cookies', cookies)) 139 | if not self.check_login(): 140 | print s % (1, 91, ' !! cookie is invalid, please login\n') 141 | sys.exit(1) 142 | except: 143 | open(cookie_file, 'w').close() 144 | print s % (1, 97, ' please login') 145 | sys.exit(1) 146 | else: 147 | print s % (1, 91, ' !! cookie_file is missing, please login') 148 | sys.exit(1) 149 | 150 | def check_login(self): 151 | #print s % (1, 97, '\n -- check_login') 152 | url = 'http://www.xiami.com/task/signin' 153 | r = ss.get(url) 154 | if r.content: 155 | #print s % (1, 92, ' -- check_login success\n') 156 | # self.save_cookies() 157 | return True 158 | else: 159 | print s % (1, 91, ' -- login fail, please check email and password\n') 160 | return False 161 | 162 | # manually, add cookies 163 | # you must know how to get the cookie 164 | def add_member_auth(self, member_auth): 165 | member_auth = member_auth.rstrip(';') 166 | self.save_cookies(member_auth) 167 | ss.cookies.update({'member_auth': member_auth}) 168 | 169 | def login(self, email, password): 170 | print s % (1, 97, '\n -- login') 171 | 172 | #validate = self.get_validate() 173 | data = { 174 | 'email': email, 175 | 'password': password, 176 | #'validate': validate, 177 | 'remember': 1, 178 | 'LoginButton': '登录' 179 | } 180 | 181 | hds = { 182 | 'Origin': 'http://www.xiami.com', 183 | 'Accept-Encoding': 'gzip, deflate', 184 | 'Accept-Language': 'en-US,en;q=0.8', 185 | 'Upgrade-Insecure-Requests': '1', 186 | 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36', 187 | 'Content-Type': 'application/x-www-form-urlencoded', 188 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 189 | 'Cache-Control': 'max-age=1', 190 | 'Referer': 'http://www.xiami.com/web/login', 191 | 'Connection': 'keep-alive', 192 | } 193 | 194 | cookies = { 195 | '_xiamitoken': hashlib.md5(str(time.time())).hexdigest() 196 | } 197 | 198 | url = 'https://login.xiami.com/web/login' 199 | 200 | for i in xrange(2): 201 | res = ss.post(url, headers=hds, data=data, cookies=cookies) 202 | if ss.cookies.get('member_auth'): 203 | self.save_cookies() 204 | return True 205 | else: 206 | if 'checkcode' not in res.content: 207 | return False 208 | validate = self.get_validate(res.content) 209 | data['validate'] = validate 210 | 211 | return False 212 | 213 | # {{{ code from https://github.com/ly0/xiami-tools/blob/master/xiami.py 214 | def login_taobao(self, username, password): 215 | print s % (1, 97, '\n -- login taobao') 216 | 217 | p = { 218 | "lang": "zh_cn", 219 | "appName": "xiami", 220 | "appEntrance": "taobao", 221 | "cssLink": "", 222 | "styleType": "vertical", 223 | "bizParams": "", 224 | "notLoadSsoView": "", 225 | "notKeepLogin": "", 226 | "appName": "xiami", 227 | "appEntrance": "taobao", 228 | "cssLink": "https://h.alipayobjects.com/static/applogin/" \ 229 | "assets/login/mini-login-form-min.css", 230 | "styleType": "vertical", 231 | "bizParams": "", 232 | "notLoadSsoView": "true", 233 | "notKeepLogin": "true", 234 | "rnd": str(random.random()), 235 | } 236 | url = 'https://passport.alipay.com/mini_login.htm' 237 | r = ss.get(url, params=p, verify=True) 238 | cm = r.content 239 | 240 | data = { 241 | "loginId": username, 242 | "password": password, 243 | "appName": "xiami", 244 | "appEntrance": "taobao", 245 | "hsid": re.search(r'"hsid" value="(.+?)"', cm).group(1), 246 | "cid": re.search(r'"cid" value="(.+?)"', cm).group(1), 247 | "rdsToken": re.search(r'"rdsToken" value="(.+?)"', cm).group(1), 248 | "umidToken": re.search(r'"umidToken" value="(.+?)"', cm).group(1), 249 | "_csrf_token": re.search(r'"_csrf_token" value="(.+?)"', cm).group(1), 250 | "checkCode": "", 251 | } 252 | url = 'https://passport.alipay.com/newlogin/login.do?fromSite=0' 253 | theaders = headers 254 | theaders['Referer'] = 'https://passport.alipay.com/mini_login.htm' 255 | 256 | while True: 257 | r = ss.post(url, data=data, headers=theaders, verify=True) 258 | j = r.json() 259 | 260 | if j['content']['status'] == -1: 261 | if 'titleMsg' not in j['content']['data']: continue 262 | err_msg = j['content']['data']['titleMsg'] 263 | if err_msg == u'请输入验证码' or err_msg == u'验证码错误,请重新输入': 264 | captcha_url = 'http://pin.aliyun.com/get_img?' \ 265 | 'identity=passport.alipay.com&sessionID=%s' % data['cid'] 266 | tr = ss.get(captcha_url, headers=theaders) 267 | path = os.path.join(os.path.expanduser('~'), 'vcode.jpg') 268 | with open(path, 'w') as g: 269 | img = tr.content 270 | g.write(img) 271 | print " ++ 验证码已经保存至", s % (2, 91, path) 272 | captcha = raw_input( 273 | (s % (2, 92, ' ++ %s: ' % err_msg)).encode('utf8')) 274 | data['checkCode'] = captcha 275 | continue 276 | 277 | if not j['content']['data'].get('st'): 278 | print s % (2, 91, " !! 输入的 username 或 password 有误.") 279 | sys.exit(1) 280 | 281 | url = 'http://www.xiami.com/accounts/back?st=%s' \ 282 | % j['content']['data']['st'] 283 | ss.get(url, headers=theaders) 284 | 285 | self.save_cookies() 286 | return 287 | # }}} 288 | 289 | def get_validate(self, cn): 290 | #url = 'https://login.xiami.com/coop/checkcode?forlogin=1&%s' \ 291 | #% int(time.time()) 292 | url = re.search(r'src="(http.+checkcode.+?)"', cn).group(1) 293 | path = os.path.join(os.path.expanduser('~'), 'vcode.png') 294 | with open(path, 'w') as g: 295 | data = ss.get(url).content 296 | g.write(data) 297 | print " ++ 验证码已经保存至", s % (2, 91, path) 298 | validate = raw_input(s % (2, 92, ' 请输入验证码: ')) 299 | return validate 300 | 301 | def save_cookies(self, member_auth=None): 302 | if not member_auth: 303 | member_auth = ss.cookies.get_dict()['member_auth'] 304 | with open(cookie_file, 'w') as g: 305 | cookies = { 'cookies': { 'member_auth': member_auth } } 306 | json.dump(cookies, g) 307 | 308 | def get_durl(self, id_): 309 | while True: 310 | try: 311 | if not args.low: 312 | url = 'http://www.xiami.com/song/gethqsong/sid/%s' 313 | j = ss.get(url % id_).json() 314 | t = j['location'] 315 | else: 316 | url = 'http://www.xiami.com/song/playlist/id/%s' 317 | cn = ss.get(url % id_).text 318 | t = re.search(r'location>(.+?)(http.+?)', xml) 376 | if not t: return None 377 | lyric_url = t.group(1) 378 | data = ss.get(lyric_url).content.replace('\r\n', '\n') 379 | data = lyric_parser(data) 380 | if data: 381 | return data.decode('utf8', 'ignore') 382 | else: 383 | return None 384 | 385 | def get_disc_description(self, album_url, info): 386 | if not self.html: 387 | self.html = ss.get(album_url).text 388 | t = re.findall(re_disc_description, self.html) 389 | t = dict([(a, modificate_text(parser.unescape(b))) \ 390 | for a, b in t]) 391 | self.disc_description_archives = dict(t) 392 | if self.disc_description_archives.has_key(info['cd_serial']): 393 | disc_description = self.disc_description_archives[info['cd_serial']] 394 | return u'(%s)' % disc_description 395 | else: 396 | return u'' 397 | 398 | def modified_id3(self, file_name, info): 399 | id3 = ID3() 400 | id3.add(TRCK(encoding=3, text=info['track'])) 401 | id3.add(TDRC(encoding=3, text=info['year'])) 402 | id3.add(TIT2(encoding=3, text=info['song_name'])) 403 | id3.add(TALB(encoding=3, text=info['album_name'])) 404 | id3.add(TPE1(encoding=3, text=info['artist_name'])) 405 | id3.add(TPOS(encoding=3, text=info['cd_serial'])) 406 | lyric_data = self.get_lyric(info) 407 | id3.add(USLT(encoding=3, text=lyric_data)) if lyric_data else None 408 | #id3.add(TCOM(encoding=3, text=info['composer'])) 409 | #id3.add(WXXX(encoding=3, desc=u'xiami_song_url', text=info['song_url'])) 410 | #id3.add(TCON(encoding=3, text=u'genre')) 411 | #id3.add(TSST(encoding=3, text=info['sub_title'])) 412 | #id3.add(TSRC(encoding=3, text=info['disc_code'])) 413 | id3.add(COMM(encoding=3, desc=u'Comment', \ 414 | text=info['comment'])) 415 | id3.add(APIC(encoding=3, mime=u'image/jpeg', type=3, \ 416 | desc=u'Front Cover', data=self.get_cover(info))) 417 | id3.save(file_name) 418 | 419 | def url_parser(self, urls): 420 | for url in urls: 421 | if '/collect/' in url: 422 | self.collect_id = re.search(r'/collect/(\d+)', url).group(1) 423 | #print(s % (2, 92, u'\n -- 正在分析精选集信息 ...')) 424 | self.download_collect() 425 | 426 | elif '/album/' in url: 427 | self.album_id = re.search(r'/album/(\d+)', url).group(1) 428 | #print(s % (2, 92, u'\n -- 正在分析专辑信息 ...')) 429 | self.download_album() 430 | 431 | elif '/artist/' in url or 'i.xiami.com' in url: 432 | def get_artist_id(url): 433 | html = ss.get(url).text 434 | artist_id = re.search(r'artist_id = \'(\d+)\'', html).group(1) 435 | return artist_id 436 | 437 | self.artist_id = re.search(r'/artist/(\d+)', url).group(1) \ 438 | if '/artist/' in url else get_artist_id(url) 439 | code = raw_input(' >> a # 艺术家所有专辑.\n' \ 440 | ' >> r # 艺术家 radio\n' \ 441 | ' >> t # 艺术家top 20歌曲.\n >> ') 442 | if code == 'a': 443 | #print(s % (2, 92, u'\n -- 正在分析艺术家专辑信息 ...')) 444 | self.download_artist_albums() 445 | elif code == 't': 446 | #print(s % (2, 92, u'\n -- 正在分析艺术家top20信息 ...')) 447 | self.download_artist_top_20_songs() 448 | elif code == 'r': 449 | self.download_artist_radio() 450 | else: 451 | print(s % (1, 92, u' --> Over')) 452 | 453 | elif '/song/' in url: 454 | self.song_id = re.search(r'/song/(\d+)', url).group(1) 455 | #print(s % (2, 92, u'\n -- 正在分析歌曲信息 ...')) 456 | self.download_song() 457 | 458 | elif '/u/' in url: 459 | self.user_id = re.search(r'/u/(\d+)', url).group(1) 460 | code = raw_input(' >> m # 该用户歌曲库.\n' \ 461 | ' >> c # 最近在听\n' \ 462 | ' >> s # 分享的音乐\n' 463 | ' >> rm # 私人电台:来源于"收藏的歌曲","收藏的专辑",\ 464 | "喜欢的艺人","收藏的精选集"\n' 465 | ' >> rc # 虾米猜:基于试听行为所建立的个性电台\n >> ') 466 | if code == 'm': 467 | #print(s % (2, 92, u'\n -- 正在分析用户歌曲库信息 ...')) 468 | self.download_user_songs(url_lib_songs, u'收藏的歌曲') 469 | elif code == 'c': 470 | self.download_user_songs(url_recent, u'最近在听的歌曲') 471 | elif code == 's': 472 | url_shares = 'http://www.xiami.com' \ 473 | '/space/feed/u/%s/type/3/page/%s' % (self.user_id, '%s') 474 | self.download_user_shares(url_shares) 475 | elif code == 'rm': 476 | #print(s % (2, 92, u'\n -- 正在分析该用户的虾米推荐 ...')) 477 | url_rndsongs = url_radio_my 478 | self.download_user_radio(url_rndsongs) 479 | elif code == 'rc': 480 | url_rndsongs = url_radio_c 481 | self.download_user_radio(url_rndsongs) 482 | else: 483 | print(s % (1, 92, u' --> Over')) 484 | 485 | elif '/chart/' in url: 486 | self.chart_id = re.search(r'/c/(\d+)', url).group(1) \ 487 | if '/c/' in url else 101 488 | type_ = re.search(r'/type/(\d+)', url).group(1) \ 489 | if '/type/' in url else 0 490 | self.download_chart(type_) 491 | 492 | elif '/genre/' in url: 493 | if '/gid/' in url: 494 | self.genre_id = re.search(r'/gid/(\d+)', url).group(1) 495 | url_genre = 'http://www.xiami.com' \ 496 | '/genre/songs/gid/%s/page/%s' 497 | elif '/sid/' in url: 498 | self.genre_id = re.search(r'/sid/(\d+)', url).group(1) 499 | url_genre = 'http://www.xiami.com' \ 500 | '/genre/songs/sid/%s/page/%s' 501 | else: 502 | print s % (1, 91, ' !! Error: missing genre id at url') 503 | sys.exit(1) 504 | 505 | code = raw_input(' >> t # 风格推荐\n' \ 506 | ' >> r # 风格radio\n >> ') 507 | if code == 't': 508 | self.download_genre(url_genre) 509 | elif code == 'r': 510 | self.download_genre_radio(url_genre) 511 | 512 | elif 'luoo.net' in url: 513 | self.hack_luoo(url) 514 | 515 | elif 'sid=' in url: 516 | _mod = re.search(r'sid=([\d+,]+\d)', url) 517 | if _mod: 518 | song_ids = _mod.group(1).split(',') 519 | self.download_songs(song_ids) 520 | 521 | else: 522 | print(s % (2, 91, u' 请正确输入虾米网址.')) 523 | 524 | def get_songs(self, album_id, song_id=None): 525 | html = ss.get(url_album % album_id).text 526 | html = html.split('
(.+?)<', html1).group(1) 530 | album_name = modificate_text(t) 531 | 532 | t = re.search(r'"/artist/\d+.+?>(.+?)<', html1).group(1) 533 | artist_name = modificate_text(t) 534 | 535 | t = re.findall(u'(\d+)年(\d+)月(\d+)', html1) 536 | year = '-'.join(t[0]) if t else '' 537 | 538 | album_description = '' 539 | t = re.search(u'专辑介绍:(.+?)
', 540 | html2, re.DOTALL) 541 | if t: 542 | t = t.group(1) 543 | t = re.sub(r'<.+?>', '', t) 544 | t = parser.unescape(t) 545 | t = parser.unescape(t) 546 | t = re.sub(r'\s\s+', u'\n', t).strip() 547 | t = re.sub(r'<.+?(http://.+?)".+?>', r'\1', t) 548 | t = re.sub(r'<.+?>([^\n])', r'\1', t) 549 | t = re.sub(r'<.+?>(\r\n|)', u'\n', t) 550 | album_description = t 551 | 552 | #t = re.search(r'href="(.+?)" id="albumCover"', html1).group(1) 553 | t = re.search(r'id="albumCover".+?"(http://.+?)" ', html1).group(1) 554 | #tt = t.rfind('.') 555 | #t = '%s_4%s' % (t[:tt], t[tt:]) 556 | t = t.replace('_2.', '_4.') 557 | album_pic_url = t 558 | 559 | songs = [] 560 | for c in html2.split('class="trackname"')[1:]: 561 | disc = re.search(r'>disc (\d+)', c).group(1) 562 | 563 | t = re.search(r'>disc .+?\[(.+?)\]', c) 564 | disc_description = modificate_text(t.group(1)) if t else '' 565 | 566 | # find track 567 | t = re.findall(r'"trackid">(\d+)', c) 568 | tracks = [i.lstrip('0') for i in t] 569 | z = len(str(len(tracks))) 570 | 571 | # find all song_ids and song_names 572 | t = re.findall(r'(.+?)(.*?)<', c) 579 | song_played = [int(i) if i.isdigit() else 0 for i in t] 580 | 581 | if len(tracks) != len(song_ids) != len(song_names): 582 | print s % (1, 91, ' !! Error: ' \ 583 | 'len(tracks) != len(song_ids) != len(song_names)') 584 | sys.exit(1) 585 | 586 | for i in xrange(len(tracks)): 587 | song_info = {} 588 | song_info['song_id'] = song_ids[i] 589 | song_info['song_played'] = song_played[i] 590 | song_info['album_id'] = album_id 591 | song_info['song_url'] = u'http://www.xiami.com/song/' \ 592 | + song_ids[i] 593 | song_info['track'] = tracks[i] 594 | song_info['cd_serial'] = disc 595 | song_info['year'] = year 596 | song_info['album_pic_url'] = album_pic_url 597 | song_info['song_name'] = song_names[i] 598 | song_info['album_name'] = album_name 599 | song_info['artist_name'] = artist_name 600 | song_info['z'] = z 601 | song_info['disc_description'] = disc_description 602 | t = '%s\n\n%s%s' % (song_info['song_url'], 603 | disc_description + u'\n\n' \ 604 | if disc_description else '', 605 | album_description) 606 | song_info['comment'] = t 607 | 608 | songs.append(song_info) 609 | 610 | cd_serial_auth = int(songs[-1]['cd_serial']) > 1 611 | for i in xrange(len(songs)): 612 | z = songs[i]['z'] 613 | file_name = songs[i]['track'].zfill(z) + '.' \ 614 | + songs[i]['song_name'] \ 615 | + ' - ' + songs[i]['artist_name'] + '.mp3' 616 | if cd_serial_auth: 617 | songs[i]['file_name'] = ''.join([ 618 | '[Disc-', 619 | songs[i]['cd_serial'], 620 | ' # ' + songs[i]['disc_description'] \ 621 | if songs[i]['disc_description'] else '', '] ', 622 | file_name]) 623 | else: 624 | songs[i]['file_name'] = file_name 625 | 626 | t = [i for i in songs if i['song_id'] == song_id] \ 627 | if song_id else songs 628 | songs = t 629 | 630 | return songs 631 | 632 | def get_song(self, song_id): 633 | html = ss.get(url_song % song_id).text 634 | html = html.split('
> ' + u'1 首歌曲将要下载.')) \ 646 | if not args.play else '' 647 | #self.song_infos = [song_info] 648 | self.download(songs) 649 | 650 | def download_songs(self, song_ids): 651 | for song_id in song_ids: 652 | self.song_id = song_id 653 | songs = self.get_song(self.song_id) 654 | self.download(songs) 655 | 656 | 657 | def download_album(self): 658 | songs = self.get_songs(self.album_id) 659 | song = songs[0] 660 | 661 | d = song['album_name'] + ' - ' + song['artist_name'] 662 | dir_ = os.path.join(os.getcwdu(), d) 663 | self.dir_ = modificate_file_name_for_wget(dir_) 664 | 665 | amount_songs = unicode(len(songs)) 666 | songs = songs[args.from_ - 1:] 667 | print(s % (2, 97, u'\n >> ' + amount_songs + u' 首歌曲将要下载.')) \ 668 | if not args.play else '' 669 | self.download(songs, amount_songs, args.from_) 670 | 671 | def download_collect(self): 672 | html = ss.get(url_collect % self.collect_id).text 673 | html = html.split('
(.+?)<', html).group(1) 675 | d = collect_name 676 | dir_ = os.path.join(os.getcwdu(), d) 677 | self.dir_ = modificate_file_name_for_wget(dir_) 678 | song_ids = re.findall('/song/(\d+)" title', html) 679 | amount_songs = unicode(len(song_ids)) 680 | song_ids = song_ids[args.from_ - 1:] 681 | print(s % (2, 97, u'\n >> ' + amount_songs + u' 首歌曲将要下载.')) \ 682 | if not args.play else '' 683 | n = args.from_ 684 | for i in song_ids: 685 | songs = self.get_song(i) 686 | self.download(songs, amount_songs, n) 687 | self.html = '' 688 | self.disc_description_archives = {} 689 | n += 1 690 | 691 | def download_artist_albums(self): 692 | ii = 1 693 | album_ids = [] 694 | while True: 695 | html = ss.get( 696 | url_artist_albums % (self.artist_id, str(ii))).text 697 | t = re.findall(r'/album/(\d+)"', html) 698 | if album_ids == t: break 699 | album_ids = t 700 | if album_ids: 701 | for i in album_ids: 702 | print ' ++ http://www.xiami.com/album/%s' % i 703 | self.album_id = i 704 | self.download_album() 705 | self.html = '' 706 | self.disc_description_archives = {} 707 | else: 708 | break 709 | ii += 1 710 | 711 | def download_artist_top_20_songs(self): 712 | html = ss.get(url_artist_top_song % self.artist_id).text 713 | song_ids = re.findall(r'/song/(.+?)" title', html) 714 | artist_name = re.search( 715 | r'

(.+?)<', html).group(1) 716 | d = modificate_text(artist_name + u' - top 20') 717 | dir_ = os.path.join(os.getcwdu(), d) 718 | self.dir_ = modificate_file_name_for_wget(dir_) 719 | amount_songs = unicode(len(song_ids)) 720 | print(s % (2, 97, u'\n >> ' + amount_songs + u' 首歌曲将要下载.')) \ 721 | if not args.play else '' 722 | n = 1 723 | for i in song_ids: 724 | songs = self.get_song(i) 725 | self.download(songs, amount_songs, n) 726 | self.html = '' 727 | self.disc_description_archives = {} 728 | n += 1 729 | 730 | def download_artist_radio(self): 731 | html = ss.get(url_artist_top_song % self.artist_id).text 732 | artist_name = re.search( 733 | r'

(.+?)<', html).group(1) 734 | d = modificate_text(artist_name + u' - radio') 735 | dir_ = os.path.join(os.getcwdu(), d) 736 | self.dir_ = modificate_file_name_for_wget(dir_) 737 | 738 | url_artist_radio = "http://www.xiami.com/radio/xml/type/5/id/%s" \ 739 | % self.artist_id 740 | n = 1 741 | while True: 742 | xml = ss.get(url_artist_radio).text 743 | song_ids = re.findall(r'(\d+)', xml) 744 | for i in song_ids: 745 | songs = self.get_song(i) 746 | self.download(songs, n=n) 747 | self.html = '' 748 | self.disc_description_archives = {} 749 | n += 1 750 | 751 | def download_user_songs(self, url, desc): 752 | dir_ = os.path.join(os.getcwdu(), 753 | u'虾米用户 %s %s' % (self.user_id, desc)) 754 | self.dir_ = modificate_file_name_for_wget(dir_) 755 | ii = 1 756 | n = 1 757 | while True: 758 | html = ss.get(url % (self.user_id, str(ii))).text 759 | song_ids = re.findall(r'/song/(.+?)"', html) 760 | if song_ids: 761 | for i in song_ids: 762 | songs = self.get_song(i) 763 | self.download(songs, n) 764 | self.html = '' 765 | self.disc_description_archives = {} 766 | n += 1 767 | else: 768 | break 769 | ii += 1 770 | 771 | def download_user_shares(self, url_shares): 772 | d = modificate_text(u'%s 的分享' % self.user_id) 773 | dir_ = os.path.join(os.getcwdu(), d) 774 | self.dir_ = modificate_file_name_for_wget(dir_) 775 | page = 1 776 | while True: 777 | html = ss.get(url_shares % page).text 778 | shares = re.findall(r'play.*\(\'\d+\'\)', html) 779 | for share in shares: 780 | if 'album' in share: 781 | self.album_id = re.search(r'\d+', share).group() 782 | self.download_album() 783 | else: 784 | self.song_id = re.search(r'\d+', share).group() 785 | self.download_song() 786 | if not shares: break 787 | page += 1 788 | 789 | def download_user_radio(self, url_rndsongs): 790 | d = modificate_text(u'%s 的虾米推荐' % self.user_id) 791 | dir_ = os.path.join(os.getcwdu(), d) 792 | self.dir_ = modificate_file_name_for_wget(dir_) 793 | n = 1 794 | while True: 795 | xml = ss.get(url_rndsongs % self.user_id).text 796 | song_ids = re.findall(r'(\d+)', xml) 797 | for i in song_ids: 798 | songs = self.get_song(i) 799 | self.download(songs, n=n) 800 | self.html = '' 801 | self.disc_description_archives = {} 802 | n += 1 803 | 804 | def download_chart(self, type_): 805 | html = ss.get('http://www.xiami.com/chart/index/c/%s' \ 806 | % self.chart_id).text 807 | title = re.search(r'(.+?)', html).group(1) 808 | d = modificate_text(title) 809 | dir_ = os.path.join(os.getcwdu(), d) 810 | self.dir_ = modificate_file_name_for_wget(dir_) 811 | 812 | html = ss.get( 813 | 'http://www.xiami.com/chart/data?c=%s&limit=200&type=%s' \ 814 | % (self.chart_id, type_)).text 815 | song_ids = re.findall(r'/song/(\d+)', html) 816 | n = 1 817 | for i in song_ids: 818 | songs = self.get_song(i) 819 | self.download(songs, n=n) 820 | self.html = '' 821 | self.disc_description_archives = {} 822 | n += 1 823 | 824 | def download_genre(self, url_genre): 825 | html = ss.get(url_genre % (self.genre_id, 1)).text 826 | if '/gid/' in url_genre: 827 | t = re.search( 828 | r'/genre/detail/gid/%s".+?title="(.+?)"' \ 829 | % self.genre_id, html).group(1) 830 | elif '/sid/' in url_genre: 831 | t = re.search( 832 | r'/genre/detail/sid/%s" title="(.+?)"' \ 833 | % self.genre_id, html).group(1) 834 | d = modificate_text(u'%s - 代表曲目 - xiami' % t) 835 | dir_ = os.path.join(os.getcwdu(), d) 836 | self.dir_ = modificate_file_name_for_wget(dir_) 837 | 838 | n = 1 839 | page = 2 840 | while True: 841 | song_ids = re.findall(r'/song/(\d+)', html) 842 | if not song_ids: break 843 | for i in song_ids: 844 | songs = self.get_song(i) 845 | self.download(songs, n=n) 846 | self.html = '' 847 | self.disc_description_archives = {} 848 | n += 1 849 | html = ss.get(url_genre % (self.chart_id, page)).text 850 | page += 1 851 | 852 | def download_genre_radio(self, url_genre): 853 | html = ss.get(url_genre % (self.genre_id, 1)).text 854 | if '/gid/' in url_genre: 855 | t = re.search( 856 | r'/genre/detail/gid/%s".+?title="(.+?)"' \ 857 | % self.genre_id, html).group(1) 858 | url_genre_radio = "http://www.xiami.com/radio/xml/type/12/id/%s" \ 859 | % self.genre_id 860 | elif '/sid/' in url_genre: 861 | t = re.search( 862 | r'/genre/detail/sid/%s" title="(.+?)"' \ 863 | % self.genre_id, html).group(1) 864 | url_genre_radio = "http://www.xiami.com/radio/xml/type/13/id/%s" \ 865 | % self.genre_id 866 | d = modificate_text(u'%s - radio - xiami' % t) 867 | dir_ = os.path.join(os.getcwdu(), d) 868 | self.dir_ = modificate_file_name_for_wget(dir_) 869 | 870 | n = 1 871 | while True: 872 | xml = ss.get(url_genre_radio).text 873 | song_ids = re.findall(r'(\d+)', xml) 874 | for i in song_ids: 875 | songs = self.get_song(i) 876 | self.download(songs, n=n) 877 | self.html = '' 878 | self.disc_description_archives = {} 879 | n += 1 880 | 881 | def hack_luoo(self, url): 882 | # parse luoo.net 883 | theaders = headers 884 | theaders.pop('Referer') 885 | r = requests.get(url) 886 | if not r.ok: 887 | return None 888 | cn = r.content 889 | songs_info = re.findall(r'

(.+?)

\s+' 890 | r'

Artist: (.+?)

\s+' 891 | r'

Album: (.+?)

', cn) 892 | 893 | # search song at xiami 894 | for info in songs_info: 895 | url = 'http://www.xiami.com/web/search-songs?key=%s' \ 896 | % urllib.quote(' '.join(info)) 897 | r = ss.get(url) 898 | j = r.json() 899 | if not r.ok or not j: 900 | print s % (1, 93, ' !! no find:'), ' - '.join(info) 901 | continue 902 | self.song_id = j[0]['id'] 903 | self.download_song() 904 | 905 | def display_infos(self, i, nn, n): 906 | print n, '/', nn 907 | print s % (2, 94, i['file_name']) 908 | print s % (2, 95, i['album_name']) 909 | print 'http://www.xiami.com/song/%s' % i['song_id'] 910 | print 'http://www.xiami.com/album/%s' % i['album_id'] 911 | if i['durl_is_H'] == 'h': 912 | print s % (1, 97, 'MP3-Quality:'), s % (1, 92, 'High') 913 | else: 914 | print s % (1, 97, 'MP3-Quality:'), s % (1, 91, 'Low') 915 | print '—' * int(os.popen('tput cols').read()) 916 | 917 | def get_mp3_quality(self, durl): 918 | if 'm3.file.xiami.com' in durl or 'm6.file.xiami.com' in durl: 919 | return 'h' 920 | else: 921 | return 'l' 922 | 923 | def play(self, songs, nn=u'1', n=1): 924 | if args.play == 2: 925 | songs = sorted(songs, key=lambda k: k['song_played'], reverse=True) 926 | for i in songs: 927 | self.record(i['song_id']) 928 | durl = self.get_durl(i['song_id']) 929 | if not durl: 930 | print s % (2, 91, ' !! Error: can\'t get durl'), i['song_name'] 931 | continue 932 | 933 | mp3_quality = self.get_mp3_quality(durl) 934 | i['durl_is_H'] = mp3_quality 935 | self.display_infos(i, nn, n) 936 | n = int(n) + 1 937 | cmd = 'mpv --really-quiet ' \ 938 | '--cache 8146 ' \ 939 | '--user-agent "%s" ' \ 940 | '--http-header-fields="Referer:http://img.xiami.com' \ 941 | '/static/swf/seiya/1.4/player.swf?v=%s" ' \ 942 | '"%s"' \ 943 | % (headers['User-Agent'], int(time.time()*1000), durl) 944 | os.system(cmd) 945 | timeout = 1 946 | ii, _, _ = select.select([sys.stdin], [], [], timeout) 947 | if ii: 948 | sys.exit(0) 949 | else: 950 | pass 951 | 952 | def download(self, songs, amount_songs=u'1', n=1): 953 | dir_ = modificate_file_name_for_wget(self.dir_) 954 | cwd = os.getcwd() 955 | if dir_ != cwd: 956 | if not os.path.exists(dir_): 957 | os.mkdir(dir_) 958 | 959 | ii = 1 960 | for i in songs: 961 | num = random.randint(0, 100) % 8 962 | col = s % (2, num + 90, i['file_name']) 963 | t = modificate_file_name_for_wget(i['file_name']) 964 | file_name = os.path.join(dir_, t) 965 | if os.path.exists(file_name): ## if file exists, no get_durl 966 | if args.undownload: 967 | self.modified_id3(file_name, i) 968 | ii += 1 969 | n += 1 970 | continue 971 | else: 972 | ii += 1 973 | n += 1 974 | continue 975 | 976 | if not args.undownload: 977 | if n == None: 978 | print(u'\n ++ download: #%s/%s# %s' \ 979 | % (ii, amount_songs, col)) 980 | else: 981 | print(u'\n ++ download: #%s/%s# %s' \ 982 | % (n, amount_songs, col)) 983 | n += 1 984 | 985 | durl = self.get_durl(i['song_id']) 986 | if not durl: 987 | print s % (2, 91, ' |-- Error: can\'t get durl') 988 | continue 989 | 990 | mp3_quality = self.get_mp3_quality(durl) 991 | if mp3_quality == 'h': 992 | print ' |--', s % (1, 97, 'MP3-Quality:'), s % (1, 91, 'High') 993 | else: 994 | print ' |--', s % (1, 97, 'MP3-Quality:'), s % (1, 91, 'Low') 995 | 996 | file_name_for_wget = file_name.replace('`', '\`') 997 | quiet = ' -q' if args.quiet else ' -nv' 998 | cmd = 'wget -c%s ' \ 999 | '-U "%s" ' \ 1000 | '--header "Referer:http://img.xiami.com' \ 1001 | '/static/swf/seiya/1.4/player.swf?v=%s" ' \ 1002 | '-O "%s.tmp" %s' \ 1003 | % (quiet, headers['User-Agent'], int(time.time()*1000), 1004 | file_name_for_wget, durl) 1005 | cmd = cmd.encode('utf8') 1006 | status = os.system(cmd) 1007 | if status != 0: # other http-errors, such as 302. 1008 | wget_exit_status_info = wget_es[status] 1009 | print('\n\n ----### \x1b[1;91mERROR\x1b[0m ==> \x1b[1;91m%d ' \ 1010 | '(%s)\x1b[0m ###--- \n\n' % (status, wget_exit_status_info)) 1011 | print s % (1, 91, ' ===> '), cmd 1012 | sys.exit(1) 1013 | else: 1014 | os.rename('%s.tmp' % file_name, file_name) 1015 | 1016 | self.modified_id3(file_name, i) 1017 | ii += 1 1018 | time.sleep(5) 1019 | 1020 | def _save_do(self, id_, type, tags): 1021 | data = { 1022 | "tags": tags, 1023 | "type": type, 1024 | "id": id_, 1025 | "desc": "", 1026 | "grade": "", 1027 | "share": 0, 1028 | "shareTo": "all", 1029 | "_xiamitoken": ss.cookies['_xiamitoken'], 1030 | } 1031 | url = 'http://www.xiami.com/ajax/addtag' 1032 | r = ss.post(url, data=data) 1033 | j = r.json() 1034 | if j['status'] == 'ok': 1035 | return 0 1036 | else: 1037 | return j['status'] 1038 | 1039 | def save(self, urls): 1040 | tags = args.tags 1041 | for url in urls: 1042 | if '/collect/' in url: 1043 | collect_id = re.search(r'/collect/(\d+)', url).group(1) 1044 | print s % (1, 97, u'\n ++ save collect:'), \ 1045 | 'http://www.xiami.com/song/collect/' + collect_id 1046 | result = self._save_do(collect_id, 4, tags) 1047 | 1048 | elif '/album/' in url: 1049 | album_id = re.search(r'/album/(\d+)', url).group(1) 1050 | print s % (1, 97, u'\n ++ save album:'), \ 1051 | 'http://www.xiami.com/album/' + album_id 1052 | result = self._save_do(album_id, 5, tags) 1053 | 1054 | elif '/artist/' in url: 1055 | artist_id = re.search(r'/artist/(\d+)', url).group(1) 1056 | print s % (1, 97, u'\n ++ save artist:'), \ 1057 | 'http://www.xiami.com/artist/' + artist_id 1058 | result = self._save_do(artist_id, 6, tags) 1059 | 1060 | elif '/song/' in url: 1061 | song_id = re.search(r'/song/(\d+)', url).group(1) 1062 | print s % (1, 97, u'\n ++ save song:'), \ 1063 | 'http://www.xiami.com/song/' + song_id 1064 | result = self._save_do(song_id, 3, tags) 1065 | 1066 | else: 1067 | print(s % (2, 91, u' 请正确输入虾米网址.')) 1068 | 1069 | if result == 0: 1070 | print s % (1, 92, ' ++ success.\n') 1071 | else: 1072 | print s % (1, 91, ' !! Error at _save_do.'), result, '\n' 1073 | 1074 | def main(argv): 1075 | if len(argv) < 2: 1076 | sys.exit() 1077 | 1078 | ###################################################### 1079 | # for argparse 1080 | p = argparse.ArgumentParser(description='downloading any xiami.com') 1081 | p.add_argument('xxx', type=str, nargs='*', \ 1082 | help='命令对象.') 1083 | p.add_argument('-p', '--play', action='count', \ 1084 | help='play with mpv') 1085 | p.add_argument('-l', '--low', action='store_true', \ 1086 | help='low mp3') 1087 | p.add_argument('-q', '--quiet', action='store_true', \ 1088 | help='quiet for download') 1089 | p.add_argument('-f', '--from_', action='store', \ 1090 | default=1, type=int, \ 1091 | help='从第几个开始下载,eg: -f 42') 1092 | p.add_argument('-d', '--undescription', action='store_true', \ 1093 | help='no add disk\'s distribution') 1094 | p.add_argument('-t', '--tags', action='store', \ 1095 | type=str, default='', help='tags. eg: piano,cello') 1096 | p.add_argument('-n', '--undownload', action='store_true', \ 1097 | help='no download, using to renew id3 tags') 1098 | global args 1099 | args = p.parse_args(argv[2:]) 1100 | comd = argv[1] 1101 | xxx = args.xxx 1102 | 1103 | if comd == 'login' or comd == 'g': 1104 | # or comd == 'logintaobao' or comd == 'gt': 1105 | # taobao has updated login algorithms which is hard to hack 1106 | # so remove it. 1107 | if len(xxx) < 1: 1108 | email = raw_input(s % (1, 97, ' username: ') \ 1109 | if comd == 'logintaobao' or comd == 'gt' \ 1110 | else s % (1, 97, ' email: ')) 1111 | password = getpass(s % (1, 97, ' password: ')) 1112 | elif len(xxx) == 1: 1113 | # for add_member_auth 1114 | if '@' not in xxx[0]: 1115 | x = xiami() 1116 | x.add_member_auth(xxx[0]) 1117 | x.check_login() 1118 | return 1119 | 1120 | email = xxx[0] 1121 | password = getpass(s % (1, 97, ' password: ')) 1122 | elif len(xxx) == 2: 1123 | email = xxx[0] 1124 | password = xxx[1] 1125 | else: 1126 | print s % (1, 91, 1127 | ' login\n login email\n \ 1128 | login email password') 1129 | 1130 | x = xiami() 1131 | if comd == 'logintaobao' or comd == 'gt': 1132 | x.login_taobao(email, password) 1133 | else: 1134 | x.login(email, password) 1135 | is_signin = x.check_login() 1136 | if is_signin: 1137 | print s % (1, 92, ' ++ login succeeds.') 1138 | else: 1139 | print s % (1, 91, ' login failes') 1140 | 1141 | elif comd == 'signout': 1142 | g = open(cookie_file, 'w') 1143 | g.close() 1144 | 1145 | elif comd == 'd' or comd == 'download': 1146 | urls = xxx 1147 | x = xiami() 1148 | x.init() 1149 | x.url_parser(urls) 1150 | 1151 | elif comd == 'p' or comd == 'play': 1152 | if not args.play: args.play = 1 1153 | urls = xxx 1154 | x = xiami() 1155 | x.init() 1156 | x.url_parser(urls) 1157 | 1158 | elif comd == 's' or comd == 'save': 1159 | urls = xxx 1160 | x = xiami() 1161 | x.init() 1162 | x.save(urls) 1163 | 1164 | else: 1165 | print s % (2, 91, u' !! 命令错误\n') 1166 | 1167 | if __name__ == '__main__': 1168 | argv = sys.argv 1169 | main(argv) 1170 | --------------------------------------------------------------------------------