Skip to content

Commit

Permalink
Merge pull request #59 from 844704781/main
Browse files Browse the repository at this point in the history
下载功能重新可用
  • Loading branch information
Diaoxiaozhang authored May 18, 2024
2 parents 9d22cc9 + b332951 commit 977bc92
Show file tree
Hide file tree
Showing 4 changed files with 149 additions and 33 deletions.
10 changes: 5 additions & 5 deletions cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@
import os
import time
import argparse

from fake_useragent import UserAgent

ximalaya = main.Ximalaya()
loop = asyncio.get_event_loop()

parser = argparse.ArgumentParser()
parser.add_argument('-s', '--sound', type=int, help='')

ua = UserAgent()

if __name__ == "__main__":
print("欢迎使用喜马拉雅下载器")
Expand All @@ -31,7 +31,7 @@
print("检测到当前ip不在中国大陆,由于喜马拉雅官方限制,必须登录才能继续使用,将自动跳转到登录流程")
ximalaya.login()
headers = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1660.14",
"user-agent": ua.random,
"cookie": ximalaya.analyze_config()[0]
}
logined = True
Expand All @@ -44,7 +44,7 @@
if choice == "1":
ximalaya.login()
headers = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1660.14",
"user-agent": ua.random,
"cookie": ximalaya.analyze_config()[0]
}
logined = True
Expand All @@ -58,7 +58,7 @@
else:
print(f"已检测到有效登录信息,当前登录用户为{username},如需切换账号请删除config.json文件然后重新启动本程序!")
headers = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1660.14",
"user-agent": ua.random,
"cookie": ximalaya.analyze_config()[0]
}
logined = True
Expand Down
107 changes: 107 additions & 0 deletions decrypt.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
"use strict";

const r = new Uint8Array([188, 174, 178, 234, 171, 147, 70, 82, 76, 72, 192, 132, 60, 17, 30, 127, 184, 233, 48, 105, 38, 232, 240, 21, 47, 252, 41, 229, 209, 213, 71, 40, 63, 152, 156, 88, 51, 141, 139, 145, 133, 2, 160, 191, 11, 100, 10, 78, 253, 151, 42, 166, 92, 22, 185, 140, 164, 91, 194, 175, 239, 217, 177, 75, 19, 225, 94, 107, 125, 138, 242, 31, 182, 150, 15, 24, 226, 29, 80, 116, 168, 118, 28, 1, 186, 220, 158, 79, 59, 244, 119, 9, 189, 161, 74, 130, 221, 56, 216, 241, 212, 26, 218, 170, 85, 165, 153, 69, 238, 93, 255, 142, 3, 159, 215, 67, 33, 249, 53, 176, 77, 254, 222, 25, 115, 101, 148, 16, 13, 237, 197, 5, 58, 157, 135, 248, 223, 61, 198, 211, 110, 44, 54, 111, 52, 227, 4, 46, 205, 7, 219, 136, 14, 87, 114, 64, 104, 50, 39, 203, 81, 196, 43, 163, 173, 109, 108, 187, 102, 195, 37, 235, 65, 190, 113, 149, 143, 8, 27, 155, 207, 134, 123, 224, 129, 245, 62, 66, 172, 122, 126, 12, 162, 214, 90, 247, 251, 124, 201, 236, 117, 183, 73, 95, 89, 246, 181, 179, 83, 228, 193, 99, 6, 45, 112, 32, 154, 128, 230, 131, 206, 243, 57, 84, 146, 0, 35, 96, 250, 137, 36, 208, 103, 34, 68, 204, 231, 144, 120, 98, 202, 49, 210, 23, 200, 18, 86, 55, 121, 20, 199, 97, 167, 180, 169, 106])
,
n = new Uint8Array([20, 234, 159, 167, 230, 233, 58, 255, 158, 36, 210, 254, 133, 166, 59, 63, 209, 177, 184, 155, 85, 235, 94, 1, 242, 87, 228, 232, 191, 3, 69, 178])
,
o = new Uint8Array([183, 174, 108, 16, 131, 159, 250, 5, 239, 110, 193, 202, 153, 137, 251, 176, 119, 150, 47, 204, 97, 237, 1, 71, 177, 42, 88, 218, 166, 82, 87, 94, 14, 195, 69, 127, 215, 240, 225, 197, 238, 142, 123, 44, 219, 50, 190, 29, 181, 186, 169, 98, 139, 185, 152, 13, 141, 76, 6, 157, 200, 132, 182, 49, 20, 116, 136, 43, 155, 194, 101, 231, 162, 242, 151, 213, 53, 60, 26, 134, 211, 56, 28, 223, 107, 161, 199, 15, 229, 61, 96, 41, 66, 158, 254, 21, 165, 253, 103, 89, 3, 168, 40, 246, 81, 95, 58, 31, 172, 78, 99, 45, 148, 187, 222, 124, 55, 203, 235, 64, 68, 149, 180, 35, 113, 207, 118, 111, 91, 38, 247, 214, 7, 212, 209, 189, 241, 18, 115, 173, 25, 236, 121, 249, 75, 57, 216, 10, 175, 112, 234, 164, 70, 206, 198, 255, 140, 230, 12, 32, 83, 46, 245, 0, 62, 227, 72, 191, 156, 138, 248, 114, 220, 90, 84, 170, 128, 19, 24, 122, 146, 80, 39, 37, 8, 34, 22, 11, 93, 130, 63, 154, 244, 160, 144, 79, 23, 133, 92, 54, 102, 210, 65, 67, 27, 196, 201, 106, 143, 52, 74, 100, 217, 179, 48, 233, 126, 117, 184, 226, 85, 171, 167, 86, 2, 147, 17, 135, 228, 252, 105, 30, 192, 129, 178, 120, 36, 145, 51, 163, 77, 205, 73, 4, 188, 125, 232, 33, 243, 109, 224, 104, 208, 221, 59, 9])
,
a = new Uint8Array([204, 53, 135, 197, 39, 73, 58, 160, 79, 24, 12, 83, 180, 250, 101, 60, 206, 30, 10, 227, 36, 95, 161, 16, 135, 150, 235, 116, 242, 116, 165, 171])
, i = "function" == typeof atob
, u = "function" == typeof e;
"function" == typeof TextDecoder && new TextDecoder,
"function" == typeof TextEncoder && new TextEncoder;


const c = (e => {
let t = {};
return e.forEach(((e, r) => t[e] = r)),
t
}
)(Array.prototype.slice.call("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/="))
, s = /^(?:[A-Za-z\d+\/]{4})*?(?:[A-Za-z\d+\/]{2}(?:==)?|[A-Za-z\d+\/]{3}=?)?$/
, l = String.fromCharCode.bind(String);
"function" == typeof Uint8Array.from && Uint8Array.from.bind(Uint8Array);


const f = i ? e => atob(e.replace(/[^A-Za-z0-9\+\/]/g, "")) : u ? t => e.from(t, "base64").toString("binary") : e => {
if (e = e.replace(/\s+/g, ""),
!s.test(e))
throw new TypeError("malformed base64.");
e += "==".slice(2 - (3 & e.length));
let t, r, n, o = "";
for (let a = 0; a < e.length;)
t = c[e.charAt(a++)] << 18 | c[e.charAt(a++)] << 12 | (r = c[e.charAt(a++)]) << 6 | (n = c[e.charAt(a++)]),
o += 64 === r ? l(t >> 16 & 255) : 64 === n ? l(t >> 16 & 255, t >> 8 & 255) : l(t >> 16 & 255, t >> 8 & 255, 255 & t);
return o
}
;


function p(e, t, r) {
let n = Math.min(e.length - t, r.length);
for (let o = 0; o < n; o++)
e[o + t] = e[o + t] ^ r[o]
}

let getSoundCryptLink = function (e) {
const {link: t = "", deviceType: i = "www2"} = e;
let u = o
, c = a;
["www2", "mweb2"].includes(i) || (u = r,
c = n);
try {
let e = f(t.replace(/_/g, "/").replace(/-/g, "+"));
if (null === e || e.length < 16)
return t;
let r = new Uint8Array(e.length - 16);
for (let t = 0; t < e.length - 16; t++)
r[t] = e.charCodeAt(t);
let n = new Uint8Array(16);
for (let t = 0; t < 16; t++)
n[t] = e.charCodeAt(e.length - 16 + t);
for (let e = 0; e < r.length; e++)
r[e] = u[r[e]];
for (let e = 0; e < r.length; e += 16)
p(r, e, n);
for (let e = 0; e < r.length; e += 32)
p(r, e, c);
return function (e) {
var t, r, n, o, a, i;
for (t = "",
n = e.length,
r = 0; r < n;)
switch ((o = e[r++]) >> 4) {
case 0:
case 1:
case 2:
case 3:
case 4:
case 5:
case 6:
case 7:
t += String.fromCharCode(o);
break;
case 12:
case 13:
a = e[r++],
t += String.fromCharCode((31 & o) << 6 | 63 & a);
break;
case 14:
a = e[r++],
i = e[r++],
t += String.fromCharCode((15 & o) << 12 | (63 & a) << 6 | (63 & i) << 0)
}
return t
}(r)
} catch (e) {
return console.warn(e, "secret failed"),
""
}
}


// console.log(getSoundCryptLink({
// "deviceType": "www2",
// "link": "ZcHuyKWnJJyhwqnH2N0Ij42oYMuHR8xZmzrPUpEqb-uLDnuOR3ZwE6HpCgm-dsEPFZwuaSZJ837AeP7yM8hufjAPoY765Omc9VUK-R7AznUwiwSzg9SjflBXplmk7LpaNDih9w7XS0WhTzb5TQg-dY20Xmhs-pNB198CjD90Q9lnJuzZMuoMeUPjcua5LggAor8Oo6Ld3HzVvBJOuSvic2CLs7X975wLubA7Q5U-Xof4PA4H3cHc9w-gUZBiRti2ncFm9aJ2J0pzrIOO2QHlJj4RYB8BHIBDDOHA07fMwgMA_eIMpSHrvA"
// }))
63 changes: 35 additions & 28 deletions main.py
Original file line number Diff line number Diff line change
@@ -1,26 +1,24 @@
# -*- coding:utf-8 -*-
import asyncio
import base64
import binascii
import json
import math
import os
import re
import time
import logging
import traceback
from fake_useragent import UserAgent

import aiofiles
import aiohttp
import requests
from Crypto.Cipher import AES
from webdriver_manager.chrome import ChromeDriverManager
from webdriver_manager.microsoft import EdgeChromiumDriverManager
from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
import selenium.common.exceptions
import colorama
import execjs

colorama.init(autoreset=True)
logger = logging.getLogger('logger')
Expand All @@ -31,20 +29,21 @@
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
path = ""
ua = UserAgent()


class Ximalaya:
def __init__(self):
self.default_headers = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1660.14"
"user-agent": ua.random
}

# 解析声音,如果成功返回声音名和声音链接,否则返回False
def analyze_sound(self, sound_id, headers):
logger.debug(f'开始解析ID为{sound_id}的声音')
url = f"https://www.ximalaya.com/mobile-playpage/track/v3/baseInfo/{int(time.time() * 1000)}"
params = {
"device": "web",
"device": "www2",
"trackId": sound_id,
"trackQualityLevel": 2
}
Expand Down Expand Up @@ -120,25 +119,30 @@ def analyze_album(self, album_id):

# 协程解析声音
async def async_analyze_sound(self, sound_id, session, headers):
retries = 3
url = f"https://www.ximalaya.com/mobile-playpage/track/v3/baseInfo/{int(time.time() * 1000)}"
params = {
"device": "web",
"device": "www2",
"trackId": sound_id,
"trackQualityLevel": 2
}
try:
async with session.get(url, headers=headers, params=params, timeout=60) as response:
response_json = json.loads(await response.text())
sound_name = response_json["trackInfo"]["title"]
encrypted_url_list = response_json["trackInfo"]["playUrlList"]
except KeyError:
print(colorama.Fore.RED + f'ID为{sound_id}的声音解析失败,可能因为达到每日付费音频下载上限')
return False
except Exception as e:
print(colorama.Fore.RED + f'ID为{sound_id}的声音解析失败!')
logger.debug(f'ID为{sound_id}的声音解析失败!')
logger.debug(traceback.format_exc())
return False
while retries > 0:
try:
async with session.get(url, headers=headers, params=params, timeout=20) as response:
response_json = json.loads(await response.text())
sound_name = response_json["trackInfo"]["title"]
encrypted_url_list = response_json["trackInfo"]["playUrlList"]
break
except KeyError:
print(colorama.Fore.RED + f'ID为{sound_id}的声音解析失败,可能因为达到每日付费音频下载上限')
return False
except Exception as e:
logger.debug(f'ID为{sound_id}的声音解析失败!')
logger.debug(traceback.format_exc())
if retries == 0:
print(colorama.Fore.RED + f'ID为{sound_id}的声音解析失败!')
return False
retries -= 1
if not response_json["trackInfo"]["isAuthorized"]:
return 0 # 未购买或未登录vip账号
sound_info = {"name": sound_name, 0: "", 1: "", 2: ""}
Expand Down Expand Up @@ -274,12 +278,15 @@ async def get_selected_sounds(self, sounds, album_name, start, end, headers, qua

# 解密vip声音url
def decrypt_url(self, ciphertext):
key = binascii.unhexlify("aaad3e4fd540b0f79dca95606e72bf93")
ciphertext = base64.urlsafe_b64decode(ciphertext + '=' * (4 - len(ciphertext) % 4))
cipher = AES.new(key, AES.MODE_ECB)
plaintext = cipher.decrypt(ciphertext)
plaintext = re.sub(r"[^\x20-\x7E]", "", plaintext.decode("utf-8"))
return plaintext
# 读取 JavaScript 文件内容
with open('./decrypt.js', 'r') as file:
decrypt_js_code = file.read()
context = execjs.compile(decrypt_js_code)
result = context.call("getSoundCryptLink", {
"deviceType": "www2",
"link": ciphertext
})
return result

# 判断专辑是否为付费专辑,如果是免费专辑返回0,如果是已购买的付费专辑返回1,如果是未购买的付费专辑返回2,如果解析失败返回False
def judge_album(self, album_id, headers):
Expand Down Expand Up @@ -335,7 +342,7 @@ def analyze_config(self):
def judge_cookie(self, cookie):
url = "https://www.ximalaya.com/revision/my/getCurrentUserInfo"
headers = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/111.0.0.0 Safari/537.36 Edg/111.0.1660.14",
"user-agent": ua.random,
"cookie": cookie
}
try:
Expand Down Expand Up @@ -413,4 +420,4 @@ def login(self):
print("cookie无效,将返回主菜单,建议使用方法1自动获取cookie!")
return
username = self.judge_cookie(cookie)
print(f"成功登录账号{username}!")
print(f"成功登录账号{username}!")
2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,5 @@ pycryptodome
requests
selenium==4.9.0
webdriver_manager
fake_useragent
PyExecJS

0 comments on commit 977bc92

Please sign in to comment.