diff --git a/.gitignore b/.gitignore index b4f6842d..82ced0c9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,26 +1,35 @@ # Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] +*$py.class # C extensions *.so # Distribution / packaging .Python -env/ -bin/ build/ develop-eggs/ dist/ +downloads/ eggs/ +.eggs/ lib/ lib64/ parts/ sdist/ var/ +wheels/ *.egg-info/ .installed.cfg *.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec # Installer logs pip-log.txt @@ -30,47 +39,73 @@ pip-delete-this-directory.txt htmlcov/ .tox/ .coverage +.coverage.* .cache nosetests.xml coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ # Translations *.mo - -# Mr Developer -.mr.developer.cfg -.project -.pydevproject - -# Rope -.ropeproject +*.pot # Django stuff: *.log -*.pot +local_settings.py +db.sqlite3 + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy # Sphinx documentation docs/_build/ -# ctags -tags +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version -src/.idea/* -.idea/* +# celery beat schedule file +celerybeat-schedule -# ignore mac file -.DS_Store -src/.DS_Store +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +venv2/ +venv3/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject -# ignore demo -out.gif +# mkdocs documentation +/site -# ignore swp files -NEMbox/*.swp +# mypy +.mypy_cache/ -# ignore TEST files -TEST +# vscode +.vscode/ -# virtualenv -venv -/test.py +setup.cfg diff --git a/ChangeLog.md b/ChangeLog.md index 0387d778..1eafd92f 100644 --- a/ChangeLog.md +++ b/ChangeLog.md @@ -48,7 +48,7 @@ 2015-10-14 版本 0.1.9.7 新增歌曲播放提醒 -2015-10-13 版本 0.1.9.6 修复因 cookie 过期导致的登陆问题 +2015-10-13 版本 0.1.9.6 修复因 cookie 过期导致的登录问题 2015-10-13 版本 0.1.9.5 新增自定义全局快捷键功能 @@ -56,7 +56,7 @@ 2015-09-25 版本 0.1.9.2 新增版本检查和更新提醒功能 -2015-09-24 版本 0.1.9.0 优化登陆逻辑,修复每日推荐的登陆问题 +2015-09-24 版本 0.1.9.0 优化登录逻辑,修复每日推荐的登录问题 2015-09-23 版本 0.1.8.5 优化电台FM功能逻辑 @@ -80,7 +80,7 @@ 2015-08-02 版本 0.1.6.2 新增显示播放进度 -2015-07-30 版本 0.1.6.0 修复由于接口更换导致的用户登陆问题 +2015-07-30 版本 0.1.6.0 修复由于接口更换导致的用户登录问题 2015-06-17 版本 0.1.5.6 优化对过长歌曲信息的显示 @@ -102,7 +102,7 @@ 2015-03-18 版本 0.1.3.3 修复Ubuntu等系统SSL登录报错问题 -2015-02-28 版本 0.1.3.2 修复170等新增号段手机用户无法登陆的问题 +2015-02-28 版本 0.1.3.2 修复170等新增号段手机用户无法登录的问题 2015-02-05 版本 0.1.3.1 修复登录无法保存的问题 @@ -114,6 +114,6 @@ 2015-01-08 版本 0.1.2.0 增加音量控制 -2015-01-03 版本 0.1.1.1 修复部分仅手机注册用户登录无法登陆 (感谢Catofes反馈) +2015-01-03 版本 0.1.1.1 修复部分仅手机注册用户登录无法登录 (感谢Catofes反馈) -2015-01-02 版本 0.1.1.0 新增退出并清除用户信息功能 \ No newline at end of file +2015-01-02 版本 0.1.1.0 新增退出并清除用户信息功能 diff --git a/NEMbox/__init__.py b/NEMbox/__init__.py index 0f3db3e0..01a1e182 100644 --- a/NEMbox/__init__.py +++ b/NEMbox/__init__.py @@ -1,23 +1,21 @@ #!/usr/bin/env python -# encoding: UTF-8 +# -*- coding: utf-8 -*- ''' 网易云音乐 Entry ''' -from __future__ import print_function -from __future__ import unicode_literals -from __future__ import division -from __future__ import absolute_import -from builtins import str -from future import standard_library -standard_library.install_aliases() - +from __future__ import ( + print_function, unicode_literals, division, absolute_import +) import curses import traceback import argparse import sys +from future.builtins import str + from .menu import Menu -version = "0.2.4.2" + +version = '0.2.4.3' def start(): diff --git a/NEMbox/api.py b/NEMbox/api.py index 5b954e6d..d9ffcc0c 100644 --- a/NEMbox/api.py +++ b/NEMbox/api.py @@ -5,39 +5,29 @@ ''' 网易云音乐 Api ''' -from __future__ import print_function -from __future__ import unicode_literals -from __future__ import division -from __future__ import absolute_import -from builtins import chr -from builtins import int -from builtins import map -from builtins import open -from builtins import range -from builtins import str -from builtins import pow -from future import standard_library -standard_library.install_aliases() - +from __future__ import ( + print_function, unicode_literals, division, absolute_import +) import re import os import json import time -import hashlib import random -import base64 -import binascii - -from Crypto.Cipher import AES +from collections import OrderedDict from http.cookiejar import LWPCookieJar + from bs4 import BeautifulSoup +from future.builtins import (map, open, range, str) import requests from .config import Config from .storage import Storage -from .utils import notify +from .encrypt import encrypted_id, encrypted_request +from .utils import uniq from . import logger +log = logger.getLogger(__name__) + # 歌曲榜单地址 top_list_all = { 0: ['云音乐新歌榜', '/discover/toplist?id=3779629'], @@ -66,126 +56,141 @@ default_timeout = 10 -log = logger.getLogger(__name__) -modulus = ('00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7' - 'b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280' - '104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932' - '575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b' - '3ece0462db0a22b8e7') -nonce = '0CoJUm6Qyw8W8jud' -pubKey = '010001' - - -# 歌曲加密算法, 基于https://github.com/yanunon/NeteaseCloudMusic脚本实现 -def encrypted_id(id): - magic = bytearray('3go8&$8*3*3h0k(2)2', 'u8') - song_id = bytearray(id, 'u8') - magic_len = len(magic) - for i, sid in enumerate(song_id): - song_id[i] = sid ^ magic[i % magic_len] - m = hashlib.md5(song_id) - result = m.digest() - result = base64.b64encode(result) - result = result.replace(b'/', b'_') - result = result.replace(b'+', b'-') - return result.decode('utf-8') - - -# 登录加密算法, 基于https://github.com/stkevintan/nw_musicbox脚本实现 -def encrypted_request(text): - text = json.dumps(text) - secKey = createSecretKey(16) - encText = aesEncrypt(aesEncrypt(text, nonce), secKey) - encSecKey = rsaEncrypt(secKey, pubKey, modulus) - data = {'params': encText, 'encSecKey': encSecKey} - return data - - -def aesEncrypt(text, secKey): - pad = 16 - len(text) % 16 - text = text + chr(pad) * pad - encryptor = AES.new(secKey, 2, '0102030405060708') - ciphertext = encryptor.encrypt(text) - ciphertext = base64.b64encode(ciphertext).decode('utf-8') - return ciphertext - - -def rsaEncrypt(text, pubKey, modulus): - text = text[::-1] - rs = pow(int(binascii.hexlify(text), 16), int(pubKey, 16), int(modulus, 16)) - return format(rs, 'x').zfill(256) - - -def createSecretKey(size): - return binascii.hexlify(os.urandom(size))[:16] - - -# list去重 -def uniq(arr): - arr2 = list(set(arr)) - arr2.sort(key=arr.index) - return arr2 - - -# 获取高音质mp3 url -def geturl(song): - try: - return geturl_v1(song) - except KeyError as e: - return geturl_v3(song) - - -# 老的获取歌曲url方法 -def geturl_v1(song): - quality = Config().get_item('music_quality') - if song['hMusic'] and quality <= 0: - music = song['hMusic'] - quality = 'HD' - elif song['mMusic'] and quality <= 1: - music = song['mMusic'] - quality = 'MD' - elif song['lMusic'] and quality <= 2: - music = song['lMusic'] - quality = 'LD' - else: - return song['mp3Url'], '' - - quality = quality + ' {0}k'.format(music['bitrate'] // 1000) - song_id = str(music['dfsId']) - enc_id = encrypted_id(song_id) - url = 'http://m%s.music.126.net/%s/%s.mp3' % (random.randrange(1, 3), - enc_id, song_id) - return url, quality - -# 新的获取歌曲url方法 -def geturl_v3(song): - quality = Config().get_item('music_quality') - if song['h'] and quality <= 0: - music = song['h'] - quality = 'HD' - elif song['m'] and quality <= 1: - music = song['m'] - quality = 'MD' - elif song['l'] and quality <= 2: - music = song['l'] - quality = 'LD' - else: - return song.get('mp3Url', ''), '' - - quality = quality + ' {0}k'.format(music['br'] // 1000) - song_id = str(music['fid']) - enc_id = encrypted_id(song_id) - url = 'http://m%s.music.126.net/%s/%s.mp3' % (random.randrange(1, 3), - enc_id, song_id) - return url, quality - -def geturl_new_api(song): - br_to_quality = {128000: 'MD 128k', 320000: 'HD 320k'} - alter = NetEase().songs_detail_new_api([song['id']])[0] - url = alter['url'] - quality = br_to_quality.get(alter['br'], '') - return url, quality +class Parse(object): + + @classmethod + def _song_v1(cls, song): + # 老的获取歌曲url方法 + quality = Config().get_item('music_quality') + if song['hMusic'] and quality <= 0: + music = song['hMusic'] + quality = 'HD' + elif song['mMusic'] and quality <= 1: + music = song['mMusic'] + quality = 'MD' + elif song['lMusic'] and quality <= 2: + music = song['lMusic'] + quality = 'LD' + else: + return song['mp3Url'], '' + + quality = quality + ' {0}k'.format(music['bitrate'] // 1000) + song_id = str(music['dfsId']) + enc_id = encrypted_id(song_id) + url = 'http://m{}.music.126.net/{}/{}.mp3'.format(random.randrange(1, 3), enc_id, song_id) + return url, quality + + @classmethod + def _song_v3(cls, song): + # 新的获取歌曲url方法 + quality = Config().get_item('music_quality') + if song['h'] and quality <= 0: + music = song['h'] + quality = 'HD' + elif song['m'] and quality <= 1: + music = song['m'] + quality = 'MD' + elif song['l'] and quality <= 2: + music = song['l'] + quality = 'LD' + else: + return song.get('mp3Url', ''), '' + + quality = quality + ' {0}k'.format(music['br'] // 1000) + song_id = str(music['fid']) + enc_id = encrypted_id(song_id) + url = 'http://m{}.music.126.net/{}/{}.mp3'.format(random.randrange(1, 3), enc_id, song_id) + return url, quality + + @classmethod + def song_url(cls, song): + # 获取高音质mp3 url + try: + return cls._song_v1(song) + except KeyError as e: + return cls._song_v3(song) + + @classmethod + def song_album(cls, song): + # 对新老接口进行处理 + if 'al' in song: + if song['al'] is not None: + album_name = song['al']['name'] + album_id = song['al']['id'] + else: + album_name = '未知专辑' + album_id = '' + else: + if song['album'] is not None: + album_name = song['album']['name'] + album_id = song['album']['id'] + else: + album_name = '未知专辑' + album_id = '' + return album_name, album_id + + @classmethod + def song_artist(cls, song): + artist = '' + # 对新老接口进行处理 + if 'ar' in song: + artist = ', '.join([a['name'] for a in song['ar']]) + elif 'artists' in song: + artist = ', '.join([a['name'] for a in song['artists']]) + else: + artist = '未知艺术家' + + return artist + + @classmethod + def songs(cls, songs): + song_info_list = [] + for song in songs: + url, quality = Parse.song_url(song) + if not url: + continue + + album_name, album_id = Parse.song_album(song) + song_info = { + 'song_id': song['id'], + 'artist': Parse.song_artist(song), + 'song_name': song['name'], + 'album_name': album_name, + 'album_id': album_id, + 'mp3_url': url, + 'quality': quality + } + song_info_list.append(song_info) + return song_info_list + + @classmethod + def artists(cls, artists): + return [{ + 'artist_id': artist['id'], + 'artists_name': artist['name'], + 'alias': ''.join(artist['alias']) + } for artist in artists] + + @classmethod + def albums(cls, albums): + return [{ + 'album_id': album['id'], + 'albums_name': album['name'], + 'artists_name': album['artist']['name'] + } for album in albums] + + @classmethod + def top_playlists(cls, playlists): + return [{ + 'playlist_id': pl['id'], + 'playlist_name': pl['name'], + 'creator_name': pl['creator']['nickname'] + } for pl in playlists] + + @classmethod + def playlist_classes(cls, playlist_html): + pass class NetEase(object): @@ -203,7 +208,6 @@ def __init__(self): 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.152 Safari/537.36' # NOQA } self.cookies = {'appver': '1.5.2'} - self.playlist_class_dict = {} self.session = requests.Session() self.storage = Storage() self.session.cookies = LWPCookieJar(self.storage.cookie_path) @@ -232,42 +236,34 @@ def __init__(self): def return_toplists(self): return [l[0] for l in top_list_all.values()] - def httpRequest(self, - method, - action, - query=None, - urlencoded=None, - callback=None, - timeout=None): + def request(self, method, action, query=None, urlencoded=None, callback=None, timeout=None): connection = json.loads( - self.rawHttpRequest(method, action, query, urlencoded, callback, timeout) + self._raw_request(method, action, query, urlencoded, callback, timeout) ) return connection - def rawHttpRequest(self, - method, - action, - query=None, - urlencoded=None, - callback=None, - timeout=None): + def _raw_request(self, + method, + action, + query=None, + urlencoded=None, + callback=None, + timeout=None): if method == 'GET': url = action if query is None else action + '?' + query - connection = self.session.get(url, - headers=self.header, - timeout=default_timeout) + connection = self.session.get( + url, headers=self.header, timeout=default_timeout + ) elif method == 'POST': - connection = self.session.post(action, - data=query, - headers=self.header, - timeout=default_timeout) + connection = self.session.post( + action, data=query, headers=self.header, timeout=default_timeout + ) elif method == 'Login_POST': - connection = self.session.post(action, - data=query, - headers=self.header, - timeout=default_timeout) + connection = self.session.post( + action, data=query, headers=self.header, timeout=default_timeout + ) self.session.cookies.save() connection.encoding = 'UTF-8' @@ -278,6 +274,7 @@ def login(self, username, password): pattern = re.compile(r'^0\d{2,3}\d{7,8}$|^1[34578]\d{9}$') if pattern.match(username): return self.phone_login(username, password) + action = 'https://music.163.com/weapi/login?csrf_token=' self.session.cookies.load() text = { @@ -287,7 +284,7 @@ def login(self, username, password): } data = encrypted_request(text) try: - return self.httpRequest('Login_POST', action, data) + return self.request('Login_POST', action, data) except requests.exceptions.RequestException as e: log.error(e) return {'code': 501} @@ -302,7 +299,7 @@ def phone_login(self, username, password): } data = encrypted_request(text) try: - return self.httpRequest('Login_POST', action, data) + return self.request('Login_POST', action, data) except requests.exceptions.RequestException as e: log.error(e) return {'code': 501} @@ -313,7 +310,7 @@ def daily_signin(self, type): text = {'type': type} data = encrypted_request(text) try: - return self.httpRequest('POST', action, data) + return self.request('POST', action, data) except requests.exceptions.RequestException as e: log.error(e) return -1 @@ -323,7 +320,7 @@ def user_playlist(self, uid, offset=0, limit=100): action = 'http://music.163.com/api/user/playlist/?offset={}&limit={}&uid={}'.format( # NOQA offset, limit, uid) try: - data = self.httpRequest('GET', action) + data = self.request('GET', action) return data['playlist'] except (requests.exceptions.RequestException, KeyError) as e: log.error(e) @@ -360,7 +357,7 @@ def recommend_playlist(self): def personal_fm(self): action = 'http://music.163.com/api/radio/get' try: - data = self.httpRequest('GET', action) + data = self.request('GET', action) return data['data'] except requests.exceptions.RequestException as e: log.error(e) @@ -368,11 +365,12 @@ def personal_fm(self): # like def fm_like(self, songid, like=True, time=25, alg='itembased'): - action = 'http://music.163.com/api/radio/like?alg={}&trackId={}&like={}&time={}'.format( # NOQA - alg, songid, 'true' if like else 'false', time) + action = 'http://music.163.com/api/radio/like?alg={}&trackId={}&like={}&time={}'.format( + alg, songid, 'true' if like else 'false', time + ) try: - data = self.httpRequest('GET', action) + data = self.request('GET', action) if data['code'] == 200: return data else: @@ -383,10 +381,11 @@ def fm_like(self, songid, like=True, time=25, alg='itembased'): # FM trash def fm_trash(self, songid, time=25, alg='RT'): - action = 'http://music.163.com/api/radio/trash/add?alg={}&songId={}&time={}'.format( # NOQA - alg, songid, time) + action = 'http://music.163.com/api/radio/trash/add?alg={}&songId={}&time={}'.format( + alg, songid, time + ) try: - data = self.httpRequest('GET', action) + data = self.request('GET', action) if data['code'] == 200: return data else: @@ -405,14 +404,14 @@ def search(self, s, stype=1, offset=0, total='true', limit=60): 'total': total, 'limit': limit } - return self.httpRequest('POST', action, data) + return self.request('POST', action, data) # 新碟上架 http://music.163.com/#/discover/album/ def new_albums(self, offset=0, limit=50): action = 'http://music.163.com/api/album/new?area=ALL&offset={}&total=true&limit={}'.format( # NOQA offset, limit) try: - data = self.httpRequest('GET', action) + data = self.request('GET', action) return data['albums'] except requests.exceptions.RequestException as e: log.error(e) @@ -424,7 +423,7 @@ def top_playlists(self, category='全部', order='hot', offset=0, limit=50): category, order, offset, 'true' if offset else 'false', limit) # NOQA try: - data = self.httpRequest('GET', action) + data = self.request('GET', action) return data['playlists'] except requests.exceptions.RequestException as e: log.error(e) @@ -434,8 +433,18 @@ def top_playlists(self, category='全部', order='hot', offset=0, limit=50): def playlist_classes(self): action = 'http://music.163.com/discover/playlist/' try: - data = self.rawHttpRequest('GET', action) - return data + data = self._raw_request('GET', action) + soup = BeautifulSoup(data, 'html.parser') + dls = soup.select('dl.f-cb') + self.playlist_class_dict = OrderedDict() + + for dl in dls: + title = dl.dt.text + sub = [item.text for item in dl.select('a')] + self.playlist_class_dict[title] = sub + + return self.playlist_class_dict + except requests.exceptions.RequestException as e: log.error(e) return [] @@ -452,7 +461,8 @@ def playlist_detail(self, playlist_id): for cookie in self.session.cookies: if cookie.name == '__csrf': csrf = cookie.value - data = {'id': playlist_id, 'total': 'true', 'csrf_token': csrf, 'limit': 1000, 'n': 1000, 'offset': 0} + data = {'id': playlist_id, 'total': 'true', + 'csrf_token': csrf, 'limit': 1000, 'n': 1000, 'offset': 0} connection = self.session.post(action, data=encrypted_request(data), headers=self.header, ) @@ -465,7 +475,7 @@ def top_artists(self, offset=0, limit=100): action = 'http://music.163.com/api/artist/top?offset={}&total=false&limit={}'.format( # NOQA offset, limit) try: - data = self.httpRequest('GET', action) + data = self.request('GET', action) return data['artists'] except requests.exceptions.RequestException as e: log.error(e) @@ -493,7 +503,7 @@ def top_songlist(self, idx=0, offset=0, limit=100): def artists(self, artist_id): action = 'http://music.163.com/api/artist/{}'.format(artist_id) try: - data = self.httpRequest('GET', action) + data = self.request('GET', action) return data['hotSongs'] except requests.exceptions.RequestException as e: log.error(e) @@ -503,7 +513,7 @@ def get_artist_album(self, artist_id, offset=0, limit=50): action = 'http://music.163.com/api/artist/albums/{}?offset={}&limit={}'.format( artist_id, offset, limit) try: - data = self.httpRequest('GET', action) + data = self.request('GET', action) return data['hotAlbums'] except requests.exceptions.RequestException as e: log.error(e) @@ -513,7 +523,7 @@ def get_artist_album(self, artist_id, offset=0, limit=50): def album(self, album_id): action = 'http://music.163.com/api/album/{}'.format(album_id) try: - data = self.httpRequest('GET', action) + data = self.request('GET', action) return data['album']['songs'] except requests.exceptions.RequestException as e: log.error(e) @@ -523,7 +533,7 @@ def song_comments(self, music_id, offset=0, total='false', limit=100): action = 'http://music.163.com/api/v1/resource/comments/R_SO_4_{}/?rid=R_SO_4_{}&\ offset={}&total={}&limit={}'.format(music_id, music_id, offset, total, limit) try: - comments = self.httpRequest('GET', action) + comments = self.request('GET', action) return comments except requests.exceptions.RequestException as e: log.error(e) @@ -537,7 +547,7 @@ def songs_detail(self, ids, offset=0): action = 'http://music.163.com/api/song/detail?ids=[{}]'.format( # NOQA ','.join(tmpids)) try: - data = self.httpRequest('GET', action) + data = self.request('GET', action) # the order of data['songs'] is no longer the same as tmpids, # so just make the order back @@ -549,14 +559,13 @@ def songs_detail(self, ids, offset=0): return [] def songs_detail_new_api(self, music_ids, bit_rate=320000): - action = 'http://music.163.com/weapi/song/enhance/player/url?csrf_token=' # NOQA + action = 'http://music.163.com/weapi/song/enhance/player/url?csrf_token=' self.session.cookies.load() csrf = '' for cookie in self.session.cookies: if cookie.name == '__csrf': csrf = cookie.value - # if csrf == '': - # notify('You Need Login', 1) + action += csrf data = {'ids': music_ids, 'br': bit_rate, 'csrf_token': csrf} connection = self.session.post(action, @@ -568,9 +577,9 @@ def songs_detail_new_api(self, music_ids, bit_rate=320000): # song id --> song url ( details ) def song_detail(self, music_id): action = 'http://music.163.com/api/song/detail/?id={}&ids=[{}]'.format( - music_id, music_id) # NOQA + music_id, music_id) try: - data = self.httpRequest('GET', action) + data = self.request('GET', action) return data['songs'] except requests.exceptions.RequestException as e: log.error(e) @@ -578,10 +587,10 @@ def song_detail(self, music_id): # lyric http://music.163.com/api/song/lyric?os=osx&id= &lv=-1&kv=-1&tv=-1 def song_lyric(self, music_id): - action = 'http://music.163.com/api/song/lyric?os=osx&id={}&lv=-1&kv=-1&tv=-1'.format( # NOQA + action = 'http://music.163.com/api/song/lyric?os=osx&id={}&lv=-1&kv=-1&tv=-1'.format( music_id) try: - data = self.httpRequest('GET', action) + data = self.request('GET', action) if 'lrc' in data and data['lrc']['lyric'] is not None: lyric_info = data['lrc']['lyric'] else: @@ -592,10 +601,10 @@ def song_lyric(self, music_id): return [] def song_tlyric(self, music_id): - action = 'http://music.163.com/api/song/lyric?os=osx&id={}&lv=-1&kv=-1&tv=-1'.format( # NOQA + action = 'http://music.163.com/api/song/lyric?os=osx&id={}&lv=-1&kv=-1&tv=-1'.format( music_id) try: - data = self.httpRequest('GET', action) + data = self.request('GET', action) if 'tlyric' in data and data['tlyric'].get('lyric') is not None: lyric_info = data['tlyric']['lyric'][1:] else: @@ -629,8 +638,9 @@ def channel_detail(self, channelids, offset=0): action = 'http://music.163.com/api/dj/program/detail?id={}'.format( channelids[i]) try: - data = self.httpRequest('GET', action) - channel = self.dig_info(data['program']['mainSong'], 'channels') + data = self.request('GET', action) + channel = self.dig_info( + data['program']['mainSong'], 'channels') channels.append(channel) except requests.exceptions.RequestException as e: log.error(e) @@ -642,99 +652,26 @@ def channel_detail(self, channelids, offset=0): def get_version(self): action = 'https://pypi.org/pypi/NetEase-MusicBox/json' # JSON API try: - data = requests.get(action) - return data.content + return requests.get(action).json() except requests.exceptions.RequestException as e: log.error(e) - return "" + return {} def dig_info(self, data, dig_type): - temp = [] if dig_type == 'songs' or dig_type == 'fmsongs': - for i in range(0, len(data)): - url, quality = geturl(data[i]) - if not url: - continue - - # 对新老接口进行处理 - if 'al' in data[i]: - if data[i]['al'] is not None: - album_name = data[i]['al']['name'] - album_id = data[i]['al']['id'] - else: - album_name = '未知专辑' - album_id = '' - else: - if data[i]['album'] is not None: - album_name = data[i]['album']['name'] - album_id = data[i]['album']['id'] - else: - album_name = '未知专辑' - album_id = '' - - song_info = { - 'song_id': data[i]['id'], - 'artist': [], - 'song_name': data[i]['name'], - 'album_name': album_name, - 'album_id': album_id, - 'mp3_url': url, - 'quality': quality - } - # 对新老接口进行处理 - if 'artist' in data[i]: - song_info['artist'] = data[i]['artist'] - elif 'ar' in data[i]: - if len(data[i]['ar']) == 1: - song_info['artist'] = data[i]['ar'][0]['name'] - else: - for j in range(0, len(data[i]['ar'])): - song_info['artist'].append(data[i]['ar'][j][ - 'name']) - song_info['artist'] = ', '.join(song_info['artist']) - elif 'artists' in data[i]: - for j in range(0, len(data[i]['artists'])): - song_info['artist'].append(data[i]['artists'][j][ - 'name']) - song_info['artist'] = ', '.join(song_info['artist']) - else: - song_info['artist'] = '未知艺术家' - - temp.append(song_info) - + return Parse.songs(data) elif dig_type == 'artists': - artists = [] - for i in range(0, len(data)): - artists_info = { - 'artist_id': data[i]['id'], - 'artists_name': data[i]['name'], - 'alias': ''.join(data[i]['alias']) - } - artists.append(artists_info) - - return artists + return Parse.artists(data) elif dig_type == 'albums': - for i in range(0, len(data)): - albums_info = { - 'album_id': data[i]['id'], - 'albums_name': data[i]['name'], - 'artists_name': data[i]['artist']['name'] - } - temp.append(albums_info) + return Parse.albums(data) elif dig_type == 'top_playlists': - for i in range(0, len(data)): - playlists_info = { - 'playlist_id': data[i]['id'], - 'playlists_name': data[i]['name'], - 'creator_name': data[i]['creator']['nickname'] - } - temp.append(playlists_info) + return Parse.top_playlists(data) elif dig_type == 'channels': - url, quality = geturl(data) - channel_info = { + url, quality = Parse.song_url(data) + return { 'song_id': data['id'], 'song_name': data['name'], 'artist': data['artists'][0]['name'], @@ -742,27 +679,9 @@ def dig_info(self, data, dig_type): 'mp3_url': url, 'quality': quality } - temp = channel_info elif dig_type == 'playlist_classes': - soup = BeautifulSoup(data, 'lxml') - dls = soup.select('dl.f-cb') - for dl in dls: - title = dl.dt.text - sub = [item.text for item in dl.select('a')] - temp.append(title) - self.playlist_class_dict[title] = sub + return list(self.playlist_class_dict.keys()) elif dig_type == 'playlist_class_detail': - log.debug(data) - temp = self.playlist_class_dict[data] - - return temp - - -if __name__ == '__main__': - ne = NetEase() - print(geturl_new_api(ne.songs_detail([27902910])[0])) # MD 128k, fallback - print(ne.songs_detail_new_api([27902910])[0]['url']) - print(ne.songs_detail([405079776])[0]['mp3Url']) # old api - print(requests.get(ne.songs_detail([405079776])[0]['mp3Url']).status_code) # 404 + return self.playlist_class_dict[data] diff --git a/NEMbox/cache.py b/NEMbox/cache.py index aca79460..95fe25b0 100644 --- a/NEMbox/cache.py +++ b/NEMbox/cache.py @@ -4,19 +4,16 @@ ''' Class to cache songs into local storage. ''' -from __future__ import unicode_literals -from __future__ import print_function -from __future__ import division -from __future__ import absolute_import -from builtins import str -from future import standard_library -standard_library.install_aliases() - +from __future__ import ( + print_function, unicode_literals, division, absolute_import +) import threading import subprocess import os import signal +from future.builtins import str + from .const import Constant from .config import Config from .singleton import Singleton @@ -27,7 +24,6 @@ class Cache(Singleton): - def __init__(self): if hasattr(self, '_init'): return @@ -99,8 +95,7 @@ def start_download(self): stderr=subprocess.PIPE) self.aria2c.wait() except OSError as e: - log.warning( - '{}.\tAria2c is unavailable, fall back to wget'.format(e)) + log.warning('{}.\tAria2c is unavailable, fall back to wget'.format(e)) self._mkdir(output_path) para = ['wget', '-O', full_path, new_url] diff --git a/NEMbox/config.py b/NEMbox/config.py index 98508bf7..ee8ba39f 100644 --- a/NEMbox/config.py +++ b/NEMbox/config.py @@ -1,14 +1,10 @@ # encoding: UTF-8 - -from __future__ import unicode_literals -from __future__ import print_function -from __future__ import division -from __future__ import absolute_import -from builtins import open -from future import standard_library -standard_library.install_aliases() +from __future__ import ( + print_function, unicode_literals, division, absolute_import +) import json import os +from future.builtins import open from . import logger from .singleton import Singleton diff --git a/NEMbox/const.py b/NEMbox/const.py index 46dcde43..08aca4c4 100644 --- a/NEMbox/const.py +++ b/NEMbox/const.py @@ -1,11 +1,7 @@ # encoding: UTF-8 - -from __future__ import unicode_literals -from __future__ import print_function -from __future__ import division -from __future__ import absolute_import -from future import standard_library -standard_library.install_aliases() +from __future__ import ( + print_function, unicode_literals, division, absolute_import +) import os diff --git a/NEMbox/encrypt.py b/NEMbox/encrypt.py new file mode 100644 index 00000000..3d832179 --- /dev/null +++ b/NEMbox/encrypt.py @@ -0,0 +1,65 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +from __future__ import ( + print_function, unicode_literals, division, absolute_import +) +import base64 +import binascii +import hashlib +import json +import os + +from Cryptodome.Cipher import AES +from future.builtins import (int, pow) + +__all__ = ['encrypted_id', 'encrypted_request'] + +MODULUS = ('00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7' + 'b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280' + '104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932' + '575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b' + '3ece0462db0a22b8e7') +PUBKEY = '010001' +NONCE = b'0CoJUm6Qyw8W8jud' + + +# 歌曲加密算法, 基于https://github.com/yanunon/NeteaseCloudMusic +def encrypted_id(id): + magic = bytearray('3go8&$8*3*3h0k(2)2', 'u8') + song_id = bytearray(id, 'u8') + magic_len = len(magic) + for i, sid in enumerate(song_id): + song_id[i] = sid ^ magic[i % magic_len] + m = hashlib.md5(song_id) + result = m.digest() + result = base64.b64encode(result).replace(b'/', b'_').replace(b'+', b'-') + return result.decode('utf-8') + + +# 登录加密算法, 基于https://github.com/stkevintan/nw_musicbox +def encrypted_request(text): + # type: (str) -> dict + data = json.dumps(text).encode('utf-8') + secret = create_key(16) + params = aes(aes(data, NONCE), secret) + encseckey = rsa(secret, PUBKEY, MODULUS) + return {'params': params, 'encSecKey': encseckey} + + +def aes(text, key): + pad = 16 - len(text) % 16 + text = text + bytearray([pad] * pad) + encryptor = AES.new(key, 2, b'0102030405060708') + ciphertext = encryptor.encrypt(text) + return base64.b64encode(ciphertext) + + +def rsa(text, pubkey, modulus): + text = text[::-1] + rs = pow(int(binascii.hexlify(text), 16), + int(pubkey, 16), int(modulus, 16)) + return format(rs, 'x').zfill(256) + + +def create_key(size): + return binascii.hexlify(os.urandom(size))[:16] diff --git a/NEMbox/logger.py b/NEMbox/logger.py index 6fa1f0ce..1ccfaaa4 100644 --- a/NEMbox/logger.py +++ b/NEMbox/logger.py @@ -2,17 +2,14 @@ # -*- coding: utf-8 -*- # @Author: omi # @Date: 2014-08-24 21:51:57 - -from __future__ import unicode_literals -from __future__ import print_function -from __future__ import division -from __future__ import absolute_import -from builtins import open -from future import standard_library -standard_library.install_aliases() +from __future__ import ( + print_function, unicode_literals, division, absolute_import +) import os import logging +from future.builtins import open + from . import const FILE_NAME = const.Constant.log_path diff --git a/NEMbox/menu.py b/NEMbox/menu.py index 3a8c6b90..ad4f8a92 100644 --- a/NEMbox/menu.py +++ b/NEMbox/menu.py @@ -5,26 +5,20 @@ ''' 网易云音乐 Menu ''' -from __future__ import unicode_literals -from __future__ import print_function -from __future__ import division -from __future__ import absolute_import -from builtins import range -from builtins import str -from future import standard_library -import time -standard_library.install_aliases() +from __future__ import ( + print_function, unicode_literals, division, absolute_import +) +import time import curses import threading import sys import os -import time -import json import signal import webbrowser import locale +from future.builtins import range, str from .api import NetEase from .player import Player @@ -163,9 +157,9 @@ def check_version(self): pcsignin = self.netease.daily_signin(1) if pcsignin != -1 and pcsignin['code'] not in (-2, 301): notify('PC端签到成功', 1) - data = json.loads(self.netease.get_version()) + data = self.netease.get_version() return data['info']['version'] - except (ValueError, TypeError) as e: + except (ValueError, TypeError, KeyError) as e: log.error(e) return 0 @@ -681,7 +675,7 @@ def dispatch_enter(self, idx): songs = netease.playlist_detail(playlist_id) self.datatype = 'songs' self.datalist = netease.dig_info(songs, 'songs') - self.title += ' > ' + datalist[idx]['playlists_name'] + self.title += ' > ' + datalist[idx]['playlist_name'] # 分类精选 elif datatype == 'playlist_classes': @@ -696,8 +690,7 @@ def dispatch_enter(self, idx): # 子类别 data = self.datalist[idx] self.datatype = 'top_playlists' - self.datalist = netease.dig_info( - netease.top_playlists(data), self.datatype) + self.datalist = netease.dig_info(netease.top_playlists(data), self.datatype) self.title += ' > ' + data # 歌曲评论 @@ -713,7 +706,7 @@ def dispatch_enter(self, idx): for one_comment in hotcomments: self.datalist.append( u'(热评 %s❤️ ️)%s:%s' % (one_comment['likedCount'], one_comment['user']['nickname'], - one_comment['content'])) + one_comment['content'])) for one_comment in comcomments: self.datalist.append(one_comment['content']) self.datatype = 'comments' @@ -870,17 +863,15 @@ def choice_channel(self, idx): # 精选歌单 elif idx == 3: - self.datalist = [ - { - 'title': '全站置顶', - 'datatype': 'top_playlists', - 'callback': netease.top_playlists - }, { - 'title': '分类精选', - 'datatype': 'playlist_classes', - 'callback': netease.playlist_classes - } - ] + self.datalist = [{ + 'title': '全站置顶', + 'datatype': 'top_playlists', + 'callback': netease.top_playlists + }, { + 'title': '分类精选', + 'datatype': 'playlist_classes', + 'callback': netease.playlist_classes + }] self.title += ' > 精选歌单' self.datatype = 'playlists' diff --git a/NEMbox/osdlyrics.py b/NEMbox/osdlyrics.py index f82655ab..52ea6949 100644 --- a/NEMbox/osdlyrics.py +++ b/NEMbox/osdlyrics.py @@ -2,17 +2,15 @@ # -*- coding: utf-8 -*- # osdlyrics.py --- desktop lyrics for musicbox # Copyright (c) 2015-2016 omi & Contributors +from __future__ import ( + print_function, unicode_literals, division, absolute_import +) -from __future__ import unicode_literals -from __future__ import print_function -from __future__ import division -from __future__ import absolute_import -from builtins import super -from future import standard_library -standard_library.install_aliases() import sys from multiprocessing import Process +from future.builtins import super + from . import logger from .config import Config @@ -58,7 +56,7 @@ def initUI(self): QtGui.QApplication.desktop().cursor().pos()) bl = QtGui.QApplication.desktop().screenGeometry(scn).bottomLeft() br = QtGui.QApplication.desktop().screenGeometry(scn).bottomRight() - bc = (bl+br)/2 + bc = (bl + br) / 2 frameGeo = self.frameGeometry() frameGeo.moveCenter(bc) frameGeo.moveBottom(bc.y()) diff --git a/NEMbox/player.py b/NEMbox/player.py index d948f9fe..674996d5 100644 --- a/NEMbox/player.py +++ b/NEMbox/player.py @@ -7,15 +7,10 @@ ''' 网易云音乐 Player ''' -from __future__ import unicode_literals -from __future__ import print_function -from __future__ import division -from __future__ import absolute_import -from builtins import range -from builtins import str -from future import standard_library -standard_library.install_aliases() # Let's make some noise +from __future__ import ( + print_function, unicode_literals, division, absolute_import +) import subprocess import threading @@ -25,6 +20,8 @@ import re import platform +from future.builtins import range, str + from .ui import Ui from .storage import Storage from .api import NetEase @@ -149,7 +146,7 @@ def runInThread(onExit, arg): log.error(e) break else: - #有遇到播放玩后没有退出,mpg123一直在发送空消息的情况,此处直接终止处理 + # 有播放后没有退出,mpg123一直在发送空消息的情况,此处直接终止处理 if len(strout) == 0: endless_loop_cnt += 1 if platform.system() == 'Darwin' or endless_loop_cnt > 100: @@ -159,11 +156,9 @@ def runInThread(onExit, arg): self.popen_handler.stdin.flush() self.popen_handler.kill() except IOError as e: - try: - log.error(e) - except Exception as e1: - pass + log.error(e) break + if self.playing_flag: self.next_idx() onExit() diff --git a/NEMbox/scrollstring.py b/NEMbox/scrollstring.py index 68f568eb..74e40e9b 100644 --- a/NEMbox/scrollstring.py +++ b/NEMbox/scrollstring.py @@ -1,16 +1,12 @@ #!/usr/bin/python # -*- coding: utf-8 -*- - -from __future__ import division -from __future__ import unicode_literals -from __future__ import print_function -from __future__ import absolute_import -from builtins import int -from builtins import chr -from future import standard_library -standard_library.install_aliases() +from __future__ import ( + print_function, unicode_literals, division, absolute_import +) from time import time +from future.builtins import int, chr + class scrollstring(object): diff --git a/NEMbox/singleton.py b/NEMbox/singleton.py index 0db60846..8317171a 100644 --- a/NEMbox/singleton.py +++ b/NEMbox/singleton.py @@ -1,3 +1,6 @@ +# -*- coding: utf-8 -*- + + class Singleton(object): """Singleton Class This is a class to make some class being a Singleton class. diff --git a/NEMbox/storage.py b/NEMbox/storage.py index 55a52468..05ae079f 100644 --- a/NEMbox/storage.py +++ b/NEMbox/storage.py @@ -4,15 +4,14 @@ ''' Class to stores everything into a json file. ''' -from __future__ import unicode_literals -from __future__ import print_function -from __future__ import division -from __future__ import absolute_import -from builtins import open -from future import standard_library -standard_library.install_aliases() +from __future__ import ( + print_function, unicode_literals, division, absolute_import +) + import json +from future.builtins import open + from .const import Constant from .singleton import Singleton from .utils import utf8_data_to_file diff --git a/NEMbox/terminalsize.py b/NEMbox/terminalsize.py index 7b3346f9..f202bb75 100644 --- a/NEMbox/terminalsize.py +++ b/NEMbox/terminalsize.py @@ -1,17 +1,16 @@ #!/usr/bin/env python -from __future__ import print_function -from __future__ import unicode_literals -from __future__ import division -from __future__ import absolute_import -from builtins import int -from future import standard_library -standard_library.install_aliases() +from __future__ import ( + print_function, unicode_literals, division, absolute_import +) + import os import shlex import struct import platform import subprocess +from future.builtins import int + from . import logger log = logger.getLogger(__name__) diff --git a/NEMbox/tests/__init__.py b/NEMbox/tests/__init__.py new file mode 100644 index 00000000..faa18be5 --- /dev/null +++ b/NEMbox/tests/__init__.py @@ -0,0 +1,2 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- diff --git a/NEMbox/tests/test_api.py b/NEMbox/tests/test_api.py new file mode 100644 index 00000000..755d70ce --- /dev/null +++ b/NEMbox/tests/test_api.py @@ -0,0 +1,14 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +import unittest + +from future.builtins import str +from NEMbox.api import NetEase + + +class TestApi(unittest.TestCase): + def test_api(self): + ne = NetEase() + self.assertIsInstance(ne.songs_detail_new_api([27902910])[0]['url'], str) + self.assertIsNone(ne.songs_detail([405079776])[0]['mp3Url']) # old api diff --git a/NEMbox/ui.py b/NEMbox/ui.py index 67fdb3fc..97d89ba3 100644 --- a/NEMbox/ui.py +++ b/NEMbox/ui.py @@ -5,19 +5,16 @@ ''' 网易云音乐 Ui ''' -from __future__ import division -from __future__ import unicode_literals -from __future__ import print_function -from __future__ import absolute_import -from builtins import range -from builtins import str -from builtins import int -from future import standard_library -standard_library.install_aliases() +from __future__ import ( + print_function, unicode_literals, division, absolute_import +) + import hashlib import re import curses +from future.builtins import range, str, int + from .api import NetEase from .scrollstring import truelen, scrollstring from .storage import Storage @@ -38,10 +35,10 @@ def break_str(s, start, max_len=80): - l = len(s) + length = len(s) i, x = 0, max_len res = [] - while i < l: + while i < length: res.append(s[i:i + max_len]) i += x return '\n{}'.format(' ' * start).join(res) @@ -209,7 +206,7 @@ def build_process_bar(self, now_playing, total_length, playing_flag, # 计算下一句歌词,判断刷新时的歌词和上一次是否相同来进行index计算 if not (self.now_lyric == re.sub('\[.*?\]', '', line)): self.now_lyric_index = self.now_lyric_index + 1 - if index < len(song['lyric']) - 1 : + if index < len(song['lyric']) - 1: self.post_lyric = song['lyric'][index + 1] else: self.post_lyric = '' @@ -221,11 +218,11 @@ def build_process_bar(self, now_playing, total_length, playing_flag, if key in tline and self.config.get_item( 'translation'): self.now_lyric = tline + ' || ' + self.now_lyric # NOQA - if not (self.post_lyric == '') and tindex < len(song['tlyric']) -1: - self.post_lyric = song['tlyric'][tindex+1] + ' || ' + self.post_lyric - #此处已经拿到,直接break即可 + if not (self.post_lyric == '') and tindex < len(song['tlyric']) - 1: + self.post_lyric = song['tlyric'][tindex + 1] + ' || ' + self.post_lyric + # 此处已经拿到,直接break即可 break - #此处已经拿到,直接break即可 + # 此处已经拿到,直接break即可 break index += 1 self.now_lyric = re.sub('\[.*?\]', '', self.now_lyric) @@ -233,7 +230,7 @@ def build_process_bar(self, now_playing, total_length, playing_flag, if dbus_activity and self.config.get_item('osdlyrics'): try: bus = dbus.SessionBus().get_object('org.musicbox.Bus', '/') - #TODO 环境问题,没有试过桌面歌词,此处需要了解的人加个刷界面操作 + # TODO 环境问题,没有试过桌面歌词,此处需要了解的人加个刷界面操作 if self.now_lyric == '暂无歌词 ~>_<~ \n': bus.refresh_lyrics(self.now_playing, dbus_interface='local.musicbox.Lyrics') @@ -243,7 +240,7 @@ def build_process_bar(self, now_playing, total_length, playing_flag, except Exception as e: log.error(e) pass - #根据索引计算双行歌词的显示,其中当前歌词颜色为红色,下一句歌词颜色为白色; + # 根据索引计算双行歌词的显示,其中当前歌词颜色为红色,下一句歌词颜色为白色; # 当前歌词从下一句歌词刷新颜色变换,所以当前歌词和下一句歌词位置会交替 if self.now_lyric_index % 2 == 0: self.addstr(4, self.startcol - 2, str(self.now_lyric), @@ -404,13 +401,13 @@ def build_menu(self, datatype, title, datalist, offset, index, step, if i == index: self.addstr( i - offset + 9, self.indented_startcol, '-> ' + - str(i) + '. ' + datalist[i]['playlists_name'] + + str(i) + '. ' + datalist[i]['playlist_name'] + self.space + datalist[i]['creator_name'], curses.color_pair(2)) else: self.addstr( i - offset + 9, self.startcol, - str(i) + '. ' + datalist[i]['playlists_name'] + + str(i) + '. ' + datalist[i]['playlist_name'] + self.space + datalist[i][ 'creator_name']) @@ -580,7 +577,7 @@ def build_login_bar(self): curses.noecho() self.screen.move(4, 1) self.screen.clrtobot() - self.addstr(5, self.startcol, '请输入登录信息(支持手机登陆)', + self.addstr(5, self.startcol, '请输入登录信息(支持手机登录)', curses.color_pair(1)) self.addstr(8, self.startcol, '账号:', curses.color_pair(1)) self.addstr(9, self.startcol, '密码:', curses.color_pair(1)) @@ -651,7 +648,7 @@ def update_size(self): size = terminalsize.get_terminal_size() x = max(size[0], 10) y = max(size[1], 25) - if (x, y) == (self.x, self.y): # no need to resize + if (x, y) == (self.x, self.y): # no need to resize return self.x, self.y = x, y diff --git a/NEMbox/utils.py b/NEMbox/utils.py index a0c2ab47..67532633 100644 --- a/NEMbox/utils.py +++ b/NEMbox/utils.py @@ -2,20 +2,29 @@ # -*- coding: utf-8 -*- # utils.py --- utils for musicbox # Copyright (c) 2015-2016 omi & Contributors +from __future__ import ( + print_function, unicode_literals, division, absolute_import +) -from __future__ import unicode_literals -from __future__ import print_function -from __future__ import division -from __future__ import absolute_import -from builtins import str -from future import standard_library -from . import logger -standard_library.install_aliases() import platform import subprocess +from collections import OrderedDict + +from future.builtins import str + +from . import logger log = logger.getLogger(__name__) +__all__ = [ + 'utf8_data_to_file', 'notify', 'uniq' +] + + +def uniq(arr): + return list(OrderedDict.fromkeys(arr).keys()) + + def utf8_data_to_file(f, data): if hasattr(data, 'decode'): f.write(data.decode('utf-8')) @@ -27,13 +36,13 @@ def notify_command_osx(msg, msg_type, t=None): command = ['/usr/bin/osascript', '-e'] tpl = "display notification \"{}\" {} with title \"musicbox\"" sound = 'sound name \"/System/Library/Sounds/Ping.aiff\"' if msg_type else '' - command.append(tpl.format(msg, sound).encode('UTF-8')) + command.append(tpl.format(msg, sound).encode('utf-8')) return command def notify_command_linux(msg, t=None): command = ['/usr/bin/notify-send'] - command.append(msg.encode('UTF-8')) + command.append(msg.encode('utf-8')) if t: command.extend(['-t', str(t)]) command.extend(['-h', 'int:transient:1']) @@ -51,6 +60,7 @@ def notify(msg, msg_type=0, t=None): except OSError as e: log.warning('Sending notification error.') + if __name__ == "__main__": notify("I'm test 1", msg_type=1, t=1000) notify("I'm test 2", msg_type=0, t=1000) diff --git a/README.md b/README.md index cb244ca4..bdb734b9 100644 --- a/README.md +++ b/README.md @@ -75,7 +75,7 @@ NetEase-MusicBox ### PyPi安装 $ pip(3) install NetEase-MusicBox - + ### Git clone最新版 $ git clone https://github.com/darknessomi/musicbox.git && cd musicbox $ python(3) setup.py install @@ -141,7 +141,6 @@ NetEase-MusicBox #### 已知问题及解决方案 - [#374](https://github.com/darknessomi/musicbox/issues/374) i3wm下播放杂音或快进问题,此问题常见于Arch Linux。尝试更改mpg123配置。 -- [#404](https://github.com/darknessomi/musicbox/issues/404) 某些环境缺乏lxml。尝试利用包管理器安装lxml或者直接`pip install lxml`。 - [#405](https://github.com/darknessomi/musicbox/issues/405) 32位Python下cookie时间戳超出了32位整数最大值。尝试使用64位版本的Python或者拷贝cookie文件到对应位置。 - [#347](https://github.com/darknessomi/musicbox/issues/347) 暂停时间超过一定长度(数分钟)之后mpg123停止输出,导致切换到下一首歌。此问题是mpg123的bug,暂时无解决方案。 - [#536](https://github.com/darknessomi/musicbox/issues/536) 从浏览器登录之后把cookie copy到配置文件中,并且设置username和userid之后就能达到登录效果。 diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 00000000..2a723f76 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,3 @@ +-r requirements.txt +flake8 +autopep8 diff --git a/requirements.txt b/requirements.txt index d777b817..26a9ec4f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -requests>=2.9.1 -BeautifulSoup4>=4.4.1 -pycrypto>=2.6.1 -future>=0.15.2 +requests +BeautifulSoup4 +pycryptodomex +future diff --git a/setup.py b/setup.py index bdc9043b..a31ee1e9 100644 --- a/setup.py +++ b/setup.py @@ -37,14 +37,13 @@ setup( name='NetEase-MusicBox', - version='0.2.4.2', + version='0.2.4.3', packages=find_packages(), install_requires=[ 'requests', 'BeautifulSoup4', - 'pycrypto', - 'future', - 'lxml' + 'pycryptodomex', + 'future' ], entry_points={