diff --git a/daily/musi.js b/daily/musi.js new file mode 100644 index 0000000..69ed317 --- /dev/null +++ b/daily/musi.js @@ -0,0 +1,182 @@ +/* +------------------------------------------ +@Author: sm +@Date: 2024.06.07 19:15 +@Description: 慕思小程序 签到 +cron: 30 10 * * * +------------------------------------------ +#Notice: +抓取https://atom.musiyoujia.com抓包 请求头里的 api_token,请求包里的 openId #拼接 +多账号&或换行 +抓包后不要打开小程序避免刷新了token,有效期待测试,如出现 “Token有误” 则是 token过期了请重新抓取 +⚠️【免责声明】 +------------------------------------------ +1、此脚本仅用于学习研究,不保证其合法性、准确性、有效性,请根据情况自行判断,本人对此不承担任何保证责任。 +2、由于此脚本仅用于学习研究,您必须在下载后 24 小时内将所有内容从您的计算机或手机或任何存储设备中完全删除,若违反规定引起任何事件本人对此均不负责。 +3、请勿将此脚本用于任何商业或非法目的,若违反规定请自行对此负责。 +4、此脚本涉及应用与本人无关,本人对因此引起的任何隐私泄漏或其他后果不承担任何责任。 +5、本人对任何脚本引发的问题概不负责,包括但不限于由脚本错误引起的任何损失和损害。 +6、如果任何单位或个人认为此脚本可能涉嫌侵犯其权利,应及时通知并提供身份证明,所有权证明,我们将在收到认证文件确认后删除此脚本。 +7、所有直接或间接使用、查看此脚本的人均应该仔细阅读此声明。本人保留随时更改或补充此声明的权利。一旦您使用或复制了此脚本,即视为您已接受此免责声明。 +*/ + +const { Env } = require("./tools/env") +const $ = new Env("慕思小程序"); +let ckName = `musi`; +const strSplitor = "#"; +const axios = require("axios"); +const defaultUserAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 MicroMessenger/8.0.31(0x18001e31) NetType/WIFI Language/zh_CN miniProgram" + + +class Task { + constructor(env) { + this.index = $.userIdx++ + this.user = env.split(strSplitor); + this.activedAuthToken = this.user[0]; + this.openId = this.user[1]; + this.isSigned = false; + this.valid = false; + this.customId = "" + + } + + async run() { + await this.getUserInfo() + await this.getJob() + if (!this.isSigned) { + await this.doSign() + } + } + MD5_Encrypt(str) { + const crypto = require("crypto") + return crypto.createHash('md5').update(str).digest('hex'); + } + async getUserInfo() { + try { + const timestamp = new Date().getTime(); + let options = { + method: 'POST', + url: `https://atom.musiyoujia.com/member/wechatlogin/selectuserinfo`, + headers: { + 'Content-Type': 'application/json', + 'User-Agent': defaultUserAgent, + "api_client_code": "65", + "api_version": "1.0.0", + 'api_timestamp': timestamp, + 'api_token': this.activedAuthToken, + + 'api_sign': this.MD5_Encrypt(`api_client_code=65&api_version=1.0.0&api_timestamp=${timestamp}`)?.toUpperCase() + + }, + data: { "appId": "wx03527497c5369a2c", "appType": "WECHAT_MINI_PROGRAM", "openId": `${this.openId}` } + } + let { data: result } = await axios.request(options) + if (result?.msg === "success") { + this.valid = true; + this.customId = result?.data.resMemberInfo.memberId; + $.log(`账号[${this.index}] 查询个人信息成功,积分:${result?.data?.memberInfo?.pointInfo?.point}`) + } else { + $.log(`账号[${this.index}] 查询个人信息失败:${result?.msg || JSON.stringify(result)}`) + this.valid = false + } + + } catch (e) { + console.log(e) + } + } + + async getJob() { + try { + const timestamp = new Date().getTime(); + let options = { + method: "POST", + url: `https://atom.musiyoujia.com/member/memberbehavior/getBehaviorInfos`, + headers: { + 'Content-Type': 'application/json', + 'User-Agent': defaultUserAgent, + "api_client_code": "65", + "api_version": "1.0.0", + 'api_token': this.activedAuthToken, + + 'api_timestamp': timestamp, + 'api_sign': this.MD5_Encrypt(`api_token=${this.activedAuthToken}&api_client_code=65&api_version=1.0.0&api_timestamp=${timestamp}`)?.toUpperCase() + + }, + data: { "appId": "wx03527497c5369a2c", "appType": "WECHAT_MINI_PROGRAM", "behaviorIds": [1, 2, 10203, 10204, 10205, 5], "sourceChannel": "会员小程序", "source": `${this.customId}`, "openId": `${this.openId}` } + } + let { data: result } = await axios.request(options) + + if (result?.msg === "success") { + this.isSigned = result?.data[0].acts['每天已获得积分次数'] === 1; + $.log(`账号[${this.index}] 获取任务列表成功,${this.isSigned ? '已签到' : '未签到'}`) + } else { + $.log(`账号[${this.index}] 获取任务列表失败:${result?.msg || JSON.stringify(result)}`) + } + + } catch (e) { + console.log(e) + } + } + + async doSign() { + try { + const timestamp = new Date().getTime(); + const eventAttr2 = $.time('yyyy.MM.dd') + let options = { + method: 'POST', + url: `https://atom.musiyoujia.com/member/memberbehavior/add`, + headers: { + 'Content-Type': 'application/json', + 'User-Agent': defaultUserAgent, + "api_client_code": "65", + 'api_token': this.activedAuthToken, + "api_version": "1.0.0", + 'api_timestamp': timestamp, + 'api_sign': this.MD5_Encrypt(`api_token=${this.activedAuthToken}&api_client_code=65&api_version=1.0.0&api_timestamp=${timestamp}`)?.toUpperCase() + + }, + data: { "appId": "wx03527497c5369a2c", "appType": "WECHAT_MINI_PROGRAM", "osType": "windows", "model": "microsoft", "browser": "微信小程序", "platform": "1", "sourceType": "5", "sourceChannel": "会员小程序", "siteId": "", "visitorId": "", "deviceId": "", "spotId": "", "campaignId": "", "deviceType": "", "eventLabel": "", "eventValue": "", "eventAttr2": `${eventAttr2}`, "eventAttr3": "", "eventAttr4": "", "eventAttr5": "", "eventAttr6": "", "googleCampaignName": "", "googleCampaignSource": "", "googleCampaignMedium": "", "googleCampaignContent": "", "memberType": "DeRUCCI", "customId": `${this.customId}`, "locationUrl": "/pages/user/signIn", "url": "/pages/user/signIn", "pageTitle": "每日签到", "logType": "event", "behaviorIds": [1, 3], "eventCategory": "用户签到", "eventAction": "签到", "eventAttr1": 2, "openId": `${this.openId}` } + } + let { data: result } = await axios.request(options) + + if (result?.msg === "success") { + $.log(`账号[${this.index}] 签到成功,获得积分:${result?.data?.point}`) + } else { + $.log(`账号[${this.index}] 签到失败:${result?.msg || JSON.stringify(result)}`) + } + + } catch (e) { + console.log(e) + } + } + + + + + + + +} + +!(async () => { + await getNotice() + $.checkEnv(ckName); + + for (let user of $.userList) { + await new Task(user).run(); + } +})() + .catch((e) => console.log(e)) + .finally(() => $.done()); + +async function getNotice() { + let options = { + url: `https://ghproxy.net/https://raw.githubusercontent.com/smallfawn/Note/refs/heads/main/Notice.json`, + headers: { + "User-Agent": defaultUserAgent, + } + } + let { data: res } = await axios.request(options); + $.log(res) + return res +} diff --git a/daily/notify.py b/daily/notify.py new file mode 100644 index 0000000..25f1a1f --- /dev/null +++ b/daily/notify.py @@ -0,0 +1,1107 @@ +#!/usr/bin/env python3 +# _*_ coding:utf-8 _*_ +import base64 +import hashlib +import hmac +import json +import os +import re +import threading +import time +import urllib.parse +import smtplib +from email.mime.text import MIMEText +from email.header import Header +from email.utils import formataddr + +import requests + +# 原先的 print 函数和主线程的锁 +_print = print +mutex = threading.Lock() + + +# 定义新的 print 函数 +def print(text, *args, **kw): + """ + 使输出有序进行,不出现多线程同一时间输出导致错乱的问题。 + """ + with mutex: + _print(text, *args, **kw) + + +# 通知服务 +# fmt: off +push_config = { + 'HITOKOTO': True, # 启用一言(随机句子) + + 'BARK_PUSH': '', # bark IP 或设备码,例:https://api.day.app/DxHcxxxxxRxxxxxxcm/ + 'BARK_ARCHIVE': '', # bark 推送是否存档 + 'BARK_GROUP': '', # bark 推送分组 + 'BARK_SOUND': '', # bark 推送声音 + 'BARK_ICON': '', # bark 推送图标 + 'BARK_LEVEL': '', # bark 推送时效性 + 'BARK_URL': '', # bark 推送跳转URL + + 'CONSOLE': False, # 控制台输出 + + 'DD_BOT_SECRET': '', # 钉钉机器人的 DD_BOT_SECRET + 'DD_BOT_TOKEN': '', # 钉钉机器人的 DD_BOT_TOKEN + + 'FSKEY': '', # 飞书机器人的 FSKEY + 'FSSECRET': '', # 飞书机器人的 FSSECRET,对应安全设置里的签名校验密钥 + + 'GOBOT_URL': '', # go-cqhttp + # 推送到个人QQ:http://127.0.0.1/send_private_msg + # 群:http://127.0.0.1/send_group_msg + 'GOBOT_QQ': '', # go-cqhttp 的推送群或用户 + # GOBOT_URL 设置 /send_private_msg 时填入 user_id=个人QQ + # /send_group_msg 时填入 group_id=QQ群 + 'GOBOT_TOKEN': '', # go-cqhttp 的 access_token + + 'GOTIFY_URL': '', # gotify地址,如https://push.example.de:8080 + 'GOTIFY_TOKEN': '', # gotify的消息应用token + 'GOTIFY_PRIORITY': 0, # 推送消息优先级,默认为0 + + 'IGOT_PUSH_KEY': '', # iGot 聚合推送的 IGOT_PUSH_KEY + + 'PUSH_KEY': '', # server 酱的 PUSH_KEY,兼容旧版与 Turbo 版 + + 'DEER_KEY': '', # PushDeer 的 PUSHDEER_KEY + 'DEER_URL': '', # PushDeer 的 PUSHDEER_URL + + 'CHAT_URL': '', # synology chat url + 'CHAT_TOKEN': '', # synology chat token + + 'PUSH_PLUS_TOKEN': '', # pushplus 推送的用户令牌 + 'PUSH_PLUS_USER': '', # pushplus 推送的群组编码 + 'PUSH_PLUS_TEMPLATE': 'html', # pushplus 发送模板,支持html,txt,json,markdown,cloudMonitor,jenkins,route,pay + 'PUSH_PLUS_CHANNEL': 'wechat', # pushplus 发送渠道,支持wechat,webhook,cp,mail,sms + 'PUSH_PLUS_WEBHOOK': '', # pushplus webhook编码,可在pushplus公众号上扩展配置出更多渠道 + 'PUSH_PLUS_CALLBACKURL': '', # pushplus 发送结果回调地址,会把推送最终结果通知到这个地址上 + 'PUSH_PLUS_TO': '', # pushplus 好友令牌,微信公众号渠道填写好友令牌,企业微信渠道填写企业微信用户id + + 'WE_PLUS_BOT_TOKEN': '', # 微加机器人的用户令牌 + 'WE_PLUS_BOT_RECEIVER': '', # 微加机器人的消息接收者 + 'WE_PLUS_BOT_VERSION': 'pro', # 微加机器人的调用版本 + + 'QMSG_KEY': '', # qmsg 酱的 QMSG_KEY + 'QMSG_TYPE': '', # qmsg 酱的 QMSG_TYPE + + 'QYWX_ORIGIN': '', # 企业微信代理地址 + + 'QYWX_AM': '', # 企业微信应用 + + 'QYWX_KEY': '', # 企业微信机器人 + + 'TG_BOT_TOKEN': '', # tg 机器人的 TG_BOT_TOKEN,例:1407203283:AAG9rt-6RDaaX0HBLZQq0laNOh898iFYaRQ + 'TG_USER_ID': '', # tg 机器人的 TG_USER_ID,例:1434078534 + 'TG_API_HOST': '', # tg 代理 api + 'TG_PROXY_AUTH': '', # tg 代理认证参数 + 'TG_PROXY_HOST': '', # tg 机器人的 TG_PROXY_HOST + 'TG_PROXY_PORT': '', # tg 机器人的 TG_PROXY_PORT + + 'AIBOTK_KEY': '', # 智能微秘书 个人中心的apikey 文档地址:http://wechat.aibotk.com/docs/about + 'AIBOTK_TYPE': '', # 智能微秘书 发送目标 room 或 contact + 'AIBOTK_NAME': '', # 智能微秘书 发送群名 或者好友昵称和type要对应好 + + 'SMTP_SERVER': '', # SMTP 发送邮件服务器,形如 smtp.exmail.qq.com:465 + 'SMTP_SSL': 'false', # SMTP 发送邮件服务器是否使用 SSL,填写 true 或 false + 'SMTP_EMAIL': '', # SMTP 收发件邮箱,通知将会由自己发给自己 + 'SMTP_PASSWORD': '', # SMTP 登录密码,也可能为特殊口令,视具体邮件服务商说明而定 + 'SMTP_NAME': '', # SMTP 收发件人姓名,可随意填写 + + 'PUSHME_KEY': '', # PushMe 的 PUSHME_KEY + 'PUSHME_URL': '', # PushMe 的 PUSHME_URL + + 'CHRONOCAT_QQ': '', # qq号 + 'CHRONOCAT_TOKEN': '', # CHRONOCAT 的token + 'CHRONOCAT_URL': '', # CHRONOCAT的url地址 + + 'WEBHOOK_URL': '', # 自定义通知 请求地址 + 'WEBHOOK_BODY': '', # 自定义通知 请求体 + 'WEBHOOK_HEADERS': '', # 自定义通知 请求头 + 'WEBHOOK_METHOD': '', # 自定义通知 请求方法 + 'WEBHOOK_CONTENT_TYPE': '', # 自定义通知 content-type + + 'NTFY_URL': '', # ntfy地址,如https://ntfy.sh + 'NTFY_TOPIC': '', # ntfy的消息应用topic + 'NTFY_PRIORITY':'3', # 推送消息优先级,默认为3 + 'NTFY_TOKEN': '', # 推送token,可选 + 'NTFY_USERNAME': '', # 推送用户名称,可选 + 'NTFY_PASSWORD': '', # 推送用户密码,可选 + 'NTFY_ACTIONS': '', # 推送用户动作,可选 + + 'WXPUSHER_APP_TOKEN': '', # wxpusher 的 appToken 官方文档: https://wxpusher.zjiecode.com/docs/ 管理后台: https://wxpusher.zjiecode.com/admin/ + 'WXPUSHER_TOPIC_IDS': '', # wxpusher 的 主题ID,多个用英文分号;分隔 topic_ids 与 uids 至少配置一个才行 + 'WXPUSHER_UIDS': '', # wxpusher 的 用户ID,多个用英文分号;分隔 topic_ids 与 uids 至少配置一个才行 +} +# fmt: on + +for k in push_config: + if os.getenv(k): + v = os.getenv(k) + push_config[k] = v + + +def bark(title: str, content: str) -> None: + """ + 使用 bark 推送消息。 + """ + if not push_config.get("BARK_PUSH"): + return + print("bark 服务启动") + + if push_config.get("BARK_PUSH").startswith("http"): + url = f'{push_config.get("BARK_PUSH")}' + else: + url = f'https://api.day.app/{push_config.get("BARK_PUSH")}' + + bark_params = { + "BARK_ARCHIVE": "isArchive", + "BARK_GROUP": "group", + "BARK_SOUND": "sound", + "BARK_ICON": "icon", + "BARK_LEVEL": "level", + "BARK_URL": "url", + } + data = { + "title": title, + "body": content, + } + for pair in filter( + lambda pairs: pairs[0].startswith("BARK_") + and pairs[0] != "BARK_PUSH" + and pairs[1] + and bark_params.get(pairs[0]), + push_config.items(), + ): + data[bark_params.get(pair[0])] = pair[1] + headers = {"Content-Type": "application/json;charset=utf-8"} + response = requests.post( + url=url, data=json.dumps(data), headers=headers, timeout=15 + ).json() + + if response["code"] == 200: + print("bark 推送成功!") + else: + print("bark 推送失败!") + + +def console(title: str, content: str) -> None: + """ + 使用 控制台 推送消息。 + """ + print(f"{title}\n\n{content}") + + +def dingding_bot(title: str, content: str) -> None: + """ + 使用 钉钉机器人 推送消息。 + """ + if not push_config.get("DD_BOT_SECRET") or not push_config.get("DD_BOT_TOKEN"): + return + print("钉钉机器人 服务启动") + + timestamp = str(round(time.time() * 1000)) + secret_enc = push_config.get("DD_BOT_SECRET").encode("utf-8") + string_to_sign = "{}\n{}".format(timestamp, push_config.get("DD_BOT_SECRET")) + string_to_sign_enc = string_to_sign.encode("utf-8") + hmac_code = hmac.new( + secret_enc, string_to_sign_enc, digestmod=hashlib.sha256 + ).digest() + sign = urllib.parse.quote_plus(base64.b64encode(hmac_code)) + url = f'https://oapi.dingtalk.com/robot/send?access_token={push_config.get("DD_BOT_TOKEN")}×tamp={timestamp}&sign={sign}' + headers = {"Content-Type": "application/json;charset=utf-8"} + data = {"msgtype": "text", "text": {"content": f"{title}\n\n{content}"}} + response = requests.post( + url=url, data=json.dumps(data), headers=headers, timeout=15 + ).json() + + if not response["errcode"]: + print("钉钉机器人 推送成功!") + else: + print("钉钉机器人 推送失败!") + + +def feishu_bot(title: str, content: str) -> None: + """ + 使用 飞书机器人 推送消息。 + """ + if not push_config.get("FSKEY"): + return + print("飞书 服务启动") + + url = f'https://open.feishu.cn/open-apis/bot/v2/hook/{push_config.get("FSKEY")}' + data = {"msg_type": "text", "content": {"text": f"{title}\n\n{content}"}} + + # Add signature if secret is provided + # Note: Feishu's signature algorithm uses timestamp+"\n"+secret as the HMAC key + # and signs an empty message, which differs from typical HMAC usage + if push_config.get("FSSECRET"): + timestamp = str(int(time.time())) + string_to_sign = f'{timestamp}\n{push_config.get("FSSECRET")}' + hmac_code = hmac.new( + string_to_sign.encode("utf-8"), digestmod=hashlib.sha256 + ).digest() + sign = base64.b64encode(hmac_code).decode("utf-8") + data["timestamp"] = timestamp + data["sign"] = sign + + response = requests.post(url, data=json.dumps(data)).json() + + if response.get("StatusCode") == 0 or response.get("code") == 0: + print("飞书 推送成功!") + else: + print("飞书 推送失败!错误信息如下:\n", response) + + +def go_cqhttp(title: str, content: str) -> None: + """ + 使用 go_cqhttp 推送消息。 + """ + if not push_config.get("GOBOT_URL") or not push_config.get("GOBOT_QQ"): + return + print("go-cqhttp 服务启动") + + url = f'{push_config.get("GOBOT_URL")}?access_token={push_config.get("GOBOT_TOKEN")}&{push_config.get("GOBOT_QQ")}&message=标题:{title}\n内容:{content}' + response = requests.get(url).json() + + if response["status"] == "ok": + print("go-cqhttp 推送成功!") + else: + print("go-cqhttp 推送失败!") + + +def gotify(title: str, content: str) -> None: + """ + 使用 gotify 推送消息。 + """ + if not push_config.get("GOTIFY_URL") or not push_config.get("GOTIFY_TOKEN"): + return + print("gotify 服务启动") + + url = f'{push_config.get("GOTIFY_URL")}/message?token={push_config.get("GOTIFY_TOKEN")}' + data = { + "title": title, + "message": content, + "priority": push_config.get("GOTIFY_PRIORITY"), + } + response = requests.post(url, data=data).json() + + if response.get("id"): + print("gotify 推送成功!") + else: + print("gotify 推送失败!") + + +def iGot(title: str, content: str) -> None: + """ + 使用 iGot 推送消息。 + """ + if not push_config.get("IGOT_PUSH_KEY"): + return + print("iGot 服务启动") + + url = f'https://push.hellyw.com/{push_config.get("IGOT_PUSH_KEY")}' + data = {"title": title, "content": content} + headers = {"Content-Type": "application/x-www-form-urlencoded"} + response = requests.post(url, data=data, headers=headers).json() + + if response["ret"] == 0: + print("iGot 推送成功!") + else: + print(f'iGot 推送失败!{response["errMsg"]}') + + +def serverJ(title: str, content: str) -> None: + """ + 通过 serverJ 推送消息。 + """ + if not push_config.get("PUSH_KEY"): + return + print("serverJ 服务启动") + + data = {"text": title, "desp": content.replace("\n", "\n\n")} + + match = re.match(r"sctp(\d+)t", push_config.get("PUSH_KEY")) + if match: + num = match.group(1) + url = f'https://{num}.push.ft07.com/send/{push_config.get("PUSH_KEY")}.send' + else: + url = f'https://sctapi.ftqq.com/{push_config.get("PUSH_KEY")}.send' + + response = requests.post(url, data=data).json() + + if response.get("errno") == 0 or response.get("code") == 0: + print("serverJ 推送成功!") + else: + print(f'serverJ 推送失败!错误码:{response["message"]}') + + +def pushdeer(title: str, content: str) -> None: + """ + 通过PushDeer 推送消息 + """ + if not push_config.get("DEER_KEY"): + return + print("PushDeer 服务启动") + data = { + "text": title, + "desp": content, + "type": "markdown", + "pushkey": push_config.get("DEER_KEY"), + } + url = "https://api2.pushdeer.com/message/push" + if push_config.get("DEER_URL"): + url = push_config.get("DEER_URL") + + response = requests.post(url, data=data).json() + + if len(response.get("content").get("result")) > 0: + print("PushDeer 推送成功!") + else: + print("PushDeer 推送失败!错误信息:", response) + + +def chat(title: str, content: str) -> None: + """ + 通过Chat 推送消息 + """ + if not push_config.get("CHAT_URL") or not push_config.get("CHAT_TOKEN"): + return + print("chat 服务启动") + data = "payload=" + json.dumps({"text": title + "\n" + content}) + url = push_config.get("CHAT_URL") + push_config.get("CHAT_TOKEN") + response = requests.post(url, data=data) + + if response.status_code == 200: + print("Chat 推送成功!") + else: + print("Chat 推送失败!错误信息:", response) + + +def pushplus_bot(title: str, content: str) -> None: + """ + 通过 pushplus 推送消息。 + """ + if not push_config.get("PUSH_PLUS_TOKEN"): + return + print("PUSHPLUS 服务启动") + + url = "https://www.pushplus.plus/send" + data = { + "token": push_config.get("PUSH_PLUS_TOKEN"), + "title": title, + "content": content, + "topic": push_config.get("PUSH_PLUS_USER"), + "template": push_config.get("PUSH_PLUS_TEMPLATE"), + "channel": push_config.get("PUSH_PLUS_CHANNEL"), + "webhook": push_config.get("PUSH_PLUS_WEBHOOK"), + "callbackUrl": push_config.get("PUSH_PLUS_CALLBACKURL"), + "to": push_config.get("PUSH_PLUS_TO"), + } + body = json.dumps(data).encode(encoding="utf-8") + headers = {"Content-Type": "application/json"} + response = requests.post(url=url, data=body, headers=headers).json() + + code = response["code"] + if code == 200: + print("PUSHPLUS 推送请求成功,可根据流水号查询推送结果:" + response["data"]) + print( + "注意:请求成功并不代表推送成功,如未收到消息,请到pushplus官网使用流水号查询推送最终结果" + ) + elif code == 900 or code == 903 or code == 905 or code == 999: + print(response["msg"]) + + else: + url_old = "http://pushplus.hxtrip.com/send" + headers["Accept"] = "application/json" + response = requests.post(url=url_old, data=body, headers=headers).json() + + if response["code"] == 200: + print("PUSHPLUS(hxtrip) 推送成功!") + + else: + print("PUSHPLUS 推送失败!") + + +def weplus_bot(title: str, content: str) -> None: + """ + 通过 微加机器人 推送消息。 + """ + if not push_config.get("WE_PLUS_BOT_TOKEN"): + return + print("微加机器人 服务启动") + + template = "txt" + if len(content) > 800: + template = "html" + + url = "https://www.weplusbot.com/send" + data = { + "token": push_config.get("WE_PLUS_BOT_TOKEN"), + "title": title, + "content": content, + "template": template, + "receiver": push_config.get("WE_PLUS_BOT_RECEIVER"), + "version": push_config.get("WE_PLUS_BOT_VERSION"), + } + body = json.dumps(data).encode(encoding="utf-8") + headers = {"Content-Type": "application/json"} + response = requests.post(url=url, data=body, headers=headers).json() + + if response["code"] == 200: + print("微加机器人 推送成功!") + else: + print("微加机器人 推送失败!") + + +def qmsg_bot(title: str, content: str) -> None: + """ + 使用 qmsg 推送消息。 + """ + if not push_config.get("QMSG_KEY") or not push_config.get("QMSG_TYPE"): + return + print("qmsg 服务启动") + + url = f'https://qmsg.zendee.cn/{push_config.get("QMSG_TYPE")}/{push_config.get("QMSG_KEY")}' + payload = {"msg": f'{title}\n\n{content.replace("----", "-")}'.encode("utf-8")} + response = requests.post(url=url, params=payload).json() + + if response["code"] == 0: + print("qmsg 推送成功!") + else: + print(f'qmsg 推送失败!{response["reason"]}') + + +def wecom_app(title: str, content: str) -> None: + """ + 通过 企业微信 APP 推送消息。 + """ + if not push_config.get("QYWX_AM"): + return + QYWX_AM_AY = re.split(",", push_config.get("QYWX_AM")) + if 4 < len(QYWX_AM_AY) > 5: + print("QYWX_AM 设置错误!!") + return + print("企业微信 APP 服务启动") + + corpid = QYWX_AM_AY[0] + corpsecret = QYWX_AM_AY[1] + touser = QYWX_AM_AY[2] + agentid = QYWX_AM_AY[3] + try: + media_id = QYWX_AM_AY[4] + except IndexError: + media_id = "" + wx = WeCom(corpid, corpsecret, agentid) + # 如果没有配置 media_id 默认就以 text 方式发送 + if not media_id: + message = title + "\n\n" + content + response = wx.send_text(message, touser) + else: + response = wx.send_mpnews(title, content, media_id, touser) + + if response == "ok": + print("企业微信推送成功!") + else: + print("企业微信推送失败!错误信息如下:\n", response) + + +class WeCom: + def __init__(self, corpid, corpsecret, agentid): + self.CORPID = corpid + self.CORPSECRET = corpsecret + self.AGENTID = agentid + self.ORIGIN = "https://qyapi.weixin.qq.com" + if push_config.get("QYWX_ORIGIN"): + self.ORIGIN = push_config.get("QYWX_ORIGIN") + + def get_access_token(self): + url = f"{self.ORIGIN}/cgi-bin/gettoken" + values = { + "corpid": self.CORPID, + "corpsecret": self.CORPSECRET, + } + req = requests.post(url, params=values) + data = json.loads(req.text) + return data["access_token"] + + def send_text(self, message, touser="@all"): + send_url = ( + f"{self.ORIGIN}/cgi-bin/message/send?access_token={self.get_access_token()}" + ) + send_values = { + "touser": touser, + "msgtype": "text", + "agentid": self.AGENTID, + "text": {"content": message}, + "safe": "0", + } + send_msges = bytes(json.dumps(send_values), "utf-8") + respone = requests.post(send_url, send_msges) + respone = respone.json() + return respone["errmsg"] + + def send_mpnews(self, title, message, media_id, touser="@all"): + send_url = ( + f"{self.ORIGIN}/cgi-bin/message/send?access_token={self.get_access_token()}" + ) + send_values = { + "touser": touser, + "msgtype": "mpnews", + "agentid": self.AGENTID, + "mpnews": { + "articles": [ + { + "title": title, + "thumb_media_id": media_id, + "author": "Author", + "content_source_url": "", + "content": message.replace("\n", "
"), + "digest": message, + } + ] + }, + } + send_msges = bytes(json.dumps(send_values), "utf-8") + respone = requests.post(send_url, send_msges) + respone = respone.json() + return respone["errmsg"] + + +def wecom_bot(title: str, content: str) -> None: + """ + 通过 企业微信机器人 推送消息。 + """ + if not push_config.get("QYWX_KEY"): + return + print("企业微信机器人服务启动") + + origin = "https://qyapi.weixin.qq.com" + if push_config.get("QYWX_ORIGIN"): + origin = push_config.get("QYWX_ORIGIN") + + url = f"{origin}/cgi-bin/webhook/send?key={push_config.get('QYWX_KEY')}" + headers = {"Content-Type": "application/json;charset=utf-8"} + data = {"msgtype": "text", "text": {"content": f"{title}\n\n{content}"}} + response = requests.post( + url=url, data=json.dumps(data), headers=headers, timeout=15 + ).json() + + if response["errcode"] == 0: + print("企业微信机器人推送成功!") + else: + print("企业微信机器人推送失败!") + + +def telegram_bot(title: str, content: str) -> None: + """ + 使用 telegram 机器人 推送消息。 + """ + if not push_config.get("TG_BOT_TOKEN") or not push_config.get("TG_USER_ID"): + return + print("tg 服务启动") + + if push_config.get("TG_API_HOST"): + url = f"{push_config.get('TG_API_HOST')}/bot{push_config.get('TG_BOT_TOKEN')}/sendMessage" + else: + url = ( + f"https://api.telegram.org/bot{push_config.get('TG_BOT_TOKEN')}/sendMessage" + ) + headers = {"Content-Type": "application/x-www-form-urlencoded"} + payload = { + "chat_id": str(push_config.get("TG_USER_ID")), + "text": f"{title}\n\n{content}", + "disable_web_page_preview": "true", + } + proxies = None + if push_config.get("TG_PROXY_HOST") and push_config.get("TG_PROXY_PORT"): + if push_config.get("TG_PROXY_AUTH") is not None and "@" not in push_config.get( + "TG_PROXY_HOST" + ): + push_config["TG_PROXY_HOST"] = ( + push_config.get("TG_PROXY_AUTH") + + "@" + + push_config.get("TG_PROXY_HOST") + ) + proxyStr = "http://{}:{}".format( + push_config.get("TG_PROXY_HOST"), push_config.get("TG_PROXY_PORT") + ) + proxies = {"http": proxyStr, "https": proxyStr} + response = requests.post( + url=url, headers=headers, params=payload, proxies=proxies + ).json() + + if response["ok"]: + print("tg 推送成功!") + else: + print("tg 推送失败!") + + +def aibotk(title: str, content: str) -> None: + """ + 使用 智能微秘书 推送消息。 + """ + if ( + not push_config.get("AIBOTK_KEY") + or not push_config.get("AIBOTK_TYPE") + or not push_config.get("AIBOTK_NAME") + ): + return + print("智能微秘书 服务启动") + + if push_config.get("AIBOTK_TYPE") == "room": + url = "https://api-bot.aibotk.com/openapi/v1/chat/room" + data = { + "apiKey": push_config.get("AIBOTK_KEY"), + "roomName": push_config.get("AIBOTK_NAME"), + "message": {"type": 1, "content": f"【青龙快讯】\n\n{title}\n{content}"}, + } + else: + url = "https://api-bot.aibotk.com/openapi/v1/chat/contact" + data = { + "apiKey": push_config.get("AIBOTK_KEY"), + "name": push_config.get("AIBOTK_NAME"), + "message": {"type": 1, "content": f"【青龙快讯】\n\n{title}\n{content}"}, + } + body = json.dumps(data).encode(encoding="utf-8") + headers = {"Content-Type": "application/json"} + response = requests.post(url=url, data=body, headers=headers).json() + print(response) + if response["code"] == 0: + print("智能微秘书 推送成功!") + else: + print(f'智能微秘书 推送失败!{response["error"]}') + + +def smtp(title: str, content: str) -> None: + """ + 使用 SMTP 邮件 推送消息。 + """ + if ( + not push_config.get("SMTP_SERVER") + or not push_config.get("SMTP_SSL") + or not push_config.get("SMTP_EMAIL") + or not push_config.get("SMTP_PASSWORD") + or not push_config.get("SMTP_NAME") + ): + return + print("SMTP 邮件 服务启动") + + message = MIMEText(content, "plain", "utf-8") + message["From"] = formataddr( + ( + Header(push_config.get("SMTP_NAME"), "utf-8").encode(), + push_config.get("SMTP_EMAIL"), + ) + ) + message["To"] = formataddr( + ( + Header(push_config.get("SMTP_NAME"), "utf-8").encode(), + push_config.get("SMTP_EMAIL"), + ) + ) + message["Subject"] = Header(title, "utf-8") + + try: + smtp_server = ( + smtplib.SMTP_SSL(push_config.get("SMTP_SERVER")) + if push_config.get("SMTP_SSL") == "true" + else smtplib.SMTP(push_config.get("SMTP_SERVER")) + ) + smtp_server.login( + push_config.get("SMTP_EMAIL"), push_config.get("SMTP_PASSWORD") + ) + smtp_server.sendmail( + push_config.get("SMTP_EMAIL"), + push_config.get("SMTP_EMAIL"), + message.as_bytes(), + ) + smtp_server.close() + print("SMTP 邮件 推送成功!") + except Exception as e: + print(f"SMTP 邮件 推送失败!{e}") + + +def pushme(title: str, content: str) -> None: + """ + 使用 PushMe 推送消息。 + """ + if not push_config.get("PUSHME_KEY"): + return + print("PushMe 服务启动") + + url = ( + push_config.get("PUSHME_URL") + if push_config.get("PUSHME_URL") + else "https://push.i-i.me/" + ) + data = { + "push_key": push_config.get("PUSHME_KEY"), + "title": title, + "content": content, + "date": push_config.get("date") if push_config.get("date") else "", + "type": push_config.get("type") if push_config.get("type") else "", + } + response = requests.post(url, data=data) + + if response.status_code == 200 and response.text == "success": + print("PushMe 推送成功!") + else: + print(f"PushMe 推送失败!{response.status_code} {response.text}") + + +def chronocat(title: str, content: str) -> None: + """ + 使用 CHRONOCAT 推送消息。 + """ + if ( + not push_config.get("CHRONOCAT_URL") + or not push_config.get("CHRONOCAT_QQ") + or not push_config.get("CHRONOCAT_TOKEN") + ): + return + + print("CHRONOCAT 服务启动") + + user_ids = re.findall(r"user_id=(\d+)", push_config.get("CHRONOCAT_QQ")) + group_ids = re.findall(r"group_id=(\d+)", push_config.get("CHRONOCAT_QQ")) + + url = f'{push_config.get("CHRONOCAT_URL")}/api/message/send' + headers = { + "Content-Type": "application/json", + "Authorization": f'Bearer {push_config.get("CHRONOCAT_TOKEN")}', + } + + for chat_type, ids in [(1, user_ids), (2, group_ids)]: + if not ids: + continue + for chat_id in ids: + data = { + "peer": {"chatType": chat_type, "peerUin": chat_id}, + "elements": [ + { + "elementType": 1, + "textElement": {"content": f"{title}\n\n{content}"}, + } + ], + } + response = requests.post(url, headers=headers, data=json.dumps(data)) + if response.status_code == 200: + if chat_type == 1: + print(f"QQ个人消息:{ids}推送成功!") + else: + print(f"QQ群消息:{ids}推送成功!") + else: + if chat_type == 1: + print(f"QQ个人消息:{ids}推送失败!") + else: + print(f"QQ群消息:{ids}推送失败!") + + +def ntfy(title: str, content: str) -> None: + """ + 通过 Ntfy 推送消息 + """ + + def encode_rfc2047(text: str) -> str: + """将文本编码为符合 RFC 2047 标准的格式""" + encoded_bytes = base64.b64encode(text.encode("utf-8")) + encoded_str = encoded_bytes.decode("utf-8") + return f"=?utf-8?B?{encoded_str}?=" + + if not push_config.get("NTFY_TOPIC"): + return + print("ntfy 服务启动") + priority = "3" + if not push_config.get("NTFY_PRIORITY"): + print("ntfy 服务的NTFY_PRIORITY 未设置!!默认设置为3") + else: + priority = push_config.get("NTFY_PRIORITY") + + # 使用 RFC 2047 编码 title + encoded_title = encode_rfc2047(title) + + data = content.encode(encoding="utf-8") + headers = {"Title": encoded_title, "Priority": priority, "Icon": "https://qn.whyour.cn/logo.png"} # 使用编码后的 title + if push_config.get("NTFY_TOKEN"): + headers['Authorization'] = "Bearer " + push_config.get("NTFY_TOKEN") + elif push_config.get("NTFY_USERNAME") and push_config.get("NTFY_PASSWORD"): + authStr = push_config.get("NTFY_USERNAME") + ":" + push_config.get("NTFY_PASSWORD") + headers['Authorization'] = "Basic " + base64.b64encode(authStr.encode('utf-8')).decode('utf-8') + if push_config.get("NTFY_ACTIONS"): + headers['Actions'] = encode_rfc2047(push_config.get("NTFY_ACTIONS")) + + url = push_config.get("NTFY_URL") + "/" + push_config.get("NTFY_TOPIC") + response = requests.post(url, data=data, headers=headers) + if response.status_code == 200: # 使用 response.status_code 进行检查 + print("Ntfy 推送成功!") + else: + print("Ntfy 推送失败!错误信息:", response.text) + + +def wxpusher_bot(title: str, content: str) -> None: + """ + 通过 wxpusher 推送消息。 + 支持的环境变量: + - WXPUSHER_APP_TOKEN: appToken + - WXPUSHER_TOPIC_IDS: 主题ID, 多个用英文分号;分隔 + - WXPUSHER_UIDS: 用户ID, 多个用英文分号;分隔 + """ + if not push_config.get("WXPUSHER_APP_TOKEN"): + return + + url = "https://wxpusher.zjiecode.com/api/send/message" + + # 处理topic_ids和uids,将分号分隔的字符串转为数组 + topic_ids = [] + if push_config.get("WXPUSHER_TOPIC_IDS"): + topic_ids = [ + int(id.strip()) + for id in push_config.get("WXPUSHER_TOPIC_IDS").split(";") + if id.strip() + ] + + uids = [] + if push_config.get("WXPUSHER_UIDS"): + uids = [ + uid.strip() + for uid in push_config.get("WXPUSHER_UIDS").split(";") + if uid.strip() + ] + + # topic_ids uids 至少有一个 + if not topic_ids and not uids: + print("wxpusher 服务的 WXPUSHER_TOPIC_IDS 和 WXPUSHER_UIDS 至少设置一个!!") + return + + print("wxpusher 服务启动") + + data = { + "appToken": push_config.get("WXPUSHER_APP_TOKEN"), + "content": f"

{title}


{content}
", + "summary": title, + "contentType": 2, + "topicIds": topic_ids, + "uids": uids, + "verifyPayType": 0, + } + + headers = {"Content-Type": "application/json"} + response = requests.post(url=url, json=data, headers=headers).json() + + if response.get("code") == 1000: + print("wxpusher 推送成功!") + else: + print(f"wxpusher 推送失败!错误信息:{response.get('msg')}") + + +def parse_headers(headers): + if not headers: + return {} + + parsed = {} + lines = headers.split("\n") + + for line in lines: + i = line.find(":") + if i == -1: + continue + + key = line[:i].strip().lower() + val = line[i + 1 :].strip() + parsed[key] = parsed.get(key, "") + ", " + val if key in parsed else val + + return parsed + + +def parse_string(input_string, value_format_fn=None): + matches = {} + pattern = r"(\w+):\s*((?:(?!\n\w+:).)*)" + regex = re.compile(pattern) + for match in regex.finditer(input_string): + key, value = match.group(1).strip(), match.group(2).strip() + try: + value = value_format_fn(value) if value_format_fn else value + json_value = json.loads(value) + matches[key] = json_value + except: + matches[key] = value + return matches + + +def parse_body(body, content_type, value_format_fn=None): + if not body or content_type == "text/plain": + return value_format_fn(body) if value_format_fn and body else body + + parsed = parse_string(body, value_format_fn) + + if content_type == "application/x-www-form-urlencoded": + data = urllib.parse.urlencode(parsed, doseq=True) + return data + + if content_type == "application/json": + data = json.dumps(parsed) + return data + + return parsed + + +def custom_notify(title: str, content: str) -> None: + """ + 通过 自定义通知 推送消息。 + """ + if not push_config.get("WEBHOOK_URL") or not push_config.get("WEBHOOK_METHOD"): + return + + print("自定义通知服务启动") + + WEBHOOK_URL = push_config.get("WEBHOOK_URL") + WEBHOOK_METHOD = push_config.get("WEBHOOK_METHOD") + WEBHOOK_CONTENT_TYPE = push_config.get("WEBHOOK_CONTENT_TYPE") + WEBHOOK_BODY = push_config.get("WEBHOOK_BODY") + WEBHOOK_HEADERS = push_config.get("WEBHOOK_HEADERS") + + if "$title" not in WEBHOOK_URL and "$title" not in WEBHOOK_BODY: + print("请求头或者请求体中必须包含 $title 和 $content") + return + + headers = parse_headers(WEBHOOK_HEADERS) + body = parse_body( + WEBHOOK_BODY, + WEBHOOK_CONTENT_TYPE, + lambda v: v.replace("$title", title.replace("\n", "\\n")).replace( + "$content", content.replace("\n", "\\n") + ), + ) + formatted_url = WEBHOOK_URL.replace( + "$title", urllib.parse.quote_plus(title) + ).replace("$content", urllib.parse.quote_plus(content)) + response = requests.request( + method=WEBHOOK_METHOD, url=formatted_url, headers=headers, timeout=15, data=body + ) + + if response.status_code == 200: + print("自定义通知推送成功!") + else: + print(f"自定义通知推送失败!{response.status_code} {response.text}") + + +def one() -> str: + """ + 获取一条一言。 + :return: + """ + url = "https://v1.hitokoto.cn/" + res = requests.get(url).json() + return res["hitokoto"] + " ----" + res["from"] + + +def add_notify_function(): + notify_function = [] + if push_config.get("BARK_PUSH"): + notify_function.append(bark) + if push_config.get("CONSOLE"): + notify_function.append(console) + if push_config.get("DD_BOT_TOKEN") and push_config.get("DD_BOT_SECRET"): + notify_function.append(dingding_bot) + if push_config.get("FSKEY"): + notify_function.append(feishu_bot) + if push_config.get("GOBOT_URL") and push_config.get("GOBOT_QQ"): + notify_function.append(go_cqhttp) + if push_config.get("GOTIFY_URL") and push_config.get("GOTIFY_TOKEN"): + notify_function.append(gotify) + if push_config.get("IGOT_PUSH_KEY"): + notify_function.append(iGot) + if push_config.get("PUSH_KEY"): + notify_function.append(serverJ) + if push_config.get("DEER_KEY"): + notify_function.append(pushdeer) + if push_config.get("CHAT_URL") and push_config.get("CHAT_TOKEN"): + notify_function.append(chat) + if push_config.get("PUSH_PLUS_TOKEN"): + notify_function.append(pushplus_bot) + if push_config.get("WE_PLUS_BOT_TOKEN"): + notify_function.append(weplus_bot) + if push_config.get("QMSG_KEY") and push_config.get("QMSG_TYPE"): + notify_function.append(qmsg_bot) + if push_config.get("QYWX_AM"): + notify_function.append(wecom_app) + if push_config.get("QYWX_KEY"): + notify_function.append(wecom_bot) + if push_config.get("TG_BOT_TOKEN") and push_config.get("TG_USER_ID"): + notify_function.append(telegram_bot) + if ( + push_config.get("AIBOTK_KEY") + and push_config.get("AIBOTK_TYPE") + and push_config.get("AIBOTK_NAME") + ): + notify_function.append(aibotk) + if ( + push_config.get("SMTP_SERVER") + and push_config.get("SMTP_SSL") + and push_config.get("SMTP_EMAIL") + and push_config.get("SMTP_PASSWORD") + and push_config.get("SMTP_NAME") + ): + notify_function.append(smtp) + if push_config.get("PUSHME_KEY"): + notify_function.append(pushme) + if ( + push_config.get("CHRONOCAT_URL") + and push_config.get("CHRONOCAT_QQ") + and push_config.get("CHRONOCAT_TOKEN") + ): + notify_function.append(chronocat) + if push_config.get("WEBHOOK_URL") and push_config.get("WEBHOOK_METHOD"): + notify_function.append(custom_notify) + if push_config.get("NTFY_TOPIC"): + notify_function.append(ntfy) + if push_config.get("WXPUSHER_APP_TOKEN") and ( + push_config.get("WXPUSHER_TOPIC_IDS") or push_config.get("WXPUSHER_UIDS") + ): + notify_function.append(wxpusher_bot) + if not notify_function: + print(f"无推送渠道,请检查通知变量是否正确") + return notify_function + + +def send(title: str, content: str, ignore_default_config: bool = False, **kwargs): + if kwargs: + global push_config + if ignore_default_config: + push_config = kwargs # 清空从环境变量获取的配置 + else: + push_config.update(kwargs) + + if not content: + print(f"{title} 推送内容为空!") + return + + # 根据标题跳过一些消息推送,环境变量:SKIP_PUSH_TITLE 用回车分隔 + skipTitle = os.getenv("SKIP_PUSH_TITLE") + if skipTitle: + if title in re.split("\n", skipTitle): + print(f"{title} 在SKIP_PUSH_TITLE环境变量内,跳过推送!") + return + + hitokoto = push_config.get("HITOKOTO") + content += "\n\n" + one() if hitokoto != "false" else "" + + notify_function = add_notify_function() + ts = [ + threading.Thread(target=mode, args=(title, content), name=mode.__name__) + for mode in notify_function + ] + [t.start() for t in ts] + [t.join() for t in ts] + + +def main(): + send("title", "content") + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/qiehuang.py b/daily/qiehuang.py similarity index 99% rename from qiehuang.py rename to daily/qiehuang.py index b332171..60e75fc 100644 --- a/qiehuang.py +++ b/daily/qiehuang.py @@ -1,3 +1,6 @@ +# cron: 25 10 * * * +# new Env('统一快乐星球茄皇') + import requests import json import os @@ -7,7 +10,7 @@ import random from collections import defaultdict from notify import send -users = os.getenv("QH", "").splitlines() +users = os.getenv("QH", '').splitlines() # 清洗数据:去除每个用户字符串的首尾空格,过滤空行/全空格的无效数据 users = [user.strip() for user in users if user.strip()] diff --git a/musi.js b/musi.js deleted file mode 100644 index 99df15c..0000000 --- a/musi.js +++ /dev/null @@ -1,572 +0,0 @@ - -/** - * 慕思 - * const $ = new Env("慕思"); - * cron 10 6,15 * * * 慕思.js - * 活动入口:https://pic.imgdb.cn/item/64eaffc4661c6c8e54806be6.jpg - - * ========= 青龙--配置文件 =========== - * # 项目名称 - * export mshy='api_token#openId' - * 自己抓包 请求头里的 api_token,请求包里的 openId - * 抓包后不要打开小程序避免刷新了token,有效期待测试,如出现 “Token有误” 则是 token过期了请重新抓取 - * 多账号可用 换行、&、@ 符号隔开,支持新建多个同名环境变量来设置多账号 - * 奖励:签到每天得积分,积分兑换实物 - * ==================================== - */ -const $ = new Env("慕思"); -const utils = require("./utils") -let envParams = 'mshy'; - -let envSplitor = ['\n', '@', '&'] - -let processEnvParams = ($.isNode() ? process.env[envParams] : $.getdata(envParams)) || ''; - -let initedJobForTokens = [] -let currentUserIndex = 0 - -class JobTask { - constructor(str) { - this.requestUA = generateRandomUA(); - this.index = ++currentUserIndex - this.valid = false; - try { - [this.activedAuthToken, this.openId] = str; - } catch (error) { - console.log('参数不完整:自己抓包 请求头里的 api_token,请求包里的 openId') - } - } - - async taskApi (fn, method, url, body, moreHeaders = {}) { - let result = null - try { - let urlObject = { - url: url, - headers: { - 'Accept': '*/*', - 'Accept-Language': 'zh-CN,zh', - 'Connection': 'keep-alive', - 'Host': 'atom.musiyoujia.com', - 'Referer': 'https://servicewechat.com/wx03527497c5369a2c/94/page-frame.html', - 'Sec-Fetch-Dest': 'empty', - 'Sec-Fetch-Mode': 'cors', - 'Sec-Fetch-Site': 'cross-site', - 'User-Agent': this.requestUA || 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.36 MicroMessenger/7.0.20.1781(0x6700143B) NetType/WIFI MiniProgramEnv/Windows WindowsWechat/WMPF XWEB/8379', - 'api_client_code': '65', - 'api_token': this.activedAuthToken, - 'api_version': '1.0.0', - 'xweb_xhr': '1', - 'Content-Type': 'application/json', - ...moreHeaders - }, - timeout: 60000, - } - if (this.requestUA) { - urlObject.headers['User-Agent'] = this.requestUA - } - if (body) { - urlObject.body = body - // urlObject.headers['Content-Length'] = body?.length - } - // console.log('urlObject:', urlObject); - await httpRequest(method, urlObject).then(async (ret) => { - if (ret.resp?.statusCode == 200) { - if (ret.resp?.body) { - result = JSON.parse(ret.resp.body) - } else { - console.log(`账号[${this.index}]调用${method}[${fn}]出错,返回为空`) - } - } else { - console.log(`账号[${this.index}]调用${method}[${fn}]出错,返回状态码[${ret.resp?.statusCode || ''}]`, '返回结果:', ret.resp?.body || ret?.err) - } - }) - } catch (e) { - console.log(e) - } finally { - return Promise.resolve(result); - } - } - - async GetUserInfo () { - try { - let fn = 'GetUserInfo' - let method = 'post' - let url = `https://atom.musiyoujia.com/member/wechatlogin/selectuserinfo`; - const timestamp = new Date().getTime(); - let moreHeaders = { - 'api_timestamp': timestamp, - 'api_sign': utils?.MD5_Encrypt(`api_client_code=65&api_version=1.0.0&api_timestamp=${timestamp}`)?.toUpperCase() - } - let body = `{"appId":"wx03527497c5369a2c","appType":"WECHAT_MINI_PROGRAM","openId":"${this.openId}"}`; - await this.taskApi(fn, method, url, body, moreHeaders).then(async (result) => { - if (result?.msg === "success") { - this.valid = true; - this.customId = result?.data.resMemberInfo.memberId; - console.log(`账号[${this.index}] 查询个人信息成功,积分:${result?.data?.memberInfo?.pointInfo?.point}`) - } else { - console.log(`账号[${this.index}] 查询个人信息失败:${result?.msg || JSON.stringify(result)}`) - this.valid = false - } - }) - } catch (e) { - console.log(e) - } - } - - async GetJob () { - try { - let fn = 'GetJob' - let method = 'post' - let url = `https://atom.musiyoujia.com/member/memberbehavior/getBehaviorInfos`; - let body = `{"appId":"wx03527497c5369a2c","appType":"WECHAT_MINI_PROGRAM","behaviorIds":[1,2,10203,10204,10205,5],"sourceChannel":"会员小程序","source":"${this.customId}","openId":"${this.openId}"}`; - const timestamp = new Date().getTime(); - let moreHeaders = { - 'api_timestamp': timestamp, - 'api_sign': utils?.MD5_Encrypt(`api_token=${this.activedAuthToken}&api_client_code=65&api_version=1.0.0&api_timestamp=${timestamp}`)?.toUpperCase() - } - await this.taskApi(fn, method, url, body, moreHeaders).then(async (result) => { - if (result?.msg === "success") { - this.isSigned = result?.data[0].acts['每天已获得积分次数'] === 1; - console.log(`账号[${this.index}] 获取任务列表成功,${this.isSigned ? '已签到' : '未签到'}`) - } else { - console.log(`账号[${this.index}] 获取任务列表失败:${result?.msg || JSON.stringify(result)}`) - } - }) - } catch (e) { - console.log(e) - } - } - - async Sign () { - try { - let fn = 'Sign' - let method = 'post' - let url = `https://atom.musiyoujia.com/member/memberbehavior/add`; - const currentDate = new Date(); - const year = currentDate.getFullYear(); - const month = currentDate.getMonth() + 1; - const day = currentDate.getDate(); - let body = `{"appId":"wx03527497c5369a2c","appType":"WECHAT_MINI_PROGRAM","osType":"windows","model":"microsoft","browser":"微信小程序","platform":"1","sourceType":"5","sourceChannel":"会员小程序","siteId":"","visitorId":"","deviceId":"","spotId":"","campaignId":"","deviceType":"","eventLabel":"","eventValue":"","eventAttr2":"${`${year}.${month}.${day}`}","eventAttr3":"","eventAttr4":"","eventAttr5":"","eventAttr6":"","googleCampaignName":"","googleCampaignSource":"","googleCampaignMedium":"","googleCampaignContent":"","memberType":"DeRUCCI","customId":"${this.customId}","locationUrl":"/pages/user/signIn","url":"/pages/user/signIn","pageTitle":"每日签到","logType":"event","behaviorIds":[1,3],"eventCategory":"用户签到","eventAction":"签到","eventAttr1":2,"openId":"${this.openId}"}`; - const timestamp = new Date().getTime(); - let moreHeaders = { - 'api_timestamp': timestamp, - 'api_sign': utils?.MD5_Encrypt(`api_token=${this.activedAuthToken}&api_client_code=65&api_version=1.0.0&api_timestamp=${timestamp}`)?.toUpperCase() - } - await this.taskApi(fn, method, url, body, moreHeaders).then(async (result) => { - if (result?.msg === "success") { - console.log(`账号[${this.index}] 签到成功,获得积分:${result?.data?.point}`) - } else { - console.log(`账号[${this.index}] 签到失败:${result?.msg || JSON.stringify(result)}`) - } - }) - } catch (e) { - console.log(e) - } - } - - async doTask () { - try { - console.log(`\n============= 账号[${this.index}] 开始获取任务=============`); - await this.GetJob(); - if (!this.isSigned) { - console.log(`\n============= 账号[${this.index}] 开始执行 签到任务=============`); - await this.Sign(); - } - } catch (e) { - console.log(e) - } - } -} - -!(async () => { - if (typeof $request !== "undefined") { - await GetRewrite() - } else { - if (!(await checkEnv())) return; - - console.log('\n================ 开始执行 ================') - for (let user of initedJobForTokens) { - console.log(`----------- 执行 第 [${user.index}] 个账号 -----------`) - await user.GetUserInfo(); - } - - let validUserList = initedJobForTokens.filter(x => x.valid) - - if (initedJobForTokens.length > 0) { - console.log('\n================ 任务队列构建完毕 ================') - for (let user of initedJobForTokens) { - console.log(`----------- 账号[${user.index}] -----------`) - await user.doTask(); - } - } else { - console.log('\n====幻生提示:无可用账号,请检查配置============ 任务结束 ================') - } - - await $.showmsg(); - } -})() - .catch((e) => console.log(e)) - .finally(() => $.done()) - -async function GetRewrite () { -} - -async function checkEnv () { - if (processEnvParams) { - let splitor = envSplitor[0]; - for (let sp of envSplitor) { - if (processEnvParams.indexOf(sp) > -1) { - splitor = sp; - break; - } - } - for (let token of processEnvParams.split(splitor)) { - if (token) initedJobForTokens.push(new JobTask(token?.split('#'))) - } - userCount = initedJobForTokens.length - } else { - console.log('未找到 配置信息,请检查是否配置 变量:', envParams) - return; - } - - console.log(`共找到${userCount}个账号`) - return true -} - -function generateRandomUA () { - var androidVersion = Math.floor(Math.random() * 6) + 8; // 生成 Android 版本号 8-13 之间的随机数 - var androidModels = [ - "PBBM00", "SM-G975F", "Pixel 4", "OnePlus 7T", "Redmi Note 8 Pro", - "iPhone X", "Galaxy S10", "Huawei P30 Pro", "LG G8 ThinQ", "Sony Xperia 1", - "Moto G7", "Nokia 9 PureView", "Xiaomi Mi 9", "Google Pixel 3a", "OnePlus 6T", - "Redmi Note 7", "Samsung Galaxy A50", "Huawei Mate 20 Pro", "LG V40 ThinQ", "Sony Xperia XZ3", - "Moto Z4", "Nokia 7.1", "Xiaomi Mi Mix 3", "Google Pixel 2", "OnePlus 5T", - "Redmi Note 6 Pro", "Samsung Galaxy A70", "Huawei P20 Pro", "LG G7 ThinQ", "Sony Xperia XZ2", - "Moto G6", "Nokia 6.1", "Xiaomi Mi 8", "Google Pixel", "OnePlus 5", - "Redmi Note 5 Pro", "Samsung Galaxy A30", "Huawei Mate 10 Pro", "LG V30", "Sony Xperia XZ1", - "Moto X4", "Nokia 5.1", "Xiaomi Mi 6", "Google Pixel XL", "OnePlus 3T", - "Redmi Note 4", "Samsung Galaxy A20", "Huawei P10 Plus", "LG G6", "Sony Xperia XZ", - "Moto G5 Plus", "Nokia 3.1", "Xiaomi Mi 5", "Google Pixel 3", "OnePlus 3", - "Redmi Note 3", "Samsung Galaxy A10", "Huawei Mate 9", "LG V20", "Sony Xperia X Compact", - "Moto G4", "Nokia 2.2", "Xiaomi Mi 4", "Google Pixel 2 XL", "OnePlus 2", - "Redmi Note 2", "Samsung Galaxy A5", "Huawei P9", "LG G5", "Sony Xperia X", - "Moto E6", "Nokia 1", "Xiaomi Mi 3", "Google Pixel 4", "OnePlus One", - "Redmi Note", "Samsung Galaxy A3", "Huawei P8", "LG G4", "Sony Xperia Z5", - "Moto E5", "Nokia 3310", "Xiaomi Mi 2", "Google Pixel 4 XL", "OnePlus Nord" - ]; // 常见的手机型号 - - var chromeVersion = "Chrome/" + (Math.floor(Math.random() * 30) + 50) + ".0." + (Math.floor(Math.random() * 1000) + 100); // 生成 Chrome 版本号 50.0.100-79.0.999 之间的随机数 - - var ua = "Mozilla/5.0 (Linux; Android " + androidVersion + "; " + androidModels[Math.floor(Math.random() * androidModels.length)] + " Build/PPR1.180610.011; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 " + chromeVersion + " Mobile Safari/537.36;xsb_wangchao;xsb_wangchao;5.3.1;native_app"; - - return ua; -} - -// 无参数加密:请求地址+时间戳+A749380BBD5A4D93B55B4BE245A42988+Token -// 示例数据:https://api.ikbang.cn/v2/iclick-new/signIn/sign1692923683034A749380BBD5A4D93B55B4BE245A4298839D21A3B3EFF817154C47470BF13E62CF05F956446D53C359688A094E91A9B1F2A67065862923551F147C32E3AFAF778 -// 如果请求URL有参数则是 在时间戳 和 盐 之间增加 stringify后的参数(POST) - -async function httpRequest (method, url) { - httpErr = null, httpReq = null, httpResp = null; - return new Promise((resolve) => { - $.send(method, url, async (err, req, resp) => { - httpErr = err, httpReq = req, httpResp = resp; - resolve({ err, req, resp }) - }) - }); -} - -function Env (name, env) { - "undefined" != typeof process && JSON.stringify(process.env).indexOf("xcxx") > -1 && process.exit(0); - return new class { - constructor(name, env) { - this.name = name - this.notifyStr = '' - this.startTime = (new Date).getTime() - Object.assign(this, env) - console.log(`${this.name} 开始运行:\n`) - } - isNode () { - return "undefined" != typeof module && !!module.exports - } - isQuanX () { - return "undefined" != typeof $task - } - isSurge () { - return "undefined" != typeof $httpClient && "undefined" == typeof $loon - } - isLoon () { - return "undefined" != typeof $loon - } - getdata (t) { - let e = this.getval(t); - if (/^@/.test(t)) { - const [, s, i] = /^@(.*?)\.(.*?)$/.exec(t), - r = s ? this.getval(s) : ""; - if (r) - try { - const t = JSON.parse(r); - e = t ? this.lodash_get(t, i, "") : e - } catch (t) { - e = "" - } - } - return e - } - setdata (t, e) { - let s = !1; - if (/^@/.test(e)) { - const [, i, r] = /^@(.*?)\.(.*?)$/.exec(e), - o = this.getval(i), - h = i ? "null" === o ? null : o || "{}" : "{}"; - try { - const e = JSON.parse(h); - this.lodash_set(e, r, t), - s = this.setval(JSON.stringify(e), i) - } catch (e) { - const o = {}; - this.lodash_set(o, r, t), - s = this.setval(JSON.stringify(o), i) - } - } - else { - s = this.setval(t, e); - } - return s - } - getval (t) { - return this.isSurge() || this.isLoon() ? $persistentStore.read(t) : this.isQuanX() ? $prefs.valueForKey(t) : this.isNode() ? (this.data = this.loaddata(), this.data[t]) : this.data && this.data[t] || null - } - setval (t, e) { - return this.isSurge() || this.isLoon() ? $persistentStore.write(t, e) : this.isQuanX() ? $prefs.setValueForKey(t, e) : this.isNode() ? (this.data = this.loaddata(), this.data[e] = t, this.writedata(), !0) : this.data && this.data[e] || null - } - send (m, t, e = (() => { })) { - if (m != 'get' && m != 'post' && m != 'put' && m != 'delete') { - console.log(`无效的http方法:${m}`); - return; - } - if (m == 'get' && t.headers) { - delete t.headers["Content-Type"]; - delete t.headers["Content-Length"]; - } else if (t.body && t.headers) { - if (!t.headers["Content-Type"]) t.headers["Content-Type"] = "application/x-www-form-urlencoded"; - } - if (this.isSurge() || this.isLoon()) { - if (this.isSurge() && this.isNeedRewrite) { - t.headers = t.headers || {}; - Object.assign(t.headers, { "X-Surge-Skip-Scripting": !1 }); - } - let conf = { - method: m, - url: t.url, - headers: t.headers, - timeout: t.timeout, - data: t.body - }; - if (m == 'get') delete conf.data - $axios(conf).then(t => { - const { - status: i, - request: q, - headers: r, - data: o - } = t; - e(null, q, { - statusCode: i, - headers: r, - body: o - }); - }).catch(err => console.log(err)) - } else if (this.isQuanX()) { - t.method = m.toUpperCase(), this.isNeedRewrite && (t.opts = t.opts || {}, Object.assign(t.opts, { - hints: !1 - })), - $task.fetch(t).then(t => { - const { - statusCode: i, - request: q, - headers: r, - body: o - } = t; - e(null, q, { - statusCode: i, - headers: r, - body: o - }) - }, t => e(t)) - } else if (this.isNode()) { - this.got = this.got ? this.got : require("got"); - const { - url: s, - ...i - } = t; - this.instance = this.got.extend({ - followRedirect: false - }); - this.instance[m](s, i).then(t => { - const { - statusCode: i, - request: q, - headers: r, - body: o - } = t; - e(null, q, { - statusCode: i, - headers: r, - body: o - }) - }, t => { - const { - message: s, - request: q, - response: i - } = t; - e(s, q, i) - }) - } - } - time (t, x = null) { - let xt = x ? new Date(x) : new Date - let e = { - "M+": xt.getMonth() + 1, - "d+": xt.getDate(), - "h+": xt.getHours(), - "m+": xt.getMinutes(), - "s+": xt.getSeconds(), - "q+": Math.floor((xt.getMonth() + 3) / 3), - S: xt.getMilliseconds() - }; - /(y+)/.test(t) && (t = t.replace(RegExp.$1, (xt.getFullYear() + "").substr(4 - RegExp.$1.length))); - for (let s in e) - new RegExp("(" + s + ")").test(t) && (t = t.replace(RegExp.$1, 1 == RegExp.$1.length ? e[s] : ("00" + e[s]).substr(("" + e[s]).length))); - return t - } - async showmsg () { - if (!this.notifyStr) return; - let notifyBody = this.name + " 运行通知\n\n" + this.notifyStr - if ($.isNode()) { - var notify = require('./sendNotify'); - console.log('\n============== 推送 ==============') - await notify.sendNotify(this.name, notifyBody); - } else { - this.msg(notifyBody); - } - } - logAndNotify (str) { - console.log(str) - this.notifyStr += str - this.notifyStr += '\n' - } - logAndNotifyWithTime (str) { - let t = '[' + this.time('hh:mm:ss.S') + ']' + str - console.log(t) - this.notifyStr += t - this.notifyStr += '\n' - } - logWithTime (str) { - console.log('[' + this.time('hh:mm:ss.S') + ']' + str) - } - msg (e = t, s = "", i = "", r) { - const o = t => { - if (!t) - return t; - if ("string" == typeof t) - return this.isLoon() ? t : this.isQuanX() ? { - "open-url": t - } - : this.isSurge() ? { - url: t - } - : void 0; - if ("object" == typeof t) { - if (this.isLoon()) { - let e = t.openUrl || t.url || t["open-url"], - s = t.mediaUrl || t["media-url"]; - return { - openUrl: e, - mediaUrl: s - } - } - if (this.isQuanX()) { - let e = t["open-url"] || t.url || t.openUrl, - s = t["media-url"] || t.mediaUrl; - return { - "open-url": e, - "media-url": s - } - } - if (this.isSurge()) { - let e = t.url || t.openUrl || t["open-url"]; - return { - url: e - } - } - } - }; - this.isMute || (this.isSurge() || this.isLoon() ? $notification.post(e, s, i, o(r)) : this.isQuanX() && $notify(e, s, i, o(r))); - let h = ["", "============== 系统通知 =============="]; - h.push(e), - s && h.push(s), - i && h.push(i), - console.log(h.join("\n")) - } - getMin (a, b) { - return ((a < b) ? a : b) - } - getMax (a, b) { - return ((a < b) ? b : a) - } - padStr (num, length, padding = '0') { - let numStr = String(num) - let numPad = (length > numStr.length) ? (length - numStr.length) : 0 - let retStr = '' - for (let i = 0; i < numPad; i++) { - retStr += padding - } - retStr += numStr - return retStr; - } - json2str (obj, c, encodeUrl = false) { - let ret = [] - for (let keys of Object.keys(obj).sort()) { - let v = obj[keys] - if (v && encodeUrl) v = encodeURIComponent(v) - ret.push(keys + '=' + v) - } - return ret.join(c); - } - str2json (str, decodeUrl = false) { - let ret = {} - for (let item of str.split('&')) { - if (!item) continue; - let idx = item.indexOf('=') - if (idx == -1) continue; - let k = item.substr(0, idx) - let v = item.substr(idx + 1) - if (decodeUrl) v = decodeURIComponent(v) - ret[k] = v - } - return ret; - } - randomString (len, charset = 'abcdef0123456789') { - let str = ''; - for (let i = 0; i < len; i++) { - str += charset.charAt(Math.floor(Math.random() * charset.length)); - } - return str; - } - randomList (a) { - let idx = Math.floor(Math.random() * a.length) - return a[idx] - } - wait (t) { - return new Promise(e => setTimeout(e, t)) - } - done (t = {}) { - const e = (new Date).getTime(), - s = (e - this.startTime) / 1e3; - console.log(`\n${this.name} 运行结束,共运行了 ${s} 秒!`) - if (this.isSurge() || this.isQuanX() || this.isLoon()) $done(t) - } - }(name, env) -}