├── .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 | 
10 | 
11 | 
12 | 
13 | 
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 |
--------------------------------------------------------------------------------