├── README.md ├── README_image └── 001.png ├── bot.py ├── dingLog.py ├── setting.json └── 云函数压缩包 ├── CloudMusicBot.zip └── Crypto_SCF_Layer_Python3.6.zip /README.md: -------------------------------------------------------------------------------- 1 | # **📌评分部分一眼假,后续仍需使用请自行修改🥱** 2 | 3 | # 网易云音乐 音乐合伙人脚本 4 | 5 | **不是“网易音乐人”的脚本呐!!!** 6 | 7 | ## 🔥 功能 8 | 9 | [手动](#-本地手动运行)或使用[华为云函数](#-华为云函数)实现自动完成**音乐合伙人**任务。 10 | 11 | (可选) 使用[钉钉机器人](https://open.dingtalk.com/document/robots/custom-robot-access/)返回任务完成情况。 12 | 13 | ![钉钉机器人发送的任务执行情况](README_image/001.png) 14 | 15 | ## 📖 使用说明 16 | 17 | ### 💻 本地手动运行 18 | 19 | 1. 下载 "bot.py", "dingLog.py", "setting.json", **需放到同一文件夹内** 20 | 2. 填写"setting.json",[参数说明](#-参数说明) 21 | 3. 直接执行"bot.py" 22 | 23 | ### ☁ [华为云函数](https://console.huaweicloud.com/functiongraph) 24 | 25 | ⚠ 请留意免费额度,以免产生不必要的支出! 26 | 27 | 1. 下载 "CloudMusicBot.zip", "Crypto_SCF_Layer_Python3.6.zip" (在"**云函数压缩包**"文件夹内) 28 | 2. 新建云函数(版本选择**Python 3.6**),将 "CloudMusicBot.zip" 上传为函数代码 29 | 3. 将函数执行入口设为 "**bot.handler**" 30 | 4. 执行超时时间延长至60秒 (为保险起见可适当延长) 31 | 5. 填写环境变量,[参数说明](#-参数说明) 32 | 6. 将 "Crypto_SCF_Layer_Python3.6.zip" 上传为依赖包,**并在代码依赖包中添加** 33 | 7. 添加触发器,这里以每天中午12点运行一次为例:Cron表达式「0 0 12 * * ?」 34 | 35 | ### ⚙ 参数说明 36 | 37 | | 参数名 | 说明 | 获取 | 38 | |:---------------|:----------------|:------------------------------------------------------------------------| 39 | | Cookie_MUSIC_U | 网易云音乐cookie | [网易云音乐](https://music.163.com/) | 40 | | Cookie___csrf | 网易云音乐cookie | [网易云音乐](https://music.163.com/) | 41 | | BOT_URL | 机器人Webhook (选填) | [机器人文档](https://open.dingtalk.com/document/robots/custom-robot-access/) | 42 | 43 | * Cookie两周左右就会过期,请及时更新参数 44 | * 如不使用钉钉机器人反馈结果(本地运行),可将"BOT_URL"填写为(不包括双引号): 45 | * "" (将结果打印到屏幕) 46 | * "ignore" (不输出结果) 47 | 48 | ## 🔈 特别声明 49 | 50 | - 本仓库发布的脚本及其中涉及的任何解锁和解密分析脚本,仅用于测试和学习研究,禁止用于商业用途,不能保证其合法性,准确性,完整性和有效性,请根据情况自行判断。 51 | 52 | - 本项目内所有资源文件,禁止任何公众号、自媒体进行任何形式的转载、发布。 53 | 54 | - 本人对任何脚本问题概不负责,包括但不限于由任何脚本错误导致的任何损失或损害。 55 | 56 | - 间接使用脚本的任何用户,包括但不限于建立VPS或在某些行为违反国家/地区法律或相关法规的情况下进行传播, 57 | 本人对于由此引起的任何隐私泄漏或其他后果概不负责。 58 | 59 | - 请勿将本仓库的任何内容用于商业或非法目的,否则后果自负。 60 | 61 | - 如果任何单位或个人认为该项目的脚本可能涉嫌侵犯其权利,则应及时通知并提供身份证明,所有权证明,我们将在收到认证文件后删除相关脚本。 62 | 63 | - 任何以任何方式查看此项目的人或直接或间接使用该项目的任何脚本的使用者都应仔细阅读此声明。本人保留随时更改或补充此免责声明的权利。一旦使用并复制了任何相关脚本或Script项目的规则,则视为您已接受此免责声明。 64 | 65 | **您必须在下载后的24小时内从计算机或手机中完全删除以上内容** 66 | 67 | > ***您使用或者复制了本仓库且本人制作的任何脚本,则视为 `已接受` 此声明,请仔细阅读*** 68 | 69 | ## 😘 鸣谢 70 | 71 | 代码参考 & "README":https://github.com/KotoriMinami/qinglong-sign 72 | 73 | 依赖包:https://github.com/LeonX86/Music-copartner-sverless 74 | 75 | 76 | -------------------------------------------------------------------------------- /README_image/001.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C20C01/CloudMusicBot/bfeddf88fa4280b828c4f9fb4bbc74ba6ec18073/README_image/001.png -------------------------------------------------------------------------------- /bot.py: -------------------------------------------------------------------------------- 1 | # 手动或使用华为云函数实现自动完成网易云音乐合伙人任务 2 | # 代码参考:https://github.com/KotoriMinami/qinglong-sign 3 | 4 | import base64 5 | import codecs 6 | import random 7 | import re 8 | import string 9 | from typing import Callable, Any 10 | 11 | import requests 12 | import json 13 | 14 | from dingLog import DingLog 15 | from Crypto.Cipher import AES 16 | 17 | 18 | class Bot: 19 | def __init__(self, context, log): 20 | self.userInfoUrl = "https://music.163.com/api/nuser/account/get" 21 | self.taskDataUrl = "https://interface.music.163.com/api/music/partner/daily/task/get" 22 | 23 | self.session = requests.session() 24 | self.log = log 25 | self.context = context 26 | 27 | def run(self) -> bool: 28 | try: 29 | self.__loadCookie() 30 | self.__getUserName() 31 | complete, taskData = self.__getUserTask() 32 | if not complete: 33 | self.__sign(taskData) 34 | except RuntimeError: 35 | return False 36 | return True 37 | 38 | def __loadCookie(self): 39 | flag = False 40 | for key in ["MUSIC_U", "__csrf"]: 41 | cookie = self.context.getUserData("Cookie_" + key) 42 | if cookie: 43 | self.session.cookies.set(key, cookie) 44 | else: 45 | self.log.info(f"未填写cookie「{key}」") 46 | flag = True 47 | if flag: 48 | raise RuntimeError 49 | 50 | def __getUserName(self): 51 | profile = self.session.get(url=self.userInfoUrl).json()["profile"] 52 | if profile: 53 | self.log.info(f'用户名: {profile["nickname"]}') 54 | else: 55 | self.log.info("未能获取用户信息,请重新设置Cookie") 56 | raise RuntimeError 57 | 58 | def __getUserTask(self) -> [bool, json]: 59 | taskData = self.session.get(url=self.taskDataUrl).json()["data"] 60 | count = taskData["count"] 61 | completedCount = taskData["completedCount"] 62 | todayTask = f"[{completedCount}/{count}]" 63 | complete = count == completedCount 64 | self.log.info(f'今日任务:{"已完成" if complete else "未完成"}{todayTask}') 65 | return complete, taskData 66 | 67 | def __sign(self, taskData): 68 | self.log.info("开始评分...") 69 | signer = Signer(self.session, taskData["id"], self.log) 70 | for task in taskData["works"]: 71 | work = task["work"] 72 | if task["completed"]: 73 | self.log.info(f'{work["name"]}「{work["authorName"]}」已有评分:{int(task["score"])}分') 74 | else: 75 | signer.sign(work) 76 | 77 | 78 | def addTo16(data: str): 79 | while len(data) % 16 != 0: 80 | data += '\0' 81 | return str.encode(data) 82 | 83 | 84 | class Signer: 85 | def __init__(self, session: requests.Session, taskID, log): 86 | self.signUrl = "https://interface.music.163.com/weapi/music/partner/work/evaluate?csrf_token=" 87 | 88 | self.randomStr = "".join(random.choice( 89 | string.ascii_letters + string.digits) for _ in range(16)) # 随机生成长度为16的字符串 90 | self.pubKey = "010001" # buU9L(["流泪", "强"])的值 91 | # buU9L(Rg4k.md)的值 92 | self.modulus = "00e0b509f6259df8642dbc35662901477df22677ec152b5ff68ace615bb7b725152b3ab17a876aea8a5aa76d2e417629ec4ee341f56135fccf695280104e0312ecbda92557c93870114af6c9d05c4f7f0c3685b7a46bee255932575cce10b424d813cfe4875d3e82047b97ddef52741d546b8e289dc6935b3ece0462db0a22b8e7" 93 | self.iv = "0102030405060708" # 偏移量 94 | self.aesKey = '0CoJUm6Qyw8W8jud' # buU9L(["爱心", "女孩", "惊恐", "大笑"])的值 95 | 96 | self.pattern = re.compile('.*[a-zA-Z].*') 97 | self.session = session 98 | self.taskID = taskID 99 | self.log = log 100 | 101 | def __getScoreAndTag(self, work) -> [str, str]: 102 | star = "3" 103 | if self.pattern.match(work["name"] + work["authorName"]): 104 | star = "4" 105 | return star, star + "-A-1" 106 | 107 | def __getAesEncrypt(self, data: str, key: str): 108 | bs = AES.block_size 109 | pad2: Callable[[Any], Any] = lambda s: s + (bs - len(s) % bs) * chr(bs - len(s) % bs) 110 | encryptor = AES.new(addTo16(key), AES.MODE_CBC, addTo16(self.iv)) 111 | encrypt_aes = encryptor.encrypt(str.encode(pad2(data))) 112 | encrypt_text = str(base64.encodebytes(encrypt_aes), encoding='utf-8') 113 | return encrypt_text 114 | 115 | def __getParams(self, data) -> str: 116 | return self.__getAesEncrypt(self.__getAesEncrypt(str(data), self.aesKey), self.randomStr) 117 | 118 | def __getEncSecKey(self) -> str: 119 | text = self.randomStr[::-1] 120 | rs = int(codecs.encode(text.encode('utf-8'), 'hex_codec'), 16) ** int(self.pubKey, 16) % int(self.modulus, 16) 121 | return format(rs, 'x').zfill(256) 122 | 123 | def sign(self, work): 124 | try: 125 | csrf = str(self.session.cookies["__csrf"]) 126 | score, tag = self.__getScoreAndTag(work) 127 | data = { 128 | "params": self.__getParams({ 129 | "taskId": self.taskID, 130 | "workId": work['id'], 131 | "score": score, 132 | "tags": tag, 133 | "customTags": "%5B%5D", 134 | "comment": "", 135 | "syncYunCircle": "true", 136 | "csrf_token": csrf 137 | }).replace("\n", ""), 138 | "encSecKey": self.__getEncSecKey() 139 | } 140 | response = self.session.post(url=f'{self.signUrl}={csrf}', data=data).json() 141 | if response["code"] == 200: 142 | self.log.info(f'{work["name"]}「{work["authorName"]}」评分完成:{score}分') 143 | except Exception as e: 144 | self.log.info(f'歌曲「{work["name"]}」评分异常:{str(e)}') 145 | raise RuntimeError 146 | 147 | 148 | # 以下是云函数的执行入口 149 | def handler(event, context): 150 | log = DingLog(context.getUserData("BOT_URL")) 151 | if Bot(context, log).run(): 152 | log.end("✅ 执行成功") 153 | else: 154 | log.end("❌ 执行失败", True) 155 | 156 | 157 | # 以下是本地使用时的代码 158 | class Context: 159 | def __init__(self): 160 | # 需要在同一文件夹下建一个json文件,写入"Cookie_MUSIC_U","Cookie___csrf","BOT_URL"(供钉钉机器人使用,选填) 161 | with open("setting.json", "r", encoding="utf-8") as file: 162 | self.dic = json.loads(file.read()) 163 | 164 | def getUserData(self, key): 165 | return self.dic[key] 166 | 167 | 168 | if __name__ == "__main__": 169 | myContext = Context() 170 | myLog = DingLog(myContext.getUserData("BOT_URL")) # 使用文档内的参数 171 | if Bot(myContext, myLog).run(): 172 | myLog.end("✅ 执行成功") 173 | else: 174 | myLog.end("❌ 执行失败", True) 175 | -------------------------------------------------------------------------------- /dingLog.py: -------------------------------------------------------------------------------- 1 | # 实现在钉钉使用机器人发送任务日志 2 | 3 | from datetime import datetime 4 | 5 | import requests 6 | 7 | 8 | class DingLog: 9 | def __init__(self, url: str): 10 | now = datetime.now() 11 | self.msg = now.strftime("%Y/%m/%d (%A) %H:%M:%S") 12 | self.msg += "\n= = = = = = = = = = = = = = = = = =" 13 | self.url = url 14 | 15 | def end(self, msg: str, atAll=False): 16 | headers = {"Content-Type": "application/json"} 17 | self.msg += "\n= = = = = = = = = = = = = = = = = =" 18 | self.info(msg) 19 | if self.url is None or self.url == "ignore": 20 | pass 21 | elif self.url == "": 22 | print(self.msg) 23 | else: 24 | data = {"msgtype": "text", "text": {"content": self.msg}} 25 | if atAll: 26 | data["at"] = {"isAtAll": "true"} 27 | data = str(data).encode("utf-8") 28 | requests.session().post(url=self.url, headers=headers, data=data) 29 | 30 | def info(self, msg: str): 31 | self.msg += "\n" + msg 32 | -------------------------------------------------------------------------------- /setting.json: -------------------------------------------------------------------------------- 1 | { 2 | "Cookie_MUSIC_U": "", 3 | "Cookie___csrf": "", 4 | "BOT_URL": "" 5 | } -------------------------------------------------------------------------------- /云函数压缩包/CloudMusicBot.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C20C01/CloudMusicBot/bfeddf88fa4280b828c4f9fb4bbc74ba6ec18073/云函数压缩包/CloudMusicBot.zip -------------------------------------------------------------------------------- /云函数压缩包/Crypto_SCF_Layer_Python3.6.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/C20C01/CloudMusicBot/bfeddf88fa4280b828c4f9fb4bbc74ba6ec18073/云函数压缩包/Crypto_SCF_Layer_Python3.6.zip --------------------------------------------------------------------------------