├── .gitignore ├── icon.png ├── public ├── mv.png ├── home.png ├── song.png ├── playlist.png └── settings.png ├── README.md ├── addon.xml ├── .github └── workflows │ └── upload.yml ├── encrypt.py ├── resources ├── settings.xml └── language │ └── resource.language.zh_cn │ └── strings.po ├── api.py └── addon.py /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | tempCodeRunnerFile.py 3 | __pycache__/ 4 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen310/plugin.audio.music163/HEAD/icon.png -------------------------------------------------------------------------------- /public/mv.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen310/plugin.audio.music163/HEAD/public/mv.png -------------------------------------------------------------------------------- /public/home.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen310/plugin.audio.music163/HEAD/public/home.png -------------------------------------------------------------------------------- /public/song.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen310/plugin.audio.music163/HEAD/public/song.png -------------------------------------------------------------------------------- /public/playlist.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen310/plugin.audio.music163/HEAD/public/playlist.png -------------------------------------------------------------------------------- /public/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/chen310/plugin.audio.music163/HEAD/public/settings.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 网易云音乐 Kodi 插件 2 | 3 | ### 下载 4 | 5 | 到 [Actions](https://github.com/chen310/plugin.audio.music163/actions)(推荐) 或 [Releases](https://github.com/chen310/plugin.audio.music163/releases)(更新不及时) 中下载 6 | 7 | ### 截图 8 | 9 | ![主页](https://cdn.jsdelivr.net/gh/chen310/plugin.audio.music163/public/home.png) 10 | ![歌单](https://cdn.jsdelivr.net/gh/chen310/plugin.audio.music163/public/playlist.png) 11 | ![播放歌曲](https://cdn.jsdelivr.net/gh/chen310/plugin.audio.music163/public/song.png) 12 | ![播放MV](https://cdn.jsdelivr.net/gh/chen310/plugin.audio.music163/public/mv.png) 13 | ![设置](https://cdn.jsdelivr.net/gh/chen310/plugin.audio.music163/public/settings.png) 14 | -------------------------------------------------------------------------------- /addon.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | audio 14 | 15 | 16 | 17 | 18 | all 19 | 20 | 21 | icon.png 22 | 23 | 网易云音乐是一款专注于发现与分享的音乐产品,依托专业音乐人、DJ、好友推荐及社交功能,为用户打造全新的音乐产品 24 | 25 | 26 | -------------------------------------------------------------------------------- /.github/workflows/upload.yml: -------------------------------------------------------------------------------- 1 | name: "upload" 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - master 8 | 9 | concurrency: 10 | group: upload-workflow 11 | cancel-in-progress: true 12 | 13 | jobs: 14 | upload-files: 15 | name: upload 16 | runs-on: ubuntu-latest 17 | steps: 18 | - name: Clone local repository 19 | uses: actions/checkout@v2 20 | - name: Get version 21 | run: | 22 | ver=$(cat addon.xml | grep plugin.audio.music163 | grep -o "[0-9]\.[\.0-9]*") 23 | echo "VERSION=$ver" >> $GITHUB_ENV 24 | - name: Copy language files 25 | run: | 26 | if [ ! -d "resources/language/resource.language.en_gb" ];then 27 | mkdir resources/language/resource.language.en_gb 28 | fi 29 | cp -r resources/language/resource.language.zh_cn/* resources/language/resource.language.en_gb 30 | sed -i 's/msgstr\s*".*"/msgstr ""/g' resources/language/resource.language.en_gb/strings.po 31 | - name: Move files 32 | run: | 33 | mkdir plugin.audio.music163 34 | rm -rf .github .git .gitignore public 35 | shopt -s extglob 36 | mv !(plugin.audio.music163) plugin.audio.music163 37 | - name: Upload addon 38 | uses: actions/upload-artifact@v4 39 | with: 40 | name: plugin.audio.music163-${{ env.VERSION }}-python3 41 | path: ./ 42 | - name: Change python version 43 | run: | 44 | sed -i "s/3\.0\.0/2\.7\.0/g" ./plugin.audio.music163/addon.xml 45 | - name: Upload addon 46 | uses: actions/upload-artifact@v4 47 | with: 48 | name: plugin.audio.music163-${{ env.VERSION }}-python2 49 | path: ./ 50 | -------------------------------------------------------------------------------- /encrypt.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | from __future__ import print_function, unicode_literals, division, absolute_import 4 | import base64 5 | import binascii 6 | import hashlib 7 | import json 8 | import os 9 | import sys 10 | 11 | from Cryptodome.Cipher import AES 12 | from future.builtins import int, pow 13 | 14 | PY3 = sys.version_info.major >= 3 15 | 16 | __all__ = ["encrypted_id", "encrypted_request"] 17 | 18 | MODULUS = ( 19 | "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7" 20 | "b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280" 21 | "104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932" 22 | "575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b" 23 | "3ece0462db0a22b8e7" 24 | ) 25 | PUBKEY = "010001" 26 | NONCE = b"0CoJUm6Qyw8W8jud" 27 | 28 | 29 | # 歌曲加密算法, 基于https://github.com/yanunon/NeteaseCloudMusic 30 | def encrypted_id(id): 31 | magic = bytearray("3go8&$8*3*3h0k(2)2", "u8") 32 | song_id = bytearray(id, "u8") 33 | magic_len = len(magic) 34 | for i, sid in enumerate(song_id): 35 | song_id[i] = sid ^ magic[i % magic_len] 36 | m = hashlib.md5(song_id) 37 | result = m.digest() 38 | result = base64.b64encode(result).replace(b"/", b"_").replace(b"+", b"-") 39 | return result.decode("utf-8") 40 | 41 | # 登录加密算法, 基于https://github.com/stkevintan/nw_musicbox 42 | def encrypted_request(text): 43 | # type: (str) -> dict 44 | data = json.dumps(text).encode("utf-8") 45 | secret = create_key(16) 46 | params = aes(aes(data, NONCE), secret) 47 | encseckey = rsa(secret, PUBKEY, MODULUS) 48 | return {"params": params, "encSecKey": encseckey} 49 | 50 | if PY3: 51 | def aes(text, key): 52 | pad = 16 - len(text) % 16 53 | text = text + bytearray([pad] * pad) 54 | encryptor = AES.new(key, 2, b"0102030405060708") 55 | ciphertext = encryptor.encrypt(text) 56 | return base64.b64encode(ciphertext) 57 | else: 58 | def aes(text, key): 59 | pad = 16 - len(text) % 16 60 | text = text.encode() + bytearray([pad] * pad) 61 | encryptor = AES.new(key.encode(), 2, b"0102030405060708") 62 | ciphertext = encryptor.encrypt(str(text).encode()) 63 | return base64.b64encode(ciphertext) 64 | 65 | 66 | 67 | def rsa(text, pubkey, modulus): 68 | text = text[::-1] 69 | rs = pow(int(binascii.hexlify(text), 16), int(pubkey, 16), int(modulus, 16)) 70 | return format(rs, "x").zfill(256) 71 | 72 | 73 | def create_key(size): 74 | return binascii.hexlify(os.urandom(size))[:16] 75 | -------------------------------------------------------------------------------- /resources/settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | -------------------------------------------------------------------------------- /resources/language/resource.language.zh_cn/strings.po: -------------------------------------------------------------------------------- 1 | msgid "" 2 | msgstr "" 3 | 4 | ################################ 5 | 6 | msgctxt "#30001" 7 | msgid "Login Settings" 8 | msgstr "登录设置" 9 | 10 | msgctxt "#30002" 11 | msgid "Account Settings" 12 | msgstr "账号设置" 13 | 14 | msgctxt "#30003" 15 | msgid "Login" 16 | msgstr "账号密码登录" 17 | 18 | msgctxt "#30004" 19 | msgid "QR code login" 20 | msgstr "扫码登录" 21 | 22 | msgctxt "#30005" 23 | msgid "Logout" 24 | msgstr "退出登录" 25 | 26 | msgctxt "#30006" 27 | msgid "Proxy Settings" 28 | msgstr "代理设置" 29 | 30 | msgctxt "#30007" 31 | msgid "Host" 32 | msgstr "服务器" 33 | 34 | msgctxt "#30008" 35 | msgid "Port" 36 | msgstr "端口" 37 | 38 | msgctxt "#30009" 39 | msgid "Enable proxy" 40 | msgstr "使用代理" 41 | 42 | msgctxt "#30010" 43 | msgid "Play Settings" 44 | msgstr "播放设置" 45 | 46 | msgctxt "#30011" 47 | msgid "Play Settings" 48 | msgstr "播放设置" 49 | 50 | msgctxt "#30012" 51 | msgid "Songs per page" 52 | msgstr "每页歌曲数" 53 | 54 | msgctxt "#30013" 55 | msgid "Show index of songs" 56 | msgstr "显示歌曲序号" 57 | 58 | msgctxt "#30014" 59 | msgid "Play MV first" 60 | msgstr "歌曲有MV时优先播放MV" 61 | 62 | msgctxt "#30015" 63 | msgid "Audio quality" 64 | msgstr "默认音质" 65 | 66 | msgctxt "#30016" 67 | msgid "Standard" 68 | msgstr "标准" 69 | 70 | msgctxt "#30017" 71 | msgid "High" 72 | msgstr "较高" 73 | 74 | msgctxt "#30018" 75 | msgid "Very high" 76 | msgstr "极高" 77 | 78 | msgctxt "#30019" 79 | msgid "Lossless" 80 | msgstr "无损" 81 | 82 | msgctxt "#30020" 83 | msgid "Hi-Res" 84 | msgstr "Hi-Res" 85 | 86 | msgctxt "#30021" 87 | msgid "Spatial Audio" 88 | msgstr "高清环绕声" 89 | 90 | msgctxt "#30022" 91 | msgid "Surround Audio" 92 | msgstr "沉浸环绕声" 93 | 94 | msgctxt "#30023" 95 | msgid "Master" 96 | msgstr "超清母带" 97 | 98 | msgctxt "#30100" 99 | msgid "Dolby" 100 | msgstr "杜比全景声" 101 | 102 | msgctxt "#30024" 103 | msgid "Video resolution" 104 | msgstr "默认画质" 105 | 106 | # 标清 107 | msgctxt "#30025" 108 | msgid "240" 109 | msgstr "240" 110 | 111 | # 高清 112 | msgctxt "#30026" 113 | msgid "480" 114 | msgstr "480" 115 | 116 | # 超清 117 | msgctxt "#30027" 118 | msgid "720" 119 | msgstr "720" 120 | 121 | # 1080P 122 | msgctxt "#30028" 123 | msgid "1080" 124 | msgstr "1080" 125 | 126 | msgctxt "#30029" 127 | msgid "Song naming format" 128 | msgstr "歌曲命名格式" 129 | 130 | msgctxt "#30030" 131 | msgid "artist - song" 132 | msgstr "歌手 - 歌曲名" 133 | 134 | msgctxt "#30031" 135 | msgid "song - artist" 136 | msgstr "歌曲名 - 歌手" 137 | 138 | msgctxt "#30032" 139 | msgid "song" 140 | msgstr "歌曲名" 141 | 142 | msgctxt "#30033" 143 | msgid "Reverse radio" 144 | msgstr "播单(声音)逆序" 145 | 146 | msgctxt "#30034" 147 | msgid "Automatically play the MV when the song cannot be played" 148 | msgstr "歌曲不能播放时自动播放MV(如有)" 149 | 150 | msgctxt "#30035" 151 | msgid "Hide unplayable songs" 152 | msgstr "隐藏不能播放的歌曲" 153 | 154 | msgctxt "#30036" 155 | msgid "Hide cover songs in search reaults" 156 | msgstr "隐藏搜索结果中的翻唱歌曲" 157 | 158 | msgctxt "#30037" 159 | msgid "Show album name below song name" 160 | msgstr "歌名下方显示专辑名" 161 | 162 | msgctxt "#30038" 163 | msgid "Upload music playback record to Netease Cloud Music" 164 | msgstr "上传音乐播放记录至网易云音乐" 165 | 166 | msgctxt "#30039" 167 | msgid "Delete thumbnails (thumbnails of other addon will also be deleted)" 168 | msgstr "删除缩略图(其他插件的缩略图也会被删除)" 169 | 170 | ################################ 171 | 172 | msgctxt "#30040" 173 | msgid "Function Settings" 174 | msgstr "功能设置" 175 | 176 | msgctxt "#30041" 177 | msgid "Function Switch" 178 | msgstr "功能开关" 179 | 180 | msgctxt "#30042" 181 | msgid "Ranking list" 182 | msgstr "排行榜" 183 | 184 | msgctxt "#30043" 185 | msgid "Top artist" 186 | msgstr "热门歌手" 187 | 188 | msgctxt "#30044" 189 | msgid "Daily recommend" 190 | msgstr "每日推荐" 191 | 192 | msgctxt "#30045" 193 | msgid "Personal FM" 194 | msgstr "私人FM" 195 | 196 | msgctxt "#30046" 197 | msgid "My playlist" 198 | msgstr "我的歌单" 199 | 200 | msgctxt "#30047" 201 | msgid "Recommend playlists" 202 | msgstr "推荐歌单" 203 | 204 | msgctxt "#30048" 205 | msgid "My sublist" 206 | msgstr "我的收藏" 207 | 208 | msgctxt "#30049" 209 | msgid "Search" 210 | msgstr "搜索" 211 | 212 | msgctxt "#30050" 213 | msgid "Colud disk" 214 | msgstr "我的云盘" 215 | 216 | msgctxt "#30051" 217 | msgid "New albums" 218 | msgstr "新碟上架" 219 | 220 | msgctxt "#30052" 221 | msgid "New songs" 222 | msgstr "新歌速递" 223 | 224 | msgctxt "#30053" 225 | msgid "Homepage" 226 | msgstr "个人主页" 227 | 228 | msgctxt "#30054" 229 | msgid "Top MV" 230 | msgstr "热门MV" 231 | 232 | msgctxt "#30055" 233 | msgid "Mlog" 234 | msgstr "Mlog" 235 | 236 | msgctxt "#30056" 237 | msgid "Vip timemachine" 238 | msgstr "黑胶时光机" 239 | 240 | msgctxt "#30057" 241 | msgid "Tag Settings" 242 | msgstr "标志设置" 243 | 244 | msgctxt "#30058" 245 | msgid "Tag Switch" 246 | msgstr "标志开关" 247 | 248 | msgctxt "#30059" 249 | msgid "MV tag" 250 | msgstr "MV标志" 251 | 252 | msgctxt "#30060" 253 | msgid "VIP tag" 254 | msgstr "VIP标志" 255 | 256 | msgctxt "#30061" 257 | msgid "Cloud disk tag" 258 | msgstr "云盘标志 ☁" 259 | 260 | msgctxt "#30062" 261 | msgid "Exclusive tag" 262 | msgstr "独家标志" 263 | 264 | msgctxt "#30063" 265 | msgid "SQ tag" 266 | msgstr "SQ标志" 267 | 268 | msgctxt "#30064" 269 | msgid "Pay tag" 270 | msgstr "付费标志" 271 | 272 | msgctxt "#30065" 273 | msgid "PreSell tag" 274 | msgstr "预售标志" 275 | 276 | msgctxt "#30066" 277 | msgid "Like tag" 278 | msgstr "喜欢标志 [COLOR red]♥[/COLOR]" 279 | -------------------------------------------------------------------------------- /api.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | import json 3 | import os 4 | import sys 5 | import time 6 | import requests 7 | from encrypt import encrypted_request 8 | from xbmcswift2 import xbmc, xbmcaddon, xbmcplugin 9 | from http.cookiejar import Cookie 10 | from http.cookiejar import MozillaCookieJar 11 | import xbmcvfs 12 | try: 13 | xbmc.translatePath = xbmcvfs.translatePath 14 | except AttributeError: 15 | pass 16 | 17 | DEFAULT_TIMEOUT = 10 18 | 19 | BASE_URL = "https://music.163.com" 20 | 21 | PROFILE = xbmc.translatePath(xbmcaddon.Addon().getAddonInfo('profile')) 22 | if not os.path.exists(PROFILE): 23 | os.makedirs(PROFILE) 24 | COOKIE_PATH = os.path.join(PROFILE, 'cookie.txt') 25 | if not os.path.exists(COOKIE_PATH): 26 | with open(COOKIE_PATH, 'w') as f: 27 | f.write('# Netscape HTTP Cookie File\n') 28 | 29 | 30 | class NetEase(object): 31 | def __init__(self): 32 | self.header = { 33 | "Accept": "*/*", 34 | "Accept-Encoding": "gzip,deflate,sdch", 35 | "Accept-Language": "zh-CN,zh;q=0.8,gl;q=0.6,zh-TW;q=0.4", 36 | "Connection": "keep-alive", 37 | "Content-Type": "application/x-www-form-urlencoded", 38 | "Host": "music.163.com", 39 | "Referer": "http://music.163.com", 40 | "User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36", 41 | } 42 | 43 | cookie_jar = MozillaCookieJar(COOKIE_PATH) 44 | cookie_jar.load() 45 | self.session = requests.Session() 46 | self.session.cookies = cookie_jar 47 | 48 | for cookie in cookie_jar: 49 | if cookie.is_expired(): 50 | cookie_jar.clear() 51 | break 52 | 53 | self.enable_proxy = False 54 | if xbmcplugin.getSetting(int(sys.argv[1]), 'enable_proxy') == 'true': 55 | self.enable_proxy = True 56 | proxy = xbmcplugin.getSetting(int(sys.argv[1]), 'host').strip( 57 | ) + ':' + xbmcplugin.getSetting(int(sys.argv[1]), 'port').strip() 58 | self.proxies = { 59 | 'http': 'http://' + proxy, 60 | 'https': 'https://' + proxy, 61 | } 62 | 63 | def _raw_request(self, method, endpoint, data=None): 64 | if method == "GET": 65 | if not self.enable_proxy: 66 | resp = self.session.get( 67 | endpoint, params=data, headers=self.header, timeout=DEFAULT_TIMEOUT 68 | ) 69 | else: 70 | resp = self.session.get( 71 | endpoint, params=data, headers=self.header, timeout=DEFAULT_TIMEOUT, proxies=self.proxies, verify=False 72 | ) 73 | elif method == "POST": 74 | if not self.enable_proxy: 75 | resp = self.session.post( 76 | endpoint, data=data, headers=self.header, timeout=DEFAULT_TIMEOUT 77 | ) 78 | else: 79 | resp = self.session.post( 80 | endpoint, data=data, headers=self.header, timeout=DEFAULT_TIMEOUT, proxies=self.proxies, verify=False 81 | ) 82 | return resp 83 | 84 | # 生成Cookie对象 85 | def make_cookie(self, name, value): 86 | return Cookie( 87 | version=0, 88 | name=name, 89 | value=value, 90 | port=None, 91 | port_specified=False, 92 | domain="music.163.com", 93 | domain_specified=True, 94 | domain_initial_dot=False, 95 | path="/", 96 | path_specified=True, 97 | secure=False, 98 | expires=None, 99 | discard=False, 100 | comment=None, 101 | comment_url=None, 102 | rest={}, 103 | ) 104 | 105 | def request(self, method, path, params={}, default={"code": -1}, custom_cookies={'os': 'android', 'appver': '9.2.70'}): 106 | endpoint = "{}{}".format(BASE_URL, path) 107 | csrf_token = "" 108 | for cookie in self.session.cookies: 109 | if cookie.name == "__csrf": 110 | csrf_token = cookie.value 111 | break 112 | params.update({"csrf_token": csrf_token}) 113 | data = default 114 | 115 | for key, value in custom_cookies.items(): 116 | cookie = self.make_cookie(key, value) 117 | self.session.cookies.set_cookie(cookie) 118 | 119 | params = encrypted_request(params) 120 | try: 121 | resp = self._raw_request(method, endpoint, params) 122 | data = resp.json() 123 | except requests.exceptions.RequestException as e: 124 | print(e) 125 | except ValueError as e: 126 | print("Path: {}, response: {}".format(path, resp.text[:200])) 127 | finally: 128 | return data 129 | 130 | def login(self, username, password): 131 | if username.isdigit(): 132 | path = "/weapi/login/cellphone" 133 | params = dict(phone=username, password=password, 134 | rememberLogin="true") 135 | else: 136 | # magic token for login 137 | # see https://github.com/Binaryify/NeteaseCloudMusicApi/blob/master/router/login.js#L15 138 | client_token = ( 139 | "1_jVUMqWEPke0/1/Vu56xCmJpo5vP1grjn_SOVVDzOc78w8OKLVZ2JH7IfkjSXqgfmh" 140 | ) 141 | path = "/weapi/login" 142 | params = dict( 143 | username=username, 144 | password=password, 145 | rememberLogin="true", 146 | clientToken=client_token, 147 | ) 148 | data = self.request("POST", path, params) 149 | # 保存cookie 150 | self.session.cookies.save() 151 | return data 152 | 153 | # 每日签到 154 | def daily_task(self, is_mobile=True): 155 | path = "/weapi/point/dailyTask" 156 | params = dict(type=0 if is_mobile else 1) 157 | return self.request("POST", path, params) 158 | 159 | # 用户歌单 160 | def user_playlist(self, uid, offset=0, limit=1000, includeVideo=True): 161 | path = "/weapi/user/playlist" 162 | params = dict(uid=uid, offset=offset, limit=limit, 163 | includeVideo=includeVideo, csrf_token="") 164 | return self.request("POST", path, params) 165 | # specialType:5 喜欢的歌曲; 200 视频歌单; 0 普通歌单 166 | 167 | # 每日推荐歌单 168 | def recommend_resource(self): 169 | path = "/weapi/v1/discovery/recommend/resource" 170 | return self.request("POST", path) 171 | 172 | # 每日推荐歌曲 173 | def recommend_playlist(self, total=True, offset=0, limit=20): 174 | path = "/weapi/v3/discovery/recommend/songs" 175 | params = dict(total=total, offset=offset, limit=limit, csrf_token="") 176 | return self.request("POST", path, params) 177 | 178 | # 获取历史日推可用日期 179 | def history_recommend_recent(self): 180 | path = "/weapi/discovery/recommend/songs/history/recent" 181 | return self.request("POST", path) 182 | 183 | # 获取历史日推 184 | def history_recommend_detail(self, date=''): 185 | path = "/weapi/discovery/recommend/songs/history/detail" 186 | params = dict(date=date) 187 | return self.request("POST", path, params) 188 | 189 | # 私人FM 190 | def personal_fm(self): 191 | path = "/weapi/v1/radio/get" 192 | return self.request("POST", path) 193 | 194 | # 搜索单曲(1),歌手(100),专辑(10),歌单(1000),用户(1002),歌词(1006),主播电台(1009),MV(1004),视频(1014),综合(1018) *(type)* 195 | def search(self, keywords, stype=1, offset=0, total="true", limit=100): 196 | path = "/weapi/search/get" 197 | params = dict(s=keywords, type=stype, offset=offset, 198 | total=total, limit=limit) 199 | return self.request("POST", path, params) 200 | 201 | # 新碟上架 202 | def new_albums(self, offset=0, limit=50): 203 | path = "/weapi/album/new" 204 | params = dict(area="ALL", offset=offset, total=True, limit=limit) 205 | return self.request("POST", path, params) 206 | 207 | # 歌单(网友精选碟) hot||new http://music.163.com/#/discover/playlist/ 208 | def top_playlists(self, category="全部", order="hot", offset=0, limit=50): 209 | path = "/weapi/playlist/list" 210 | params = dict( 211 | cat=category, order=order, offset=offset, total="true", limit=limit 212 | ) 213 | return self.request("POST", path, params) 214 | 215 | def playlist_catelogs(self): 216 | path = "/weapi/playlist/catalogue" 217 | return self.request("POST", path) 218 | 219 | # 歌单详情 220 | def playlist_detail(self, id, shareUserId=0): 221 | path = "/weapi/v6/playlist/detail" 222 | params = dict(id=id, t=int(time.time()), n=1000, 223 | s=5, shareUserId=shareUserId) 224 | 225 | return (self.request("POST", path, params)) 226 | 227 | # 热门歌手 http://music.163.com/#/discover/artist/ 228 | def top_artists(self, offset=0, limit=100, total=True): 229 | path = "/weapi/artist/top" 230 | params = dict(offset=offset, total=total, limit=limit) 231 | return self.request("POST", path, params) 232 | 233 | # 歌手单曲 234 | def artists(self, artist_id): 235 | path = "/weapi/v1/artist/{}".format(artist_id) 236 | return self.request("POST", path) 237 | 238 | def artist_album(self, artist_id, offset=0, limit=50): 239 | path = "/weapi/artist/albums/{}".format(artist_id) 240 | params = dict(offset=offset, total=True, limit=limit) 241 | return self.request("POST", path, params) 242 | 243 | # album id --> song id set 244 | def album(self, album_id): 245 | path = "/weapi/v1/album/{}".format(album_id) 246 | return self.request("POST", path) 247 | 248 | def song_comments(self, music_id, offset=0, total="false", limit=100): 249 | path = "/weapi/v1/resource/comments/R_SO_4_{}/".format(music_id) 250 | params = dict(rid=music_id, offset=offset, total=total, limit=limit) 251 | return self.request("POST", path, params) 252 | 253 | # song ids --> song urls ( details ) 254 | def songs_detail(self, ids): 255 | path = "/weapi/v3/song/detail" 256 | params = dict(c=json.dumps([{"id": _id} 257 | for _id in ids]), ids=json.dumps(ids)) 258 | return self.request("POST", path, params) 259 | 260 | def songs_url(self, ids, bitrate): 261 | path = "/weapi/song/enhance/player/url" 262 | params = dict(ids=ids, br=bitrate) 263 | return self.request("POST", path, params) 264 | 265 | def songs_url_v1(self, ids, level): 266 | path = "/weapi/song/enhance/player/url/v1" 267 | if level == 'dolby': 268 | params = dict(ids=ids, level='hires', effects='["dolby"]', encodeType='mp4') 269 | return self.request("POST", path, params, custom_cookies={'os': 'pc', 'appver': '2.10.11.201538'}) 270 | else: 271 | params = dict(ids=ids, level=level, encodeType='flac') 272 | return self.request("POST", path, params) 273 | 274 | # lyric http://music.163.com/api/song/lyric?os=osx&id= &lv=-1&kv=-1&tv=-1 275 | def song_lyric(self, music_id): 276 | path = "/weapi/song/lyric" 277 | params = dict(os="osx", id=music_id, lv=-1, kv=-1, tv=-1) 278 | return self.request("POST", path, params) 279 | 280 | # 今日最热(0), 本周最热(10),历史最热(20),最新节目(30) 281 | def djchannels(self, offset=0, limit=50): 282 | path = "/weapi/djradio/hot/v1" 283 | params = dict(limit=limit, offset=offset) 284 | return self.request("POST", path, params) 285 | 286 | def dj_program(self, radio_id, asc=False, offset=0, limit=50): 287 | path = "/weapi/dj/program/byradio" 288 | params = dict(asc=asc, radioId=radio_id, offset=offset, limit=limit) 289 | return self.request("POST", path, params) 290 | 291 | def dj_sublist(self, offset=0, limit=50): 292 | path = "/weapi/djradio/get/subed" 293 | params = dict(offset=offset, limit=limit, total=True) 294 | return self.request("POST", path, params) 295 | 296 | def dj_detail(self, id): 297 | path = "/weapi/dj/program/detail" 298 | params = dict(id=id) 299 | return self.request("POST", path, params) 300 | 301 | # 打卡 302 | def daka(self, id, sourceId=0, time=240): 303 | path = "/weapi/feedback/weblog" 304 | params = {'logs': json.dumps([{ 305 | 'action': 'play', 306 | 'json': { 307 | "download": 0, 308 | "end": 'playend', 309 | "id": id, 310 | "sourceId": sourceId, 311 | "time": time, 312 | "type": 'song', 313 | "wifi": 0, 314 | } 315 | }])} 316 | return self.request("POST", path, params) 317 | 318 | # 云盘歌曲 319 | def cloud_songlist(self, offset=0, limit=50): 320 | path = "/weapi/v1/cloud/get" 321 | params = dict(offset=offset, limit=limit, csrf_token="") 322 | return self.request("POST", path, params) 323 | 324 | # 歌手信息 325 | def artist_info(self, artist_id): 326 | path = "/weapi/v1/artist/{}".format(artist_id) 327 | return self.request("POST", path) 328 | 329 | def artist_songs(self, id, limit=50, offset=0): 330 | path = "/weapi/v1/artist/songs" 331 | params = dict(id=id, limit=limit, offset=offset, 332 | private_cloud=True, work_type=1, order='hot') 333 | return self.request("POST", path, params) 334 | 335 | # 获取MV url 336 | def mv_url(self, id, r=1080): 337 | path = "/weapi/song/enhance/play/mv/url" 338 | params = dict(id=id, r=r) 339 | return self.request("POST", path, params) 340 | 341 | # 收藏的歌手 342 | def artist_sublist(self, offset=0, limit=50, total=True): 343 | path = "/weapi/artist/sublist" 344 | params = dict(offset=offset, limit=limit, total=total) 345 | return self.request("POST", path, params) 346 | 347 | # 收藏的专辑 348 | def album_sublist(self, offset=0, limit=50, total=True): 349 | path = "/weapi/album/sublist" 350 | params = dict(offset=offset, limit=limit, total=total) 351 | return self.request("POST", path, params) 352 | 353 | # 收藏的视频 354 | def video_sublist(self, offset=0, limit=50, total=True): 355 | path = "/weapi/cloudvideo/allvideo/sublist" 356 | params = dict(offset=offset, limit=limit, total=total) 357 | return self.request("POST", path, params) 358 | 359 | # 获取视频url 360 | def video_url(self, id, resolution=1080): 361 | path = "/weapi/cloudvideo/playurl" 362 | params = dict(ids='["' + id + '"]', resolution=resolution) 363 | return self.request("POST", path, params) 364 | 365 | # 我的数字专辑 366 | def digitalAlbum_purchased(self, offset=0, limit=50, total=True): 367 | path = "/api/digitalAlbum/purchased" 368 | params = dict(offset=offset, limit=limit, total=total) 369 | return self.request("POST", path, params) 370 | 371 | # 已购单曲 372 | def single_purchased(self, offset=0, limit=1000, total=True): 373 | path = "/weapi/single/mybought/song/list" 374 | params = dict(offset=offset, limit=limit) 375 | return self.request("POST", path, params) 376 | 377 | # 排行榜 378 | def toplists(self): 379 | path = "/api/toplist" 380 | return self.request("POST", path) 381 | 382 | # 新歌速递 全部:0 华语:7 欧美:96 日本:8 韩国:16 383 | def new_songs(self, areaId=0, total=True): 384 | path = "/weapi/v1/discovery/new/songs" 385 | params = dict(areaId=areaId, total=total) 386 | return self.request("POST", path, params) 387 | 388 | # 歌手MV 389 | def artist_mvs(self, id, offset=0, limit=50, total=True): 390 | path = "/weapi/artist/mvs" 391 | params = dict(artistId=id, offset=offset, limit=limit, total=total) 392 | return self.request("POST", path, params) 393 | 394 | # 相似歌手 395 | def similar_artist(self, artistid): 396 | path = "/weapi/discovery/simiArtist" 397 | params = dict(artistid=artistid) 398 | return self.request("POST", path, params) 399 | 400 | # 用户信息 401 | def user_detail(self, id): 402 | path = "/weapi/v1/user/detail/{}".format(id) 403 | return self.request("POST", path) 404 | 405 | # 关注用户 406 | def user_follow(self, id): 407 | path = "/weapi/user/follow/{}".format(id) 408 | return self.request("POST", path) 409 | 410 | # 取消关注用户 411 | def user_delfollow(self, id): 412 | path = "/weapi/user/delfollow/{}".format(id) 413 | return self.request("POST", path) 414 | 415 | # 用户关注列表 416 | def user_getfollows(self, id, offset=0, limit=50, order=True): 417 | path = "/weapi/user/getfollows/{}".format(id) 418 | params = dict(offset=offset, limit=limit, order=order) 419 | return self.request("POST", path, params) 420 | 421 | # 用户粉丝列表 422 | def user_getfolloweds(self, userId, offset=0, limit=30): 423 | path = "/weapi/user/getfolloweds" 424 | params = dict(userId=userId, offset=offset, 425 | limit=limit, getcounts=True) 426 | return self.request("POST", path, params) 427 | 428 | # 听歌排行 type: 0 全部时间 1最近一周 429 | def play_record(self, uid, type=0): 430 | path = "/weapi/v1/play/record" 431 | params = dict(uid=uid, type=type) 432 | return self.request("POST", path, params) 433 | 434 | # MV排行榜 area: 地区,可选值为内地,港台,欧美,日本,韩国,不填则为全部 435 | def top_mv(self, area='', limit=50, offset=0, total=True): 436 | path = "/weapi/mv/toplist" 437 | params = dict(area=area, limit=limit, offset=offset, total=total) 438 | return self.request("POST", path, params) 439 | 440 | def mlog_socialsquare(self, channelId=1001, pagenum=0): 441 | path = "/weapi/socialsquare/v1/get" 442 | params = dict(pagenum=pagenum, netstate=1, first=( 443 | str(pagenum) == '0'), channelId=channelId, dailyHot=(str(pagenum) == '0')) 444 | return self.request("POST", path, params) 445 | 446 | # 推荐MLOG 447 | def mlog_rcmd(self, id, limit=3, type=1, rcmdType=0, lastRcmdResType=1, lastRcmdResId='', viewCount=1, channelId=1001): 448 | path = "/weapi/mlog/rcmd/v3" 449 | params = dict(id=id, limit=limit, type=type, rcmdType=rcmdType, 450 | lastRcmdResType=lastRcmdResType, extInfo=dict(channelId=channelId), viewCount=viewCount) 451 | return self.request("POST", path, params) 452 | 453 | # MLOG详情 454 | def mlog_detail(self, id, resolution=720, type=1): 455 | path = "/weapi/mlog/detail/v1" 456 | params = dict(id=id, resolution=resolution, type=type) 457 | return self.request("POST", path, params) 458 | 459 | # 创建歌单 privacy:0 为普通歌单,10 为隐私歌单;type:NORMAL|VIDEO 460 | def playlist_create(self, name, privacy=0, ptype='NORMAL'): 461 | path = "/weapi/playlist/create" 462 | params = dict(name=name, privacy=privacy, type=ptype) 463 | return self.request("POST", path, params) 464 | 465 | # 删除歌单 466 | def playlist_delete(self, ids): 467 | path = "/weapi/playlist/remove" 468 | params = dict(ids=ids) 469 | return self.request("POST", path, params) 470 | # {'code': 200} 471 | 472 | # 添加MV到视频歌单中 473 | def playlist_add(self, pid, ids): 474 | path = "/weapi/playlist/track/add" 475 | ids = [{'type': 3, 'id': song_id} for song_id in ids] 476 | params = {'id': pid, 'tracks': json.dumps(ids)} 477 | return self.request("POST", path, params) 478 | 479 | # 添加/删除单曲到歌单 480 | # op:'add'|'del' 481 | def playlist_tracks(self, pid, ids, op='add'): 482 | path = "/weapi/playlist/manipulate/tracks" 483 | params = {'op': op, 'pid': pid, 484 | 'trackIds': json.dumps(ids), 'imme': 'true'} 485 | result = self.request("POST", path, params) 486 | # 可以收藏收费歌曲和下架歌曲 487 | if result['code'] != 200: 488 | ids.extend(ids) 489 | params = {'op': op, 'pid': pid, 490 | 'trackIds': json.dumps(ids), 'imme': 'true'} 491 | result = self.request("POST", path, params) 492 | return result 493 | 494 | # 收藏歌单 495 | def playlist_subscribe(self, id): 496 | path = "/weapi/playlist/subscribe" 497 | params = dict(id=id) 498 | return self.request("POST", path, params) 499 | 500 | # 取消收藏歌单 501 | def playlist_unsubscribe(self, id): 502 | path = "/weapi/playlist/unsubscribe" 503 | params = dict(id=id) 504 | return self.request("POST", path, params) 505 | 506 | def user_level(self): 507 | path = "/weapi/user/level" 508 | return self.request("POST", path) 509 | 510 | def login_qr_key(self): 511 | path = '/weapi/login/qrcode/unikey' 512 | params = dict(type=1) 513 | return self.request("POST", path, params) 514 | 515 | def login_qr_check(self, key): 516 | path = '/weapi/login/qrcode/client/login' 517 | params = dict(key=key, type=1) 518 | data = self.request("POST", path, params) 519 | if data.get('code', 0) == 803: 520 | self.session.cookies.save() 521 | return data 522 | 523 | def vip_timemachine(self, startTime, endTime, limit=60): 524 | path = '/weapi/vipmusic/newrecord/weekflow' 525 | params = dict(startTime=startTime, 526 | endTime=endTime, type=1, limit=limit) 527 | return self.request("POST", path, params) 528 | -------------------------------------------------------------------------------- /addon.py: -------------------------------------------------------------------------------- 1 | # -*- coding:utf-8 -*- 2 | from api import NetEase 3 | from xbmcswift2 import Plugin, xbmcgui, xbmcplugin, xbmc, xbmcaddon 4 | import re 5 | import sys 6 | import hashlib 7 | import time 8 | import os 9 | import xbmcvfs 10 | import qrcode 11 | from datetime import datetime 12 | import json 13 | try: 14 | xbmc.translatePath = xbmcvfs.translatePath 15 | except AttributeError: 16 | pass 17 | 18 | PY3 = sys.version_info.major >= 3 19 | if not PY3: 20 | reload(sys) 21 | sys.setdefaultencoding('utf-8') 22 | 23 | plugin = Plugin() 24 | 25 | account = plugin.get_storage('account') 26 | if 'uid' not in account: 27 | account['uid'] = '' 28 | if 'logined' not in account: 29 | account['logined'] = False 30 | if 'first_run' not in account: 31 | account['first_run'] = True 32 | 33 | music = NetEase() 34 | 35 | PROFILE = xbmc.translatePath(xbmcaddon.Addon().getAddonInfo('profile')) 36 | qrcode_path = os.path.join(PROFILE, 'qrcode') 37 | 38 | 39 | def delete_files(path): 40 | files = os.listdir(path) 41 | for f in files: 42 | f_path = os.path.join(path, f) 43 | if os.path.isdir(f_path): 44 | delete_files(f_path) 45 | else: 46 | os.remove(f_path) 47 | 48 | 49 | def caculate_size(path): 50 | count = 0 51 | size = 0 52 | files = os.listdir(path) 53 | for f in files: 54 | f_path = os.path.join(path, f) 55 | if os.path.isdir(f_path): 56 | count_, size_ = caculate_size(f_path) 57 | count += count_ 58 | size += size_ 59 | else: 60 | count += 1 61 | size += os.path.getsize(f_path) 62 | return count, size 63 | 64 | 65 | @plugin.route('/delete_thumbnails/') 66 | def delete_thumbnails(): 67 | path = xbmc.translatePath('special://thumbnails') 68 | count, size = caculate_size(path) 69 | dialog = xbmcgui.Dialog() 70 | result = dialog.yesno('删除缩略图', '一共 {} 个文件,{} MB,确认删除吗?'.format( 71 | count, B2M(size)), '取消', '确认') 72 | if not result: 73 | return 74 | delete_files(path) 75 | dialog.notification('删除缩略图', '删除成功', 76 | xbmcgui.NOTIFICATION_INFO, 800, False) 77 | 78 | 79 | @plugin.route('/login/') 80 | def login(): 81 | keyboard = xbmc.Keyboard('', '请输入手机号或邮箱') 82 | keyboard.doModal() 83 | if (keyboard.isConfirmed()): 84 | username = keyboard.getText().strip() 85 | if not username: 86 | return 87 | else: 88 | return 89 | 90 | keyboard = xbmc.Keyboard('', '请输入密码') 91 | keyboard.doModal() 92 | if (keyboard.isConfirmed()): 93 | password = keyboard.getText().strip() 94 | if not username: 95 | return 96 | else: 97 | return 98 | password = hashlib.md5(password.encode('UTF-8')).hexdigest() 99 | 100 | login = music.login(username, password) 101 | if login['code'] == 200: 102 | account['logined'] = True 103 | account['uid'] = login['profile']['userId'] 104 | dialog = xbmcgui.Dialog() 105 | dialog.notification('登录成功', '请重启软件以解锁更多功能', 106 | xbmcgui.NOTIFICATION_INFO, 800, False) 107 | elif login['code'] == -1: 108 | dialog = xbmcgui.Dialog() 109 | dialog.notification('登录失败', '可能是网络问题', 110 | xbmcgui.NOTIFICATION_INFO, 800, False) 111 | elif login['code'] == -462: 112 | dialog = xbmcgui.Dialog() 113 | dialog.notification('登录失败', '-462: 需要验证', 114 | xbmcgui.NOTIFICATION_INFO, 800, False) 115 | else: 116 | dialog = xbmcgui.Dialog() 117 | dialog.notification('登录失败', str(login['code']) + ': ' + login.get('msg', ''), 118 | xbmcgui.NOTIFICATION_INFO, 800, False) 119 | 120 | 121 | @plugin.route('/logout/') 122 | def logout(): 123 | account['logined'] = False 124 | account['uid'] = '' 125 | liked_songs = plugin.get_storage('liked_songs') 126 | liked_songs['pid'] = 0 127 | liked_songs['ids'] = [] 128 | COOKIE_PATH = os.path.join(PROFILE, 'cookie.txt') 129 | with open(COOKIE_PATH, 'w') as f: 130 | f.write('# Netscape HTTP Cookie File\n') 131 | dialog = xbmcgui.Dialog() 132 | dialog.notification( 133 | '退出成功', '账号退出成功', xbmcgui.NOTIFICATION_INFO, 800, False) 134 | 135 | 136 | #limit = int(xbmcplugin.getSetting(int(sys.argv[1]),'number_of_songs_per_page')) 137 | limit = xbmcplugin.getSetting(int(sys.argv[1]), 'number_of_songs_per_page') 138 | if limit == '': 139 | limit = 100 140 | else: 141 | limit = int(limit) 142 | 143 | quality = xbmcplugin.getSetting(int(sys.argv[1]), 'quality') 144 | if quality == '0': 145 | level = 'standard' 146 | elif quality == '1': 147 | level = 'higher' 148 | elif quality == '2': 149 | level = 'exhigh' 150 | elif quality == '3': 151 | level = 'lossless' 152 | elif quality == '4': 153 | level = 'hires' 154 | elif quality == '5': 155 | level = 'jyeffect' 156 | elif quality == '6': 157 | level = 'sky' 158 | elif quality == '7': 159 | level = 'jymaster' 160 | elif quality == '8': 161 | level = 'dolby' 162 | else: 163 | level = 'standard' 164 | 165 | resolution = xbmcplugin.getSetting(int(sys.argv[1]), 'resolution') 166 | if resolution == '0': 167 | r = 240 168 | elif resolution == '1': 169 | r = 480 170 | elif resolution == '2': 171 | r = 720 172 | elif resolution == '3': 173 | r = 1080 174 | else: 175 | r = 720 176 | 177 | 178 | def tag(info, color='red'): 179 | return '[COLOR ' + color + ']' + info + '[/COLOR]' 180 | 181 | 182 | def trans_num(num): 183 | if num > 100000000: 184 | return str(round(num/100000000, 1)) + '亿' 185 | elif num > 10000: 186 | return str(round(num/10000, 1)) + '万' 187 | else: 188 | return str(num) 189 | 190 | 191 | def trans_time(t): 192 | return time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(t//1000)) 193 | 194 | 195 | def trans_date(t): 196 | return time.strftime('%Y-%m-%d', time.localtime(t//1000)) 197 | 198 | 199 | def B2M(size): 200 | return str(round(size/1048576, 1)) 201 | 202 | 203 | def get_songs(songs, privileges=[], picUrl=None, source=''): 204 | datas = [] 205 | for i in range(len(songs)): 206 | song = songs[i] 207 | 208 | # song data 209 | if 'song' in song: 210 | song = song['song'] 211 | # 云盘 212 | elif 'simpleSong' in song: 213 | tempSong = song 214 | song = song['simpleSong'] 215 | elif 'songData' in song: 216 | song = song['songData'] 217 | elif 'mainSong' in song: 218 | song = song['mainSong'] 219 | data = {} 220 | 221 | # song id 222 | if 'id' in song: 223 | data['id'] = song['id'] 224 | elif 'songId' in song: 225 | data['id'] = song['songId'] 226 | data['name'] = song['name'] 227 | 228 | # mv id 229 | if 'mv' in song: 230 | data['mv_id'] = song['mv'] 231 | elif 'mvid' in song: 232 | data['mv_id'] = song['mvid'] 233 | elif 'mv_id' in song: 234 | data['mv_id'] = song['mv_id'] 235 | 236 | artist = "" 237 | artists = [] 238 | data['picUrl'] = None 239 | if 'ar' in song: 240 | if song['ar'] is not None: 241 | artist = "/".join([a["name"] 242 | for a in song["ar"] if a["name"] is not None]) 243 | artists = [[a['name'], a['id']] for a in song["ar"] if a["name"] is not None] 244 | if artist == "" and "pc" in song: 245 | artist = "未知艺术家" if song["pc"]["ar"] is None else song["pc"]["ar"] 246 | 247 | if picUrl is not None: 248 | data['picUrl'] = picUrl 249 | elif 'picUrl' in song['ar'] and song['ar']['picUrl'] is not None: 250 | data['picUrl'] = song['ar']['picUrl'] 251 | elif 'img1v1Url' in song['ar'] and song['ar']['img1v1Url'] is not None: 252 | data['picUrl'] = song['ar']['img1v1Url'] 253 | else: 254 | if 'simpleSong' in tempSong and 'artist' in tempSong and tempSong['artist'] != '': 255 | artist = tempSong['artist'] 256 | else: 257 | artist = "未知艺术家" 258 | 259 | elif 'artists' in song: 260 | artists = [[a['name'], a['id']] for a in song["artists"]] 261 | artist = "/".join([a["name"] for a in song["artists"]]) 262 | 263 | if picUrl is not None: 264 | data['picUrl'] = picUrl 265 | elif 'picUrl' in song['artists'][0] and song['artists'][0]['picUrl'] is not None: 266 | data['picUrl'] = song['artists'][0]['picUrl'] 267 | elif 'img1v1Url' in song['artists'][0] and song['artists'][0]['img1v1Url'] is not None: 268 | data['picUrl'] = song['artists'][0]['img1v1Url'] 269 | else: 270 | artist = "未知艺术家" 271 | artists = [] 272 | # if 'simpleSong' in tempSong and 'ar' not in song and 'artist' in tempSong and tempSong['artist']!='': 273 | # artist = tempSong['artist'] 274 | # else: 275 | # artist = "未知艺术家" 276 | data['artist'] = artist 277 | data['artists'] = artists 278 | 279 | if "al" in song: 280 | if song["al"] is not None: 281 | album_name = song["al"]["name"] 282 | album_id = song["al"]["id"] 283 | if 'picUrl' in song['al']: 284 | data['picUrl'] = song['al']['picUrl'] 285 | else: 286 | if 'simpleSong' in tempSong and 'album' in tempSong and tempSong['album'] != '': 287 | album_name = tempSong['album'] 288 | album_id = 0 289 | else: 290 | album_name = "未知专辑" 291 | album_id = 0 292 | 293 | elif "album" in song: 294 | if song["album"] is not None: 295 | album_name = song["album"]["name"] 296 | album_id = song["album"]["id"] 297 | else: 298 | album_name = "未知专辑" 299 | album_id = 0 300 | 301 | if 'picUrl' in song['album']: 302 | data['picUrl'] = song['album']['picUrl'] 303 | 304 | data['album_name'] = album_name 305 | data['album_id'] = album_id 306 | 307 | if 'alia' in song and song['alia'] is not None and len(song['alia']) > 0: 308 | data['alia'] = song['alia'][0] 309 | 310 | if 'cd' in song: 311 | data['disc'] = song['cd'] 312 | elif 'disc' in song: 313 | data['disc'] = song['disc'] 314 | else: 315 | data['disc'] = 1 316 | 317 | if 'no' in song: 318 | data['no'] = song['no'] 319 | else: 320 | data['no'] = 1 321 | 322 | if 'dt' in song: 323 | data['dt'] = song['dt'] 324 | elif 'duration' in song: 325 | data['dt'] = song['duration'] 326 | 327 | if 'privilege' in song: 328 | privilege = song['privilege'] 329 | elif len(privileges) > 0: 330 | privilege = privileges[i] 331 | else: 332 | privilege = None 333 | 334 | if privilege is None: 335 | data['privilege'] = None 336 | else: 337 | data['privilege'] = privilege 338 | 339 | # 搜索歌词 340 | if source == 'search_lyric' and 'lyrics' in song: 341 | data['lyrics'] = song['lyrics'] 342 | data['second_line'] = '' 343 | txt = song['lyrics']['txt'] 344 | 345 | index_list = [i.start() for i in re.finditer('\n', txt)] 346 | temps = [] 347 | for words in song['lyrics']['range']: 348 | first = words['first'] 349 | second = words['second'] 350 | left = -1 351 | right = -1 352 | for index in range(len(index_list)): 353 | if index_list[index] <= first: 354 | left = index 355 | if index_list[index] >= second: 356 | right = index 357 | break 358 | temps.append({'first': first, 'second': second, 359 | 'left': left, 'right': right}) 360 | skip = [] 361 | for index in range(len(temps)): 362 | if index in skip: 363 | break 364 | line = '' 365 | if left == -1: 366 | line += txt[0:temps[index]['first']] 367 | else: 368 | line += txt[index_list[temps[index]['left']] + 369 | 1:temps[index]['first']] 370 | line += tag(txt[temps[index]['first']: temps[index]['second']], 'blue') 371 | 372 | for index2 in range(index+1, len(temps)): 373 | if temps[index2]['left'] == temps[index]['left']: 374 | line += txt[temps[index2-1]['second']: temps[index2]['first']] 375 | line += tag(txt[temps[index2]['first']: temps[index2]['second']], 'blue') 376 | skip.append(index2) 377 | else: 378 | break 379 | if right == -1: 380 | line += txt[temps[index]['second']: len(txt)] 381 | else: 382 | line += txt[temps[index]['second']: index_list[temps[index]['right']]] + '...' 383 | 384 | data['second_line'] += line 385 | else: 386 | if xbmcplugin.getSetting(int(sys.argv[1]), 'show_album_name') == 'true': 387 | data['second_line'] = data['album_name'] 388 | datas.append(data) 389 | return datas 390 | 391 | 392 | def get_songs_items(datas, privileges=[], picUrl=None, offset=0, getmv=True, source='', sourceId=0, enable_index=True): 393 | songs = get_songs(datas, privileges, picUrl, source) 394 | items = [] 395 | for play in songs: 396 | # 隐藏不能播放的歌曲 397 | if play['privilege']['pl'] == 0 and xbmcplugin.getSetting(int(sys.argv[1]), 'hide_songs') == 'true': 398 | continue 399 | # 显示序号 400 | if xbmcplugin.getSetting(int(sys.argv[1]), 'show_index') == 'true' and enable_index: 401 | offset += 1 402 | if offset < 10: 403 | str_offset = '0' + str(offset) + '.' 404 | else: 405 | str_offset = str(offset) + '.' 406 | else: 407 | str_offset = '' 408 | 409 | ar_name = play['artist'] 410 | 411 | mv_id = play['mv_id'] 412 | song_naming_format = xbmcplugin.getSetting(int(sys.argv[1]), 'song_naming_format') 413 | if song_naming_format == '0': 414 | label = str_offset + ar_name + ' - ' + play['name'] 415 | elif song_naming_format == '1': 416 | label = str_offset + play['name'] + ' - ' + ar_name 417 | elif song_naming_format == '2': 418 | label = str_offset + play['name'] 419 | else: 420 | label = str_offset + ar_name + ' - ' + play['name'] 421 | if 'alia' in play: 422 | label += tag('('+play['alia']+')', 'gray') 423 | 424 | if play['privilege'] and play['privilege']['st'] < 0: 425 | label = tag(label, 'grey') 426 | liked_songs = plugin.get_storage('liked_songs') 427 | if play['id'] in liked_songs['ids'] and xbmcplugin.getSetting(int(sys.argv[1]), 'like_tag') == 'true': 428 | label = tag('♥ ') + label 429 | if play['privilege'] is not None: 430 | if play['privilege']['st'] < 0: 431 | label = tag(label, 'grey') 432 | if play['privilege']['fee'] == 1 and xbmcplugin.getSetting(int(sys.argv[1]), 'vip_tag') == 'true': 433 | label += tag(' vip') 434 | if play['privilege']['cs'] and xbmcplugin.getSetting(int(sys.argv[1]), 'cloud_tag') == 'true': 435 | label += ' ☁' 436 | if (play['privilege']['flag'] & 64) > 0 and xbmcplugin.getSetting(int(sys.argv[1]), 'exclusive_tag') == 'true': 437 | label += tag(' 独家') 438 | # if play['privilege']['downloadMaxbr']>=999000 and xbmcplugin.getSetting(int(sys.argv[1]),'sq_tag') == 'true': 439 | if xbmcplugin.getSetting(int(sys.argv[1]), 'sq_tag') == 'true': 440 | if 'playMaxBrLevel' in play['privilege']: 441 | if play['privilege']['playMaxBrLevel'] == 'hires': 442 | label += tag(' Hi-Res') 443 | elif play['privilege']['playMaxBrLevel'] == 'lossless': 444 | label += tag(' SQ') 445 | elif play['privilege']['playMaxBrLevel'] == 'jyeffect': 446 | label += tag(' 环绕声') 447 | elif play['privilege']['playMaxBrLevel'] == 'sky': 448 | label += tag(' 沉浸声') 449 | elif play['privilege']['playMaxBrLevel'] == 'jymaster': 450 | label += tag(' 超清母带') 451 | elif play['privilege']['playMaxBrLevel'] == 'dolby': 452 | label += tag(' 杜比全景声') 453 | elif play['privilege']['maxbr'] >= 999000: 454 | label += tag(' SQ') 455 | # payed: 0 未付费 | 3 付费单曲 | 5 付费专辑 456 | if 'preSell' in play['privilege'] and play['privilege']['preSell'] == True and xbmcplugin.getSetting(int(sys.argv[1]), 'presell_tag') == 'true': 457 | label += tag(' 预售') 458 | elif play['privilege']['fee'] == 4 and play['privilege']['pl'] == 0 and xbmcplugin.getSetting(int(sys.argv[1]), 'pay_tag') == 'true': 459 | label += tag(' 付费') 460 | if mv_id > 0 and xbmcplugin.getSetting(int(sys.argv[1]), 'mv_tag') == 'true': 461 | label += tag(' MV', 'green') 462 | 463 | if 'second_line' in play and play['second_line']: 464 | label += '\n' + play['second_line'] 465 | context_menu = [] 466 | if play['artists']: 467 | context_menu.append(('跳转到歌手: ' + play['artist'], 'RunPlugin(%s)' % plugin.url_for('to_artist', artists=json.dumps(play['artists'])))) 468 | if play['album_name'] and play['album_id']: 469 | context_menu.append(('跳转到专辑: ' + play['album_name'], 'Container.Update(%s)' % plugin.url_for('album', id=play['album_id']))) 470 | if mv_id > 0 and xbmcplugin.getSetting(int(sys.argv[1]), 'mvfirst') == 'true' and getmv: 471 | context_menu.extend([ 472 | ('播放歌曲', 'RunPlugin(%s)' % plugin.url_for('song_contextmenu', action='play_song', meida_type='song', 473 | song_id=str(play['id']), mv_id=str(mv_id), sourceId=str(sourceId), dt=str(play['dt']//1000))), 474 | ('收藏到歌单', 'RunPlugin(%s)' % plugin.url_for('song_contextmenu', action='sub_playlist', meida_type='song', 475 | song_id=str(play['id']), mv_id=str(mv_id), sourceId=str(sourceId), dt=str(play['dt']//1000))), 476 | ('收藏到视频歌单', 'RunPlugin(%s)' % plugin.url_for('song_contextmenu', action='sub_video_playlist', meida_type='song', 477 | song_id=str(play['id']), mv_id=str(mv_id), sourceId=str(sourceId), dt=str(play['dt']//1000))), 478 | ]) 479 | items.append({ 480 | 'label': label, 481 | 'path': plugin.url_for('play', meida_type='mv', song_id=str(play['id']), mv_id=str(mv_id), sourceId=str(sourceId), dt=str(play['dt']//1000)), 482 | 'is_playable': True, 483 | 'icon': play.get('picUrl', None), 484 | 'thumbnail': play.get('picUrl', None), 485 | 'context_menu': context_menu, 486 | 'info': { 487 | 'mediatype': 'video', 488 | 'title': play['name'], 489 | 'album': play['album_name'], 490 | }, 491 | 'info_type': 'video', 492 | }) 493 | else: 494 | context_menu.extend([ 495 | ('收藏到歌单', 'RunPlugin(%s)' % plugin.url_for('song_contextmenu', action='sub_playlist', meida_type='song', 496 | song_id=str(play['id']), mv_id=str(mv_id), sourceId=str(sourceId), dt=str(play['dt']//1000))), 497 | ('歌曲ID:'+str(play['id']), ''), 498 | ]) 499 | 500 | if mv_id > 0: 501 | context_menu.append(('收藏到视频歌单', 'RunPlugin(%s)' % plugin.url_for('song_contextmenu', action='sub_video_playlist', 502 | meida_type='song', song_id=str(play['id']), mv_id=str(mv_id), sourceId=str(sourceId), dt=str(play['dt']//1000)))) 503 | context_menu.append(('播放MV', 'RunPlugin(%s)' % plugin.url_for('song_contextmenu', action='play_mv', meida_type='song', song_id=str( 504 | play['id']), mv_id=str(mv_id), sourceId=str(sourceId), dt=str(play['dt']//1000)))) 505 | 506 | # 歌曲不能播放时播放MV 507 | if play['privilege'] is not None and play['privilege']['st'] < 0 and mv_id > 0 and xbmcplugin.getSetting(int(sys.argv[1]), 'auto_play_mv') == 'true': 508 | items.append({ 509 | 'label': label, 510 | 'path': plugin.url_for('play', meida_type='song', song_id=str(play['id']), mv_id=str(mv_id), sourceId=str(sourceId), dt=str(play['dt']//1000)), 511 | 'is_playable': True, 512 | 'icon': play.get('picUrl', None), 513 | 'thumbnail': play.get('picUrl', None), 514 | 'context_menu': context_menu, 515 | 'info': { 516 | 'mediatype': 'video', 517 | 'title': play['name'], 518 | 'album': play['album_name'], 519 | }, 520 | 'info_type': 'video', 521 | }) 522 | else: 523 | items.append({ 524 | 'label': label, 525 | 'path': plugin.url_for('play', meida_type='song', song_id=str(play['id']), mv_id=str(mv_id), sourceId=str(sourceId), dt=str(play['dt']//1000)), 526 | 'is_playable': True, 527 | 'icon': play.get('picUrl', None), 528 | 'thumbnail': play.get('picUrl', None), 529 | 'fanart': play.get('picUrl', None), 530 | 'context_menu': context_menu, 531 | 'info': { 532 | 'mediatype': 'music', 533 | 'title': play['name'], 534 | 'artist': ar_name, 535 | 'album': play['album_name'], 536 | 'tracknumber': play['no'], 537 | 'discnumber': play['disc'], 538 | 'duration': play['dt']//1000, 539 | 'dbid': play['id'], 540 | }, 541 | 'info_type': 'music', 542 | 'properties': { 543 | 'ncmid': str(play['id']) 544 | }, 545 | }) 546 | return items 547 | 548 | 549 | @plugin.route('/to_artist//') 550 | def to_artist(artists): 551 | artists = json.loads(artists) 552 | if len(artists) == 1: 553 | xbmc.executebuiltin('Container.Update(%s)' % plugin.url_for('artist', id=artists[0][1])) 554 | return 555 | sel = xbmcgui.Dialog().select('选择要跳转的歌手', [a[0] for a in artists]) 556 | if sel < 0: 557 | return 558 | xbmc.executebuiltin('Container.Update(%s)' % plugin.url_for('artist', id=artists[sel][1])) 559 | 560 | @plugin.route('/song_contextmenu//////
/') 561 | def song_contextmenu(action, meida_type, song_id, mv_id, sourceId, dt): 562 | if action == 'sub_playlist': 563 | ids = [] 564 | names = [] 565 | names.append('+ 新建歌单') 566 | playlists = music.user_playlist( 567 | account['uid'], includeVideo=False).get('playlist', []) 568 | for playlist in playlists: 569 | if str(playlist['userId']) == str(account['uid']): 570 | ids.append(playlist['id']) 571 | names.append(playlist['name']) 572 | dialog = xbmcgui.Dialog() 573 | ret = dialog.contextmenu(names) 574 | if ret == 0: 575 | keyboard = xbmc.Keyboard('', '请输入歌单名称') 576 | keyboard.doModal() 577 | if (keyboard.isConfirmed()): 578 | name = keyboard.getText() 579 | else: 580 | return 581 | 582 | create_result = music.playlist_create(name) 583 | if create_result['code'] == 200: 584 | playlist_id = create_result['id'] 585 | else: 586 | dialog = xbmcgui.Dialog() 587 | dialog.notification( 588 | '创建失败', '歌单创建失败', xbmcgui.NOTIFICATION_INFO, 800, False) 589 | elif ret >= 1: 590 | playlist_id = ids[ret-1] 591 | 592 | if ret >= 0: 593 | result = music.playlist_tracks(playlist_id, [song_id], op='add') 594 | msg = '' 595 | if result['code'] == 200: 596 | msg = '收藏成功' 597 | liked_songs = plugin.get_storage('liked_songs') 598 | if liked_songs['pid'] == playlist_id: 599 | liked_songs['ids'].append(int(song_id)) 600 | xbmc.executebuiltin('Container.Refresh') 601 | elif 'message' in result and result['message'] is not None: 602 | msg = str(result['code'])+'错误:'+result['message'] 603 | else: 604 | msg = str(result['code'])+'错误' 605 | dialog = xbmcgui.Dialog() 606 | dialog.notification( 607 | '收藏', msg, xbmcgui.NOTIFICATION_INFO, 800, False) 608 | elif action == 'sub_video_playlist': 609 | ids = [] 610 | names = [] 611 | playlists = music.user_playlist( 612 | account['uid'], includeVideo=True).get("playlist", []) 613 | for playlist in playlists: 614 | if str(playlist['userId']) == str(account['uid']) and playlist['specialType'] == 200: 615 | ids.append(playlist['id']) 616 | names.append(playlist['name']) 617 | dialog = xbmcgui.Dialog() 618 | ret = dialog.contextmenu(names) 619 | if ret >= 0: 620 | result = music.playlist_add(ids[ret], [mv_id]) 621 | msg = '' 622 | if result['code'] == 200: 623 | msg = '收藏成功' 624 | elif 'msg' in result: 625 | msg = result['message'] 626 | else: 627 | msg = '收藏失败' 628 | dialog = xbmcgui.Dialog() 629 | dialog.notification( 630 | '收藏', msg, xbmcgui.NOTIFICATION_INFO, 800, False) 631 | elif action == 'play_song': 632 | songs = music.songs_url_v1([song_id], level=level).get("data", []) 633 | urls = [song['url'] for song in songs] 634 | url = urls[0] 635 | if url is None: 636 | dialog = xbmcgui.Dialog() 637 | dialog.notification( 638 | '播放', '该歌曲无法播放', xbmcgui.NOTIFICATION_INFO, 800, False) 639 | else: 640 | xbmc.executebuiltin('PlayMedia(%s)' % url) 641 | elif action == 'play_mv': 642 | mv = music.mv_url(mv_id, r).get("data", {}) 643 | url = mv['url'] 644 | if url is None: 645 | dialog = xbmcgui.Dialog() 646 | dialog.notification( 647 | '播放', '该视频已删除', xbmcgui.NOTIFICATION_INFO, 800, False) 648 | else: 649 | xbmc.executebuiltin('PlayMedia(%s)' % url) 650 | 651 | 652 | @plugin.route('/play/////
/') 653 | def play(meida_type, song_id, mv_id, sourceId, dt): 654 | if meida_type == 'mv': 655 | mv = music.mv_url(mv_id, r).get("data", {}) 656 | url = mv['url'] 657 | if url is None: 658 | dialog = xbmcgui.Dialog() 659 | dialog.notification('MV播放失败', '自动播放歌曲', 660 | xbmcgui.NOTIFICATION_INFO, 800, False) 661 | 662 | songs = music.songs_url_v1([song_id], level=level).get("data", []) 663 | urls = [song['url'] for song in songs] 664 | if len(urls) == 0: 665 | url = None 666 | else: 667 | url = urls[0] 668 | elif meida_type == 'song': 669 | songs = music.songs_url_v1([song_id], level=level).get("data", []) 670 | urls = [song['url'] for song in songs] 671 | # 一般是网络错误 672 | if len(urls) == 0: 673 | url = None 674 | else: 675 | url = urls[0] 676 | if url is None: 677 | if int(mv_id) > 0 and xbmcplugin.getSetting(int(sys.argv[1]), 'auto_play_mv') == 'true': 678 | mv = music.mv_url(mv_id, r).get("data", {}) 679 | url = mv['url'] 680 | if url is not None: 681 | msg = '该歌曲无法播放,自动播放MV' 682 | else: 683 | msg = '该歌曲和MV无法播放' 684 | else: 685 | msg = '该歌曲无法播放' 686 | dialog = xbmcgui.Dialog() 687 | dialog.notification( 688 | '播放失败', msg, xbmcgui.NOTIFICATION_INFO, 800, False) 689 | else: 690 | if xbmcplugin.getSetting(int(sys.argv[1]), 'upload_play_record') == 'true': 691 | music.daka(song_id, time=dt) 692 | elif meida_type == 'dj': 693 | result = music.dj_detail(song_id) 694 | song_id = result['program']['mainSong']['id'] 695 | songs = music.songs_url_v1([song_id], level=level).get("data", []) 696 | urls = [song['url'] for song in songs] 697 | if len(urls) == 0: 698 | url = None 699 | else: 700 | url = urls[0] 701 | if url is None: 702 | msg = '该节目无法播放' 703 | dialog = xbmcgui.Dialog() 704 | dialog.notification( 705 | '播放失败', msg, xbmcgui.NOTIFICATION_INFO, 800, False) 706 | elif meida_type == 'mlog': 707 | result = music.mlog_detail(mv_id, r) 708 | url = result['data']['resource']['content']['video']['urlInfo']['url'] 709 | 710 | # else: 711 | # music.daka(song_id,sourceId,dt) 712 | 713 | plugin.set_resolved_url(url) 714 | 715 | 716 | # 主目录 717 | @plugin.route('/') 718 | def index(): 719 | if account['first_run']: 720 | account['first_run'] = False 721 | xbmcgui.Dialog().ok('使用提示', '在设置中登录账号以解锁更多功能') 722 | items = [] 723 | status = account['logined'] 724 | 725 | liked_songs = plugin.get_storage('liked_songs') 726 | if 'pid' not in liked_songs: 727 | liked_songs['pid'] = 0 728 | if 'ids' not in liked_songs: 729 | liked_songs['ids'] = [] 730 | if xbmcplugin.getSetting(int(sys.argv[1]), 'like_tag') == 'true' and liked_songs['pid']: 731 | res = music.playlist_detail(liked_songs['pid']) 732 | if res['code'] == 200: 733 | liked_songs['ids'] = [s['id'] for s in res.get('playlist', {}).get('trackIds', [])] 734 | 735 | if xbmcplugin.getSetting(int(sys.argv[1]), 'daily_recommend') == 'true' and status: 736 | items.append( 737 | {'label': '每日推荐', 'path': plugin.url_for('recommend_songs')}) 738 | if xbmcplugin.getSetting(int(sys.argv[1]), 'personal_fm') == 'true' and status: 739 | items.append({'label': '私人FM', 'path': plugin.url_for('personal_fm')}) 740 | if xbmcplugin.getSetting(int(sys.argv[1]), 'my_playlists') == 'true' and status: 741 | items.append({'label': '我的歌单', 'path': plugin.url_for( 742 | 'user_playlists', uid=account['uid'])}) 743 | if xbmcplugin.getSetting(int(sys.argv[1]), 'sublist') == 'true' and status: 744 | items.append({'label': '我的收藏', 'path': plugin.url_for('sublist')}) 745 | if xbmcplugin.getSetting(int(sys.argv[1]), 'recommend_playlists') == 'true' and status: 746 | items.append( 747 | {'label': '推荐歌单', 'path': plugin.url_for('recommend_playlists')}) 748 | if xbmcplugin.getSetting(int(sys.argv[1]), 'vip_timemachine') == 'true' and status: 749 | items.append( 750 | {'label': '黑胶时光机', 'path': plugin.url_for('vip_timemachine')}) 751 | if xbmcplugin.getSetting(int(sys.argv[1]), 'rank') == 'true': 752 | items.append({'label': '排行榜', 'path': plugin.url_for('toplists')}) 753 | if xbmcplugin.getSetting(int(sys.argv[1]), 'top_artist') == 'true': 754 | items.append({'label': '热门歌手', 'path': plugin.url_for('top_artists')}) 755 | if xbmcplugin.getSetting(int(sys.argv[1]), 'top_mv') == 'true': 756 | items.append( 757 | {'label': '热门MV', 'path': plugin.url_for('top_mvs', offset='0')}) 758 | if xbmcplugin.getSetting(int(sys.argv[1]), 'search') == 'true': 759 | items.append({'label': '搜索', 'path': plugin.url_for('search')}) 760 | if xbmcplugin.getSetting(int(sys.argv[1]), 'cloud_disk') == 'true' and status: 761 | items.append( 762 | {'label': '我的云盘', 'path': plugin.url_for('cloud', offset='0')}) 763 | if xbmcplugin.getSetting(int(sys.argv[1]), 'home_page') == 'true' and status: 764 | items.append( 765 | {'label': '我的主页', 'path': plugin.url_for('user', id=account['uid'])}) 766 | if xbmcplugin.getSetting(int(sys.argv[1]), 'new_albums') == 'true': 767 | items.append( 768 | {'label': '新碟上架', 'path': plugin.url_for('new_albums', offset='0')}) 769 | if xbmcplugin.getSetting(int(sys.argv[1]), 'new_albums') == 'true': 770 | items.append({'label': '新歌速递', 'path': plugin.url_for('new_songs')}) 771 | if xbmcplugin.getSetting(int(sys.argv[1]), 'mlog') == 'true': 772 | items.append( 773 | {'label': 'Mlog', 'path': plugin.url_for('mlog_category')}) 774 | 775 | return items 776 | 777 | 778 | @plugin.route('/vip_timemachine/') 779 | def vip_timemachine(): 780 | time_machine = plugin.get_storage('time_machine') 781 | items = [] 782 | now = datetime.now() 783 | this_year_start = datetime(now.year, 1, 1) 784 | next_year_start = datetime(now.year + 1, 1, 1) 785 | this_year_start_timestamp = int( 786 | time.mktime(this_year_start.timetuple()) * 1000) 787 | this_year_end_timestamp = int(time.mktime( 788 | next_year_start.timetuple()) * 1000) - 1 789 | resp = music.vip_timemachine( 790 | this_year_start_timestamp, this_year_end_timestamp) 791 | 792 | if resp['code'] != 200: 793 | return items 794 | weeks = resp.get('data', {}).get('detail', []) 795 | time_machine['weeks'] = weeks 796 | for index, week in enumerate(weeks): 797 | start_date = time.strftime( 798 | "%m.%d", time.localtime(week['weekStartTime']//1000)) 799 | end_date = time.strftime( 800 | "%m.%d", time.localtime(week['weekEndTime']//1000)) 801 | title = week['data']['keyword'] + ' ' + \ 802 | tag(start_date + '-' + end_date, 'red') 803 | 804 | if 'subTitle' in week['data'] and week['data']['subTitle']: 805 | second_line = '' 806 | subs = week['data']['subTitle'].split('##1') 807 | for i, sub in enumerate(subs): 808 | if i % 2 == 0: 809 | second_line += tag(sub, 'gray') 810 | else: 811 | second_line += tag(sub, 'blue') 812 | title += '\n' + second_line 813 | plot_info = '' 814 | plot_info += '[B]听歌数据:[/B]' + '\n' 815 | listenSongs = tag(str(week['data']['listenSongs']) + '首', 'pink') 816 | listenCount = tag(str(week['data']['listenWeekCount']) + '次', 'pink') 817 | listentime = '' 818 | t = week['data']['listenWeekTime'] 819 | if t == 0: 820 | listentime += '0秒钟' 821 | else: 822 | if t >= 3600: 823 | listentime += str(t//3600) + '小时' 824 | if t % 3600 >= 0: 825 | listentime += str((t % 3600)//60) + '分钟' 826 | if t % 60 > 0: 827 | listentime += str(t % 60) + '秒钟' 828 | listentime = tag(listentime, 'pink') 829 | plot_info += '本周听歌{},共听了{}\n累计时长{}\n'.format( 830 | listenSongs, listenCount, listentime) 831 | styles = (week['data'].get('listenCommonStyle', {}) 832 | or {}).get('styleDetailList', []) 833 | if styles: 834 | # if plot_info: 835 | # plot_info += '\n' 836 | plot_info += '[B]常听曲风:[/B]' + '\n' 837 | for style in styles: 838 | plot_info += tag(style['styleName'], 'blue') + tag(' %.2f%%' % 839 | round(float(style['percent']) * 100, 2), 'pink') + '\n' 840 | emotions = (week['data'].get('musicEmotion', {}) 841 | or {}).get('subTitle', []) 842 | if emotions: 843 | # if plot_info: 844 | # plot_info += '\n' 845 | plot_info += '[B]音乐情绪:[/B]' + '\n' + '你本周的音乐情绪是' 846 | emotions = [tag(e, 'pink') for e in emotions] 847 | if len(emotions) > 2: 848 | plot_info += '、'.join(emotions[:-1]) + \ 849 | '与' + emotions[-1] + '\n' 850 | else: 851 | plot_info += '与'.join(emotions) + '\n' 852 | items.append({ 853 | 'label': title, 854 | 'path': plugin.url_for('vip_timemachine_week', index=index), 855 | 'info': { 856 | 'plot': plot_info 857 | }, 858 | 'info_type': 'video', 859 | }) 860 | return items 861 | 862 | 863 | @plugin.route('/vip_timemachine_week//') 864 | def vip_timemachine_week(index): 865 | time_machine = plugin.get_storage('time_machine') 866 | data = time_machine['weeks'][int(index)]['data'] 867 | temp = [] 868 | if 'song' in data: 869 | if 'tag' not in data['song'] or not data['song']['tag']: 870 | data['song']['tag'] = '高光歌曲' 871 | temp.append(data['song']) 872 | temp.extend(data.get('favoriteSongs', [])) 873 | temp.extend((data.get('musicYear', {}) or {}).get('yearSingles', [])) 874 | temp.extend((data.get('listenSingle', {}) or {}).get('singles', [])) 875 | temp.extend(data.get('songInfos', [])) 876 | songs_dict = {} 877 | for s in temp: 878 | if s['songId'] not in songs_dict: 879 | songs_dict[s['songId']] = s 880 | elif not songs_dict[s['songId']]['tag']: 881 | songs_dict[s['songId']]['tag'] = s['tag'] 882 | ids = list(songs_dict.keys()) 883 | songs = list(songs_dict.values()) 884 | resp = music.songs_detail(ids) 885 | datas = resp['songs'] 886 | privileges = resp['privileges'] 887 | items = get_songs_items(datas, privileges=privileges, enable_index=False) 888 | for i, item in enumerate(items): 889 | if songs[i]['tag']: 890 | item['label'] = tag('[{}]'.format( 891 | songs[i]['tag']), 'pink') + item['label'] 892 | 893 | return items 894 | 895 | 896 | def qrcode_check(): 897 | if not os.path.exists(qrcode_path): 898 | SUCCESS = xbmcvfs.mkdir(qrcode_path) 899 | if not SUCCESS: 900 | dialog = xbmcgui.Dialog() 901 | dialog.notification('失败', '目录创建失败,无法使用该功能', 902 | xbmcgui.NOTIFICATION_INFO, 800, False) 903 | return False 904 | else: 905 | temp_path = os.path.join(qrcode_path, str(int(time.time()))+'.png') 906 | img = qrcode.make('temp_img') 907 | img.save(temp_path) 908 | 909 | _, files = xbmcvfs.listdir(qrcode_path) 910 | for file in files: 911 | xbmcvfs.delete(os.path.join(qrcode_path, file)) 912 | return True 913 | 914 | 915 | def check_login_status(key): 916 | for i in range(10): 917 | check_result = music.login_qr_check(key) 918 | if check_result['code'] == 803: 919 | account['logined'] = True 920 | resp = music.user_level() 921 | account['uid'] = resp['data']['userId'] 922 | dialog = xbmcgui.Dialog() 923 | dialog.notification('登录成功', '请重启软件以解锁更多功能', 924 | xbmcgui.NOTIFICATION_INFO, 800, False) 925 | xbmc.executebuiltin('Action(Back)') 926 | break 927 | time.sleep(3) 928 | xbmc.executebuiltin('Action(Back)') 929 | 930 | 931 | @plugin.route('/qrcode_login/') 932 | def qrcode_login(): 933 | if not qrcode_check(): 934 | return 935 | result = music.login_qr_key() 936 | key = result.get('unikey', '') 937 | login_path = 'https://music.163.com/login?codekey={}'.format(key) 938 | 939 | temp_path = os.path.join(qrcode_path, str(int(time.time()))+'.png') 940 | qr = qrcode.QRCode( 941 | version=1, 942 | error_correction=qrcode.constants.ERROR_CORRECT_H, 943 | box_size=10, 944 | border=20 945 | ) 946 | qr.add_data(login_path) 947 | qr.make(fit=True) 948 | img = qr.make_image() 949 | img.save(temp_path) 950 | dialog = xbmcgui.Dialog() 951 | result = dialog.yesno('扫码登录', '请在在30秒内扫码登录', '取消', '确认') 952 | if not result: 953 | return 954 | xbmc.executebuiltin('ShowPicture(%s)' % temp_path) 955 | check_login_status(key) 956 | 957 | 958 | # Mlog广场 959 | @plugin.route('/mlog_category/') 960 | def mlog_category(): 961 | categories = { 962 | '广场': 1001, 963 | '热门': 2124301, 964 | 'MV': 1002, 965 | '演唱': 4, 966 | '现场': 2, 967 | '情感': 2130301, 968 | 'ACG': 2131301, 969 | '明星': 2132301, 970 | '演奏': 3, 971 | '生活': 8001, 972 | '舞蹈': 6001, 973 | '影视': 3001, 974 | '知识': 2125301, 975 | } 976 | 977 | items = [] 978 | for category in categories: 979 | if categories[category] == 1001: 980 | items.append({'label': category, 'path': plugin.url_for( 981 | 'mlog', cid=categories[category], pagenum=1)}) 982 | else: 983 | items.append({'label': category, 'path': plugin.url_for( 984 | 'mlog', cid=categories[category], pagenum=0)}) 985 | return items 986 | 987 | 988 | # Mlog 989 | @plugin.route('/mlog///') 990 | def mlog(cid, pagenum): 991 | items = [] 992 | resp = music.mlog_socialsquare(cid, pagenum) 993 | mlogs = resp['data']['feeds'] 994 | for video in mlogs: 995 | mid = video['id'] 996 | if cid == '1002': 997 | path = plugin.url_for('play', meida_type='mv', 998 | song_id=0, mv_id=mid, sourceId=cid, dt=0) 999 | else: 1000 | path = plugin.url_for('play', meida_type='mlog', 1001 | song_id=0, mv_id=mid, sourceId=cid, dt=0) 1002 | 1003 | items.append({ 1004 | 'label': video['resource']['mlogBaseData']['text'], 1005 | 'path': path, 1006 | 'is_playable': True, 1007 | 'icon': video['resource']['mlogBaseData']['coverUrl'], 1008 | 'thumbnail': video['resource']['mlogBaseData']['coverUrl'], 1009 | 'info': { 1010 | 'mediatype': 'video', 1011 | 'title': video['resource']['mlogBaseData']['text'], 1012 | 'duration': video['resource']['mlogBaseData']['duration']//1000 1013 | }, 1014 | 'info_type': 'video', 1015 | }) 1016 | items.append({'label': tag('下一页', 'yellow'), 'path': plugin.url_for( 1017 | 'mlog', cid=cid, pagenum=int(pagenum)+1)}) 1018 | return items 1019 | 1020 | 1021 | # 热门MV 1022 | @plugin.route('/top_mvs//') 1023 | def top_mvs(offset): 1024 | offset = int(offset) 1025 | result = music.top_mv(offset=offset, limit=limit) 1026 | more = result['hasMore'] 1027 | mvs = result['data'] 1028 | items = get_mvs_items(mvs) 1029 | if more: 1030 | items.append({'label': tag('下一页', 'yellow'), 'path': plugin.url_for( 1031 | 'top_mvs', offset=str(offset+limit))}) 1032 | return items 1033 | 1034 | 1035 | # 新歌速递 1036 | @plugin.route('/new_songs/') 1037 | def new_songs(): 1038 | return get_songs_items(music.new_songs().get("data", [])) 1039 | 1040 | 1041 | # 新碟上架 1042 | @plugin.route('/new_albums//') 1043 | def new_albums(offset): 1044 | offset = int(offset) 1045 | result = music.new_albums(offset=offset, limit=limit) 1046 | total = result.get('total', 0) 1047 | albums = result.get('albums', []) 1048 | items = get_albums_items(albums) 1049 | if len(albums) + offset < total: 1050 | items.append({'label': tag('下一页', 'yellow'), 'path': plugin.url_for( 1051 | 'new_albums', offset=str(offset+limit))}) 1052 | return items 1053 | 1054 | 1055 | # 排行榜 1056 | @plugin.route('/toplists/') 1057 | def toplists(): 1058 | items = get_playlists_items(music.toplists().get("list", [])) 1059 | return items 1060 | 1061 | 1062 | # 热门歌手 1063 | @plugin.route('/top_artists/') 1064 | def top_artists(): 1065 | return get_artists_items(music.top_artists().get("artists", [])) 1066 | 1067 | 1068 | # 每日推荐 1069 | @plugin.route('/recommend_songs/') 1070 | def recommend_songs(): 1071 | return get_songs_items(music.recommend_playlist().get('data', {}).get('dailySongs', [])) 1072 | 1073 | 1074 | # 历史日推 1075 | @plugin.route('/history_recommend_songs//') 1076 | def history_recommend_songs(date): 1077 | return get_songs_items(music.history_recommend_detail(date).get('data', {}).get('songs', [])) 1078 | 1079 | 1080 | def get_albums_items(albums): 1081 | items = [] 1082 | for album in albums: 1083 | if 'name' in album: 1084 | name = album['name'] 1085 | elif 'albumName' in album: 1086 | name = album['albumName'] 1087 | if 'size' in album: 1088 | plot_info = '[COLOR pink]' + name + \ 1089 | '[/COLOR] 共' + str(album['size']) + '首歌\n' 1090 | else: 1091 | plot_info = '[COLOR pink]' + name + '[/COLOR]\n' 1092 | if 'paidTime' in album and album['paidTime']: 1093 | plot_info += '购买时间: ' + trans_time(album['paidTime']) + '\n' 1094 | if 'type' in album and album['type']: 1095 | plot_info += '类型: ' + album['type'] 1096 | if 'subType' in album and album['subType']: 1097 | plot_info += ' - ' + album['subType'] + '\n' 1098 | else: 1099 | plot_info += '\n' 1100 | if 'company' in album and album['company']: 1101 | plot_info += '公司: ' + album['company'] + '\n' 1102 | if 'id' in album: 1103 | plot_info += '专辑id: ' + str(album['id'])+'\n' 1104 | album_id = album['id'] 1105 | elif 'albumId' in album: 1106 | plot_info += '专辑id: ' + str(album['albumId'])+'\n' 1107 | album_id = album['albumId'] 1108 | if 'publishTime' in album and album['publishTime'] is not None: 1109 | plot_info += '发行时间: '+trans_date(album['publishTime'])+'\n' 1110 | if 'subTime' in album and album['subTime'] is not None: 1111 | plot_info += '收藏时间: '+trans_date(album['subTime'])+'\n' 1112 | if 'description' in album and album['description'] is not None: 1113 | plot_info += album['description'] + '\n' 1114 | if 'picUrl' in album: 1115 | picUrl = album['picUrl'] 1116 | elif 'cover' in album: 1117 | picUrl = album['cover'] 1118 | 1119 | artists = [[a['name'], a['id']] for a in album['artists']] 1120 | artists_str = '/'.join([a[0] for a in artists]) 1121 | context_menu = [ 1122 | ('跳转到歌手: ' + artists_str, 'RunPlugin(%s)' % plugin.url_for('to_artist', artists=json.dumps(artists))) 1123 | ] 1124 | items.append({ 1125 | 'label': artists_str + ' - ' + name, 1126 | 'path': plugin.url_for('album', id=album_id), 1127 | 'icon': picUrl, 1128 | 'thumbnail': picUrl, 1129 | 'context_menu': context_menu, 1130 | 'info': {'plot': plot_info}, 1131 | 'info_type': 'video', 1132 | }) 1133 | return items 1134 | 1135 | 1136 | @plugin.route('/albums///') 1137 | def albums(artist_id, offset): 1138 | offset = int(offset) 1139 | result = music.artist_album(artist_id, offset=offset, limit=limit) 1140 | more = result.get('more', False) 1141 | albums = result.get('hotAlbums', []) 1142 | items = get_albums_items(albums) 1143 | if more: 1144 | items.append({'label': tag('下一页', 'yellow'), 'path': plugin.url_for( 1145 | 'albums', artist_id=artist_id, offset=str(offset+limit))}) 1146 | return items 1147 | 1148 | 1149 | @plugin.route('/album//') 1150 | def album(id): 1151 | result = music.album(id) 1152 | return get_songs_items(result.get("songs", []), sourceId=id, picUrl=result.get('album', {}).get('picUrl', '')) 1153 | 1154 | 1155 | @plugin.route('/artist//') 1156 | def artist(id): 1157 | items = [ 1158 | {'label': '热门50首', 'path': plugin.url_for('hot_songs', id=id)}, 1159 | {'label': '所有歌曲', 'path': plugin.url_for( 1160 | 'artist_songs', id=id, offset=0)}, 1161 | {'label': '专辑', 'path': plugin.url_for( 1162 | 'albums', artist_id=id, offset='0')}, 1163 | {'label': 'MV', 'path': plugin.url_for('artist_mvs', id=id, offset=0)}, 1164 | ] 1165 | 1166 | info = music.artist_info(id).get("artist", {}) 1167 | if 'accountId' in info: 1168 | items.append({'label': '用户页', 'path': plugin.url_for( 1169 | 'user', id=info['accountId'])}) 1170 | 1171 | if account['logined']: 1172 | items.append( 1173 | {'label': '相似歌手', 'path': plugin.url_for('similar_artist', id=id)}) 1174 | return items 1175 | 1176 | 1177 | @plugin.route('/similar_artist//') 1178 | def similar_artist(id): 1179 | artists = music.similar_artist(id).get("artists", []) 1180 | return get_artists_items(artists) 1181 | 1182 | 1183 | @plugin.route('/artist_mvs///') 1184 | def artist_mvs(id, offset): 1185 | offset = int(offset) 1186 | result = music.artist_mvs(id, offset, limit) 1187 | more = result.get('more', False) 1188 | mvs = result.get("mvs", []) 1189 | items = get_mvs_items(mvs) 1190 | if more: 1191 | items.append({'label': tag('下一页', 'yellow'), 'path': plugin.url_for( 1192 | 'albums', id=id, offset=str(offset+limit))}) 1193 | return items 1194 | 1195 | 1196 | @plugin.route('/hot_songs//') 1197 | def hot_songs(id): 1198 | result = music.artists(id).get("hotSongs", []) 1199 | ids = [a['id'] for a in result] 1200 | resp = music.songs_detail(ids) 1201 | datas = resp['songs'] 1202 | privileges = resp['privileges'] 1203 | return get_songs_items(datas, privileges=privileges) 1204 | 1205 | 1206 | @plugin.route('/artist_songs///') 1207 | def artist_songs(id, offset): 1208 | result = music.artist_songs(id, limit=limit, offset=offset) 1209 | ids = [a['id'] for a in result.get('songs', [])] 1210 | resp = music.songs_detail(ids) 1211 | datas = resp['songs'] 1212 | privileges = resp['privileges'] 1213 | items = get_songs_items(datas, privileges=privileges) 1214 | if result['more']: 1215 | items.append({'label': '[COLOR yellow]下一页[/COLOR]', 'path': plugin.url_for( 1216 | 'artist_songs', id=id, offset=int(offset)+limit)}) 1217 | return items 1218 | 1219 | 1220 | # 我的收藏 1221 | @plugin.route('/sublist/') 1222 | def sublist(): 1223 | items = [ 1224 | {'label': '歌手', 'path': plugin.url_for('artist_sublist')}, 1225 | {'label': '专辑', 'path': plugin.url_for('album_sublist')}, 1226 | {'label': '视频', 'path': plugin.url_for('video_sublist')}, 1227 | {'label': '播单', 'path': plugin.url_for('dj_sublist', offset=0)}, 1228 | {'label': '我的数字专辑', 'path': plugin.url_for('digitalAlbum_purchased')}, 1229 | {'label': '已购单曲', 'path': plugin.url_for('song_purchased', offset=0)}, 1230 | ] 1231 | return items 1232 | 1233 | 1234 | @plugin.route('/song_purchased//') 1235 | def song_purchased(offset): 1236 | result = music.single_purchased(offset=offset, limit=limit) 1237 | ids = [a['songId'] for a in result.get('data', {}).get('list', [])] 1238 | resp = music.songs_detail(ids) 1239 | datas = resp['songs'] 1240 | privileges = resp['privileges'] 1241 | items = get_songs_items(datas, privileges=privileges) 1242 | 1243 | if result.get('data', {}).get('hasMore', False): 1244 | items.append({'label': '[COLOR yellow]下一页[/COLOR]', 1245 | 'path': plugin.url_for('song_purchased', offset=int(offset)+limit)}) 1246 | return items 1247 | 1248 | 1249 | @plugin.route('/dj_sublist//') 1250 | def dj_sublist(offset): 1251 | result = music.dj_sublist(offset=offset, limit=limit) 1252 | items = get_djlists_items(result.get('djRadios', [])) 1253 | if result['hasMore']: 1254 | items.append({'label': '[COLOR yellow]下一页[/COLOR]', 1255 | 'path': plugin.url_for('dj_sublist', offset=int(offset)+limit)}) 1256 | return items 1257 | 1258 | 1259 | def get_djlists_items(playlists): 1260 | items = [] 1261 | for playlist in playlists: 1262 | context_menu = [] 1263 | plot_info = '[COLOR pink]' + playlist['name'] + \ 1264 | '[/COLOR] 共' + str(playlist['programCount']) + '个声音\n' 1265 | if 'lastProgramCreateTime' in playlist and playlist['lastProgramCreateTime'] is not None: 1266 | plot_info += '更新时间: ' + \ 1267 | trans_time(playlist['lastProgramCreateTime']) + '\n' 1268 | if 'subCount' in playlist and playlist['subCount'] is not None: 1269 | plot_info += '收藏人数: '+trans_num(playlist['subCount'])+'\n' 1270 | plot_info += '播单id: ' + str(playlist['id'])+'\n' 1271 | if 'dj' in playlist and playlist['dj'] is not None: 1272 | plot_info += '创建用户: ' + \ 1273 | playlist['dj']['nickname'] + ' id: ' + \ 1274 | str(playlist['dj']['userId']) + '\n' 1275 | context_menu.append(('跳转到用户: ' + playlist['dj']['nickname'], 'Container.Update(%s)' % plugin.url_for('user', id=playlist['dj']['userId']))) 1276 | if 'createTime' in playlist and playlist['createTime'] is not None: 1277 | plot_info += '创建时间: '+trans_time(playlist['createTime'])+'\n' 1278 | if 'desc' in playlist and playlist['desc'] is not None: 1279 | plot_info += playlist['desc'] + '\n' 1280 | 1281 | if 'coverImgUrl' in playlist and playlist['coverImgUrl'] is not None: 1282 | img_url = playlist['coverImgUrl'] 1283 | elif 'picUrl' in playlist and playlist['picUrl'] is not None: 1284 | img_url = playlist['picUrl'] 1285 | else: 1286 | img_url = '' 1287 | 1288 | name = playlist['name'] 1289 | 1290 | items.append({ 1291 | 'label': name, 1292 | 'path': plugin.url_for('djlist', id=playlist['id'], offset=0), 1293 | 'icon': img_url, 1294 | 'thumbnail': img_url, 1295 | 'context_menu': context_menu, 1296 | 'info': { 1297 | 'plot': plot_info 1298 | }, 1299 | 'info_type': 'video', 1300 | }) 1301 | return items 1302 | 1303 | 1304 | @plugin.route('/djlist///') 1305 | def djlist(id, offset): 1306 | if xbmcplugin.getSetting(int(sys.argv[1]), 'reverse_radio') == 'true': 1307 | asc = False 1308 | else: 1309 | asc = True 1310 | resp = music.dj_program(id, asc=asc, offset=offset, limit=limit) 1311 | items = get_dj_items(resp.get('programs', []), id) 1312 | if resp.get('more', False): 1313 | items.append({'label': '[COLOR yellow]下一页[/COLOR]', 1314 | 'path': plugin.url_for('djlist', id=id, offset=int(offset)+limit)}) 1315 | return items 1316 | 1317 | 1318 | def get_dj_items(songs, sourceId): 1319 | items = [] 1320 | for play in songs: 1321 | ar_name = play['dj']['nickname'] 1322 | 1323 | label = play['name'] 1324 | 1325 | items.append({ 1326 | 'label': label, 1327 | 'path': plugin.url_for('play', meida_type='dj', song_id=str(play['id']), mv_id=str(0), sourceId=str(sourceId), dt=str(play['duration']//1000)), 1328 | 'is_playable': True, 1329 | 'icon': play.get('coverUrl', None), 1330 | 'thumbnail': play.get('coverUrl', None), 1331 | 'fanart': play.get('coverUrl', None), 1332 | 'info': { 1333 | 'mediatype': 'music', 1334 | 'title': play['name'], 1335 | 'artist': ar_name, 1336 | 'album': play['radio']['name'], 1337 | # 'tracknumber':play['no'], 1338 | # 'discnumber':play['disc'], 1339 | # 'duration': play['dt']//1000, 1340 | # 'dbid':play['id'], 1341 | }, 1342 | 'info_type': 'music', 1343 | }) 1344 | return items 1345 | 1346 | 1347 | @plugin.route('/digitalAlbum_purchased/') 1348 | def digitalAlbum_purchased(): 1349 | # items = [] 1350 | albums = music.digitalAlbum_purchased().get("paidAlbums", []) 1351 | return get_albums_items(albums) 1352 | 1353 | 1354 | def get_mvs_items(mvs): 1355 | items = [] 1356 | for mv in mvs: 1357 | context_menu = [] 1358 | if 'artists' in mv: 1359 | name = '/'.join([artist['name'] for artist in mv['artists']]) 1360 | artists = [[a['name'], a['id']] for a in mv['artists']] 1361 | context_menu.append(('跳转到歌手: ' + name, 'RunPlugin(%s)' % plugin.url_for('to_artist', artists=json.dumps(artists)))) 1362 | elif 'artist' in mv: 1363 | name = mv['artist']['name'] 1364 | artists = [[mv['artist']['name'], mv['artist']['id']]] 1365 | context_menu.append(('跳转到歌手: ' + name, 'RunPlugin(%s)' % plugin.url_for('to_artist', artists=json.dumps(artists)))) 1366 | elif 'artistName' in mv: 1367 | name = mv['artistName'] 1368 | else: 1369 | name = '' 1370 | mv_url = music.mv_url(mv['id'], r).get("data", {}) 1371 | url = mv_url['url'] 1372 | if 'cover' in mv: 1373 | cover = mv['cover'] 1374 | elif 'imgurl' in mv: 1375 | cover = mv['imgurl'] 1376 | else: 1377 | cover = None 1378 | # top_mvs->mv['subed']收藏; 1379 | items.append({ 1380 | 'label': name + ' - ' + mv['name'], 1381 | 'path': url, 1382 | 'is_playable': True, 1383 | 'icon': cover, 1384 | 'thumbnail': cover, 1385 | 'context_menu': context_menu, 1386 | 'info': { 1387 | 'mediatype': 'video', 1388 | 'title': mv['name'], 1389 | }, 1390 | 'info_type': 'video', 1391 | }) 1392 | return items 1393 | 1394 | 1395 | def get_videos_items(videos): 1396 | items = [] 1397 | for video in videos: 1398 | type = video['type'] # MV:0 , video:1 1399 | if type == 0: 1400 | type = tag('[MV]') 1401 | result = music.mv_url(video['vid'], r).get("data", {}) 1402 | url = result['url'] 1403 | else: 1404 | type = '' 1405 | result = music.video_url(video['vid'], r).get("urls", []) 1406 | url = result[0]['url'] 1407 | ar_name = '&'.join([str(creator['userName']) 1408 | for creator in video['creator']]) 1409 | items.append({ 1410 | 'label': type + ar_name + ' - ' + video['title'], 1411 | 'path': url, 1412 | 'is_playable': True, 1413 | 'icon': video['coverUrl'], 1414 | 'thumbnail': video['coverUrl'], 1415 | # 'context_menu':context_menu, 1416 | 'info': { 1417 | 'mediatype': 'video', 1418 | 'title': video['title'], 1419 | # 'duration':video['durationms']//1000 1420 | }, 1421 | 'info_type': 'video', 1422 | }) 1423 | return items 1424 | 1425 | 1426 | @plugin.route('/playlist_contextmenu///') 1427 | def playlist_contextmenu(action, id): 1428 | if action == 'subscribe': 1429 | resp = music.playlist_subscribe(id) 1430 | if resp['code'] == 200: 1431 | title = '成功' 1432 | msg = '收藏成功' 1433 | xbmc.executebuiltin('Container.Refresh') 1434 | elif resp['code'] == 401: 1435 | title = '失败' 1436 | msg = '不能收藏自己的歌单' 1437 | elif resp['code'] == 501: 1438 | title = '失败' 1439 | msg = '已经收藏过该歌单了' 1440 | else: 1441 | title = '失败' 1442 | msg = str(resp['code'])+': 未知错误' 1443 | dialog = xbmcgui.Dialog() 1444 | dialog.notification(title, msg, xbmcgui.NOTIFICATION_INFO, 800, False) 1445 | elif action == 'unsubscribe': 1446 | resp = music.playlist_unsubscribe(id) 1447 | if resp['code'] == 200: 1448 | title = '成功' 1449 | msg = '取消收藏成功' 1450 | dialog = xbmcgui.Dialog() 1451 | dialog.notification(title, msg, xbmcgui.NOTIFICATION_INFO, 800, False) 1452 | elif action == 'delete': 1453 | resp = music.playlist_delete([id]) 1454 | if resp['code'] == 200: 1455 | title = '成功' 1456 | msg = '删除成功' 1457 | xbmc.executebuiltin('Container.Refresh') 1458 | else: 1459 | title = '失败' 1460 | msg = '删除失败' 1461 | dialog = xbmcgui.Dialog() 1462 | dialog.notification(title, msg, xbmcgui.NOTIFICATION_INFO, 800, False) 1463 | 1464 | 1465 | def get_playlists_items(playlists): 1466 | items = [] 1467 | 1468 | for playlist in playlists: 1469 | if 'specialType' in playlist and playlist['specialType'] == 5: 1470 | liked_songs = plugin.get_storage('liked_songs') 1471 | if liked_songs['pid']: 1472 | liked_songs['pid'] = playlist['id'] 1473 | else: 1474 | liked_songs['pid'] = playlist['id'] 1475 | res = music.playlist_detail(liked_songs['pid']) 1476 | if res['code'] == 200: 1477 | liked_songs['ids'] = [s['id'] for s in res.get('playlist', {}).get('trackIds', [])] 1478 | 1479 | context_menu = [] 1480 | plot_info = '[COLOR pink]' + playlist['name'] + \ 1481 | '[/COLOR] 共' + str(playlist['trackCount']) + '首歌\n' 1482 | if 'updateFrequency' in playlist and playlist['updateFrequency'] is not None: 1483 | plot_info += '更新频率: ' + playlist['updateFrequency'] + '\n' 1484 | if 'updateTime' in playlist and playlist['updateTime'] is not None: 1485 | plot_info += '更新时间: ' + trans_time(playlist['updateTime']) + '\n' 1486 | 1487 | if 'subscribed' in playlist and playlist['subscribed'] is not None: 1488 | if playlist['subscribed']: 1489 | plot_info += '收藏状态: 已收藏\n' 1490 | item = ('取消收藏', 'RunPlugin(%s)' % plugin.url_for( 1491 | 'playlist_contextmenu', action='unsubscribe', id=playlist['id'])) 1492 | context_menu.append(item) 1493 | else: 1494 | if 'creator' in playlist and playlist['creator'] is not None and str(playlist['creator']['userId']) != account['uid']: 1495 | plot_info += '收藏状态: 未收藏\n' 1496 | item = ('收藏', 'RunPlugin(%s)' % plugin.url_for( 1497 | 'playlist_contextmenu', action='subscribe', id=playlist['id'])) 1498 | context_menu.append(item) 1499 | else: 1500 | if 'creator' in playlist and playlist['creator'] is not None and str(playlist['creator']['userId']) != account['uid']: 1501 | item = ('收藏', 'RunPlugin(%s)' % plugin.url_for( 1502 | 'playlist_contextmenu', action='subscribe', id=playlist['id'])) 1503 | context_menu.append(item) 1504 | 1505 | if 'subscribedCount' in playlist and playlist['subscribedCount'] is not None: 1506 | plot_info += '收藏人数: '+trans_num(playlist['subscribedCount'])+'\n' 1507 | if 'playCount' in playlist and playlist['playCount'] is not None: 1508 | plot_info += '播放次数: '+trans_num(playlist['playCount'])+'\n' 1509 | if 'playcount' in playlist and playlist['playcount'] is not None: 1510 | plot_info += '播放次数: '+trans_num(playlist['playcount'])+'\n' 1511 | plot_info += '歌单id: ' + str(playlist['id'])+'\n' 1512 | if 'creator' in playlist and playlist['creator'] is not None: 1513 | plot_info += '创建用户: '+playlist['creator']['nickname'] + \ 1514 | ' id: ' + str(playlist['creator']['userId']) + '\n' 1515 | creator_name = playlist['creator']['nickname'] 1516 | creator_id = playlist['creator']['userId'] 1517 | else: 1518 | creator_name = '网易云音乐' 1519 | creator_id = 1 1520 | context_menu.append(('跳转到用户: ' + creator_name, 'Container.Update(%s)' % plugin.url_for('user', id=creator_id))) 1521 | if 'createTime' in playlist and playlist['createTime'] is not None: 1522 | plot_info += '创建时间: '+trans_time(playlist['createTime'])+'\n' 1523 | if 'description' in playlist and playlist['description'] is not None: 1524 | plot_info += playlist['description'] + '\n' 1525 | 1526 | if 'coverImgUrl' in playlist and playlist['coverImgUrl'] is not None: 1527 | img_url = playlist['coverImgUrl'] 1528 | elif 'picUrl' in playlist and playlist['picUrl'] is not None: 1529 | img_url = playlist['picUrl'] 1530 | else: 1531 | img_url = '' 1532 | 1533 | name = playlist['name'] 1534 | 1535 | if playlist.get('privacy', 0) == 10: 1536 | name += tag(' 隐私') 1537 | 1538 | if playlist.get('specialType', 0) == 300: 1539 | name += tag(' 共享') 1540 | 1541 | if playlist.get('specialType', 0) == 200: 1542 | name += tag(' 视频') 1543 | ptype = 'video' 1544 | else: 1545 | ptype = 'normal' 1546 | if 'creator' in playlist and playlist['creator'] is not None and str(playlist['creator']['userId']) == account['uid']: 1547 | item = ('删除歌单', 'RunPlugin(%s)' % plugin.url_for( 1548 | 'playlist_contextmenu', action='delete', id=playlist['id'])) 1549 | context_menu.append(item) 1550 | 1551 | items.append({ 1552 | 'label': name, 1553 | 'path': plugin.url_for('playlist', ptype=ptype, id=playlist['id']), 1554 | 'icon': img_url, 1555 | 'thumbnail': img_url, 1556 | 'context_menu': context_menu, 1557 | 'info': { 1558 | 'plot': plot_info 1559 | }, 1560 | 'info_type': 'video', 1561 | }) 1562 | return items 1563 | 1564 | 1565 | @plugin.route('/video_sublist/') 1566 | def video_sublist(): 1567 | return get_videos_items(music.video_sublist().get("data", [])) 1568 | 1569 | 1570 | @plugin.route('/album_sublist/') 1571 | def album_sublist(): 1572 | return get_albums_items(music.album_sublist().get("data", [])) 1573 | 1574 | 1575 | def get_artists_items(artists): 1576 | items = [] 1577 | for artist in artists: 1578 | plot_info = '[COLOR pink]' + artist['name'] + '[/COLOR]' 1579 | if 'musicSize' in artist and artist['musicSize']: 1580 | plot_info += ' 共' + str(artist['musicSize']) + '首歌\n' 1581 | else: 1582 | plot_info += '\n' 1583 | 1584 | if 'albumSize' in artist and artist['albumSize']: 1585 | plot_info += '专辑数: ' + str(artist['albumSize']) + '\n' 1586 | if 'mvSize' in artist and artist['mvSize']: 1587 | plot_info += 'MV数: ' + str(artist['mvSize']) + '\n' 1588 | plot_info += '歌手id: ' + str(artist['id'])+'\n' 1589 | name = artist['name'] 1590 | if 'alias' in artist and artist['alias']: 1591 | name += '('+artist['alias'][0]+')' 1592 | elif 'trans' in artist and artist['trans']: 1593 | name += '('+artist['trans']+')' 1594 | 1595 | items.append({ 1596 | 'label': name, 1597 | 'path': plugin.url_for('artist', id=artist['id']), 1598 | 'icon': artist['picUrl'], 1599 | 'thumbnail': artist['picUrl'], 1600 | 'info': {'plot': plot_info}, 1601 | 'info_type': 'video' 1602 | }) 1603 | return items 1604 | 1605 | 1606 | def get_users_items(users): 1607 | vip_level = ['', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖', '拾'] 1608 | items = [] 1609 | for user in users: 1610 | plot_info = tag(user['nickname'], 'pink') 1611 | if 'followed' in user: 1612 | if user['followed'] == True: 1613 | plot_info += ' [COLOR red]已关注[/COLOR]\n' 1614 | context_menu = [('取消关注', 'RunPlugin(%s)' % plugin.url_for( 1615 | 'follow_user', type='0', id=user['userId']))] 1616 | else: 1617 | plot_info += '\n' 1618 | context_menu = [('关注该用户', 'RunPlugin(%s)' % plugin.url_for( 1619 | 'follow_user', type='1', id=user['userId']))] 1620 | else: 1621 | plot_info += '\n' 1622 | # userType: 0 普通用户 | 2 歌手 | 4 音乐人 | 10 官方账号 | 200 歌单达人 | 204 Mlog达人 1623 | if user['vipType'] == 10: 1624 | level_str = tag('音乐包', 'red') 1625 | if user['userType'] == 4: 1626 | plot_info += level_str + tag(' 音乐人', 'red') + '\n' 1627 | else: 1628 | plot_info += level_str + '\n' 1629 | elif user['vipType'] == 11: 1630 | level = user['vipRights']['redVipLevel'] 1631 | if 'redplus' in user['vipRights'] and user['vipRights']['redplus'] is not None: 1632 | level_str = tag('Svip·' + vip_level[level], 'gold') 1633 | else: 1634 | level_str = tag('vip·' + vip_level[level], 'red') 1635 | if user['userType'] == 4: 1636 | plot_info += level_str + tag(' 音乐人', 'red') + '\n' 1637 | else: 1638 | plot_info += level_str + '\n' 1639 | else: 1640 | level_str = '' 1641 | if user['userType'] == 4: 1642 | plot_info += tag('音乐人', 'red') + '\n' 1643 | 1644 | if 'description' in user and user['description'] != '': 1645 | plot_info += user['description'] + '\n' 1646 | if 'signature' in user and user['signature']: 1647 | plot_info += '签名: ' + user['signature'] + '\n' 1648 | plot_info += '用户id: ' + str(user['userId'])+'\n' 1649 | 1650 | items.append({ 1651 | 'label': user['nickname']+' '+level_str, 1652 | 'path': plugin.url_for('user', id=user['userId']), 1653 | 'icon': user['avatarUrl'], 1654 | 'thumbnail': user['avatarUrl'], 1655 | 'context_menu': context_menu, 1656 | 'info': {'plot': plot_info}, 1657 | 'info_type': 'video', 1658 | }) 1659 | return items 1660 | 1661 | 1662 | @plugin.route('/follow_user///') 1663 | def follow_user(type, id): 1664 | # result = music.user_follow(type, id) 1665 | if type == '1': 1666 | result = music.user_follow(id) 1667 | if 'code' in result: 1668 | if result['code'] == 200: 1669 | xbmcgui.Dialog().notification('关注用户', '关注成功', xbmcgui.NOTIFICATION_INFO, 800, False) 1670 | elif result['code'] == 201: 1671 | xbmcgui.Dialog().notification('关注用户', '您已关注过该用户', 1672 | xbmcgui.NOTIFICATION_INFO, 800, False) 1673 | elif result['code'] == 400: 1674 | xbmcgui.Dialog().notification('关注用户', '不能关注自己', 1675 | xbmcgui.NOTIFICATION_INFO, 800, False) 1676 | elif 'mas' in result: 1677 | xbmcgui.Dialog().notification( 1678 | '关注用户', result['msg'], xbmcgui.NOTIFICATION_INFO, 800, False) 1679 | else: 1680 | result = music.user_delfollow(id) 1681 | if 'code' in result: 1682 | if result['code'] == 200: 1683 | xbmcgui.Dialog().notification('取消关注用户', '取消关注成功', 1684 | xbmcgui.NOTIFICATION_INFO, 800, False) 1685 | elif result['code'] == 201: 1686 | xbmcgui.Dialog().notification('取消关注用户', '您已不关注该用户了', 1687 | xbmcgui.NOTIFICATION_INFO, 800, False) 1688 | elif 'mas' in result: 1689 | xbmcgui.Dialog().notification( 1690 | '取消关注用户', result['msg'], xbmcgui.NOTIFICATION_INFO, 800, False) 1691 | 1692 | 1693 | @plugin.route('/user//') 1694 | def user(id): 1695 | items = [ 1696 | {'label': '歌单', 'path': plugin.url_for('user_playlists', uid=id)}, 1697 | {'label': '听歌排行', 'path': plugin.url_for('play_record', uid=id)}, 1698 | {'label': '关注列表', 'path': plugin.url_for( 1699 | 'user_getfollows', uid=id, offset='0')}, 1700 | {'label': '粉丝列表', 'path': plugin.url_for( 1701 | 'user_getfolloweds', uid=id, offset=0)}, 1702 | ] 1703 | 1704 | if account['uid'] == id: 1705 | items.append( 1706 | {'label': '每日推荐', 'path': plugin.url_for('recommend_songs')}) 1707 | items.append( 1708 | {'label': '历史日推', 'path': plugin.url_for('history_recommend_dates')}) 1709 | 1710 | info = music.user_detail(id) 1711 | if 'artistId' in info.get('profile', {}): 1712 | items.append({'label': '歌手页', 'path': plugin.url_for( 1713 | 'artist', id=info['profile']['artistId'])}) 1714 | return items 1715 | 1716 | 1717 | @plugin.route('/history_recommend_dates/') 1718 | def history_recommend_dates(): 1719 | dates = music.history_recommend_recent().get('data', {}).get('dates', []) 1720 | items = [] 1721 | for date in dates: 1722 | items.append({'label': date, 'path': plugin.url_for( 1723 | 'history_recommend_songs', date=date)}) 1724 | return items 1725 | 1726 | 1727 | @plugin.route('/play_record//') 1728 | def play_record(uid): 1729 | items = [ 1730 | {'label': '最近一周', 'path': plugin.url_for( 1731 | 'show_play_record', uid=uid, type='1')}, 1732 | {'label': '全部时间', 'path': plugin.url_for( 1733 | 'show_play_record', uid=uid, type='0')}, 1734 | ] 1735 | return items 1736 | 1737 | 1738 | @plugin.route('/show_play_record///') 1739 | def show_play_record(uid, type): 1740 | result = music.play_record(uid, type) 1741 | code = result.get('code', -1) 1742 | if code == -2: 1743 | xbmcgui.Dialog().notification('无权访问', '由于对方设置,你无法查看TA的听歌排行', 1744 | xbmcgui.NOTIFICATION_INFO, 800, False) 1745 | elif code == 200: 1746 | if type == '1': 1747 | songs = result.get('weekData', []) 1748 | else: 1749 | songs = result.get('allData', []) 1750 | items = get_songs_items(songs) 1751 | 1752 | # 听歌次数 1753 | # for i in range(len(items)): 1754 | # items[i]['label'] = items[i]['label'] + ' [COLOR red]' + str(songs[i]['playCount']) + '[/COLOR]' 1755 | 1756 | return items 1757 | 1758 | 1759 | @plugin.route('/user_getfolloweds///') 1760 | def user_getfolloweds(uid, offset): 1761 | result = music.user_getfolloweds(userId=uid, offset=offset, limit=limit) 1762 | more = result['more'] 1763 | followeds = result['followeds'] 1764 | items = get_users_items(followeds) 1765 | if more: 1766 | # time = followeds[-1]['time'] 1767 | items.append({'label': tag('下一页', 'yellow'), 'path': plugin.url_for( 1768 | 'user_getfolloweds', uid=uid, offset=int(offset)+limit)}) 1769 | return items 1770 | 1771 | 1772 | @plugin.route('/user_getfollows///') 1773 | def user_getfollows(uid, offset): 1774 | offset = int(offset) 1775 | result = music.user_getfollows(uid, offset=offset, limit=limit) 1776 | more = result['more'] 1777 | follows = result['follow'] 1778 | items = get_users_items(follows) 1779 | if more: 1780 | items.append({'label': '[COLOR yellow]下一页[/COLOR]', 'path': plugin.url_for( 1781 | 'user_getfollows', uid=uid, offset=str(offset+limit))}) 1782 | return items 1783 | 1784 | 1785 | @plugin.route('/artist_sublist/') 1786 | def artist_sublist(): 1787 | return get_artists_items(music.artist_sublist().get("data", [])) 1788 | 1789 | 1790 | @plugin.route('/search/') 1791 | def search(): 1792 | items = [ 1793 | {'label': '综合搜索', 'path': plugin.url_for('sea', type='1018')}, 1794 | {'label': '单曲搜索', 'path': plugin.url_for('sea', type='1')}, 1795 | {'label': '歌手搜索', 'path': plugin.url_for('sea', type='100')}, 1796 | {'label': '专辑搜索', 'path': plugin.url_for('sea', type='10')}, 1797 | {'label': '歌单搜索', 'path': plugin.url_for('sea', type='1000')}, 1798 | {'label': '云盘搜索', 'path': plugin.url_for('sea', type='-1')}, 1799 | {'label': 'M V搜索', 'path': plugin.url_for('sea', type='1004')}, 1800 | {'label': '视频搜索', 'path': plugin.url_for('sea', type='1014')}, 1801 | {'label': '歌词搜索', 'path': plugin.url_for('sea', type='1006')}, 1802 | {'label': '用户搜索', 'path': plugin.url_for('sea', type='1002')}, 1803 | {'label': '播客搜索', 'path': plugin.url_for('sea', type='1009')}, 1804 | ] 1805 | return items 1806 | 1807 | 1808 | @plugin.route('/sea//') 1809 | def sea(type): 1810 | items = [] 1811 | keyboard = xbmc.Keyboard('', '请输入搜索内容') 1812 | keyboard.doModal() 1813 | if (keyboard.isConfirmed()): 1814 | keyword = keyboard.getText() 1815 | else: 1816 | return 1817 | 1818 | # 搜索云盘 1819 | if type == '-1': 1820 | datas = [] 1821 | kws = keyword.lower().split(' ') 1822 | while '' in kws: 1823 | kws.remove('') 1824 | if len(kws) == 0: 1825 | pass 1826 | else: 1827 | result = music.cloud_songlist(offset=0, limit=2000) 1828 | playlist = result.get('data', []) 1829 | if result.get('hasMore', False): 1830 | result = music.cloud_songlist( 1831 | offset=2000, limit=result['count']-2000) 1832 | playlist.extend(result.get('data', [])) 1833 | 1834 | for song in playlist: 1835 | if 'ar' in song['simpleSong'] and song['simpleSong']['ar'] is not None and song['simpleSong']['ar'][0]['name'] is not None: 1836 | artist = " ".join( 1837 | [a["name"] for a in song['simpleSong']["ar"] if a["name"] is not None]) 1838 | else: 1839 | artist = song['artist'] 1840 | if 'al' in song['simpleSong'] and song['simpleSong']['al'] is not None and song['simpleSong']['al']['name'] is not None: 1841 | album = song['simpleSong']['al']['name'] 1842 | else: 1843 | album = song['album'] 1844 | if 'alia' in song['simpleSong'] and song['simpleSong']['alia'] is not None: 1845 | alia = " ".join( 1846 | [a for a in song['simpleSong']["alia"] if a is not None]) 1847 | else: 1848 | alia = '' 1849 | # filename = song['fileName'] 1850 | 1851 | matched = True 1852 | for kw in kws: 1853 | if kw != '': 1854 | if (kw in song['simpleSong']['name'].lower()) or (kw in artist.lower()) or (kw in album.lower()) or (kw in alia.lower()): 1855 | pass 1856 | else: 1857 | matched = False 1858 | break 1859 | if matched: 1860 | datas.append(song) 1861 | if len(datas) > 0: 1862 | items = get_songs_items(datas) 1863 | return items 1864 | else: 1865 | dialog = xbmcgui.Dialog() 1866 | dialog.notification( 1867 | '搜索', '无搜索结果', xbmcgui.NOTIFICATION_INFO, 800, False) 1868 | return 1869 | result = music.search(keyword, stype=type).get("result", {}) 1870 | # 搜索单曲 1871 | if type == '1': 1872 | if 'songs' in result: 1873 | sea_songs = result.get('songs', []) 1874 | 1875 | if xbmcplugin.getSetting(int(sys.argv[1]), 'hide_cover_songs') == 'true': 1876 | filtered_songs = [ 1877 | song for song in sea_songs if '翻自' not in song['name'] and 'cover' not in song['name'].lower()] 1878 | else: 1879 | filtered_songs = sea_songs 1880 | 1881 | ids = [a['id'] for a in filtered_songs] 1882 | resp = music.songs_detail(ids) 1883 | datas = resp['songs'] 1884 | privileges = resp['privileges'] 1885 | # 调整云盘歌曲的次序 1886 | d1, d2, p1, p2 = [], [], [], [] 1887 | for i in range(len(datas)): 1888 | if privileges[i]['cs']: 1889 | d1.append(datas[i]) 1890 | p1.append(privileges[i]) 1891 | else: 1892 | d2.append(datas[i]) 1893 | p2.append(privileges[i]) 1894 | d1.extend(d2) 1895 | p1.extend(p2) 1896 | datas = d1 1897 | privileges = p1 1898 | items = get_songs_items(datas, privileges=privileges) 1899 | else: 1900 | dialog = xbmcgui.Dialog() 1901 | dialog.notification( 1902 | '搜索', '无搜索结果', xbmcgui.NOTIFICATION_INFO, 800, False) 1903 | return 1904 | # 搜索歌词 1905 | if type == '1006': 1906 | if 'songs' in result: 1907 | sea_songs = result.get('songs', []) 1908 | ids = [a['id'] for a in sea_songs] 1909 | resp = music.songs_detail(ids) 1910 | datas = resp['songs'] 1911 | privileges = resp['privileges'] 1912 | 1913 | for i in range(len(datas)): 1914 | datas[i]['lyrics'] = sea_songs[i]['lyrics'] 1915 | 1916 | if xbmcplugin.getSetting(int(sys.argv[1]), 'hide_cover_songs') == 'true': 1917 | filtered_datas = [] 1918 | filtered_privileges = [] 1919 | for i in range(len(datas)): 1920 | if '翻自' not in datas[i]['name'] and 'cover' not in datas[i]['name'].lower(): 1921 | filtered_datas.append(datas[i]) 1922 | filtered_privileges.append(privileges[i]) 1923 | else: 1924 | filtered_datas = datas 1925 | filtered_privileges = privileges 1926 | 1927 | items = get_songs_items( 1928 | filtered_datas, privileges=filtered_privileges, source='search_lyric') 1929 | else: 1930 | dialog = xbmcgui.Dialog() 1931 | dialog.notification( 1932 | '搜索', '无搜索结果', xbmcgui.NOTIFICATION_INFO, 800, False) 1933 | return 1934 | # 搜索专辑 1935 | elif type == '10': 1936 | if 'albums' in result: 1937 | albums = result['albums'] 1938 | items.extend(get_albums_items(albums)) 1939 | else: 1940 | dialog = xbmcgui.Dialog() 1941 | dialog.notification( 1942 | '搜索', '无搜索结果', xbmcgui.NOTIFICATION_INFO, 800, False) 1943 | return 1944 | # 搜索歌手 1945 | elif type == '100': 1946 | if 'artists' in result: 1947 | artists = result['artists'] 1948 | items.extend(get_artists_items(artists)) 1949 | else: 1950 | dialog = xbmcgui.Dialog() 1951 | dialog.notification( 1952 | '搜索', '无搜索结果', xbmcgui.NOTIFICATION_INFO, 800, False) 1953 | return 1954 | # 搜索用户 1955 | elif type == '1002': 1956 | if 'userprofiles' in result: 1957 | users = result['userprofiles'] 1958 | items.extend(get_users_items(users)) 1959 | else: 1960 | dialog = xbmcgui.Dialog() 1961 | dialog.notification( 1962 | '搜索', '无搜索结果', xbmcgui.NOTIFICATION_INFO, 800, False) 1963 | return 1964 | # 搜索歌单 1965 | elif type == '1000': 1966 | if 'playlists' in result: 1967 | playlists = result['playlists'] 1968 | items.extend(get_playlists_items(playlists)) 1969 | else: 1970 | dialog = xbmcgui.Dialog() 1971 | dialog.notification( 1972 | '搜索', '无搜索结果', xbmcgui.NOTIFICATION_INFO, 800, False) 1973 | return 1974 | # 搜索主播电台 1975 | elif type == '1009': 1976 | if 'djRadios' in result: 1977 | playlists = result['djRadios'] 1978 | items.extend(get_djlists_items(playlists)) 1979 | else: 1980 | dialog = xbmcgui.Dialog() 1981 | dialog.notification( 1982 | '搜索', '无搜索结果', xbmcgui.NOTIFICATION_INFO, 800, False) 1983 | return 1984 | # 搜索MV 1985 | elif type == '1004': 1986 | if 'mvs' in result: 1987 | mvs = result['mvs'] 1988 | items.extend(get_mvs_items(mvs)) 1989 | else: 1990 | dialog = xbmcgui.Dialog() 1991 | dialog.notification( 1992 | '搜索', '无搜索结果', xbmcgui.NOTIFICATION_INFO, 800, False) 1993 | return 1994 | # 搜索视频 1995 | elif type == '1014': 1996 | if 'videos' in result: 1997 | videos = result['videos'] 1998 | items.extend(get_videos_items(videos)) 1999 | else: 2000 | dialog = xbmcgui.Dialog() 2001 | dialog.notification( 2002 | '搜索', '无搜索结果', xbmcgui.NOTIFICATION_INFO, 800, False) 2003 | return 2004 | # 综合搜索 2005 | elif type == '1018': 2006 | is_empty = True 2007 | # 歌手 2008 | if 'artist' in result: 2009 | is_empty = False 2010 | artist = result['artist']['artists'][0] 2011 | item = get_artists_items([artist])[0] 2012 | item['label'] = tag('[歌手]') + item['label'] 2013 | items.append(item) 2014 | 2015 | # 专辑 2016 | if 'album' in result: 2017 | is_empty = False 2018 | album = result['album']['albums'][0] 2019 | item = get_albums_items([album])[0] 2020 | item['label'] = tag('[专辑]') + item['label'] 2021 | items.append(item) 2022 | 2023 | # 歌单 2024 | if 'playList' in result: 2025 | is_empty = False 2026 | playList = result['playList']['playLists'][0] 2027 | item = get_playlists_items([playList])[0] 2028 | item['label'] = tag('[歌单]') + item['label'] 2029 | items.append(item) 2030 | 2031 | # MV & 视频 2032 | if 'video' in result: 2033 | is_empty = False 2034 | # MV 2035 | for video in result['video']['videos']: 2036 | if video['type'] == 0: 2037 | mv_url = music.mv_url(video['vid'], r).get("data", {}) 2038 | url = mv_url['url'] 2039 | ar_name = '&'.join([str(creator['userName']) 2040 | for creator in video['creator']]) 2041 | name = tag('[M V]') + ar_name + '-' + video['title'] 2042 | items.append({ 2043 | 'label': name, 2044 | 'path': url, 2045 | 'is_playable': True, 2046 | 'icon': video['coverUrl'], 2047 | 'thumbnail': video['coverUrl'], 2048 | 'info': { 2049 | 'mediatype': 'video', 2050 | 'title': video['title'], 2051 | 'duration': video['durationms']//1000 2052 | }, 2053 | 'info_type': 'video', 2054 | }) 2055 | break 2056 | # 视频 2057 | for video in result['video']['videos']: 2058 | if video['type'] == 1: 2059 | video_url = music.video_url( 2060 | video['vid'], r).get("urls", []) 2061 | url = video_url[0]['url'] 2062 | ar_name = '&'.join([str(creator['userName']) 2063 | for creator in video['creator']]) 2064 | name = tag('[视频]') + ar_name + '-' + video['title'] 2065 | items.append({ 2066 | 'label': name, 2067 | 'path': url, 2068 | 'is_playable': True, 2069 | 'icon': video['coverUrl'], 2070 | 'thumbnail': video['coverUrl'], 2071 | 'info': { 2072 | 'mediatype': 'video', 2073 | 'title': video['title'], 2074 | 'duration': video['durationms']//1000 2075 | }, 2076 | 'info_type': 'video', 2077 | }) 2078 | break 2079 | # 单曲 2080 | if 'song' in result: 2081 | # is_empty = False 2082 | # items.extend(get_songs_items([song['id'] for song in result['song']['songs']],getmv=False)) 2083 | sea_songs = result['song']['songs'] 2084 | if xbmcplugin.getSetting(int(sys.argv[1]), 'hide_cover_songs') == 'true': 2085 | filtered_songs = [ 2086 | song for song in sea_songs if '翻自' not in song['name'] and 'cover' not in song['name'].lower()] 2087 | else: 2088 | filtered_songs = sea_songs 2089 | items.extend(get_songs_items(filtered_songs, getmv=False, enable_index=False)) 2090 | if len(items) > 0: 2091 | is_empty = False 2092 | 2093 | if is_empty: 2094 | dialog = xbmcgui.Dialog() 2095 | dialog.notification( 2096 | '搜索', '无搜索结果', xbmcgui.NOTIFICATION_INFO, 800, False) 2097 | return 2098 | return items 2099 | 2100 | 2101 | @plugin.route('/personal_fm/') 2102 | def personal_fm(): 2103 | songs = [] 2104 | for i in range(10): 2105 | songs.extend(music.personal_fm().get("data", [])) 2106 | return get_songs_items(songs) 2107 | 2108 | 2109 | @plugin.route('/recommend_playlists/') 2110 | def recommend_playlists(): 2111 | return get_playlists_items(music.recommend_resource().get("recommend", [])) 2112 | 2113 | 2114 | @plugin.route('/user_playlists//') 2115 | def user_playlists(uid): 2116 | return get_playlists_items(music.user_playlist(uid).get("playlist", [])) 2117 | 2118 | 2119 | @plugin.route('/playlist///') 2120 | def playlist(ptype, id): 2121 | resp = music.playlist_detail(id) 2122 | # return get_songs_items([song['id'] for song in songs],sourceId=id) 2123 | if ptype == 'video': 2124 | datas = resp.get('playlist', {}).get('videos', []) 2125 | items = [] 2126 | for data in datas: 2127 | 2128 | label = data['mlogBaseData']['text'] 2129 | if 'song' in data['mlogExtVO']: 2130 | artist = ", ".join([a["artistName"] 2131 | for a in data['mlogExtVO']['song']['artists']]) 2132 | label += tag(' (' + artist + '-' + 2133 | data['mlogExtVO']['song']['name'] + ')', 'gray') 2134 | context_menu = [ 2135 | ('相关歌曲:%s' % (artist + '-' + data['mlogExtVO']['song']['name']), 'RunPlugin(%s)' % plugin.url_for('song_contextmenu', action='play_song', meida_type='song', song_id=str( 2136 | data['mlogExtVO']['song']['id']), mv_id=str(data['mlogBaseData']['id']), sourceId=str(id), dt=str(data['mlogExtVO']['song']['duration']//1000))), 2137 | ] 2138 | else: 2139 | context_menu = [] 2140 | 2141 | if data['mlogBaseData']['type'] == 2: 2142 | # https://interface3.music.163.com/eapi/mlog/video/url 2143 | meida_type = 'mlog' 2144 | elif data['mlogBaseData']['type'] == 3: 2145 | label = tag('[MV]') + label 2146 | meida_type = 'mv' 2147 | else: 2148 | meida_type = '' 2149 | 2150 | items.append({ 2151 | 'label': label, 2152 | 'path': plugin.url_for('play', meida_type=meida_type, song_id=str(data['mlogExtVO']['song']['id']), mv_id=str(data['mlogBaseData']['id']), sourceId=str(id), dt='0'), 2153 | 'is_playable': True, 2154 | 'icon': data['mlogBaseData']['coverUrl'], 2155 | 'thumbnail': data['mlogBaseData']['coverUrl'], 2156 | 'context_menu': context_menu, 2157 | 'info': { 2158 | 'mediatype': 'video', 2159 | 'title': data['mlogBaseData']['text'], 2160 | }, 2161 | 'info_type': 'video', 2162 | }) 2163 | return items 2164 | else: 2165 | datas = resp.get('playlist', {}).get('tracks', []) 2166 | privileges = resp.get('privileges', []) 2167 | trackIds = resp.get('playlist', {}).get('trackIds', []) 2168 | 2169 | songs_number = len(trackIds) 2170 | # 歌单中超过1000首歌 2171 | if songs_number > len(datas): 2172 | ids = [song['id'] for song in trackIds] 2173 | resp2 = music.songs_detail(ids[len(datas):]) 2174 | datas.extend(resp2.get('songs', [])) 2175 | privileges.extend(resp2.get('privileges', [])) 2176 | return get_songs_items(datas, privileges=privileges, sourceId=id) 2177 | 2178 | 2179 | @plugin.route('/cloud//') 2180 | def cloud(offset): 2181 | offset = int(offset) 2182 | result = music.cloud_songlist(offset=offset, limit=limit) 2183 | more = result['hasMore'] 2184 | playlist = result['data'] 2185 | items = get_songs_items(playlist, offset=offset) 2186 | if more: 2187 | items.append({'label': tag('下一页', 'yellow'), 'path': plugin.url_for( 2188 | 'cloud', offset=str(offset+limit))}) 2189 | return items 2190 | 2191 | 2192 | if __name__ == '__main__': 2193 | plugin.run() 2194 | --------------------------------------------------------------------------------