├── .gitignore ├── LICENSE ├── README.md ├── api_ran_time.py ├── check.sample.json ├── check.sample.toml ├── checksendNotify.py ├── ck_dingdong.py ├── ck_giant.py ├── ck_hema_town.py ├── ck_manmanbuy.py ├── ck_pt_sign.py ├── ck_pupu_buy.py ├── ck_pupu_collectcards.py ├── ck_pupu_coupon.py ├── ck_pupu_history.py ├── ck_pupu_lottery.py ├── ck_pupu_sign.py ├── ck_rrtv.py ├── ck_wyxw.py ├── dailycheckin_scripts └── README.md ├── oc_xqz.py ├── other_scripts └── README.md ├── pupu_api.py ├── pupu_types.py └── utils.py /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | __pycache__/ 3 | 4 | .vscode/ 5 | 6 | .cache/ 7 | 8 | *.toml.lock 9 | 10 | check.toml 11 | 12 | *.log 13 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Sitoi 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
2 |

签到盒青龙版

3 |
4 | 5 | ![GitHub stars](https://img.shields.io/github/stars/cddjr/check?style=flat-square) 6 | ![GitHub forks](https://img.shields.io/github/forks/cddjr/check?style=flat-square) 7 | ![GitHub issues](https://img.shields.io/github/issues/cddjr/check?style=flat-square) 8 | ![GitHub issues](https://img.shields.io/github/languages/code-size/cddjr/check?style=flat-square) 9 | 10 | 11 | # 一个运行在青龙的签到函数 12 | 13 | [青龙](https://github.com/whyour/qinglong.git) 14 | 15 | ## 特别声明 16 | 17 | - 本仓库发布的脚本及其中涉及的任何解锁和解密分析脚本,仅用于测试和学习研究,禁止用于商业用途,不能保证其合法性,准确性,完整性和有效性,请根据情况自行判断。 18 | 19 | - 本项目内所有资源文件,禁止任何公众号、自媒体进行任何形式的转载、发布。 20 | 21 | - 本人对任何脚本问题概不负责,包括但不限于由任何脚本错误导致的任何损失或损害。 22 | 23 | - 间接使用脚本的任何用户,包括但不限于建立VPS或在某些行为违反国家/地区法律或相关法规的情况下进行传播, 本人对于由此引起的任何隐私泄漏或其他后果概不负责。 24 | 25 | - 请勿将本仓库的任何内容用于商业或非法目的,否则后果自负。 26 | 27 | - 如果任何单位或个人认为该项目的脚本可能涉嫌侵犯其权利,则应及时通知并提供身份证明,所有权证明,我们将在收到认证文件后删除相关脚本。 28 | 29 | - 任何以任何方式查看此项目的人或直接或间接使用该项目的任何脚本的使用者都应仔细阅读此声明。本人保留随时更改或补充此免责声明的权利。一旦使用并复制了任何相关脚本或Script项目的规则,则视为您已接受此免责声明。 30 | 31 | **您必须在下载后的24小时内从计算机或手机中完全删除以上内容** 32 | 33 | > ***您使用或者复制了本仓库且本人制作的任何脚本,则视为 `已接受` 此声明,请仔细阅读*** 34 | 35 | ## 支持的签到列表 36 | 37 | 可以在各文件夹查看 38 | 39 | #### 1.dailycheckin_scripts: 40 | 41 | 该文件夹下是 [sitoi/dailycheckin](https://github.com/sitoi/dailycheckin) 该项目的全部支持脚本 42 | 43 | [配置方式查看](https://github.com/cddjr/check/blob/master/dailycheckin_scripts/README.md) 44 | 45 | AcFun | 百度搜索资源平台 | Bilibili | 天翼云盘 | CSDN | 多看阅读 | 恩山论坛 | Fa米家 | 网易云游戏 | 葫芦侠 | 爱奇艺 | 全民K歌 | MEIZU 社区 | 芒果 TV | 小米运动 | 网易云音乐 | 一加手机社区官方论坛 | 哔咔漫画 | 吾爱破解 | 什么值得买 | 百度贴吧 | V2EX | 腾讯视频 | 微博 | 联通沃邮箱 | 哔咔网单 | 王者营地 | 有道云笔记 | 智友邦 | 机场签到 | 欢太商城 | NGA | 掘金 | GLaDOS | HiFiNi | 时光相册 | 联通营业厅 46 | 47 | ## 使用方法 48 | 49 | **进入容器后运行以下命令**(docker exec -it ql bash)修改ql为你的青龙容器名字 50 | 51 | 以下命令全部都是进入容器后输入 52 | 53 | ### 1.拉取仓库 54 | 55 | 只使用dailycheckin_scripts: 56 | 57 | ``` 58 | ql repo https://github.com/cddjr/check.git "ck_|api_" "" "checksend|utils|pupu" 59 | ``` 60 | 61 | 只使用others_scripts: 62 | 63 | ``` 64 | ql repo https://github.com/cddjr/check.git "oc_|api_" "" "checksend|utils|pupu" 65 | ``` 66 | 67 | 我全都要: 68 | 69 | ``` 70 | ql repo https://github.com/cddjr/check.git "ck_|oc_|api_" "" "checksend|utils|pupu" 71 | ``` 72 | 73 | ### 2.运行以下命令 74 | 75 | 旧版(青龙v2.12以下) 76 | 77 | ```shell 78 | cd /ql/repo/cddjr_check && python3 utils.py 79 | ``` 80 | 81 | 新版 82 | 83 | ```shell 84 | cd /ql/data/repo/cddjr_check && python3 utils.py 85 | ``` 86 | 87 | 然后不出意外的话你可以在青龙面板的配置文件下找到check.toml或check.json文件 88 | 89 | 然后根据各文件夹下REDEME修改配置[这里](https://sitoi.gitee.io/dailycheckin/settings/) 90 | 91 | ### 3.说明 92 | 93 | 1.本仓库在12.21日的更新中同时支持了json和toml两种格式的配置文件,但是推荐使用toml格式配置文件 94 | 95 | 2.当toml和json配置文件共存时优先使用toml文件 96 | 97 | 3.为避免未设置的签到项目推送,请禁止该签到任务,或注释掉配置文件中关于这个任务的配置项目 98 | 99 | 4.在运行修改运行时间后若出现未知错误 100 | 101 | **请先确认database.sqlite.back或crontab.db.back是否存在**,然后 102 | 103 | ``` 104 | cd /ql/data/db/ && rm database.sqlite && cp database.sqlite.back database.sqlite #v2.12+ 105 | ``` 106 | 107 | ``` 108 | cd /ql/db/ && rm database.sqlite && cp database.sqlite.back database.sqlite #v2.11+ 109 | ``` 110 | 111 | ``` 112 | cd /ql/db/ && rm crontab.db && cp crontab.db.back crontab.db #v2.11- 113 | ``` 114 | 115 | ### 4.**更新支持了多账号** 116 | 117 | toml配置方式 118 | 119 | ```toml 120 | [[ACFUN]] 121 | password = "Sitoi" 122 | phone = "188xxxxxxxx" 123 | 124 | [[ACFUN]] 125 | password = "123456" 126 | phone = "135xxxxxxxx" 127 | ``` 128 | 129 | json配置方式 130 | 131 | ```json 132 | "ACFUN" : [ 133 | { 134 | "password": "Sitoi", 135 | "phone": "18888xxxxxx" 136 | }, 137 | { 138 | "password": "多账号 密码填写,请参考上面", 139 | "phone": "多账号 手机号填写,请参考上面" 140 | } 141 | ], 142 | ``` 143 | 144 | ### 5.通知配置 145 | 146 | 来自于青龙的config.sh 147 | 148 | **在2022.4.10更新接入消息推送APP** 149 | 150 | 环境变量为设置别名的内容 151 | 152 | ```shell 153 | export MI_PUSH_ALIAS="********" 154 | ``` 155 | 156 | ## 其他 157 | 158 | #### 1.关于 toml 的语法参考: 159 | 160 | * [toml-lang/toml](https://github.com/toml-lang/toml) 161 | * [中文知乎介绍](https://zhuanlan.zhihu.com/p/50412485) 162 | * [TOML 教程中文版](https://toml.io/cn/v1.0.0) 163 | 164 | #### 2.排错指引 165 | 166 | 1.在sitoi/dailycheckin的某次更新中修改了键名,请尽量删除原配置文件后重新配置 167 | 168 | 2.本库找配置文件时使用了正则表达式,在最外层配置时可以不区分大小写,且只要包含字段就可以,甚至可以写中文(强烈不建议这么写,貌似toml不支持) 169 | 170 | 3.很多脚本并没有测试 171 | 172 | ## 致谢 173 | 174 | [@Wenmoux](https://github.com/Wenmoux/) 175 | 176 | [@Sitoi](https://github.com/Sitoi) 177 | 178 | [@Oreomeow](https://github.com/Oreomeow) 179 | 180 | 181 | ## Stargazers over time 182 | 183 | [![Stargazers over time](https://starchart.cc/cddjr/check.svg)](https://starchart.cc/cddjr/check) 184 | 185 | -------------------------------------------------------------------------------- /api_ran_time.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | 20220822 适配check仓库 4 | 20220916 适配青龙2.13.9+ 5 | :author @night-raise from github 6 | cron: 0 0 * * * 7 | new Env('随机定时'); 8 | """ 9 | 10 | from abc import ABC 11 | from random import randrange 12 | from typing import Dict, List 13 | 14 | import requests 15 | 16 | from utils import check, log 17 | 18 | 19 | class ClientApi(ABC): 20 | def __init__(self): 21 | self.cid = "" 22 | self.sct = "" 23 | self.url = "http://localhost:5700/" 24 | self.twice = False 25 | self.token = "" 26 | self.cron: List[Dict] = [] 27 | self.excluded: List[str] = [] 28 | self.required: List[str] = [] 29 | 30 | def init_cron(self): 31 | raise NotImplementedError 32 | 33 | def shuffle_cron(self): 34 | raise NotImplementedError 35 | 36 | def run(self): 37 | self.init_cron() 38 | self.shuffle_cron() 39 | 40 | @staticmethod 41 | def get_ran_min() -> str: 42 | return str(randrange(0, 60)) 43 | 44 | def get_ran_hour(self, is_api: bool = False) -> str: 45 | if is_api: 46 | return str(randrange(7, 9)) 47 | if self.twice: 48 | start = randrange(0, 12) 49 | return f"{start},{start + randrange(6, 12)}" 50 | # 由于部分脚本耗时可能超过30分钟 51 | # 为了避免跨越0点 限制时间在晚上23点前 52 | return str(randrange(0, 23)) 53 | 54 | def random_time(self, origin_time: str, command: str): 55 | if not any(kw in command for kw in self.required): 56 | # 不是必须随机的任务 57 | if any(kw in command for kw in self.excluded): 58 | # 在黑名单中 59 | return origin_time 60 | if command.find("ran_time") != -1 or command.find(" now") != -1: 61 | # 排除自身或者明确定义了now参数的任务 62 | return origin_time 63 | time = origin_time.split(" ") 64 | # 兼容带秒的定时 65 | ofst_hour = 1 if len(time) <= 5 else 2 66 | ofst_day = 2 if len(time) <= 5 else 3 67 | if command.find("rssbot") != -1 or command.find("hax") != -1: 68 | return ClientApi.get_ran_min() + " " + " ".join(time[ofst_hour:]) 69 | if command.find("api") != -1: 70 | return ( 71 | ClientApi.get_ran_min() 72 | + " " 73 | + self.get_ran_hour(True) 74 | + " " 75 | + " ".join(time[ofst_day:]) 76 | ) 77 | return ( 78 | ClientApi.get_ran_min() 79 | + " " 80 | + self.get_ran_hour() 81 | + " " 82 | + " ".join(time[ofst_day:]) 83 | ) 84 | 85 | 86 | class QLClient(ClientApi): 87 | def __init__(self, client_info: Dict): 88 | super().__init__() 89 | if ( 90 | not client_info 91 | or not (cid := client_info.get("client_id")) 92 | or not (sct := client_info.get("client_secret")) 93 | or not (keywords := client_info.get("keywords")) 94 | ): 95 | raise ValueError("无法获取 client 相关参数") 96 | else: 97 | self.cid = cid 98 | self.sct = sct 99 | self.keywords = keywords 100 | self.excluded = client_info.get("excluded", []) 101 | self.required = client_info.get("required", []) 102 | self.url = client_info.get("url", self.url).rstrip("/") + "/" 103 | self.twice = client_info.get("twice", False) 104 | self.token = requests.get( 105 | url=self.url + "open/auth/token", 106 | params={"client_id": self.cid, "client_secret": self.sct}, 107 | ).json()["data"]["token"] 108 | if not self.token: 109 | raise ValueError("无法获取 token") 110 | 111 | def init_cron(self): 112 | data = requests.get( 113 | url=self.url + "open/crons", 114 | headers={"Authorization": f"Bearer {self.token}"}, 115 | ).json()["data"] 116 | if isinstance(data, dict): 117 | # 兼容 v2.13.9+ 青龙 118 | data = data["data"] 119 | self.cron: List[Dict] = list( 120 | filter( 121 | lambda x: not x.get("isDisabled", 1) 122 | and x.get("command", "").find(self.keywords) != -1, 123 | data, 124 | ) 125 | ) 126 | 127 | def shuffle_cron(self): 128 | for c in self.cron: 129 | json = { 130 | "labels": c.get("labels", None), 131 | "command": c["command"], 132 | "schedule": self.random_time(c["schedule"], c["command"]), 133 | "name": c["name"], 134 | "id": c["id"], 135 | } 136 | requests.put( 137 | url=self.url + "open/crons", 138 | json=json, 139 | headers={"Authorization": f"Bearer {self.token}"}, 140 | ) 141 | 142 | 143 | @check(run_script_name="随机定时", run_script_expression="RANDOM", interval_max=0) 144 | def main(*args, **kwargs): 145 | msg = [] 146 | try: 147 | QLClient(client_info=kwargs.get("value", {})).run() 148 | log("处于启动状态的任务定时修改成功", msg) 149 | except ValueError as e: 150 | log(f"配置错误,{e},请检查你的配置文件", msg) 151 | except AttributeError: 152 | log("你的系统不支持运行随机定时", msg) 153 | return "\n".join(msg) 154 | 155 | 156 | if __name__ == "__main__": 157 | main() 158 | -------------------------------------------------------------------------------- /check.sample.json: -------------------------------------------------------------------------------- 1 | { 2 | "IQIYI": [ 3 | { 4 | "cookie": "__dfp=xxxxxx; QP0013=xxxxxx; QP0022=xxxxxx; QYABEX=xxxxxx; P00001=xxxxxx; P00002=xxxxxx; P00003=xxxxxx; P00007=xxxxxx; QC163=xxxxxx; QC175=xxxxxx; QC179=xxxxxx; QC170=xxxxxx; P00010=xxxxxx; P00PRU=xxxxxx; P01010=xxxxxx; QC173=xxxxxx; QC180=xxxxxx; P00004=xxxxxx; QP0030=xxxxxx; QC006=xxxxxx; QC007=xxxxxx; QC008=xxxxxx; QC010=xxxxxx; nu=xxxxxx; __uuid=xxxxxx; QC005=xxxxxx;" 5 | }, 6 | { 7 | "cookie": "多账号 cookie 填写,请参考上面,cookie 以实际获取为准(遇到特殊字符如双引号\" 请加反斜杠转义)" 8 | } 9 | ], 10 | "VQQ": [ 11 | { 12 | "auth_refresh": "https://access.video.qq.com/user/auth_refresh?vappid=xxxxxx&vsecret=xxxxxx&type=qq&g_tk=&g_vstk=xxxxxx&g_actk=xxxxxx&callback=xxxxxx&_=xxxxxx", 13 | "cookie": "pgv_pvid=xxxxxx; pac_uid=xxxxxx; RK=xxxxxx; ptcz=xxxxxx; tvfe_boss_uuid=xxxxxx; video_guid=xxxxxx; video_platform=xxxxxx; pgv_info=xxxxxx; main_login=xxxxxx; vqq_access_token=xxxxxx; vqq_appid=xxxxxx; vqq_openid=xxxxxx; vqq_vuserid=xxxxxx; vqq_refresh_token=xxxxxx; login_time_init=xxxxxx; uid=xxxxxx; vqq_vusession=xxxxxx; vqq_next_refresh_time=xxxxxx; vqq_login_time_init=xxxxxx; login_time_last=xxxxxx;" 14 | }, 15 | { 16 | "auth_refresh": "多账号 refresh url,请参考上面,以实际获取为准", 17 | "cookie": "多账号 cookie 填写,请参考上面,cookie 以实际获取为准(遇到特殊字符如双引号\" 请加反斜杠转义)" 18 | } 19 | ], 20 | "YOUDAO": [ 21 | { 22 | "cookie": "JSESSIONID=xxxxxx; __yadk_uid=xxxxxx; OUTFOX_SEARCH_USER_ID_NCOO=xxxxxx; YNOTE_SESS=xxxxxx; YNOTE_PERS=xxxxxx; YNOTE_LOGIN=xxxxxx; YNOTE_CSTK=xxxxxx; _ga=xxxxxx; _gid=xxxxxx; _gat=xxxxxx; PUBLIC_SHARE_18a9dde3de846b6a69e24431764270c4=xxxxxx;" 23 | }, 24 | { 25 | "cookie": "多账号 cookie 填写,请参考上面,cookie 以实际获取为准(遇到特殊字符如双引号\" 请加反斜杠转义)" 26 | } 27 | ], 28 | "KGQQ": [ 29 | { 30 | "cookie": "muid=xxxxxx; uid=xxxxxx; userlevel=xxxxxx; openid=xxxxxx; openkey=xxxxxx; opentype=xxxxxx; qrsig=xxxxxx; pgv_pvid=xxxxxx;" 31 | }, 32 | { 33 | "cookie": "多账号 cookie 填写,请参考上面,cookie 以实际获取为准(遇到特殊字符如双引号\" 请加反斜杠转义)" 34 | } 35 | ], 36 | "MUSIC163": [ 37 | { 38 | "password": "Sitoi", 39 | "phone": "18888xxxxxx" 40 | }, 41 | { 42 | "password": "多账号 密码", 43 | "phone": "多账号 手机号" 44 | } 45 | ], 46 | "ONEPLUSBBS": [ 47 | { 48 | "cookie": "acw_tc=xxxxxx; qKc3_0e8d_saltkey=xxxxxx; qKc3_0e8d_lastvisit=xxxxxx; bbs_avatar=xxxxxx; qKc3_0e8d_sendmail=xxxxxx; opcid=xxxxxx; opcct=xxxxxx; oppt=xxxxxx; opsid=xxxxxx; opsct=xxxxxx; opbct=xxxxxx; UM_distinctid=xxxxxx; CNZZDATA1277373783=xxxxxx; www_clear=xxxxxx; ONEPLUSID=xxxxxx; qKc3_0e8d_sid=xxxxxx; bbs_uid=xxxxxx; bbs_uname=xxxxxx; bbs_grouptitle=xxxxxx; opuserid=xxxxxx; bbs_sign=xxxxxx; bbs_formhash=xxxxxx; qKc3_0e8d_ulastactivity=xxxxxx; opsertime=xxxxxx; qKc3_0e8d_lastact=xxxxxx; qKc3_0e8d_checkpm=xxxxxx; qKc3_0e8d_noticeTitle=xxxxxx; optime_browser=xxxxxx; opnt=xxxxxx; opstep=xxxxxx; opstep_event=xxxxxx; fp=xxxxxx;" 49 | }, 50 | { 51 | "cookie": "多账号 cookie 填写,请参考上面,cookie 以实际获取为准(遇到特殊字符如双引号\" 请加反斜杠转义)" 52 | } 53 | ], 54 | "BAIDU": [ 55 | { 56 | "data_url": "https://cdn.jsdelivr.net/gh/Sitoi/Sitoi.github.io/baidu_urls.txt", 57 | "submit_url": "http://data.zz.baidu.com/urls?site=https://sitoi.cn&token=xxxxxx", 58 | "times": 10 59 | }, 60 | { 61 | "data_url": "多账号 data_url 链接地址,以实际获取为准", 62 | "submit_url": "多账号 submit_url 链接地址,以实际获取为准", 63 | "times": 10 64 | } 65 | ], 66 | "FMAPP": [ 67 | { 68 | "blackbox": "eyJlcnJxxxxxx", 69 | "cookie": "sensorsdata2015jssdkcross=xxxxxx", 70 | "device_id": "xxxxxx-xxxx-xxxx-xxxx-xxxxxx", 71 | "fmversion": "xxxxxx", 72 | "os": "xxxxxx", 73 | "token": "xxxxxx.xxxxxx-xxxxxx-xxxxxx.xxxxxx-xxxxxx", 74 | "useragent": "xxxxxx" 75 | }, 76 | { 77 | "blackbox": "多账号 blackbox 填写,请参考上面,blackbox 以实际获取为准(遇到特殊字符如双引号\" 请加反斜杠转义)", 78 | "cookie": "多账号 cookie 填写,请参考上面,cookie 以实际获取为准(遇到特殊字符如双引号\" 请加反斜杠转义)", 79 | "device_id": "多账号 device_id 填写,请参考上面,以实际获取为准", 80 | "fmversion": "多账号 fmVersion 填写,请参考上面,以实际获取为准", 81 | "os": "多账号 os 填写,请参考上面,以实际获取为准", 82 | "token": "多账号 token 填写,请参考上面,以实际获取为准", 83 | "useragent": "多账号 User-Agent 填写,请参考上面,以实际获取为准" 84 | } 85 | ], 86 | "TIEBA": [ 87 | { 88 | "cookie": "BIDUPSID=xxxxxx; PSTM=xxxxxx; BAIDUID=xxxxxx; BAIDUID_BFESS=xxxxxx; delPer=xxxxxx; PSINO=xxxxxx; H_PS_PSSID=xxxxxx; BA_HECTOR=xxxxxx; BDORZ=xxxxxx; TIEBA_USERTYPE=xxxxxx; st_key_id=xxxxxx; BDUSS=xxxxxx; BDUSS_BFESS=xxxxxx; STOKEN=xxxxxx; TIEBAUID=xxxxxx; ab_sr=xxxxxx; st_data=xxxxxx; st_sign=xxxxxx;" 89 | }, 90 | { 91 | "cookie": "多账号 cookie 填写,请参考上面,cookie 以实际获取为准(遇到特殊字符如双引号\" 请加反斜杠转义)" 92 | } 93 | ], 94 | "BILIBILI": [ 95 | { 96 | "cookie": "_uuid=xxxxxx; rpdid=xxxxxx; LIVE_BUVID=xxxxxx; PVID=xxxxxx; blackside_state=xxxxxx; CURRENT_FNVAL=xxxxxx; buvid3=xxxxxx; fingerprint3=xxxxxx; fingerprint=xxxxxx; buivd_fp=xxxxxx; buvid_fp_plain=xxxxxx; DedeUserID=xxxxxx; DedeUserID__ckMd5=xxxxxx; SESSDATA=xxxxxx; bili_jct=xxxxxx; bsource=xxxxxx; finger=xxxxxx; fingerprint_s=xxxxxx;", 97 | "coin_num": 0, 98 | "coin_type": 1, 99 | "silver2coin": true 100 | }, 101 | { 102 | "cookie": "多账号 cookie 填写,请参考上面,cookie 以实际获取为准(遇到特殊字符如双引号\" 请加反斜杠转义)", 103 | "coin_num": 0, 104 | "coin_type": 1, 105 | "silver2coin": true 106 | } 107 | ], 108 | "V2EX": [ 109 | { 110 | "cookie": "_ga=xxxxxx; __cfduid=xxxxxx; PB3_SESSION=xxxxxx; A2=xxxxxx; V2EXSETTINGS=xxxxxx; V2EX_REFERRER=xxxxxx; V2EX_LANG=xxxxxx; _gid=xxxxxx; V2EX_TAB=xxxxxx;", 111 | "proxy": "使用代理的信息,无密码例子: http://127.0.0.1:1080 有密码例子: http://username:password@127.0.0.1:1080" 112 | }, 113 | { 114 | "cookie": "多账号 cookie 填写,请参考上面,cookie 以实际获取为准(遇到特殊字符如双引号\" 请加反斜杠转义)", 115 | "proxy": "使用代理的信息,无密码例子: http://127.0.0.1:1080 有密码例子: http://username:password@127.0.0.1:1080" 116 | } 117 | ], 118 | "WWW2NZZ": [ 119 | { 120 | "cookie": "YPx9_2132_saltkey=xxxxxx; YPx9_2132_lastvisit=xxxxxx; YPx9_2132_sendmail=xxxxxx; YPx9_2132_con_request_uri=xxxxxx; YPx9_2132_sid=xxxxxx; YPx9_2132_client_created=xxxxxx; YPx9_2132_client_token=xxxxxx; YPx9_2132_ulastactivity=xxxxxx; YPx9_2132_auth=xxxxxx; YPx9_2132_connect_login=xxxxxx; YPx9_2132_connect_is_bind=xxxxxx; YPx9_2132_connect_uin=xxxxxx; YPx9_2132_stats_qc_login=xxxxxx; YPx9_2132_checkpm=xxxxxx; YPx9_2132_noticeTitle=xxxxxx; YPx9_2132_nofavfid=xxxxxx; YPx9_2132_lastact=xxxxxx;" 121 | }, 122 | { 123 | "cookie": "多账号 cookie 填写,请参考上面,cookie 以实际获取为准(遇到特殊字符如双引号\" 请加反斜杠转义)" 124 | } 125 | ], 126 | "SMZDM": [ 127 | { 128 | "cookie": "__jsluid_s=xxxxxx; __ckguid=xxxxxx; device_id=xxxxxx; homepage_sug=xxxxxx; r_sort_type=xxxxxx; _zdmA.vid=xxxxxx; sajssdk_2015_cross_new_user=xxxxxx; sensorsdata2015jssdkcross=xxxxxx; footer_floating_layer=xxxxxx; ad_date=xxxxxx; ad_json_feed=xxxxxx; zdm_qd=xxxxxx; sess=xxxxxx; user=xxxxxx; _zdmA.uid=xxxxxx; smzdm_id=xxxxxx; userId=xxxxxx; bannerCounter=xxxxxx; _zdmA.time=xxxxxx;" 129 | }, 130 | { 131 | "cookie": "多账号 cookie 填写,请参考上面,cookie 以实际获取为准(遇到特殊字符如双引号\" 请加反斜杠转义)" 132 | } 133 | ], 134 | "MIMOTION": [ 135 | { 136 | "max_step": "20000", 137 | "min_step": "10000", 138 | "password": "Sitoi", 139 | "phone": "18888xxxxxx" 140 | }, 141 | { 142 | "max_step": "多账号 最大步数填写,请参考上面", 143 | "min_step": "多账号 最小步数填写,请参考上面", 144 | "password": "多账号 密码填写,请参考上面", 145 | "phone": "多账号 手机号填写,请参考上面" 146 | } 147 | ], 148 | "ACFUN": [ 149 | { 150 | "password": "Sitoi", 151 | "phone": "18888xxxxxx" 152 | }, 153 | { 154 | "password": "多账号 密码填写,请参考上面", 155 | "phone": "多账号 手机号填写,请参考上面" 156 | } 157 | ], 158 | "CLOUD189": [ 159 | { 160 | "password": "Sitoi", 161 | "phone": "18888xxxxxx" 162 | }, 163 | { 164 | "password": "多账号 密码填写,请参考上面", 165 | "phone": "多账号 手机号填写,请参考上面" 166 | } 167 | ], 168 | "POJIE": [ 169 | { 170 | "cookie": "htVD_2132_client_token=xxxxxx; htVD_2132_connect_is_bind=xxxxxx; htVD_2132_connect_uin=xxxxxx; htVD_2132_nofavfid=xxxxxx; htVD_2132_smile=xxxxxx; Hm_lvt_46d556462595ed05e05f009cdafff31a=xxxxxx; htVD_2132_saltkey=xxxxxx; htVD_2132_lastvisit=xxxxxx; htVD_2132_client_created=xxxxxx; htVD_2132_auth=xxxxxx; htVD_2132_connect_login=xxxxxx; htVD_2132_home_diymode=xxxxxx; htVD_2132_visitedfid=xxxxxx; htVD_2132_viewid=xxxxxx; KF4=xxxxxx; htVD_2132_st_p=xxxxxx; htVD_2132_lastcheckfeed=xxxxxx; htVD_2132_sid=xxxxxx; htVD_2132_ulastactivity=xxxxxx; htVD_2132_noticeTitle=xxxxxx;" 171 | }, 172 | { 173 | "cookie": "多账号 cookie 填写,请参考上面,cookie 以实际获取为准(遇到特殊字符如双引号\" 请加反斜杠转义)" 174 | } 175 | ], 176 | "MGTV": [ 177 | { 178 | "params": "uuid=xxxxxx&uid=xxxxxx&ticket=xxxxxx&token=xxxxxx&device=iPhone&did=xxxxxx&deviceId=xxxxxx&appVersion=6.8.2&osType=ios&platform=iphone&abroad=0&aid=xxxxxx&nonce=xxxxxx×tamp=xxxxxx&appid=xxxxxx&type=1&sign=xxxxxx&callback=xxxxxx" 179 | }, 180 | { 181 | "params": "多账号 请求参数填写,请参考上面" 182 | } 183 | ], 184 | "PICACOMIC": [ 185 | { 186 | "email": "Sitoi", 187 | "password": "xxxxxx" 188 | }, 189 | { 190 | "email": "多账号 账号填写,请参考上面", 191 | "password": "多账号 密码填写,请参考上面" 192 | } 193 | ], 194 | "MEIZU": [ 195 | { 196 | "draw_count": "1", 197 | "cookie": "aliyungf_tc=xxxxxx; logined_uid=xxxxxx; acw_tc=xxxxxx; LT=xxxxxx; MZBBS_2132_saltkey=xxxxxx; MZBBS_2132_lastvisit=xxxxxx; MZBBSUC_2132_auth=xxxxxx; MZBBSUC_2132_loginmember=xxxxxx; MZBBSUC_2132_ticket=xxxxxx; MZBBS_2132_sid=xxxxxx; MZBBS_2132_ulastactivity=xxxxxx; MZBBS_2132_auth=xxxxxx; MZBBS_2132_loginmember=xxxxxx; MZBBS_2132_lastcheckfeed=xxxxxx; MZBBS_2132_checkfollow=xxxxxx; MZBBS_2132_lastact=xxxxxx;" 198 | }, 199 | { 200 | "draw_count": "多账号 抽奖次数设置", 201 | "cookie": "多账号 cookie 填写,请参考上面,cookie 以实际获取为准(遇到特殊字符如双引号\" 请加反斜杠转义)" 202 | } 203 | ], 204 | "ZHIYOO": [ 205 | { 206 | "cookie": "ikdQ_9242_saltkey=xxxxxx; ikdQ_9242_lastvisit=xxxxxx; ikdQ_9242_onlineusernum=xxxxxx; ikdQ_9242_sendmail=1; ikdQ_9242_seccode=xxxxxx; ikdQ_9242_ulastactivity=xxxxxx; ikdQ_9242_auth=xxxxxx; ikdQ_9242_connect_is_bind=xxxxxx; ikdQ_9242_nofavfid=xxxxxx; ikdQ_9242_checkpm=xxxxxx; ikdQ_9242_noticeTitle=1; ikdQ_9242_sid=xxxxxx; ikdQ_9242_lip=xxxxxx; ikdQ_9242_lastact=xxxxxx" 207 | }, 208 | { 209 | "cookie": "多账号 cookie 填写,请参考上面,cookie 以实际获取为准(遇到特殊字符如双引号\" 请加反斜杠转义)" 210 | } 211 | ], 212 | "WEIBO": [ 213 | { 214 | "url": "https://api.weibo.cn/2/users/show?wm=xxxxxx&launchid=xxxxxx&b=xxxxxx&from=xxxxxx&c=xxxxxx&networktype=xxxxxx&v_p=xxxxxx&skin=xxxxxx&v_f=xxxxxx&lang=xxxxxx&sflag=xxxxxx&ua=xxxxxx&ft=xxxxxx&aid=xxxxxx&has_extend=xxxxxx&uid=xxxxxx&gsid=xxxxxx&sourcetype=&get_teenager=xxxxxx&s=xxxxxx&has_profile=xxxxxx" 215 | }, 216 | { 217 | "url": "多账号 show_url 填写,请参考上面,show_url 以实际获取为准(遇到特殊字符如双引号\" 请加反斜杠转义)" 218 | } 219 | ], 220 | "DUOKAN": [ 221 | { 222 | "cookie": "user_id=xxxxxx; token=xxxxxx; user_gender=xxxxxx; device_id=xxxxxx; app_id=xxxxxx; build=xxxxxx; short_version=xxxxxx" 223 | }, 224 | { 225 | "cookie": "多账号 cookie 填写,请参考上面,cookie 以实际获取为准(遇到特殊字符如双引号\" 请加反斜杠转义)" 226 | } 227 | ], 228 | "CSDN": [ 229 | { 230 | "cookie": "uuid_tt_dd=xxxxxx; _ga=xxxxxx; UserName=xxxxxx; UserInfo=xxxxxx; UserToken=xxxxxx; UserNick=xxxxxx; AU=768; UN=xxxxxx; BT=xxxxxx; p_uid=xxxxxx; Hm_up_6bcd52f51e9b3dce32bec4a3997715ac=xxxxxx; Hm_ct_6bcd52f51e9b3dce32bec4a3997715ac=xxxxxx; Hm_lvt_6bcd52f51e9b3dce32bec4a3997715ac=xxxxxx dc_sid=xxxxxx; c_segment=xxxxxx; dc_session_id=xxxxxx; csrfToken=xxxxxx; c_first_ref=xxxxxx; c_first_page=xxxxxx; c_page_id=xxxxxx; announcement-new=xxxxxx; log_Id_click=xxxxxx; c_pref=xxxxxx; c_ref=xxxxxx; dc_tos=xxxxxx; log_Id_pv=xxxxxx; log_Id_view=xxxxxx" 231 | }, 232 | { 233 | "cookie": "多账号 cookie 填写,请参考上面,cookie 以实际获取为准(遇到特殊字符如双引号\" 请加反斜杠转义)" 234 | } 235 | ], 236 | "WZYD": [ 237 | { 238 | "data": "areaId=xxxxxx&roleId=xxxxxx&gameId=xxxxxx&serverId=xxxxxx&gameOpenid=xxxxxx&userId=xxxxxx&appVersion=xxxxxx&cClientVersionName=xxxxxx&platid=xxxxxx&source=xxxxxx&algorithm=xxxxxx&version=xxxxxx×tamp=xxxxxx&appid=xxxxxx&openid=xxxxxx&sig=xxxxxx&encode=2&msdkEncodeParam=xxxxxx&cSystem=xxxxxx&h5Get=xxxxxx&msdkToken=&appOpenid=xxxxxx" 239 | }, 240 | { 241 | "data": "多账号 data 填写,请参考上面,data 以实际获取为准(遇到特殊字符如双引号\" 请加反斜杠转义)" 242 | } 243 | ], 244 | "WOMAIL": [ 245 | { 246 | "url": "https://nyan.mail.wo.cn/cn/sign/index/index?mobile=xxxxxx&userName=&openId=xxxxxx", 247 | "pause21days": true, 248 | "password": "Sitoi", 249 | "phone": "18888xxxxxx" 250 | }, 251 | { 252 | "url": "多账号 url 填写,请参考上面,url 以实际获取为准(遇到特殊字符如双引号\" 请加反斜杠转义)", 253 | "pause21days": true, 254 | "password": "多账号 密码填写,请参考上面", 255 | "phone": "多账号 手机号填写,请参考上面" 256 | } 257 | ], 258 | "HEYTAP": [ 259 | { 260 | "cookie": "sa_distinct_id=xxxxxx;Personalized=xxxxxx;s_channel=xxxxxx;source_type=xxxxxx;app_param=xxxxxx;ENCODE_TOKENSID=xxxxxx;scene_id=xxxxxx;apkPkg=xxxxxx;exp_id=;app_utm=xxxxxx;TOKENSID=xxxxxx;strategy_id=xxxxxx;referer=;experiment_id=xxxxxx;section_id=;s_version=xxxxxx;app_innerutm=xxxxxx;retrieve_id=;log_id=;", 261 | "useragent": "xxxxxx", 262 | "draw": false 263 | }, 264 | { 265 | "cookie": "多账号 cookie 填写,请参考上面,cookie 以实际获取为准(遇到特殊字符如双引号\" 请加反斜杠转义)", 266 | "useragent": "多账号 User-Agent 填写,请参考上面,以实际获取为准", 267 | "draw": false 268 | } 269 | ], 270 | "UNICOM": [ 271 | { 272 | "mobile": "18888xxxxxx", 273 | "password": "xxxxxx", 274 | "app_id": "xxxxxx" 275 | }, 276 | { 277 | "mobile": "多账号 手机号", 278 | "password": "多账号 密码", 279 | "app_id": "多账号 appId" 280 | } 281 | ], 282 | "EVERPHOTO": [ 283 | { 284 | "mobile": "+8618888xxxxxx", 285 | "password": "xxxxxx" 286 | }, 287 | { 288 | "mobile": "多账号 手机号", 289 | "password": "多账号 密码" 290 | } 291 | ] 292 | } -------------------------------------------------------------------------------- /check.sample.toml: -------------------------------------------------------------------------------- 1 | # 配置文件 2 | #by @Oreomeow 3 | #有些脚本未进库配置也没什么用 4 | # cookie 以实际获取为准,如遇到带双引号的需用 \ 转义,或改为单引号,或将最外层双引号改为单引号 5 | # e.g.1 cookie = "xxx; app_param={\"brand\":\"iPhone\"}" 6 | # e.g.2 cookie = "xxx; app_param={'brand':'iPhone'}" 7 | # e.g.3 cookie = 'xxx; app_param={"brand":"iPhone"}' 8 | 9 | ## 爱企查e卡监控 10 | #ECARDCHECK = false 11 | # 12 | ## Hax 监控 13 | #HAX = false 14 | # 15 | ## LeetCode 每日一题 16 | #LEETCODE = false 17 | # 18 | ## 每日一句 19 | #MOTTO = true 20 | # 21 | ## 每日新闻 22 | #NEWS = true 23 | # 24 | # 随机定时 25 | # 青龙 id;青龙 api secret;青龙的接口链接;是否所有签到每日运行两次;需要定时的命令关键字 26 | [[RANDOM]] 27 | client_id = "" 28 | client_secret = "" 29 | url = "http://localhost:5700" 30 | twice = false 31 | keywords = "cddjr_check" 32 | # 33 | ## 天气预报 34 | #CITY = ["上海", "朝阳区"] 35 | 36 | # AcFun 37 | # https://www.acfun.cn 38 | ######### 多账号示例 ######### 39 | [[ACFUN]] 40 | password = "Sitoi" 41 | phone = "188xxxxxxxx" 42 | [[ACFUN]] 43 | password = "123456" 44 | phone = "135xxxxxxxx" 45 | ######### 多账号示例 ######### 46 | 47 | # 机场签到 48 | # 邮箱 49 | # 密码 50 | # 链接,只支持 SSPanel 类型。删除 /auth/login 51 | [[AIRPORT]] 52 | email = "xxxxxxxx@qq.com" 53 | password = "xxxxxxxx" 54 | url = "https://xxxxxxxx.xxx" 55 | 56 | # 爱企查【WEB】 57 | # https://www.baidu.com,同百度贴吧 cookie 一样 58 | # 批量查询任务需手动抓包查询之后的 exportkey,可不填 59 | [[AQC]] 60 | cookie = 'log_guid=xxxxxx; BDPPN=xxx"xxx;...' 61 | exportkey = "" 62 | 63 | # 百度搜索资源平台 64 | # 提交网站的 URL 链接 65 | # https://ziyuan.baidu.com/site/index# 提交百度网站的目标 URL 66 | # 每日对同个网站提交次数 67 | [[BAIDU]] 68 | data_url = "https://cdn.jsdelivr.net/gh/Sitoi/Sitoi.github.io/baidu_urls.txt" 69 | submit_url = "http://data.zz.baidu.com/urls?site=https://sitoi.cn&token=xxxxxx" 70 | times = 10 71 | 72 | # Bilibili【WEB】 73 | # 每日投币数量 74 | # 投币方式:1-关注用户列表,0-随机;若关注用户视频不足,则其余随机 75 | # https://www.bilibili.com cookie 76 | # 是否开启银币兑换硬币 77 | [[BILIBILI]] 78 | coin_num = 0 79 | coin_type = 1 80 | cookie = "_uuid=xxxxxx; rpdid=xxx'xxx; LIVE_BUVID=xxxxxx; PVID=xxxxxx; blackside_state=xxxxxx; CURRENT_FNVAL=xxxxxx; buvid3=xxxxxx; fingerprint3=xxxxxx; fingerprint=xxxxxx; buivd_fp=xxxxxx; buvid_fp_plain=xxxxxx; DedeUserID=xxxxxx; DedeUserID__ckMd5=xxxxxx; SESSDATA=xxxxxx; bili_jct=xxxxxx; bsource=xxxxxx; finger=xxxxxx; fingerprint_s=xxxxxx;" 81 | silver2coin = true 82 | 83 | # CCAVA【WEB】 84 | # https://pc.ccava.net/invites/CKIN66 cookie 85 | [[CCAVA]] 86 | cookie = "__vtins__JGU9XP985GXENwEs=xxxxxx; ......cookie_islog=x; cookieVf=xxxxxx; is_mochu_us_load=xxxxxx" 87 | 88 | # 天翼云盘 89 | # https://cloud.189.cn/web/login.html 90 | [[CLOUD189]] 91 | password = "Sitoi" 92 | phone = "18888xxxxxx" 93 | 94 | # CSDN【WEB】 95 | # https://www.csdn.net cookie 96 | [[CSDN]] 97 | cookie = "uuid_tt_dd=xxxxxx; _ga=xxxxxx; UserName=xxxxxx; UserInfo=xxxxxx; UserToken=xxxxxx; UserNick=xxxxxx; AU=768; UN=xxxxxx; BT=xxxxxx; p_uid=xxxxxx; Hm_up_6bcd52f51e9b3dce32bec4a3997715ac=xxxxxx; Hm_ct_6bcd52f51e9b3dce32bec4a3997715ac=xxxxxx; Hm_lvt_6bcd52f51e9b3dce32bec4a3997715ac=xxxxxx dc_sid=xxxxxx; c_segment=xxxxxx; dc_session_id=xxxxxx; csrfToken=xxxxxx; c_first_ref=xxxxxx; c_first_page=xxxxxx; c_page_id=xxxxxx; announcement-new=xxxxxx; log_Id_click=xxxxxx; c_pref=xxxxxx; c_ref=xxxxxx; dc_tos=xxxxxx; log_Id_pv=xxxxxx; log_Id_view=xxxxxx" 98 | 99 | # 网易蜗牛读书【APP】 100 | # 网易蜗牛读书 APP 签到后抓取 https://du.163.com/activity/201907/activityCenter/sign.json 下的 cookie 101 | # 自备 UA 102 | [[DU163]] 103 | cookie = "X-Auth-Token=xxxxxx; JSESSIONID-WNYD-WEB=xxxxxx; _cid=xxxxxx; _xsrf=xxxxxx" 104 | user_agent = "" 105 | 106 | # 多看阅读【APP】 107 | # 多看阅读 APP 抓取开头为 https://www.duokan.com 下的 cookie 108 | [[DUOKAN]] 109 | cookie = "user_id=xxxxxx; token=xxxxxx; user_gender=xxxxxx; device_id=xxxxxx; app_id=xxxxxx; build=xxxxxx; short_version=xxxxxx" 110 | 111 | # 企鹅电竞【WEB】 112 | # https://egame.qq.com cookie 113 | [[EGAME]] 114 | cookie = "pgv_pvi=xxxxxx; RK=xxxxxx; ptcz=xxxxxx; pgv_pvid=xxxxxx; pac_uid=xxxxxx; iip=x; o_cookie=xxxxxx; sd_userid=xxxxxx; sd_cookie_crttime=xxxxxx; tvfe_boss_uuid=xxxxxx; _tc_unionid=xxxxxx; _ga=xxxxxx; pgg_ssid=xxxxxx; new_device=x; pgv_info=xxxxxx; ts_refer=xxxxxx; ts_uid=xxxxxx; _qpsvr_localtk=xxxxxx; pgg_uid=xxxxxx; pgg_appid=xxxxxx; pgg_openid=xxxxxx; pgg_access_token=xxxxxx; pgg_type=x; pgg_user_type=x; pgg_pvid=xxxxxx; ts_last=xxxxxx; tKeplerToken=xxxxxx" 115 | 116 | # 恩山论坛【WEB】 117 | # https://www.right.com.cn/forum/forum.php cookie 118 | [[ENSHAN]] 119 | cookie = "DJyZ_2132_saltkey=xxxxxx; DJyZ_2132_lastvisit=xxxxxx; DJyZ_2132_onlineusernum=xxxx; DJyZ_2132_sendmail=1; DJyZ_2132_ulastactivity=xxxxxx; DJyZ_2132_auth=xxxxxxxx; DJyZ_2132_sid=0; DJyZ_2132_connect_is_bind=0; DJyZ_2132_nofavfid=1; DJyZ_2132_checkpm=1; DJyZ_2132_noticeTitle=1; DJyZ_2132_lastact=xxxxxx" 120 | 121 | # Epic【WEB】 122 | # https://www.epicgames.com/store/zh-CN 123 | [[EPIC]] 124 | email = "" 125 | password = "" 126 | 127 | # EUserv 128 | # https://apitruecaptcha.org/api 的 apikey 129 | # 教程:https://github.com/a-beam-of-light/eu_ex#readme 130 | # https://support.euserv.com 密码 131 | # https://apitruecaptcha.org/api 的 userid 132 | # https://support.euserv.com 邮箱 133 | #[[EUSERV]] 134 | #apikey = "xxxxxx" 135 | #mailparser_dl_url_id = "xxxxxx" 136 | #password = "xxxxxx" 137 | #userid = "xxxxxx" 138 | #username = "xxxxxx@xxx.com" 139 | 140 | # 时光相册【WEB】 141 | # https://web.everphoto.cn/#signin 请求 URL https://web.everphoto.cn/api/auth 最下方表单数据,带区号 142 | # 同上,不是原始密码 143 | [[EVERPHOTO]] 144 | mobile = "+86188xxxxxxxx" 145 | password = "c63xxxxxxxxxxxxxxxxxxxxxxxxxxxxx" 146 | 147 | # Fa米家【APP】 148 | # Fa米家 APP headers 中寻找 149 | [[FMAPP]] 150 | blackbox = "eyJlcnJxxxxxx" 151 | cookie = "sensorsdata2015jssdkcross=xxxxxx" 152 | device_id = "xxxxxx-xxxx-xxxx-xxxx-xxxxxx" 153 | fmversion = "xxxxxx" 154 | os = "xxxxxx" 155 | token = "xxxxxx.xxxxxx-xxxxxx-xxxxxx.xxxxxx-xxxxxx" 156 | useragent = "xxxxxx" 157 | 158 | # Freenom 159 | # https://www.freenom.com 160 | [[FREENOM]] 161 | password = "" 162 | username = "" 163 | 164 | # 网易云游戏【WEB】 165 | # 网易云游戏官网 https://cg.163.com/#/mobile cookie 166 | [[GAME163]] 167 | authorization = "" 168 | 169 | # GLaDOS【WEB】 170 | # https://glados.network cookie 171 | [[GLADOS]] 172 | cookie = "koa:sess=xxxxxx; koa:sess.sig=xxxxxx" 173 | 174 | # 欢太商城【APP】 175 | # 欢太商城 APP cookie,建议全部粘贴 176 | # 抽奖总开关,设置为 false 将不参与任何抽奖,应对一些抽奖黑号 177 | # 欢太商城 APP UA 178 | [[HEYTAP]] 179 | cookie = 'source_type=xxx;TOKENSID=TOKEN_xxxx;app_param=xx"xx' 180 | draw = false 181 | useragent = "" 182 | 183 | # HiFiNi【WEB】 184 | # https://www.hifini.com cookie 185 | [[HIFINI]] 186 | cookie = "bbs_sid=xxxxxx; bbs_token=xxxxxx" 187 | 188 | # 葫芦侠【APP】 189 | [[HLX]] 190 | password = "" 191 | username = "" 192 | 193 | # HOSTLOC 194 | # https://hostloc.com 195 | [[HOSTLOC]] 196 | password = "" 197 | username = "" 198 | 199 | # 爱奇艺【WEB】 200 | # https://www.iqiyi.com cookie 201 | [[IQIYI]] 202 | cookie = "__dfp=xxxxxx; QP0013=xxxxxx; QP0022=xxxxxx; QYABEX=xxxxxx; P00001=xxxxxx; P00002=xxxxxx; P00003=xxxxxx; P00007=xxxxxx; QC163=xxxxxx; QC175=xxxxxx; QC179=xxxxxx; QC170=xxxxxx; P00010=xxxxxx; P00PRU=xxxxxx; P01010=xxxxxx; QC173=xxxxxx; QC180=xxxxxx; P00004=xxxxxx; QP0030=xxxxxx; QC006=xxxxxx; QC007=xxxxxx; QC008=xxxxxx; QC010=xxxxxx; nu=xxxxxx; __uuid=xxxxxx; QC005=xxxxxx;" 203 | 204 | # 无忧行【APP】 205 | # 无忧行 APP 利用 stream 通过 mitm 抓包,获取 payload 中的 user_id 206 | [[JEGOTRIP]] 207 | user_id = "" 208 | 209 | # 掘金【WEB】 210 | # https://juejin.cn cookie 211 | [[JUEJIN]] 212 | cookie = "ttcid=xxxxxx; MONITOR_WEB_ID=xxxxxx; passport_csrf_token_default=xxxxxx; passport_csrf_token=xxxxxx; s_v_web_id=xxxxxx; MONITOR_DEVICE_ID=xxxxxx; n_mh=xxxxxx; passport_auth_status=xxxxxx; passport_auth_status_ss=xxxxxx; sid_guard=xxxxxx; uid_tt=xxxxxx; uid_tt_ss=xxxxxx; sid_tt=xxxxxx; sessionid=xxxxxx; sessionid_ss=xxxxxx; sid_ucp_v1=xxxxxx; ssid_ucp_v1=xxxxxx; odin_tt=xxxxxx; tt_scid=xxxxxx" 213 | 214 | # 全民K歌【WEB】 215 | # https://kg.qq.com/index-pc.html cookie 216 | [[KGQQ]] 217 | cookie = "muid=xxxxxx; uid=xxxxxx; userlevel=xxxxxx; openid=xxxxxx; openkey=xxxxxx; opentype=xxxxxx; qrsig=xxxxxx; pgv_pvid=xxxxxx;" 218 | 219 | # 联想乐云【WEB】 220 | # https://lecloud.lenovo.com/index cookie 221 | [[LECLOUD]] 222 | cookie = "" 223 | 224 | # 联想商城【APP】 225 | # 联想智选 APP 账户 226 | # 联想智选 APP 抓包 227 | # 联想智选 APP 密码 228 | [[LENOVO]] 229 | account = "" 230 | baseinfo = "" 231 | password = "" 232 | 233 | # MEIZU 社区【WEB】 234 | # https://bbs.meizu.cn cookie 235 | # 抽奖次数 236 | [[MEIZU]] 237 | cookie = "aliyungf_tc=xxxxxx; logined_uid=xxxxxx; acw_tc=xxxxxx; LT=xxxxxx; MZBBS_2132_saltkey=xxxxxx; MZBBS_2132_lastvisit=xxxxxx; MZBBSUC_2132_auth=xxxxxx; MZBBSUC_2132_loginmember=xxxxxx; MZBBSUC_2132_ticket=xxxxxx; MZBBS_2132_sid=xxxxxx; MZBBS_2132_ulastactivity=xxxxxx; MZBBS_2132_auth=xxxxxx; MZBBS_2132_loginmember=xxxxxx; MZBBS_2132_lastcheckfeed=xxxxxx; MZBBS_2132_checkfollow=xxxxxx; MZBBS_2132_lastact=xxxxxx;" 238 | draw_count = "1" 239 | 240 | # 芒果 TV【WEB】 241 | # https://www.mgtv.com 请求参数 242 | [[MGTV]] 243 | params = "uuid=xxxxxx&uid=xxxxxx&ticket=xxxxxx&token=xxxxxx&device=iPhone&did=xxxxxx&deviceId=xxxxxx&appVersion=6.8.2&osType=ios&platform=iphone&abroad=0&aid=xxxxxx&nonce=xxxxxx×tamp=xxxxxx&appid=xxxxxx&type=1&sign=xxxxxx&callback=xxxxxx" 244 | 245 | # 小米运动【APP】 246 | # 最大步数 247 | # 最小步数 248 | # 小米运动 APP 密码 249 | # 小米运动 APP 手机号 250 | [[MIMOTION]] 251 | max_step = "20000" 252 | min_step = "10000" 253 | password = "Sitoi" 254 | phone = "18888xxxxxx" 255 | 256 | # 网易云音乐 257 | [[MUSIC163]] 258 | password = "Sitoi" 259 | phone = "18888xxxxxx" 260 | 261 | # NGA【APP】 262 | # https://ngabbs.com/nuke.php POST 请求主体 access_token 263 | # https://ngabbs.com/nuke.php POST 请求主体 access_uid,纯数字 264 | [[NGA]] 265 | token = "X8xxxxghxxxxxxxxujxxxxxxxv9xxxxxxxxccxxx" 266 | uid = "xxxxx391" 267 | 268 | # 一加手机社区官方论坛【WEB】 269 | # http://www.oneplusbbs.com cookie 270 | [[ONEPLUSBBS]] 271 | cookie = "acw_tc=xxxxxx; qKc3_0e8d_saltkey=xxxxxx; qKc3_0e8d_lastvisit=xxxxxx; bbs_avatar=xxxxxx; qKc3_0e8d_sendmail=xxxxxx; opcid=xxxxxx; opcct=xxxxxx; oppt=xxxxxx; opsid=xxxxxx; opsct=xxxxxx; opbct=xxxxxx; UM_distinctid=xxxxxx; CNZZDATA1277373783=xxxxxx; www_clear=xxxxxx; ONEPLUSID=xxxxxx; qKc3_0e8d_sid=xxxxxx; bbs_uid=xxxxxx; bbs_uname=xxxxxx; bbs_grouptitle=xxxxxx; opuserid=xxxxxx; bbs_sign=xxxxxx; bbs_formhash=xxxxxx; qKc3_0e8d_ulastactivity=xxxxxx; opsertime=xxxxxx; qKc3_0e8d_lastact=xxxxxx; qKc3_0e8d_checkpm=xxxxxx; qKc3_0e8d_noticeTitle=xxxxxx; optime_browser=xxxxxx; opnt=xxxxxx; opstep=xxxxxx; opstep_event=xxxxxx; fp=xxxxxx;" 272 | 273 | # 哔咔漫画【APP】 274 | [[PICACOMIC]] 275 | email = "Sitoi" 276 | password = "xxxxxx" 277 | 278 | # 吾爱破解【WEB】 279 | # https://www.52pojie.cn cookie 280 | [[POJIE]] 281 | cookie = "htVD_2132_client_token=xxxxxx; htVD_2132_connect_is_bind=xxxxxx; htVD_2132_connect_uin=xxxxxx; htVD_2132_nofavfid=xxxxxx; htVD_2132_smile=xxxxxx; Hm_lvt_46d556462595ed05e05f009cdafff31a=xxxxxx; htVD_2132_saltkey=xxxxxx; htVD_2132_lastvisit=xxxxxx; htVD_2132_client_created=xxxxxx; htVD_2132_auth=xxxxxx; htVD_2132_connect_login=xxxxxx; htVD_2132_home_diymode=xxxxxx; htVD_2132_visitedfid=xxxxxx; htVD_2132_viewid=xxxxxx; KF4=xxxxxx; htVD_2132_st_p=xxxxxx; htVD_2132_lastcheckfeed=xxxxxx; htVD_2132_sid=xxxxxx; htVD_2132_ulastactivity=xxxxxx; htVD_2132_noticeTitle=xxxxxx;" 282 | 283 | # SF 轻小说【APP】 284 | [[SFACG]] 285 | authorization = "Basic xxxxxx==" 286 | cookie = ".SFCommunity=xxxxxx; session_APP=xxxxxx" 287 | sfsecurity = "nonce=xxxxxx×tamp=xxxxxx&devicetoken=xxxxxx&sign=xxxxxx" 288 | useragent = "boluobao/xxxxxx" 289 | 290 | # Site【WEB】 291 | # cookie 292 | # 三种类型,pt discuz hifi 293 | # 已测试支持站点:pterclub, btschool, pthome 294 | [[SITE]] 295 | cookie = "" 296 | type = "pt" 297 | url = "https://www.haidan.video" 298 | 299 | # 什么值得买 300 | # https://www.smzdm.com cookie 301 | [[SMZDM]] 302 | cookie = "sess=xxxxxx;" 303 | 304 | # 百度贴吧 305 | # https://tieba.baidu.com/index.html cookie 306 | [[TIEBA]] 307 | cookie = "BIDUPSID=xxxxxx; PSTM=xxxxxx; BAIDUID=xxxxxx; BAIDUID_BFESS=xxxxxx; delPer=xxxxxx; PSINO=xxxxxx; H_PS_PSSID=xxxxxx; BA_HECTOR=xxxxxx; BDORZ=xxxxxx; TIEBA_USERTYPE=xxxxxx; st_key_id=xxxxxx; BDUSS=xxxxxx; BDUSS_BFESS=xxxxxx; STOKEN=xxxxxx; TIEBAUID=xxxxxx; ab_sr=xxxxxx; st_data=xxxxxx; st_sign=xxxxxx;" 308 | 309 | # 在线工具【WEB】 310 | # https://plus.tool.lu cookie 311 | [[TOOLU]] 312 | cookie = "uuid=xxxxxx; _access=xxxxxx; XSRF-TOKEN=xxxxxx;" 313 | 314 | # 联通营业厅【APP】 315 | # 联通营业厅 APP 抓包域名为 https://m.client.10010.com/mobileService/login.htm 请求内容的 appId 316 | # 联通营业厅 APP 手机号 317 | # 联通营业厅 APP 密码 318 | [[UNICOM]] 319 | app_id = "" 320 | mobile = "18888xxxxxx" 321 | password = "" 322 | 323 | # V2EX【WEB】 324 | # https://v2ex.com,注意要抓签到那次的 ck(相当于需要先手动浏览器签到一次),不然可能签到无效 325 | # 使用代理信息,无密码示例:http://127.0.0.1:1080 有密码示例:http://username:password@127.0.0.1:1080 326 | [[V2EX]] 327 | cookie = "_ga=xxxxxx; __cfduid=xxxxxx; PB3_SESSION=xxxxxx; A2=xxxxxx; V2EXSETTINGS=xxxxxx; V2EX_REFERRER=xxxxxx; V2EX_LANG=xxxxxx; _gid=xxxxxx; V2EX_TAB=xxxxxx;" 328 | proxy = "" 329 | 330 | # 腾讯视频【WEB】 331 | # https://v.qq.com 搜索带有 auth_refresh 的 url,填写其完整的 URL 332 | # https://v.qq.com 搜索带有 auth_refresh 的 url,填写其完整的 URL 333 | [[VQQ]] 334 | auth_refresh = "https://access.video.qq.com/user/auth_refresh?vappid=xxxxxx&vsecret=xxxxxx&type=qq&g_tk=&g_vstk=xxxxxx&g_actk=xxxxxx&callback=xxxxxx&_=xxxxxx" 335 | cookie = "pgv_pvid=xxxxxx; pac_uid=xxxxxx; RK=xxxxxx; ptcz=xxxxxx; tvfe_boss_uuid=xxxxxx; video_guid=xxxxxx; video_platform=xxxxxx; pgv_info=xxxxxx; main_login=xxxxxx; vqq_access_token=xxxxxx; vqq_appid=xxxxxx; vqq_openid=xxxxxx; vqq_vuserid=xxxxxx; vqq_refresh_token=xxxxxx; login_time_init=xxxxxx; uid=xxxxxx; vqq_vusession=xxxxxx; vqq_next_refresh_time=xxxxxx; vqq_login_time_init=xxxxxx; login_time_last=xxxxxx;" 336 | 337 | # 微博【APP】 338 | # 微博 APP 抓取开头为 https://api.weibo.cn/2/users/show? 的整个 url 填入即可 339 | [[WEIBO]] 340 | url = "https://api.weibo.cn/2/users/show?wm=xxxxxx&launchid=xxxxxx&b=xxxxxx&from=xxxxxx&c=xxxxxx&networktype=xxxxxx&v_p=xxxxxx&skin=xxxxxx&v_f=xxxxxx&lang=xxxxxx&sflag=xxxxxx&ua=xxxxxx&ft=xxxxxx&aid=xxxxxx&has_extend=xxxxxx&uid=xxxxxx&gsid=xxxxxx&sourcetype=&get_teenager=xxxxxx&s=xxxxxx&has_profile=xxxxxx" 341 | 342 | # 联通沃邮箱【公众号】 343 | # 密码 344 | # true 为开启21天自动暂停,false 为关闭自动暂停、每天都签到,默认开启自动暂停 345 | # 手机号 346 | # 联通沃邮箱公众号 https://nyan.mail.wo.cn/cn/sign/index/index?mobile 开头的 URL 347 | [[WOMAIL]] 348 | password = "Sitoi" 349 | pause21days = true 350 | phone = "18888xxxxxx" 351 | url = "https://nyan.mail.wo.cn/cn/sign/index/index?mobile=xxxxxx&userName=&openId=xxxxxx" 352 | 353 | # WPS【WEB】 354 | # https://www.kdocs.cn cookie 355 | [[WPS]] 356 | cookie = "" 357 | 358 | # 咔叽网单【WEB】 359 | # http://www.2nzz.com cookie 360 | [[WWW2NZZ]] 361 | cookie = "YPx9_2132_saltkey=xxxxxx; YPx9_2132_lastvisit=xxxxxx; YPx9_2132_sendmail=xxxxxx; YPx9_2132_con_request_uri=xxxxxx; YPx9_2132_sid=xxxxxx; YPx9_2132_client_created=xxxxxx; YPx9_2132_client_token=xxxxxx; YPx9_2132_ulastactivity=xxxxxx; YPx9_2132_auth=xxxxxx; YPx9_2132_connect_login=xxxxxx; YPx9_2132_connect_is_bind=xxxxxx; YPx9_2132_connect_uin=xxxxxx; YPx9_2132_stats_qc_login=xxxxxx; YPx9_2132_checkpm=xxxxxx; YPx9_2132_noticeTitle=xxxxxx; YPx9_2132_nofavfid=xxxxxx; YPx9_2132_lastact=xxxxxx;" 362 | 363 | # 王者营地【APP】 364 | # 王者营地 APP 请求体中的 data,抓包域名为 https://ssl.kohsocial.qq.com 请求内容的全部参数 365 | [[WZYD]] 366 | data = "areaId=xxxxxx&roleId=xxxxxx&gameId=xxxxxx&serverId=xxxxxx&gameOpenid=xxxxxx&userId=xxxxxx&appVersion=xxxxxx&cClientVersionName=xxxxxx&platid=xxxxxx&source=xxxxxx&algorithm=xxxxxx&version=xxxxxx×tamp=xxxxxx&appid=xxxxxx&openid=xxxxxx&sig=xxxxxx&encode=2&msdkEncodeParam=xxxxxx&cSystem=xxxxxx&h5Get=xxxxxx&msdkToken=&appOpenid=xxxxxx" 367 | 368 | # 有道云笔记 369 | # https://note.youdao.com cookie 370 | [[YOUDAO]] 371 | cookie = "JSESSIONID=xxxxxx; __yadk_uid=xxxxxx; OUTFOX_SEARCH_USER_ID_NCOO=xxxxxx; YNOTE_SESS=xxxxxx; YNOTE_PERS=xxxxxx; YNOTE_LOGIN=xxxxxx; YNOTE_CSTK=xxxxxx; _ga=xxxxxx; _gid=xxxxxx; _gat=xxxxxx; PUBLIC_SHARE_18a9dde3de846b6a69e24431764270c4=xxxxxx;" 372 | 373 | # 智友邦【WEB】 374 | # http://bbs.zhiyoo.net cookie 375 | [[ZHIYOO]] 376 | cookie = "ikdQ_9242_saltkey=xxxxxx; ikdQ_9242_lastvisit=xxxxxx; ikdQ_9242_onlineusernum=xxxxxx; ikdQ_9242_sendmail=1; ikdQ_9242_seccode=xxxxxx; ikdQ_9242_ulastactivity=xxxxxx; ikdQ_9242_auth=xxxxxx; ikdQ_9242_connect_is_bind=xxxxxx; ikdQ_9242_nofavfid=xxxxxx; ikdQ_9242_checkpm=xxxxxx; ikdQ_9242_noticeTitle=1; ikdQ_9242_sid=xxxxxx; ikdQ_9242_lip=xxxxxx; ikdQ_9242_lastact=xxxxxx" 377 | -------------------------------------------------------------------------------- /checksendNotify.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # _*_ coding:utf-8 _*_ 3 | import base64 4 | import hashlib 5 | import hmac 6 | import json 7 | import os 8 | import re 9 | import threading 10 | import time 11 | import traceback 12 | import urllib.parse 13 | 14 | import requests 15 | import tomli 16 | 17 | # from utils_env import get_file_path 18 | 19 | link_reg = re.compile(r"(.+)<\s?/a>") 20 | bold_reg = re.compile(r"\s*(.+)\s*<\s?/b>") 21 | list_reg = re.compile(r"^(\d+\.|-)\s.+$") 22 | 23 | 24 | def html2md(content: str) -> str: 25 | content = "\n".join(map(lambda x: x if list_reg.fullmatch(x) else x + "\n", content.split("\n"))) 26 | return bold_reg.sub(r"### **\1**", link_reg.sub(r"[\2](\1)", content)) 27 | 28 | 29 | # 原先的 print 函数和主线程的锁 30 | _print = print 31 | mutex = threading.Lock() 32 | 33 | 34 | # 定义新的 print 函数 35 | def print(text, *args, **kw): 36 | """ 37 | 使输出有序进行,不出现多线程同一时间输出导致错乱的问题。 38 | """ 39 | with mutex: 40 | _print(text, *args, **kw) 41 | 42 | 43 | # 通知服务 44 | # fmt: off 45 | push_config = { 46 | 'HITOKOTO': False, # 启用一言(随机句子) 47 | 48 | 'BARK_PUSH': '', # bark IP 或设备码,例:https://api.day.app/DxHcxxxxxRxxxxxxcm 49 | 'BARK_ARCHIVE': '', # bark 推送是否存档 50 | 'BARK_GROUP': '', # bark 推送分组 51 | 'BARK_SOUND': '', # bark 推送声音 52 | 53 | 'CONSOLE': False, # 控制台输出 54 | 55 | 'DD_BOT_SECRET': '', # 钉钉机器人的 DD_BOT_SECRET 56 | 'DD_BOT_TOKEN': '', # 钉钉机器人的 DD_BOT_TOKEN 57 | 58 | 'FSKEY': '', # 飞书机器人的 FSKEY 59 | 60 | 'GOBOT_URL': '', # go-cqhttp 61 | # 推送到个人QQ:http://127.0.0.1/send_private_msg 62 | # 群:http://127.0.0.1/send_group_msg 63 | 'GOBOT_QQ': '', # go-cqhttp 的推送群或用户 64 | # GOBOT_URL 设置 /send_private_msg 时填入 user_id=个人QQ 65 | # /send_group_msg 时填入 group_id=QQ群 66 | 'GOBOT_TOKEN': '', # go-cqhttp 的 access_token 67 | 68 | 'IGOT_PUSH_KEY': '', # iGot 聚合推送的 IGOT_PUSH_KEY 69 | 70 | 'PUSH_KEY': '', # server 酱的 PUSH_KEY,兼容旧版与 Turbo 版 71 | 72 | 'PUSH_PLUS_TOKEN': '', # push+ 微信推送的用户令牌 73 | 'PUSH_PLUS_USER': '', # push+ 微信推送的群组编码 74 | 75 | 'QMSG_KEY': '', # qmsg 酱的 QMSG_KEY 76 | 'QMSG_TYPE': '', # qmsg 酱的 QMSG_TYPE 77 | 78 | 'QYWX_AM': '', # 企业微信应用 79 | 80 | 'QYWX_KEY': '', # 企业微信机器人 81 | 82 | 'TG_BOT_TOKEN': '', # tg 机器人的 TG_BOT_TOKEN,例:1407203283:AAG9rt-6RDaaX0HBLZQq0laNOh898iFYaRQ 83 | 'TG_USER_ID': '', # tg 机器人的 TG_USER_ID,例:1434078534 84 | 'TG_API_HOST': '', # tg 代理 api 85 | 'TG_PROXY_AUTH': '', # tg 代理认证参数 86 | 'TG_PROXY_HOST': '', # tg 机器人的 TG_PROXY_HOST 87 | 'TG_PROXY_PORT': '', # tg 机器人的 TG_PROXY_PORT 88 | 'MI_PUSH_ALIAS': '', 89 | } 90 | notify_function = [] 91 | # fmt: on 92 | 93 | # 首先读取 面板变量 或者 github action 运行变量 94 | for k in push_config: 95 | if v := os.getenv(k): 96 | push_config[k] = v 97 | 98 | # 读取配置文件中的变量 (会覆盖环境变量) 99 | CONFIG_PATH = os.getenv("NOTIFY_CONFIG_PATH") # or get_file_path("notify.toml") 100 | if CONFIG_PATH and os.path.exists(CONFIG_PATH): 101 | print(f"通知配置文件存在:{CONFIG_PATH}。") 102 | try: 103 | for k, v in dict(tomli.load(open(CONFIG_PATH, "rb"))).items(): 104 | if k in push_config: 105 | push_config[k] = v 106 | except tomli.TOMLDecodeError: 107 | print( 108 | f"错误:配置文件 {CONFIG_PATH} 格式不对,请学习 https://toml.io/cn/v1.0.0\n错误信息:\n{traceback.format_exc()}" 109 | ) 110 | elif CONFIG_PATH: 111 | print(f"{CONFIG_PATH} 配置的通知文件不存在,请检查文件位置或删除对应环境变量!") 112 | 113 | 114 | def bark(title: str, content: str) -> None: 115 | """ 116 | 使用 bark 推送消息。 117 | """ 118 | if not push_config.get("BARK_PUSH"): 119 | print("bark 服务的 BARK_PUSH 未设置!!\n取消推送") 120 | return 121 | print("bark 服务启动") 122 | 123 | if push_config.get("BARK_PUSH").startswith("http"): 124 | url = f'{push_config.get("BARK_PUSH").rstrip("/")}/{urllib.parse.quote_plus(title)}/{urllib.parse.quote_plus(content)}' 125 | else: 126 | url = f'https://api.day.app/{push_config.get("BARK_PUSH")}/{urllib.parse.quote_plus(title)}/{urllib.parse.quote_plus(content)}' 127 | 128 | bark_params = { 129 | "BARK_ARCHIVE": "isArchive", 130 | "BARK_GROUP": "group", 131 | "BARK_SOUND": "sound", 132 | } 133 | params = "" 134 | for pair in filter( 135 | lambda pairs: pairs[0].startswith("BARK_") 136 | and pairs[0] != "BARK_PUSH" 137 | and pairs[1] 138 | and bark_params.get(pairs[0]), 139 | push_config.items(), 140 | ): 141 | params += f"{bark_params.get(pair[0])}={pair[1]}&" 142 | if params: 143 | url = url + "?" + params.rstrip("&") 144 | 145 | datas = requests.get(url, timeout=15).json() 146 | if datas.get("code") == 200: 147 | print("bark 推送成功!") 148 | elif datas.get("code") == 400: 149 | print("bark 推送失败!找不到 Key 对应的 DeviceToken。") 150 | else: 151 | print(f"bark 推送失败!响应数据:{datas}") 152 | 153 | 154 | def console(title: str, content: str) -> None: 155 | """ 156 | 使用 控制台 推送消息。 157 | """ 158 | print(f"{title}\n\n{content}") 159 | 160 | 161 | def dingding_bot(title: str, content: str) -> None: 162 | """ 163 | 使用 钉钉机器人 推送消息。 164 | """ 165 | if not push_config.get("DD_BOT_TOKEN"): 166 | print("钉钉机器人 服务的 DD_BOT_TOKEN 未设置!!\n取消推送") 167 | return 168 | print("钉钉机器人 服务启动") 169 | 170 | url = f'https://oapi.dingtalk.com/robot/send?access_token={push_config.get("DD_BOT_TOKEN")}' 171 | if "DD_BOT_SECRET" in push_config: 172 | timestamp = str(round(time.time() * 1000)) 173 | secret_enc = push_config.get("DD_BOT_SECRET").encode("utf-8") 174 | string_to_sign = "{}\n{}".format(timestamp, push_config.get("DD_BOT_SECRET")) 175 | string_to_sign_enc = string_to_sign.encode("utf-8") 176 | hmac_code = hmac.new( 177 | secret_enc, string_to_sign_enc, digestmod=hashlib.sha256 178 | ).digest() 179 | sign = urllib.parse.quote_plus(base64.b64encode(hmac_code)) 180 | url += f'×tamp={timestamp}&sign={sign}' 181 | 182 | headers = {"Content-Type": "application/json;charset=utf-8"} 183 | data = {"msgtype": "markdown", "markdown": {"text": f'#### {title}\n\n{html2md(content)}', "title": title}} 184 | 185 | datas = requests.post( 186 | url=url, data=json.dumps(data), headers=headers, timeout=15 187 | ).json() 188 | if datas.get("errcode") == 0: 189 | print("钉钉机器人 推送成功!") 190 | else: 191 | print(f"钉钉机器人 推送失败!响应数据:{datas}") 192 | 193 | 194 | def feishu_bot(title: str, content: str) -> None: 195 | """ 196 | 使用 飞书机器人 推送消息。 197 | """ 198 | if not push_config.get("FSKEY"): 199 | print("飞书 服务的 FSKEY 未设置!!\n取消推送") 200 | return 201 | print("飞书 服务启动") 202 | 203 | url = f'https://open.feishu.cn/open-apis/bot/v2/hook/{push_config.get("FSKEY")}' 204 | data = {"msg_type": "text", "content": {"text": f"{title}\n\n{content}"}} 205 | 206 | datas = requests.post(url, data=json.dumps(data), timeout=15) 207 | datas = datas.json 208 | if datas.get("StatusCode") == 0: 209 | print("飞书 推送成功!") 210 | else: 211 | print(f"飞书 推送失败!响应数据:{datas}") 212 | 213 | 214 | def go_cqhttp(title: str, content: str) -> None: 215 | """ 216 | 使用 go_cqhttp 推送消息。 217 | """ 218 | if not push_config.get("GOBOT_URL") or not push_config.get("GOBOT_QQ"): 219 | print("go-cqhttp 服务的 GOBOT_URL 或 GOBOT_QQ 未设置!!\n取消推送") 220 | return 221 | print("go-cqhttp 服务启动") 222 | 223 | url = f'{push_config.get("GOBOT_URL")}?access_token={push_config.get("GOBOT_TOKEN")}&{push_config.get("GOBOT_QQ")}&message=标题:{title}\n内容:{content}' 224 | 225 | datas = requests.get(url, timeout=15).json() 226 | if datas.get("status") == "ok": 227 | print("go-cqhttp 推送成功!") 228 | else: 229 | print(f"go-cqhttp 推送失败!响应数据:{datas}") 230 | 231 | 232 | def iGot(title: str, content: str) -> None: 233 | """ 234 | 使用 iGot 推送消息。 235 | """ 236 | if not push_config.get("IGOT_PUSH_KEY"): 237 | print("iGot 服务的 IGOT_PUSH_KEY 未设置!!\n取消推送") 238 | return 239 | print("iGot 服务启动") 240 | 241 | url = f'https://push.hellyw.com/{push_config.get("IGOT_PUSH_KEY")}' 242 | data = {"title": title, "content": content} 243 | headers = {"Content-Type": "application/x-www-form-urlencoded"} 244 | 245 | datas = requests.post(url, data=data, headers=headers, timeout=15).json() 246 | if datas.get("ret") == 0: 247 | print("iGot 推送成功!") 248 | else: 249 | print(f'iGot 推送失败!错误信息:{datas.get("errMsg")}') 250 | 251 | 252 | def serverJ(title: str, content: str) -> None: 253 | """ 254 | 通过 serverJ 推送消息。 255 | """ 256 | if not push_config.get("PUSH_KEY"): 257 | print("serverJ 服务的 PUSH_KEY 未设置!!\n取消推送") 258 | return 259 | print("serverJ 服务启动") 260 | 261 | data = {"text": title, "desp": content.replace("\n", "\n\n")} 262 | if push_config.get("PUSH_KEY").index("SCT") != -1: 263 | url = f'https://sctapi.ftqq.com/{push_config.get("PUSH_KEY")}.send' 264 | else: 265 | url = f'https://sc.ftqq.com/${push_config.get("PUSH_KEY")}.send' 266 | 267 | datas = requests.post(url, data=data, timeout=15).json() 268 | if datas.get("errno") == 0 or datas.get("code") == 0: 269 | print("serverJ 推送成功!") 270 | elif datas.get("code") == 40001: 271 | print("serverJ 推送失败!PUSH_KEY 错误。") 272 | else: 273 | print(f'serverJ 推送失败!错误码:{datas.get("message")}') 274 | 275 | 276 | def pushplus_bot(title: str, content: str) -> None: 277 | """ 278 | 通过 push+ 推送消息。 279 | """ 280 | if not push_config.get("PUSH_PLUS_TOKEN"): 281 | print("PUSHPLUS 服务的 PUSH_PLUS_TOKEN 未设置!!\n取消推送") 282 | return 283 | print("PUSHPLUS 服务启动") 284 | 285 | url = "http://www.pushplus.plus/send" 286 | data = { 287 | "token": push_config.get("PUSH_PLUS_TOKEN"), 288 | "title": title, 289 | "content": content, 290 | "topic": push_config.get("PUSH_PLUS_USER"), 291 | } 292 | body = json.dumps(data).encode(encoding="utf-8") 293 | headers = {"Content-Type": "application/json"} 294 | 295 | datas = requests.post(url=url, data=body, headers=headers, timeout=15).json() 296 | if datas.get("code") == 200: 297 | print("PUSHPLUS 推送成功!") 298 | elif datas.get("code") == 600: 299 | url2 = "http://pushplus.hxtrip.com/send" 300 | headers["Accept"] = "application/json" 301 | datas2 = requests.post(url=url2, data=body, headers=headers, timeout=15).json() 302 | if datas2.get("code") == 200: 303 | print("PUSHPLUS(hxtrip) 推送成功!") 304 | elif datas2.get("code") == 600: 305 | print("PUSHPLUS 推送失败!PUSH_PLUS_TOKEN 错误。") 306 | else: 307 | print(f"PUSHPLUS(hxtrip) 推送失败!响应数据:{datas2}") 308 | else: 309 | print(f"PUSHPLUS 推送失败!响应数据:{datas}") 310 | 311 | 312 | def qmsg_bot(title: str, content: str) -> None: 313 | """ 314 | 使用 qmsg 推送消息。 315 | """ 316 | if not push_config.get("QMSG_KEY") or not push_config.get("QMSG_TYPE"): 317 | print("qmsg 的 QMSG_KEY 或者 QMSG_TYPE 未设置!!\n取消推送") 318 | return 319 | print("qmsg 服务启动") 320 | 321 | url = f'https://qmsg.zendee.cn/{push_config.get("QMSG_TYPE")}/{push_config.get("QMSG_KEY")}' 322 | payload = {"msg": f'{title}\n\n{content.replace("----", "-")}'.encode("utf-8")} 323 | 324 | datas = requests.post(url=url, params=payload, timeout=15).json() 325 | if datas.get("code") == 0: 326 | print("qmsg 推送成功!") 327 | else: 328 | print(f'qmsg 推送失败!错误信息:{datas.get("reason")}') 329 | 330 | 331 | def mi_push(title: str, content: str) -> None: 332 | """ 333 | 使用来自消息接收APP的推送服务 334 | """ 335 | if not push_config.get("MI_PUSH_ALIAS"): 336 | print("MI_PUSH_ALIAS 未设置!!\n取消推送") 337 | url = "http://tdtt.top" 338 | data = {"title": title, "content": content, "alias": push_config.get("MI_PUSH_ALIAS")} 339 | datas = requests.post(url=url, data=data) 340 | print(f"失败 {datas}") if not datas.status_code == requests.codes.ok else print('MI_PUSH推送成功') 341 | return 342 | 343 | 344 | def wecom_app(title: str, content: str) -> None: 345 | """ 346 | 通过 企业微信 APP 推送消息。 347 | """ 348 | if not push_config.get("QYWX_AM"): 349 | print("QYWX_AM 未设置!!\n取消推送") 350 | return 351 | QYWX_AM_AY = re.split(",", push_config.get("QYWX_AM")) 352 | if 4 < len(QYWX_AM_AY) > 5: 353 | print("QYWX_AM 设置错误!!\n取消推送") 354 | return 355 | print("企业微信 APP 服务启动") 356 | 357 | corpid = QYWX_AM_AY[0] 358 | corpsecret = QYWX_AM_AY[1] 359 | touser = QYWX_AM_AY[2] 360 | agentid = QYWX_AM_AY[3] 361 | try: 362 | media_id = QYWX_AM_AY[4] 363 | except IndexError: 364 | media_id = "" 365 | wx = WeCom(corpid, corpsecret, agentid) 366 | # 如果没有配置 media_id 默认就以 text 方式发送 367 | if not media_id: 368 | message = title + "\n\n" + content 369 | datas = wx.send_text(message, touser) 370 | else: 371 | datas = wx.send_mpnews(title, content, media_id, touser) 372 | if datas == "ok": 373 | print("企业微信推送成功!") 374 | else: 375 | print(f"企业微信推送失败!错误信息:{datas}") 376 | 377 | 378 | class WeCom: 379 | def __init__(self, corpid, corpsecret, agentid): 380 | self.CORPID = corpid 381 | self.CORPSECRET = corpsecret 382 | self.AGENTID = agentid 383 | 384 | def get_access_token(self): 385 | url = "https://qyapi.weixin.qq.com/cgi-bin/gettoken" 386 | values = { 387 | "corpid": self.CORPID, 388 | "corpsecret": self.CORPSECRET, 389 | } 390 | req = requests.post(url, params=values, timeout=15) 391 | datas = json.loads(req.text) 392 | return datas.get("access_token") 393 | 394 | def send_text(self, message, touser="@all"): 395 | send_url = ( 396 | "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=" 397 | + self.get_access_token() 398 | ) 399 | send_values = { 400 | "touser": touser, 401 | "msgtype": "text", 402 | "agentid": self.AGENTID, 403 | "text": {"content": message}, 404 | "safe": "0", 405 | } 406 | send_msges = bytes(json.dumps(send_values), "utf-8") 407 | datas = requests.post(send_url, send_msges, timeout=15).json() 408 | return datas.get("errmsg") 409 | 410 | def send_mpnews(self, title, message, media_id, touser="@all"): 411 | send_url = ( 412 | "https://qyapi.weixin.qq.com/cgi-bin/message/send?access_token=" 413 | + self.get_access_token() 414 | ) 415 | send_values = { 416 | "touser": touser, 417 | "msgtype": "mpnews", 418 | "agentid": self.AGENTID, 419 | "mpnews": { 420 | "articles": [ 421 | { 422 | "title": title, 423 | "thumb_media_id": media_id, 424 | "author": "Author", 425 | "content_source_url": "", 426 | "content": message.replace("\n", "
"), 427 | "digest": message, 428 | } 429 | ] 430 | }, 431 | } 432 | send_msges = bytes(json.dumps(send_values), "utf-8") 433 | datas = requests.post(send_url, send_msges, timeout=15).json() 434 | return datas.get("errmsg") 435 | 436 | 437 | def wecom_bot(title: str, content: str) -> None: 438 | """ 439 | 通过 企业微信机器人 推送消息。 440 | """ 441 | if not push_config.get("QYWX_KEY"): 442 | print("企业微信机器人 服务的 QYWX_KEY 未设置!!\n取消推送") 443 | return 444 | print("企业微信机器人服务启动") 445 | 446 | url = f"https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key={push_config.get('QYWX_KEY')}" 447 | headers = {"Content-Type": "application/json;charset=utf-8"} 448 | data = {"msgtype": "text", "text": {"content": f"{title}\n\n{content}"}} 449 | 450 | datas = requests.post( 451 | url=url, data=json.dumps(data), headers=headers, timeout=15 452 | ).json() 453 | if datas.get("errcode") == 0: 454 | print("企业微信机器人 推送成功!") 455 | else: 456 | print(f"企业微信机器人 推送失败!响应数据:{datas}") 457 | 458 | 459 | def telegram_bot(title: str, content: str) -> None: 460 | """ 461 | 使用 telegram 机器人 推送消息。 462 | """ 463 | if not push_config.get("TG_BOT_TOKEN") or not push_config.get("TG_USER_ID"): 464 | print("tg 服务的 bot_token 或者 user_id 未设置!!\n取消推送") 465 | return 466 | print("tg 服务启动") 467 | 468 | if push_config.get("TG_API_HOST"): 469 | url = f"https://{push_config.get('TG_API_HOST')}/bot{push_config.get('TG_BOT_TOKEN')}/sendMessage" 470 | else: 471 | url = ( 472 | f"https://api.telegram.org/bot{push_config.get('TG_BOT_TOKEN')}/sendMessage" 473 | ) 474 | headers = {"Content-Type": "application/x-www-form-urlencoded"} 475 | payload = { 476 | "chat_id": str(push_config.get("TG_USER_ID")), 477 | "text": f"{title}\n\n{content}", 478 | "disable_web_page_preview": "true", 479 | "parse_mode": "HTML", 480 | } 481 | proxies = None 482 | if push_config.get("TG_PROXY_HOST") and push_config.get("TG_PROXY_PORT"): 483 | if push_config.get("TG_PROXY_AUTH") is not None and "@" not in push_config.get("TG_PROXY_HOST"): 484 | push_config["TG_PROXY_HOST"] = ( 485 | push_config.get("TG_PROXY_AUTH") 486 | + "@" 487 | + push_config.get("TG_PROXY_HOST") 488 | ) 489 | proxyStr = "http://{}:{}".format( 490 | push_config.get("TG_PROXY_HOST"), push_config.get("TG_PROXY_PORT") 491 | ) 492 | proxies = {"http": proxyStr, "https": proxyStr} 493 | 494 | datas = requests.post( 495 | url=url, headers=headers, params=payload, proxies=proxies, timeout=15 496 | ).json() 497 | if datas.get("ok") == True: 498 | print("tg 推送成功!") 499 | elif datas.get("error_code") == 400: 500 | print("tg 推送失败!请主动给 bot 发送一条消息并检查接收用户 TG_USER_ID 是否正确。") 501 | elif datas.get("error_code") == 401: 502 | print("tg 推送失败!TG_BOT_TOKEN 填写错误。") 503 | else: 504 | print(f"tg 推送失败!响应数据:{datas}") 505 | 506 | 507 | def one() -> str: 508 | """ 509 | 获取一条一言。 510 | :return: 511 | """ 512 | try: 513 | url = "https://v1.hitokoto.cn/" 514 | res = requests.get(url).json() 515 | return res["hitokoto"] + " ----" + res["from"] 516 | except requests.exceptions.ConnectionError: 517 | return "" 518 | 519 | 520 | if push_config.get("BARK_PUSH"): 521 | notify_function.append(bark) 522 | if push_config.get("CONSOLE"): 523 | notify_function.append(console) 524 | if push_config.get("DD_BOT_TOKEN"): 525 | notify_function.append(dingding_bot) 526 | if push_config.get("FSKEY"): 527 | notify_function.append(feishu_bot) 528 | if push_config.get("GOBOT_URL") and push_config.get("GOBOT_QQ"): 529 | notify_function.append(go_cqhttp) 530 | if push_config.get("IGOT_PUSH_KEY"): 531 | notify_function.append(iGot) 532 | if push_config.get("PUSH_KEY"): 533 | notify_function.append(serverJ) 534 | if push_config.get("PUSH_PLUS_TOKEN"): 535 | notify_function.append(pushplus_bot) 536 | if push_config.get("QMSG_KEY") and push_config.get("QMSG_TYPE"): 537 | notify_function.append(qmsg_bot) 538 | if push_config.get("QYWX_AM"): 539 | notify_function.append(wecom_app) 540 | if push_config.get("QYWX_KEY"): 541 | notify_function.append(wecom_bot) 542 | if push_config.get("TG_BOT_TOKEN") and push_config.get("TG_USER_ID"): 543 | notify_function.append(telegram_bot) 544 | if push_config.get("MI_PUSH_ALIAS"): 545 | notify_function.append(mi_push) 546 | 547 | 548 | def excepthook(args, /): 549 | if issubclass(args.exc_type, requests.exceptions.RequestException): 550 | print( 551 | f"网络异常,请检查你的网络连接、推送服务器和代理配置,该错误和账号配置无关。信息:{str(args.exc_type)}, {args.thread.name}" 552 | ) 553 | elif issubclass(args.exc_type, json.JSONDecodeError): 554 | print( 555 | f"推送返回值非 json 格式,请检查网址和账号是否填写正确。信息:{str(args.exc_type)}, {args.thread.name}" 556 | ) 557 | else: 558 | global default_hook 559 | default_hook(args) 560 | 561 | 562 | default_hook = threading.excepthook 563 | threading.excepthook = excepthook 564 | 565 | 566 | def send(title: str, content: str) -> None: 567 | if not content: 568 | print(f"{title} 推送内容为空!") 569 | return 570 | 571 | if title in os.getenv("NOTIFY_SKIP_LIST", "").split("&"): 572 | print(f"{title} 已屏蔽推送") 573 | return 574 | 575 | hitokoto = push_config.get("HITOKOTO") 576 | 577 | content += "\n\n> " + one() if hitokoto else "" 578 | 579 | ts = [ 580 | threading.Thread(target=mode, args=(title, content), name=mode.__name__) 581 | for mode in notify_function 582 | ] 583 | [t.start() for t in ts] 584 | [t.join() for t in ts] 585 | 586 | 587 | def main(): 588 | send("title", "content") 589 | 590 | 591 | if __name__ == "__main__": 592 | main() 593 | -------------------------------------------------------------------------------- /ck_dingdong.py: -------------------------------------------------------------------------------- 1 | """ 2 | cron: 0 1,20 * * * 3 | new Env('叮咚买菜-签到'); 4 | 5 | 6 | """ 7 | 8 | import asyncio 9 | from traceback import format_exc 10 | from typing import Optional 11 | 12 | from utils import check, log 13 | from aiohttp_retry import JitterRetry, RetryClient 14 | 15 | 16 | class DingDong: 17 | __slots__ = ( 18 | "check_item", 19 | "token", 20 | ) 21 | 22 | def __init__(self, check_item): 23 | self.check_item: dict = check_item 24 | 25 | async def main(self): 26 | msg: list[str] = [] 27 | try: 28 | self.token = self.check_item.get("token", "") 29 | if not self.token: 30 | raise SystemExit("token 配置有误") 31 | 32 | msg += await self.sign() 33 | except Exception: 34 | log(f"失败: 请检查接口 {format_exc()}", msg) 35 | return "\n".join(msg) 36 | 37 | async def sign(self): 38 | """签到 测试""" 39 | msg: list[str] = [] 40 | try: 41 | async with RetryClient( 42 | raise_for_status=True, retry_options=JitterRetry(attempts=3) 43 | ) as client: 44 | async with client.post( 45 | url="https://sunquan.api.ddxq.mobi/api/v2/user/signin/", 46 | headers={ 47 | "Referer": "https://activity.m.ddxq.mobi/", 48 | "Cookie": f"DDXQSESSID={self.token}", 49 | "Accept-Encoding": "gzip, deflate", 50 | "ddmc-api-version": "9.7.3", 51 | "ddmc-app-client-id": "3", 52 | "ddmc-build-version": "10.3.0", 53 | "Connection": "keep-alive", 54 | "Accept-Language": "zh-CN,zh-Hans;q=0.9", 55 | "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_7_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 xzone/10.3.0", 56 | }, 57 | data={ 58 | "api_version": "9.7.3", 59 | "app_version": "1.0.0", 60 | "app_client_id": "3", 61 | "native_version": "10.3.0", 62 | }, 63 | ssl=False, 64 | ) as req: 65 | obj = await req.json() 66 | if obj["code"] == 0: 67 | data = obj["data"] 68 | new_sign_series: Optional[int] = data.get("new_sign_series") 69 | sign_series: Optional[int] = data.get("sign_series") 70 | point: Optional[int] = data.get("point") 71 | log(f"签到成功: 奖励积分+{point}", msg) 72 | log( 73 | f"连续签到: {sign_series or 0}({new_sign_series or 0})天", 74 | msg, 75 | ) 76 | else: 77 | log(f'签到失败: code={obj["code"]}, msg={obj["msg"]}') 78 | except Exception: 79 | log(f"签到异常: {format_exc()}", msg) 80 | return msg 81 | 82 | 83 | @check(run_script_name="叮咚买菜-签到", run_script_expression="dingdong") 84 | def main(*args, **kwargs): 85 | return asyncio.run(DingDong(check_item=kwargs.get("value")).main()) 86 | 87 | 88 | if __name__ == "__main__": 89 | main() 90 | -------------------------------------------------------------------------------- /ck_giant.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | cron: 0 1,20 * * * 4 | new Env('捷安特'); 5 | """ 6 | from utils import check, log, randomSleep 7 | from urllib3 import disable_warnings, Retry 8 | from requests.adapters import HTTPAdapter 9 | import requests 10 | 11 | 12 | class GIANT: 13 | 14 | def __init__(self, check_item): 15 | self.check_item = check_item 16 | self.session = requests.Session() 17 | self.session.verify = False 18 | adapter = HTTPAdapter() 19 | adapter.max_retries = Retry(connect=3, read=3, allowed_methods=None) 20 | self.session.mount("https://", adapter) 21 | self.session.mount("http://", adapter) 22 | 23 | def __sendRequest(self, method: str, url: str, data=None, json=None): 24 | """ 25 | 发起一个POST/GET/PUT请求 26 | 27 | :param jsonText: body体 28 | :return: 如果成功 返回响应的JSON对象 29 | """ 30 | headers = { 31 | "Accept": "application/json, text/plain, */*", 32 | "Accept-Language": "zh-CN,zh-Hans;q=0.9", 33 | "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 _giantapp/3.2.0", 34 | "Referer": "https://found.giant.com.cn/", 35 | "Connection": "keep-alive" 36 | } 37 | method = method.upper() 38 | response: requests.Response = self.session.request(method, 39 | url=url, headers=headers, data=data, json=json) 40 | return response.json() 41 | 42 | def sign(self, type: int): 43 | """ 44 | 签到 45 | """ 46 | msg = [] 47 | repeated = False 48 | try: 49 | obj = self.__sendRequest("post", "https://opo.giant.com.cn/opo/index.php/day_pic/do_app_pic", 50 | {"type": type, "user_id": self.user_id}) 51 | if obj["status"] == 1: 52 | log(f'{type}签到成功', msg) 53 | elif obj["status"] == 4: 54 | log(f'{type}重复签到: 忽略', msg) 55 | repeated = True 56 | else: 57 | log(f'{type}签到失败: status:{obj["status"]}, msg:{obj["msg"]}', msg) 58 | except Exception as e: 59 | log(f'{type}签到异常: 请检查接口 {e}', msg) 60 | return (msg, repeated) 61 | 62 | def getPoints(self): 63 | """ 64 | 获得当前积分 65 | """ 66 | msg = [] 67 | try: 68 | obj = self.__sendRequest("post", "https://e-gw.giant.com.cn/index.php/point_api/get_user_points", 69 | {"userid": self.user_id}) 70 | if obj["status"] == 1: 71 | points = obj["data"] 72 | log(f'当前积分: {points}', msg) 73 | else: 74 | log(f'getPoints 失败: status:{obj["status"]}, msg:{obj["msg"]}', msg) 75 | except Exception as e: 76 | log(f'getPoints 异常: 请检查接口 {e}', msg) 77 | return msg 78 | 79 | def main(self): 80 | msg = [] 81 | try: 82 | self.user_id: str = self.check_item.get("user_id", "").strip() 83 | if len(self.user_id) < 4: 84 | raise SystemExit("user_id 配置有误") 85 | msg1, repeated1 = self.sign(type=1) # 每日签到 86 | randomSleep() 87 | msg2, repeated2 = self.sign(type=2) # 每日分享 88 | randomSleep() 89 | msg3, repeated3 = self.sign(type=3) # 领取分享 90 | if all([repeated1, repeated2, repeated3]): 91 | exit() # 目前没必要执行后续的操作 92 | msg.extend(msg1) 93 | msg.extend(msg2) 94 | msg.extend(msg3) 95 | msg += self.getPoints() 96 | except Exception as e: 97 | log(f'失败: 请检查接口 {e}', msg) 98 | msg = "\n".join(msg) 99 | return msg 100 | 101 | 102 | @check(run_script_name="捷安特", run_script_expression="giant") 103 | def main(*args, **kwargs): 104 | return GIANT(check_item=kwargs.get("value")).main() 105 | 106 | 107 | if __name__ == "__main__": 108 | disable_warnings() 109 | main() 110 | -------------------------------------------------------------------------------- /ck_manmanbuy.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | cron: 25 0 * * * 4 | new Env('慢慢买'); 5 | 6 | 签到、补签等逻辑可看这个js 7 | https://apph5.manmanbuy.com/renwu/js/common.js 8 | 9 | """ 10 | import asyncio 11 | import urllib.parse 12 | from traceback import format_exc 13 | 14 | from utils import check, log 15 | from aiohttp_retry import JitterRetry, RetryClient 16 | 17 | 18 | 19 | class ManManBuy: 20 | # UA和devid都可以根据情况随机生成 21 | UA = "Mozilla/5.0 (iPhone; CPU iPhone OS 15_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 - mmbWebBrowse - ios" 22 | 23 | API_HOST = 'https://apph5.manmanbuy.com' 24 | URL_INDEX = API_HOST + '/renwu/index.aspx?m_from=my_daka' 25 | URL_LOGIN = API_HOST + '/taolijin/login.aspx' 26 | URL_TASK = API_HOST + '/renwu/index.aspx' 27 | 28 | __slots__ = ("check_item", 29 | "session", 30 | "u_name", 31 | "u_token", 32 | "c_devid", 33 | "username", 34 | ) 35 | 36 | def __init__(self, check_item): 37 | self.check_item = check_item 38 | 39 | async def InitSession(self): 40 | self.session = RetryClient(raise_for_status=True, 41 | retry_options=JitterRetry(attempts=3)) 42 | self.session._client.headers["Accept"] = "application/json, text/javascript, */*; q=0.01" 43 | self.session._client.headers["Accept-Language"] = "zh-CN,zh-Hans;q=0.9" 44 | self.session._client.headers["f-refer"] = "wv_h5" 45 | self.session._client.headers["User-Agent"] = self.UA 46 | self.session._client.headers["X-Requested-With"] = "XMLHttpRequest" 47 | self.session._client.headers["Referer"] = self.URL_INDEX 48 | self.session._client.headers["Connection"] = "keep-alive" 49 | 50 | async def ajax(self, method: str, url: str, data=None, json=None): 51 | '''发起一个ajax请求''' 52 | method = method.upper() 53 | if method not in ("GET", "HEAD"): 54 | headers = {"Origin": self.API_HOST} 55 | else: 56 | headers = None 57 | async with self.session.request(method=method, url=url, headers=headers, 58 | ssl=False, data=data, json=json) as response: 59 | return await response.json() 60 | 61 | async def checkin(self): 62 | '''立即签到''' 63 | msg = [] 64 | try: 65 | obj = await self.ajax("post", self.URL_TASK, 66 | data={'action': 'checkin', 67 | 'username': self.u_name, 68 | 'c_devid': self.c_devid, 69 | 'isAjaxInvoke': 'true'}) 70 | if int(obj["code"]) == 1: 71 | data = obj["data"] 72 | log(f'签到成功: 奖励积分+{data["jifen"]}', msg) 73 | log(f'已连续签到: {data["zt"]}天', msg) 74 | elif int(obj["code"]) == 0 and '签到失败' == obj["msg"]: 75 | log('重复签到: 忽略', msg) 76 | exit() # 目前没必要执行后续的操作 77 | else: 78 | log(f'签到失败: code:{obj["code"]}, msg:{obj["msg"]}', msg) 79 | except Exception: 80 | log(f'签到异常: {format_exc()}', msg) 81 | return msg 82 | 83 | async def login(self): 84 | msg = [] 85 | log(f'账号: {self.username}', msg) 86 | try: 87 | obj = await self.ajax("post", self.URL_LOGIN, 88 | data={'action': 'newtokenlogin', 89 | 'u_name': self.u_name, 90 | 'u_token': self.u_token}) 91 | if int(obj["code"]) == 1: 92 | log('登录成功') 93 | else: 94 | raise Exception(obj) 95 | finally: 96 | return msg 97 | 98 | async def main(self): 99 | msg = [] 100 | try: 101 | info = urllib.parse.parse_qs(self.check_item.get("login")) 102 | u_name = info.get('u_name') or info.get('u') 103 | u_token = info.get('u_token') or info.get('sign') 104 | if not (u_name and u_token): 105 | raise SystemExit("login配置有误 必须包含u_name和u_token") 106 | self.u_name: str = u_name[0] 107 | self.u_token: str = u_token[0] 108 | # 设备id可以不同账号随机分配一个guid 109 | self.c_devid = self.check_item.get("devid") \ 110 | or "43D5701C-AD8F-4503-BCA4-58C1D4EF42C9" 111 | self.username = self.check_item.get("name") or self.c_devid 112 | 113 | await self.InitSession() 114 | 115 | msg += await self.login() 116 | msg += await self.checkin() 117 | except Exception: 118 | log(f'失败: 请检查接口 {format_exc()}', msg) 119 | finally: 120 | await asyncio.gather(self.session.close(), 121 | asyncio.sleep(0.25)) 122 | msg = "\n".join(msg) 123 | return msg 124 | 125 | 126 | @check(run_script_name="慢慢买", run_script_expression="manmanbuy") 127 | def main(*args, **kwargs): 128 | return asyncio.run(ManManBuy(check_item=kwargs.get("value")).main()) 129 | 130 | 131 | if __name__ == "__main__": 132 | main() 133 | -------------------------------------------------------------------------------- /ck_pt_sign.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | cron: 13 6,20 * * * 4 | new Env('PT签到'); 5 | 6 | """ 7 | import asyncio 8 | import sys 9 | from dataclasses import dataclass 10 | from datetime import date 11 | from time import time 12 | from traceback import format_exc 13 | 14 | from utils import GetScriptConfig, check, log 15 | import json_codec 16 | from aiohttp import ClientResponse 17 | from aiohttp_retry import JitterRetry, RetryClient 18 | 19 | 20 | assert sys.version_info >= (3, 9) 21 | 22 | """ 23 | https://pt.btschool.club/index.php?action=addbonus 24 | 成功 “今天签到您获得XXX点魔力值” 25 | 失败 无提示语 26 | 27 | https://www.pttime.org/attendance.php 28 | “签到成功” “这是你的第 XX 次签到,已连续签到 XX 天,本次签到获得 XXX 个魔力值。” 29 | "已经签到过了" 30 | 31 | https://hdfun.me/attendance.php 32 | “签到成功” “这是您的第 XX 次签到,已连续签到 XX 天,本次签到获得 XXX 个魔力值。” 33 | “已经签到过了” 34 | """ 35 | 36 | 37 | @dataclass 38 | class PT_Record: 39 | timestamp: int = 0 40 | moli: int = -1 41 | can_retry: bool = True 42 | 43 | 44 | class PT: 45 | def __init__(self, check_item) -> None: 46 | self.check_item: dict = check_item 47 | self.database = None 48 | self.database_dirty = False 49 | self.records = {} 50 | self.header_base = { 51 | "User-Agent": "Mozilla/5.0 (iPhone; CPU iPhone OS 15_7_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.6.3 Mobile/15E148 Safari/604.1", 52 | "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", 53 | "Accept-Language": "zh-CN,zh-Hans;q=0.9", 54 | } 55 | 56 | async def main(self): 57 | msg: list[str] = [] 58 | try: 59 | self.load_database() 60 | co_tasks = [] 61 | # 学校单独处理 62 | btschool_cookie = self.check_item.get("btschool", "") 63 | if btschool_cookie and self.is_sign_needed("btschool"): 64 | co_tasks.append(self.btschool_sign(btschool_cookie)) 65 | # 好大单独处理 66 | hdarea_cookie = self.check_item.get("hdarea", "") 67 | if hdarea_cookie and self.is_sign_needed("hdarea"): 68 | co_tasks.append(self.hdarea_sign(hdarea_cookie)) 69 | # 以下使用通用流程处理签到 70 | for tag, host in [ 71 | ("pttime", "www.pttime.org"), 72 | ("hdzone", "hdfun.me"), 73 | ("ubits", "ubits.club"), 74 | ("hdatmos", "hdatmos.club"), 75 | # TODO ... 76 | ]: 77 | cookie = self.check_item.get(tag, "") 78 | if cookie and self.is_sign_needed(tag): 79 | co_tasks.append(self.common_attendance(host, tag, cookie)) 80 | if not co_tasks: 81 | raise SystemExit 82 | 83 | for info in await asyncio.gather(*co_tasks): 84 | msg += info 85 | except Exception: 86 | log(f"失败: 请检查接口 {format_exc()}", msg) 87 | finally: 88 | self.save_database() 89 | return "\n".join(msg) 90 | 91 | def __get_or_create_record(self, tag: str): 92 | if tag not in self.records: 93 | self.records[tag] = PT_Record() 94 | return self.records[tag] 95 | 96 | def __on_sign_succ(self, tag: str, moli: int): 97 | msg: list[str] = [] 98 | record = self.__get_or_create_record(tag) 99 | record.timestamp = int(time()) 100 | record.moli = moli 101 | record.can_retry = False 102 | self.database_dirty = True 103 | log(f"{tag}: 签到成功,获得{moli}魔力", msg) 104 | return msg 105 | 106 | def __on_sign_fail(self, tag: str): 107 | msg: list[str] = [] 108 | record = self.__get_or_create_record(tag) 109 | record.timestamp = int(time()) 110 | record.moli = 0 111 | record.can_retry = False 112 | self.database_dirty = True 113 | log(f"{tag}: 重复签到", msg) 114 | return msg 115 | 116 | def __on_sign_err(self, tag: str): 117 | msg: list[str] = [] 118 | record = self.__get_or_create_record(tag) 119 | record.timestamp = int(time()) 120 | record.moli = -1 121 | record.can_retry = True 122 | self.database_dirty = True 123 | log(f"{tag}: 请检查接口", msg) 124 | return msg 125 | 126 | def is_sign_needed(self, tag: str) -> bool: 127 | record = self.records.get(tag) 128 | if not record: 129 | return True 130 | time_diff = date.fromtimestamp(time()) - date.fromtimestamp(record.timestamp) 131 | if time_diff.days < 1: 132 | # 按自然日计算不足一天 133 | return record.can_retry 134 | else: 135 | return True 136 | 137 | async def btschool_sign(self, cookie: str): 138 | TAG = "btschool" 139 | 140 | async def try_parse_script(session: RetryClient, resp: ClientResponse): 141 | """ 142 | 学校的CF盾现在降低了安全级别 143 | 可以尝试解析脚本 144 | """ 145 | text = await resp.text() 146 | if len(text) > 1024: 147 | return text 148 | PATTERN = "window.location=" 149 | pos = text.find(PATTERN) 150 | if pos < 0: 151 | return text 152 | pos += len(PATTERN) 153 | pos2 = text.find(";", pos) 154 | location = text[pos:pos2] 155 | path = location.replace(" ", "").replace('"+"', "").replace('"', "") 156 | url = resp.real_url.with_path(path, encoded=True) 157 | async with session.get(url, ssl=False) as response: 158 | return await response.text() 159 | return text 160 | 161 | try: 162 | print(f"--- {TAG} 流程开始 ---") 163 | header = self.header_base 164 | header["Referer"] = "https://pt.btschool.club/torrents.php" 165 | header["Cookie"] = cookie 166 | async with RetryClient( 167 | raise_for_status=True, 168 | retry_options=JitterRetry(attempts=3), 169 | headers=header, 170 | ) as session: 171 | async with session.get( 172 | "https://pt.btschool.club/index.php?action=addbonus", ssl=False 173 | ) as response: 174 | text = await try_parse_script(session, response) 175 | PATTERN = "今天签到您获得" 176 | pos = text.find(PATTERN) 177 | if pos >= 0: 178 | try: 179 | pos += len(PATTERN) 180 | moli = int(text[pos : text.find("点魔力值", pos)]) 181 | return self.__on_sign_succ(TAG, moli) 182 | except: 183 | return self.__on_sign_succ(TAG, moli=-1) 184 | elif "魔力值" in text: 185 | return self.__on_sign_fail(TAG) 186 | else: 187 | print(TAG) 188 | print(text) 189 | return self.__on_sign_err(TAG) 190 | except Exception: 191 | print(f"异常: 请检查接口 {format_exc()}") 192 | return self.__on_sign_err(TAG) 193 | finally: 194 | print(f"--- {TAG} 流程结束 ---") 195 | 196 | async def hdarea_sign(self, cookie: str): 197 | TAG = "hdarea" 198 | try: 199 | print(f"--- {TAG} 流程开始 ---") 200 | header = self.header_base 201 | header["Referer"] = "https://hdarea.club/" 202 | header["Cookie"] = cookie 203 | async with RetryClient( 204 | raise_for_status=True, retry_options=JitterRetry(attempts=3) 205 | ) as session: 206 | async with session.post( 207 | "https://hdarea.club/sign_in.php", 208 | data={"action": "sign_in"}, 209 | headers=header, 210 | ssl=False, 211 | ) as response: 212 | text = await response.text() 213 | # 已连续签到2天,此次签到您获得了12魔力值奖励! 214 | PATTERN = "获得了" 215 | pos = text.find(PATTERN) 216 | if pos >= 0: 217 | try: 218 | pos += len(PATTERN) 219 | moli = int(text[pos : text.find("魔力值", pos)]) 220 | return self.__on_sign_succ(TAG, moli) 221 | except: 222 | return self.__on_sign_succ(TAG, moli=-1) 223 | elif "重复签到" in text: 224 | # 请不要重复签到哦! 225 | return self.__on_sign_fail(TAG) 226 | else: 227 | print(TAG) 228 | print(text) 229 | return self.__on_sign_err(TAG) 230 | except Exception: 231 | print(f"异常: 请检查接口 {format_exc()}") 232 | return self.__on_sign_err(TAG) 233 | finally: 234 | print(f"--- {TAG} 流程结束 ---") 235 | 236 | async def common_attendance(self, host: str, tag: str, cookie: str): 237 | try: 238 | print(f"--- {tag} 流程开始 ---") 239 | header = self.header_base 240 | header["Cookie"] = cookie 241 | async with RetryClient( 242 | raise_for_status=True, retry_options=JitterRetry(attempts=3) 243 | ) as session: 244 | async with session.get( 245 | f"https://{host}/attendance.php", headers=header, ssl=False 246 | ) as response: 247 | text = await response.text() 248 | if "签到成功" in text: 249 | try: 250 | PATTERN = "签到获得 " 251 | pos = text.find(PATTERN) 252 | pos += len(PATTERN) 253 | moli = int(text[pos : text.find(" 个魔力值", pos)]) 254 | return self.__on_sign_succ(tag, moli) 255 | except: 256 | return self.__on_sign_succ(tag, moli=-1) 257 | elif "签到过了" in text or "今天已签到" in text: 258 | return self.__on_sign_fail(tag) 259 | else: 260 | print(tag) 261 | print(text) 262 | return self.__on_sign_err(tag) 263 | except Exception: 264 | print(f"异常: 请检查接口 {format_exc()}") 265 | return self.__on_sign_err(tag) 266 | finally: 267 | print(f"--- {tag} 流程结束 ---") 268 | 269 | def load_database(self): 270 | """读取数据库""" 271 | try: 272 | if self.database: 273 | # 已经读取过数据库 274 | return True 275 | self.database_dirty = False 276 | self.database = GetScriptConfig("pt_sign.json") 277 | self.records = json_codec.decode( 278 | self.database.get_value_2("records") or {} if self.database else {}, 279 | dict[str, PT_Record], 280 | ) 281 | return True 282 | except BaseException: 283 | return False 284 | 285 | def save_database(self): 286 | """保存数据库""" 287 | if self.database and self.database_dirty: 288 | try: 289 | self.database_dirty = False 290 | self.database.set_value("records", json_codec.encode(self.records)) 291 | except BaseException: 292 | return False 293 | return True 294 | 295 | 296 | @check(run_script_name="PT签到", run_script_expression="ptsign") 297 | def main(*args, **kwargs): 298 | return asyncio.run(PT(check_item=kwargs.get("value")).main()) 299 | 300 | 301 | if __name__ == "__main__": 302 | main() 303 | -------------------------------------------------------------------------------- /ck_pupu_buy.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | cron: 56 59 17,18,19,20,21 * * * 4 | new Env('朴朴抢购'); 5 | 6 | 微信登录朴朴app 7 | 找到请求https://cauth.pupuapi.com/clientauth/user/society/wechat/login?user_society_type=11 8 | 在json响应里有refresh_token 9 | 10 | goods 检测商品收藏列表中的哪些商品 数组对象 11 | keyword 商品名关键字 用于匹配收藏列表中的商品 12 | price 预期价位 当商品降价到预期价位以内将执行后续操作 13 | quantity 抢购数量(可选) 14 | buy 如果降价是否抢购 默认False只发送降价通知 15 | addr_filter 限定用哪个收货地址(可选) 默认用最近下单地址 16 | push_key 降价时用的PushDeer推送key(可选) 17 | """ 18 | import asyncio 19 | import sys 20 | from traceback import format_exc 21 | from typing import Optional # 确保兼容<=Python3.9 22 | 23 | import ck_pupu_history 24 | 25 | from pupu_api import Client as PClient 26 | from pupu_types import * 27 | from utils import check, log, aio_randomSleep 28 | 29 | from aiohttp import ClientSession, ClientTimeout 30 | from pypushdeer import PushDeer 31 | 32 | assert sys.version_info >= (3, 9) 33 | 34 | 35 | @dataclass 36 | class Goods: 37 | keyword: str 38 | price: int 39 | quantity: int 40 | 41 | 42 | class PUPU: 43 | 44 | __slots__ = ( 45 | "check_item", 46 | "device_id", 47 | "refresh_token", 48 | "_goods", 49 | "_buy", 50 | "_push_key", 51 | ) 52 | 53 | def __init__(self, check_item) -> None: 54 | self.check_item: dict = check_item 55 | 56 | async def main(self): 57 | msg: list[str] = [] 58 | try: 59 | self._buy = bool(self.check_item.get("buy", False)) 60 | self._push_key: Optional[str] = self.check_item.get("push_key") 61 | self.device_id = self.check_item.get("device_id", "") 62 | self.refresh_token = self.check_item.get("refresh_token", "") 63 | if not self.device_id: 64 | raise SystemExit("device_id 配置有误") 65 | if not self.refresh_token: 66 | raise SystemExit("refresh_token 配置有误") 67 | 68 | self._goods = self.ParseGoods() 69 | if len(self._goods) > 0: 70 | log(f"配置了{len(self._goods)}件商品的价格检测", msg) 71 | else: 72 | raise SystemExit("没有配置需要检测的商品 跳过") 73 | 74 | ck_pupu_history.load_database() 75 | 76 | msg2 = await self.Entry() 77 | if msg2: 78 | msg += msg2 79 | else: 80 | msg = [] 81 | except Exception: 82 | log(f"失败: 请检查接口 {format_exc()}", msg) 83 | finally: 84 | ck_pupu_history.save_database() 85 | 86 | return "\n".join(msg) 87 | 88 | async def DetectProducts(self, api: PClient): 89 | """检测商品是否降价""" 90 | msg: list[str] = [] 91 | products: list[PProduct] = [] 92 | PAGE_SIZE = 10 # 增加分页大小是否会被判定? 93 | page = 1 # 从第一页开始拉取 94 | while True: 95 | collections = await api.GetProductCollections(page, PAGE_SIZE) 96 | if isinstance(collections, ApiResults.Error): 97 | log(collections, msg) 98 | break 99 | products.extend(collections.products) 100 | if ( 101 | len(products) >= collections.total_count 102 | or collections.total_count < PAGE_SIZE 103 | or len(collections.products) < PAGE_SIZE 104 | ): 105 | # 不知朴朴怎么想的 空列表还会下发一个不为零的total_count 106 | break 107 | await aio_randomSleep(0.0, 0.125) 108 | page += 1 109 | log(f" 当前服务器时间: {PClient.TryGetServerTime() or 0}") 110 | price_reduction = 0 111 | order_items: list[PProduct] = [] 112 | for p in products: 113 | # 记录价格 114 | ck_pupu_history.RecordPrice(p) 115 | for goods in self._goods: 116 | if p.name.find(goods.keyword) == -1: 117 | # 排除不关心价格的 118 | continue 119 | if p.stock_quantity <= 0: 120 | # 排除没货的 121 | # log(f' 缺货: {p.name}') 122 | continue 123 | if p.sell_batches: 124 | # TODO 以该数组的最低价作为当前价格 125 | log(f" sell_batches!!: {p.name}") 126 | pass 127 | if p.price > goods.price: 128 | # 排除价格高于预期的 129 | log( 130 | f" 价格高于预期: {p.name} {p.price/100}元 > {goods.price/100}元" 131 | ) 132 | continue 133 | log(f"价格低于预期: {p.name} {p.price/100}元", msg) 134 | price_reduction += 1 135 | if not self._buy or goods.quantity <= 0: 136 | continue 137 | # p = copy.deepcopy(p) 138 | # 计算采购量 139 | p.selected_count = min( 140 | goods.quantity, 141 | p.stock_quantity, 142 | p.quantity_limit or p.stock_quantity, 143 | ) 144 | # [杀(清洗), 杀(不清洗), 不杀] 145 | p.remark = p.order_remarks[0] if p.order_remarks else "" 146 | order_items.append(p) 147 | return (msg, products, price_reduction, order_items) 148 | 149 | async def Entry(self): 150 | msg: list[str] = [] 151 | async with PClient(self.device_id, self.refresh_token) as api: 152 | result = await api.InitializeToken(self.check_item.get("addr_filter")) 153 | if isinstance(result, ApiResults.Error): 154 | log(result, msg) 155 | return msg 156 | results = await self.DetectProducts(api) 157 | if isinstance(results, ApiResults.Error): 158 | log(results, msg) 159 | return msg 160 | sub_msg, collections, price_reduction, order_items = results 161 | msg += sub_msg 162 | log(f"总共收藏了{len(collections)}件商品") 163 | if price_reduction <= 0: 164 | # 第1次检测没有降价 等待片刻 165 | await asyncio.sleep(1.0) 166 | # 开始第2次检测 总共2次 167 | retry = 2 168 | while True: 169 | log(f"第{retry}次尝试...") 170 | results = await self.DetectProducts(api) 171 | if isinstance(results, ApiResults.Error): 172 | log(results, msg) 173 | break 174 | sub_msg, collections, price_reduction, order_items = results 175 | msg += sub_msg 176 | if price_reduction > 0: 177 | # 存在降价商品 不再尝试检测 178 | break 179 | retry += 1 180 | if retry > 2: 181 | break 182 | await asyncio.sleep(1.0) 183 | if order_items: 184 | # 并行获得加购商品可用的优惠券和派送时间 185 | coupons_result, dtime_result, now = await asyncio.gather( 186 | api.GetUsableCoupons(DiscountType.ALL, order_items), 187 | api.GetDeliveryTime(order_items, 10), 188 | api.GetServerTime(), 189 | ) 190 | if isinstance(coupons_result, ApiResults.Error): 191 | log(coupons_result, msg) 192 | coupons = None 193 | else: 194 | log(f"可用{len(coupons_result.coupons)}张优惠券") 195 | coupons = coupons_result 196 | if isinstance(dtime_result, ApiResults.Error): 197 | log(dtime_result, msg) 198 | dtime = None 199 | else: 200 | dtime = dtime_result 201 | order_result = await api.CreateOrder( 202 | pay_type=15, # 云闪付 203 | coupons=coupons.coupons if coupons else None, 204 | products=order_items, 205 | dtime_type=dtime.type if dtime else DeliveryTimeType.IMMEDIATE, 206 | dtime_promise=dtime.dtime_promise if dtime else now + 1800_000, 207 | ) 208 | if isinstance(order_result, ApiResults.Error): 209 | log(order_result, msg) 210 | else: 211 | log(f"订单创建成功 {order_result.id}", msg) 212 | log(f"当前服务器时间: {PClient.TryGetServerTime() or 0}") 213 | name = self.check_item.get("name") or "" 214 | msg += await self.send("朴朴降价了", f"{name} {order_result.id}") 215 | elif price_reduction <= 0: 216 | pass # log('无降价', msg) 217 | else: 218 | log("有降价 快去下单吧~", msg) 219 | log(f"当前服务器时间: {PClient.TryGetServerTime() or 0}") 220 | return msg 221 | 222 | def ParseGoods(self): 223 | """解析商品配置""" 224 | goods_list: list[Goods] = [] 225 | for goods in self.check_item.get("goods", []): 226 | if not isinstance(goods, dict): 227 | continue 228 | keyword = goods.get("keyword") 229 | if not isinstance(keyword, str): 230 | continue 231 | price = goods.get("price") 232 | if not isinstance(price, (int, float)): 233 | continue 234 | goods_list.append( 235 | Goods( 236 | keyword, 237 | price=int(price * 100), # 转换为分 238 | quantity=int(goods.get("quantity", 0)), 239 | ) 240 | ) 241 | return goods_list 242 | 243 | @classmethod 244 | async def __serverJ(cls, key: str, title: str, content: str): 245 | """通过 ServerJ 推送消息""" 246 | msg: list[str] = [] 247 | log("serverJ 服务启动") 248 | 249 | data = {"text": title, "desp": content.replace("\n", "\n\n")} 250 | if key.startswith("SCT"): 251 | url = f"https://sctapi.ftqq.com/{key}.send" 252 | else: 253 | url = f"https://sc.ftqq.com/${key}.send" 254 | 255 | async with ClientSession( 256 | raise_for_status=True, timeout=ClientTimeout(total=15) 257 | ) as session: 258 | async with session.post(url, data=data, ssl=False) as req: 259 | datas = await req.json() 260 | if datas.get("errno") == 0 or datas.get("code") == 0: 261 | log("serverJ 推送成功!", msg) 262 | elif datas.get("code") == 40001: 263 | log("serverJ 推送失败! PUSH_KEY 错误。", msg) 264 | else: 265 | log(f'serverJ 推送失败! 错误码:{datas.get("message")}', msg) 266 | return msg 267 | 268 | @classmethod 269 | async def __pushDeer(cls, key: str, title: str, content: str): 270 | """通过 PushDeer 推送消息""" 271 | msg: list[str] = [] 272 | log("PushDeer 服务启动") 273 | pushdeer = PushDeer(pushkey=key) 274 | if pushdeer.send_text(title, desp=content.replace("\n", "\n\n")) == True: 275 | log("PushDeer 推送成功!", msg) 276 | else: 277 | log("PushDeer 推送失败!", msg) 278 | return msg 279 | 280 | async def send(self, title: str, content: str): 281 | """推送消息""" 282 | if not self._push_key: 283 | return [] 284 | if self._push_key.startswith("SC"): 285 | return await self.__serverJ(self._push_key, title, content) 286 | elif self._push_key.startswith("PD"): 287 | return await self.__pushDeer(self._push_key, title, content) 288 | else: 289 | return [] 290 | 291 | 292 | @check(run_script_name="朴朴抢购", run_script_expression="pupu", interval_max=0) 293 | def main(*args, **kwargs): 294 | return asyncio.run(PUPU(check_item=kwargs.get("value")).main()) 295 | 296 | 297 | if __name__ == "__main__": 298 | main() 299 | -------------------------------------------------------------------------------- /ck_pupu_collectcards.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | cron: 15 8,23 25-31,1-10 1,2 * 4 | new Env('朴朴集卡'); 5 | 6 | 微信登录朴朴app 7 | 找到请求https://cauth.pupuapi.com/clientauth/user/society/wechat/login?user_society_type=11 8 | 在json响应里有refresh_token 9 | 10 | enabled 是否启用集卡(默认true) 11 | lottery 是否抽奖(会消耗一张卡 默认false) 12 | keep_cards 抽奖保留几张卡片(默认1) 13 | """ 14 | import asyncio 15 | import sys 16 | from traceback import format_exc 17 | 18 | from pupu_api import Client as PClient 19 | from pupu_types import * 20 | from utils import aio_randomSleep, check, log, config_get 21 | 22 | assert sys.version_info >= (3, 9) 23 | 24 | 25 | class PUPU: 26 | __slots__ = ( 27 | "check_item", 28 | "device_id", 29 | "refresh_token", 30 | "_lottery", 31 | "_keep_cards", 32 | ) 33 | 34 | def __init__(self, check_item) -> None: 35 | self.check_item: dict = check_item 36 | 37 | @dataclass 38 | class Question: 39 | id: str 40 | keyword: str 41 | answer: str 42 | 43 | # 已知的题库 44 | QUESTIONS: list[Question] = [] 45 | 46 | def LoadQuestions(self): 47 | qlist = config_get().get_value("qa_pupu") or [] 48 | for q in qlist: 49 | id = q.get("id") or "" 50 | keyword = q.get("keyword") or "" 51 | answer = q.get("answer") or "" 52 | if (id or keyword) and answer: 53 | self.QUESTIONS.append(PUPU.Question(id, keyword, answer)) 54 | 55 | def GetAnswer(self, question: PQuestion): 56 | for q in self.QUESTIONS: 57 | if q.id and q.id == question.id: 58 | return q.answer 59 | elif q.keyword and q.keyword in question.question_title: 60 | return q.answer 61 | return None 62 | 63 | async def main(self): 64 | msg: list[str] = [] 65 | try: 66 | self.device_id = self.check_item.get("device_id", "") 67 | self.refresh_token = self.check_item.get("refresh_token", "") 68 | if not self.device_id: 69 | raise SystemExit("device_id 配置有误") 70 | if not self.refresh_token: 71 | raise SystemExit("refresh_token 配置有误") 72 | 73 | collect_cards = self.check_item.get("collect_cards", {}) 74 | if not collect_cards.get("enabled", True): 75 | raise SystemExit("没有启用") 76 | 77 | self._lottery = bool(collect_cards.get("lottery", False)) 78 | self._keep_cards = int(collect_cards.get("keep_cards", 1)) 79 | 80 | self.LoadQuestions() 81 | 82 | msg += await self.CollectCards() 83 | except Exception: 84 | log(f"失败: 请检查接口 {format_exc()}", msg) 85 | return "\n".join(msg) 86 | 87 | async def CollectCards(self): 88 | """开始集卡""" 89 | msg: list[str] = [] 90 | async with PClient(self.device_id, self.refresh_token) as api: 91 | result = await api.InitializeToken( 92 | self.check_item.get("addr_filter"), force_update_receiver=False 93 | ) 94 | if isinstance(result, ApiResults.Error): 95 | if api.nickname: 96 | log(f"账号: {api.nickname}", msg) 97 | log(result, msg) 98 | return msg 99 | 100 | log(f"账号: {api.nickname}", msg) 101 | 102 | rule = await api.GetCollectCardRule() 103 | if isinstance(rule, ApiResults.Error): 104 | log(rule, msg) 105 | return msg 106 | elif rule.status == COLLECT_CARD_STATUS.FINISHED: 107 | # 活动已结束 108 | log(f"{rule.name} 已结束", msg) 109 | return msg 110 | 111 | log(f"本期活动: {rule.name}", msg) 112 | 113 | task_groups, lottery_info = await asyncio.gather( 114 | api.GetTaskGroupsData(rule), 115 | api.GetLotteryInfo(rule.card_lottery_activity_id), 116 | ) 117 | if isinstance(task_groups, ApiResults.Error): 118 | log(task_groups, msg) 119 | return msg 120 | elif not task_groups.tasks: 121 | log(" 没有配置任务") 122 | 123 | # 同时拉取抽奖详情 124 | if isinstance(lottery_info, ApiResults.Error): 125 | log(lottery_info, msg) 126 | return msg 127 | 128 | # 然后开始做任务 129 | for task in task_groups.tasks: 130 | if task.task_status != TaskStatus.Undone: 131 | continue 132 | if task.page_rule: 133 | # 每个任务至少间隔2~5秒的时间 134 | task_result, _ = await asyncio.gather( 135 | api.PostPageTaskComplete(task), 136 | aio_randomSleep(2, 5), 137 | ) 138 | if isinstance(task_result, ApiResults.Error): 139 | log(task_result) 140 | else: 141 | log(f" {task.task_name}: 已完成") 142 | elif task.answer_rule: 143 | """ 144 | 答题任务 145 | """ 146 | if task.answer_rule.answer_is_done: 147 | # 已回答 忽略 148 | continue 149 | questionnaire, _ = await asyncio.gather( 150 | api.GetQuestionnaire(task), 151 | aio_randomSleep(2, 5), 152 | ) 153 | if isinstance(questionnaire, ApiResults.Error): 154 | log(questionnaire) 155 | else: 156 | question = None 157 | for q in questionnaire.questions: 158 | answer = self.GetAnswer(q) 159 | if answer: 160 | for options in q.options: 161 | if options.name == answer: 162 | question = q 163 | options.selected = 1 164 | break 165 | else: 166 | answer = None 167 | if not answer: 168 | print(q) 169 | if question: 170 | succ, _ = await asyncio.gather( 171 | api.SubmitQuestionnaire(questionnaire), 172 | aio_randomSleep(2, 5), 173 | ) 174 | if isinstance(succ, ApiResults.Error): 175 | log(succ) 176 | elif succ: 177 | log(f"成功答题: {question.question_title}", msg) 178 | continue 179 | log(f"答题失败", msg) 180 | 181 | # 获取抽卡次数 182 | await aio_randomSleep(2, 3) 183 | remain_chances = await api.GetCollectCardLotteryCount(rule) 184 | if isinstance(remain_chances, ApiResults.Error): 185 | log(remain_chances, msg) 186 | remain_chances = 0 187 | elif remain_chances <= 0: 188 | log(" 没有抽卡机会", msg) 189 | else: 190 | log(f" 当前有{remain_chances}次抽卡机会", msg) 191 | 192 | # 开始抽卡 193 | for i in range(remain_chances): 194 | # 每次抽卡至少间隔4~8秒的时间 195 | getcard_result, _ = await asyncio.gather( 196 | api.LotteryGetCard(rule), 197 | aio_randomSleep(4, 8), 198 | ) 199 | if isinstance(getcard_result, ApiResults.Error): 200 | log(f" 第{i+1}次抽卡: {getcard_result}", msg) 201 | else: 202 | log( 203 | f" 第{i+1}次抽卡: {getcard_result.name}, 类型: {getcard_result.card_type}", 204 | msg, 205 | ) 206 | 207 | # 获取卡片数量 208 | info = await api.GetCollectCardEntity(rule) 209 | if isinstance(info, ApiResults.Error): 210 | log(info, msg) 211 | return msg 212 | else: 213 | for card in info.already_get: 214 | log(f" {card.name}: {card.have_count}张", msg) 215 | log( 216 | f" 可合成{info.can_composite_count}张 {info.already_get[0].name}", 217 | msg, 218 | ) 219 | if info.can_composite_count: 220 | unk = await api.PostCompositeCard(rule) 221 | if isinstance(unk, ApiResults.Error): 222 | log(unk, msg) 223 | else: 224 | # TODO 尚不清楚合成的返回结构 猜测是 PCollectCard 225 | log(" 已自动合成", msg) 226 | log(unk) 227 | # 再更新一次卡片数量 228 | info = await api.GetCollectCardEntity(rule) 229 | if isinstance(info, ApiResults.Error): 230 | log(info, msg) 231 | return msg 232 | 233 | if not self._lottery: 234 | # 不抽奖 流程结束 235 | return msg 236 | 237 | c = 1 238 | # 开始消耗卡片去抽奖(规则每日最多3次) 239 | for card in info.already_get: 240 | if card.card_type == 20: 241 | # 240207 别把合成卡给抽了,会报错 :) 242 | continue 243 | if card.have_count is None: 244 | continue 245 | if card.have_count <= self._keep_cards: 246 | continue 247 | for i in range(card.have_count - self._keep_cards): 248 | # 用卡片兑换一次抽奖机会 249 | result = await api.DeleteExpendCardEntity(card) 250 | if isinstance(result, ApiResults.Error): 251 | log(result) 252 | continue 253 | log(f" 消耗了1张 {card.name}") 254 | if result != lottery_info.lottery.id: 255 | # FIXME 不应该 256 | log(f"断言不满足 {result} != {lottery_info.lottery.id}", msg) 257 | # 每次抽奖至少间隔4~8秒的时间 258 | lottery_result, _ = await asyncio.gather( 259 | api.Lottery(lottery_info.lottery), 260 | aio_randomSleep(4, 8), 261 | ) 262 | if isinstance(lottery_result, ApiResults.Error): 263 | log(f" 第{c}次抽奖: {lottery_result}", msg) 264 | else: 265 | log(f" 第{c}次抽奖: {lottery_result.prize.name}") 266 | c += 1 267 | 268 | return msg 269 | 270 | 271 | @check(run_script_name="朴朴集卡", run_script_expression="pupu") 272 | def main(*args, **kwargs): 273 | return asyncio.run(PUPU(check_item=kwargs.get("value")).main()) 274 | 275 | 276 | if __name__ == "__main__": 277 | main() 278 | -------------------------------------------------------------------------------- /ck_pupu_coupon.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | cron: 0 7,9,13,15,19 * * * 4 | new Env('朴朴领券'); 5 | 6 | 微信登录朴朴app 7 | 找到请求https://cauth.pupuapi.com/clientauth/user/society/wechat/login?user_society_type=11 8 | 在json响应里有refresh_token 9 | 10 | coupon_center.enabled 是否启用领券(默认true) 11 | """ 12 | import asyncio 13 | import sys 14 | from traceback import format_exc 15 | 16 | from pupu_api import Client as PClient 17 | from pupu_types import * 18 | from utils import aio_randomSleep, check, log 19 | 20 | import json_codec 21 | 22 | assert sys.version_info >= (3, 9) 23 | 24 | 25 | @dataclass 26 | class PCouponCenterItem: 27 | discount_id: str 28 | discount_group_id: str 29 | condition_amount: int 30 | discount_amount: int 31 | is_finished: bool = True 32 | received: int = 0 33 | received_limit: int = 0 34 | 35 | @property 36 | def is_received(self): 37 | """是否已领取""" 38 | return self.received > 0 39 | 40 | @property 41 | def can_received(self): 42 | """是否可以领取""" 43 | return not self.is_finished and self.received < self.received_limit 44 | 45 | 46 | class PUPU: 47 | 48 | __slots__ = ( 49 | "check_item", 50 | "device_id", 51 | "refresh_token", 52 | ) 53 | 54 | def __init__(self, check_item) -> None: 55 | self.check_item: dict = check_item 56 | 57 | async def main(self): 58 | msg: list[str] = [] 59 | try: 60 | self.device_id = self.check_item.get("device_id", "") 61 | self.refresh_token = self.check_item.get("refresh_token", "") 62 | if not self.device_id: 63 | raise SystemExit("device_id 配置有误") 64 | if not self.refresh_token: 65 | raise SystemExit("refresh_token 配置有误") 66 | 67 | coupon_center = self.check_item.get("coupon_center", {}) 68 | if not coupon_center.get("enabled", True): 69 | raise SystemExit("没有启用") 70 | 71 | msg += await self.CollectCoupons() 72 | except Exception: 73 | log(f"失败: 请检查接口 {format_exc()}", msg) 74 | return "\n".join(msg) 75 | 76 | async def _ReceiveCoupon(self, api: PClient, coupon: PCouponCenterItem): 77 | try: 78 | obj = await api._SendRequest( 79 | HttpMethod.kPost, 80 | "https://j1.pupuapi.com/client/coupon/entity", 81 | ClientType.kWeb, 82 | json={ 83 | "discount": coupon.discount_id, 84 | "discount_group": coupon.discount_group_id, 85 | "place_id": api.receiver.place_id, 86 | "store_id": api.receiver.store_id, 87 | "time_type": 1, 88 | }, 89 | ) 90 | if obj["errcode"] == 0: 91 | # 领取成功 92 | return 93 | else: 94 | return ApiResults.Error(obj) 95 | except: 96 | return ApiResults.Exception() 97 | 98 | async def CollectCoupons(self): 99 | msg: list[str] = [] 100 | try: 101 | async with PClient(self.device_id, self.refresh_token) as api: 102 | result = await api.InitializeToken( 103 | self.check_item.get("addr_filter"), force_update_receiver=False 104 | ) 105 | if isinstance(result, ApiResults.Error): 106 | if api.nickname: 107 | log(f"账号: {api.nickname}", msg) 108 | log(result, msg) 109 | return msg 110 | 111 | log(f"账号: {api.nickname}", msg) 112 | 113 | obj = await api._SendRequest( 114 | HttpMethod.kGet, 115 | "https://j1.pupuapi.com/client/coupon", 116 | ClientType.kWeb, 117 | params={"store_id": api.receiver.store_id, "type": 1}, 118 | ) 119 | if obj["errcode"] == 0: 120 | data = obj["data"] 121 | items = json_codec.decode( 122 | data.get("items") or [], list[PCouponCenterItem] 123 | ) 124 | if not any(item.can_received for item in items): 125 | log("没有优惠券,领取失败", msg) 126 | exit() # 目前没必要执行后续的操作 127 | return msg 128 | for coupon in items: 129 | result, _ = await asyncio.gather( 130 | self._ReceiveCoupon(api, coupon), aio_randomSleep() 131 | ) 132 | if isinstance(result, ApiResults.Error): 133 | log(result, msg) 134 | else: 135 | log( 136 | f"成功领取: 满{coupon.condition_amount/100}减{coupon.discount_amount/100}元", 137 | msg, 138 | ) 139 | else: 140 | log(ApiResults.Error(obj), msg) 141 | except Exception: 142 | log(ApiResults.Exception(), msg) 143 | finally: 144 | return msg 145 | 146 | 147 | @check(run_script_name="朴朴领券", run_script_expression="pupu") 148 | def main(*args, **kwargs): 149 | return asyncio.run(PUPU(check_item=kwargs.get("value")).main()) 150 | 151 | 152 | if __name__ == "__main__": 153 | main() 154 | -------------------------------------------------------------------------------- /ck_pupu_history.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | cron: 33 7-23/4 * * * 4 | new Env('朴朴历史价'); 5 | 6 | 微信登录朴朴app 7 | 找到请求https://cauth.pupuapi.com/clientauth/user/society/wechat/login?user_society_type=11 8 | 在json响应里有refresh_token 9 | 10 | enabled 是否启用(默认true, 多个账号复用一个数据库) 11 | """ 12 | import asyncio 13 | import sys 14 | from dataclasses import dataclass 15 | from datetime import date, datetime 16 | from enum import IntEnum 17 | from traceback import format_exc 18 | from typing import Optional, cast # 确保兼容<=Python3.9 19 | 20 | from pupu_api import Client as PClient 21 | from pupu_types import ApiResults, PProduct 22 | from utils import GetScriptConfig, check, log 23 | 24 | import json_codec 25 | 26 | assert sys.version_info >= (3, 9) 27 | 28 | __all__ = [ 29 | "load_database", 30 | "save_database", 31 | "RecordPrice", 32 | "OutputHistoryPrice", 33 | "PriceRecord", 34 | "ProductHistory", 35 | "Days", 36 | ] 37 | 38 | 39 | @dataclass 40 | class PriceRecord: 41 | create_time: int 42 | low: int 43 | high: int 44 | # 价格波动发生在何时 45 | update_time: Optional[int] = None 46 | 47 | 48 | @dataclass 49 | class ProductHistory: 50 | viewed: bool = False 51 | name: Optional[str] = None 52 | d3: Optional[PriceRecord] = None 53 | d6: Optional[PriceRecord] = None 54 | d9: Optional[PriceRecord] = None 55 | d12: Optional[PriceRecord] = None 56 | 57 | lowest: Optional[int] = None 58 | highest: Optional[int] = None 59 | 60 | @property 61 | def lowest_price(self) -> Optional[int]: 62 | if self.lowest is None: 63 | # 从已有数据提取最低价 64 | for r in [self.d3, self.d6, self.d9, self.d12]: 65 | if r and (self.lowest is None or r.low < self.lowest): 66 | self.lowest = r.low 67 | return self.lowest 68 | 69 | @property 70 | def highest_price(self) -> Optional[int]: 71 | if self.highest is None: 72 | # 从已有数据提取最高价 73 | for r in [self.d3, self.d6, self.d9, self.d12]: 74 | if r and (self.highest is None or r.high > self.highest): 75 | self.highest = r.high 76 | return self.highest 77 | 78 | @property 79 | def d3_low(self) -> str: 80 | return f"{self.d3.low/100}元" if self.d3 else "-" 81 | 82 | @property 83 | def d6_low(self) -> str: 84 | return f"{self.d6.low/100}元" if self.d6 else "-" 85 | 86 | @property 87 | def d9_low(self) -> str: 88 | return f"{self.d9.low/100}元" if self.d9 else "-" 89 | 90 | @property 91 | def d12_low(self) -> str: 92 | return f"{self.d12.low/100}元" if self.d12 else "-" 93 | 94 | @property 95 | def d3_high(self) -> str: 96 | return f"{self.d3.high/100}元" if self.d3 else "-" 97 | 98 | @property 99 | def d6_high(self) -> str: 100 | return f"{self.d6.high/100}元" if self.d6 else "-" 101 | 102 | @property 103 | def d9_high(self) -> str: 104 | return f"{self.d9.high/100}元" if self.d9 else "-" 105 | 106 | @property 107 | def d12_high(self) -> str: 108 | return f"{self.d12.high/100}元" if self.d12 else "-" 109 | 110 | 111 | class Days(IntEnum): 112 | DAY = 1 113 | DAYS_3 = 3 114 | DAYS_6 = DAYS_3 + DAYS_3 115 | DAYS_9 = DAYS_6 + DAYS_3 116 | DAYS_12 = DAYS_9 + DAYS_3 117 | 118 | 119 | _database = None 120 | _database_dirty = False 121 | _history = {} 122 | 123 | 124 | def load_database(): 125 | """读取数据库""" 126 | global _database, _history, _database_dirty 127 | try: 128 | if _database: 129 | # 已经读取过数据库 130 | return True 131 | _database_dirty = False 132 | _database = GetScriptConfig("pupu_buy.json") 133 | _history = json_codec.decode( 134 | _database.get_value_2("history") or {} if _database else {}, 135 | dict[str, ProductHistory], 136 | ) 137 | return True 138 | except BaseException: 139 | return False 140 | 141 | 142 | def save_database(): 143 | """保存数据库""" 144 | global _database, _history, _database_dirty 145 | if _database and _database_dirty: 146 | try: 147 | _database_dirty = False 148 | _database.set_value("history", json_codec.encode(_history)) 149 | except BaseException: 150 | return False 151 | return True 152 | 153 | 154 | def RecordPrice(p: PProduct) -> bool: 155 | """记录商品价格""" 156 | # TODO 改用sqlite3详细记录 157 | global _database, _history, _database_dirty 158 | dirty = False 159 | now = PClient.TryGetServerTime() or 0 160 | history_record = _history.get(p.store_product_id) or ProductHistory() 161 | 162 | if history_record.name is None or history_record.name != p.name: 163 | # 230208: 商品名称也需要记录 方便调试 164 | history_record.name = p.name 165 | dirty = True 166 | 167 | if history_record.lowest is None or history_record.highest is None: 168 | dirty = True 169 | 170 | if history_record.lowest_price is None or p.price < history_record.lowest_price: 171 | history_record.lowest = p.price 172 | dirty = True 173 | if history_record.highest_price is None or p.price > history_record.highest_price: 174 | history_record.highest = p.price 175 | dirty = True 176 | 177 | STAGES: list = [ 178 | ("d12", Days.DAYS_12, None), 179 | ("d9", Days.DAYS_9, "d12"), 180 | ("d6", Days.DAYS_6, "d9"), 181 | ("d3", Days.DAYS_3, "d6"), 182 | ] 183 | 184 | # 根据历史价格的最后更新日期进行重新归类 185 | for f, days, t in STAGES: 186 | record = cast(Optional[PriceRecord], getattr(history_record, f, None)) 187 | if record is None: 188 | continue 189 | time_diff = date.fromtimestamp(now / 1000) - date.fromtimestamp( 190 | record.create_time / 1000 191 | ) 192 | if time_diff.days < days: 193 | # 按自然日计算 194 | continue 195 | if t: 196 | setattr(history_record, t, record) 197 | setattr(history_record, f, None) 198 | dirty = True 199 | 200 | record = history_record.d3 or PriceRecord( 201 | create_time=now, low=p.price, high=p.price 202 | ) 203 | if p.price < record.low: 204 | record.low = p.price 205 | record.update_time = now 206 | dirty = True 207 | elif p.price > record.high: 208 | record.high = p.price 209 | record.update_time = now 210 | dirty = True 211 | elif history_record.d3 is None: 212 | history_record.d3 = record 213 | dirty = True 214 | 215 | _history[p.store_product_id] = history_record 216 | if dirty: 217 | history_record.viewed = False 218 | _database_dirty = dirty 219 | return not history_record.viewed 220 | 221 | 222 | def OutputHistoryPrice(p: PProduct) -> list[str]: 223 | """ 224 | 输出商品的历史价格详情 225 | 226 | --- 227 | 有机番茄: 228 | 当前价格: 7.99元 229 | 历史价格: 1.99元~18.99元 230 | 最近低价: 7.00, 15.00, 10.00, 1.00 231 | """ 232 | global _database, _history, _database_dirty 233 | msg: list[str] = [] 234 | history_record = cast(Optional[ProductHistory], _history.get(p.store_product_id)) 235 | if not history_record: 236 | # 无记录 237 | return msg 238 | log(f"- {p.name} ", msg) 239 | log(f" 当前价格: {p.price/100}元 ", msg) 240 | if ( 241 | history_record.lowest_price is not None 242 | and history_record.highest_price is not None 243 | ): 244 | log( 245 | f" 历史价格: {history_record.lowest_price/100}元~{history_record.highest_price/100}元 ", 246 | msg, 247 | ) 248 | log( 249 | f" 最近低价: {history_record.d3_low}, {history_record.d6_low}, {history_record.d9_low}, {history_record.d12_low} ", 250 | msg, 251 | ) 252 | if time := history_record.d3.update_time if history_record.d3 else None: 253 | d = datetime.fromtimestamp(time / 1000).strftime("%Y-%m-%d %H:%M:%S") 254 | log(f" 变动时间: {d} ", msg) 255 | if not history_record.viewed: 256 | history_record.viewed = True 257 | _database_dirty = True 258 | return msg 259 | 260 | 261 | async def __RecordCollectionsPrice(check_item): 262 | """记录收藏列表中商品的价格""" 263 | msg: list[str] = [] 264 | try: 265 | history_cfg = check_item.get("history", {}) 266 | if not bool(history_cfg.get("enabled", True)): 267 | raise SystemExit("没有启用") 268 | device_id = check_item.get("device_id", "") 269 | refresh_token = check_item.get("refresh_token", "") 270 | if not device_id: 271 | raise SystemExit("device_id 配置有误") 272 | if not refresh_token: 273 | raise SystemExit("refresh_token 配置有误") 274 | 275 | async with PClient(device_id, refresh_token) as api: 276 | result = await api.InitializeToken( 277 | check_item.get("addr_filter"), force_update_receiver=False 278 | ) 279 | if isinstance(result, ApiResults.Error): 280 | if api.nickname: 281 | log(f"账号: {api.nickname}", msg) 282 | log(result, msg) 283 | raise StopIteration 284 | 285 | load_database() 286 | 287 | PAGE_SIZE = 10 288 | count = 0 289 | page = 1 # 从第一页开始拉取 290 | changed_msg: list[str] = [] 291 | while True: 292 | collections = await api.GetProductCollections(page, PAGE_SIZE) 293 | if isinstance(collections, ApiResults.Error): 294 | log(collections, msg) 295 | break 296 | count += len(collections.products) 297 | for p in collections.products: 298 | # 记录价格 299 | if RecordPrice(p): 300 | changed_msg.extend(OutputHistoryPrice(p)) 301 | if ( 302 | count >= collections.total_count 303 | or collections.total_count < PAGE_SIZE 304 | ): 305 | # 不知朴朴怎么想的 空列表还会下发一个不为零的total_count 306 | break 307 | page += 1 308 | 309 | if changed_msg: 310 | log(f"账号: {api.nickname}", msg) 311 | log("以下商品价格有变化:", msg) 312 | msg.extend(changed_msg) 313 | 314 | except StopIteration: 315 | pass 316 | except Exception: 317 | log(f"失败: 请检查接口 {format_exc()}", msg) 318 | finally: 319 | save_database() 320 | return "\n".join(msg) 321 | 322 | 323 | @check(run_script_name="朴朴历史价", run_script_expression="pupu") 324 | def main(*args, **kwargs): 325 | return asyncio.run(__RecordCollectionsPrice(kwargs.get("value", {}))) 326 | 327 | 328 | if __name__ == "__main__": 329 | main() 330 | -------------------------------------------------------------------------------- /ck_pupu_lottery.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | cron: 30 9,23 * * * 4 | new Env('朴朴抽奖'); 5 | 6 | 微信登录朴朴app 7 | 找到请求https://cauth.pupuapi.com/clientauth/user/society/wechat/login?user_society_type=11 8 | 在json响应里有refresh_token 9 | 10 | lottery_id 手动配置抽奖id 支持字符串数组或单个字符串 11 | find_lottery 是否自动获取抽奖活动, 默认自动(部分藏的很深的抽奖暂时需要手动配置id) 12 | coin_exchange 每个抽奖活动可兑换多少次朴分, 默认0次不兑换 13 | """ 14 | import asyncio 15 | import sys 16 | from traceback import format_exc 17 | from typing import Iterable 18 | 19 | from pupu_api import Client as PClient 20 | from pupu_types import * 21 | from utils import aio_randomSleep, check, log 22 | 23 | assert sys.version_info >= (3, 9) 24 | 25 | 26 | class PUPU: 27 | __slots__ = ( 28 | "check_item", 29 | "device_id", 30 | "refresh_token", 31 | "exchange_limit", 32 | ) 33 | 34 | def __init__(self, check_item) -> None: 35 | self.check_item: dict = check_item 36 | 37 | async def main(self): 38 | msg: list[str] = [] 39 | try: 40 | self.device_id = self.check_item.get("device_id", "") 41 | self.refresh_token = self.check_item.get("refresh_token", "") 42 | if not self.device_id: 43 | raise SystemExit("device_id 配置有误") 44 | if not self.refresh_token: 45 | raise SystemExit("refresh_token 配置有误") 46 | 47 | msg += await self.Lottery() 48 | except Exception: 49 | log(f"失败: 请检查接口 {format_exc()}", msg) 50 | return "\n".join(msg) 51 | 52 | async def Lottery(self): 53 | msg: list[str] = [] 54 | async with PClient(self.device_id, self.refresh_token) as api: 55 | result = await api.InitializeToken( 56 | self.check_item.get("addr_filter"), force_update_receiver=False 57 | ) 58 | if isinstance(result, ApiResults.Error): 59 | if api.nickname: 60 | log(f"账号: {api.nickname}", msg) 61 | log(result, msg) 62 | return msg 63 | 64 | log(f"账号: {api.nickname}", msg) 65 | 66 | # 领取所有可领取的积分 67 | coin_ids = await api.GetCoinList() 68 | if isinstance(coin_ids, ApiResults.Error): 69 | log(coin_ids, msg) 70 | elif not coin_ids: 71 | log("当前无可领取的朴分") 72 | else: 73 | total_coin = 0 74 | for i, id in enumerate(coin_ids): 75 | coin, _ = await asyncio.gather( 76 | api.DrawCoin(id), aio_randomSleep(1, 3) 77 | ) 78 | if isinstance(coin, ApiResults.Error): 79 | log(coin, msg) 80 | else: 81 | log(f" [{i+1}/{len(coin_ids)}]成功领取{coin}朴分") 82 | total_coin += coin 83 | log(f"成功领取{total_coin}朴分", msg) 84 | 85 | lottery_ids: list[str] = [] 86 | id = self.check_item.get("lottery_id") 87 | if id: 88 | if isinstance(id, str): 89 | if id not in lottery_ids: 90 | lottery_ids.append(id) 91 | elif isinstance(id, Iterable): 92 | for i in id: 93 | if i not in lottery_ids: 94 | lottery_ids.append(i) 95 | 96 | if self.check_item.get("find_lottery", True): 97 | banner_result, coin_cfg = await asyncio.gather( 98 | api.GetBanner( 99 | BANNER_LINK_TYPE.CUSTOM_LOTTERY, 100 | position_types=[60, 220, 560, 620, 830, 850, 860, 890], 101 | ), 102 | api.GetCoinConfig(), 103 | ) 104 | if isinstance(banner_result, ApiResults.Error): 105 | log(banner_result, msg) 106 | else: 107 | banner_result.banners.sort(key=lambda b: b.title) 108 | # 把翻翻乐放在第一位 109 | for i, b in enumerate(banner_result.banners): 110 | if "翻翻乐" in b.title: 111 | if i > 0: 112 | del banner_result.banners[i] 113 | banner_result.banners.insert(0, b) 114 | break 115 | for b in banner_result.banners: 116 | if b.link_id not in lottery_ids: 117 | if b.link_id not in lottery_ids: 118 | lottery_ids.append(b.link_id) 119 | log(f" 找到抽奖: {b.title}") 120 | if isinstance(coin_cfg, ApiResults.Error): 121 | log(coin_cfg, msg) 122 | elif coin_cfg not in lottery_ids: 123 | lottery_ids.insert(0, coin_cfg) 124 | else: 125 | log(f" 跳过了自动查找活动") 126 | 127 | if len(lottery_ids) > 0: 128 | self.exchange_limit = self.check_item.get("coin_exchange", 0) 129 | log(f"朴分兑换限制数: {self.exchange_limit}次", msg) 130 | for id in lottery_ids: 131 | # 串行抽奖 确保print按顺序执行 132 | msg += await self._Lottery(api, id) 133 | else: 134 | log("无抽奖活动") 135 | exit() # 目前没必要执行后续的操作 136 | return msg 137 | 138 | async def _Lottery(self, api: PClient, id: str): 139 | """抽奖""" 140 | msg: list[str] = [] 141 | # 首先获取抽奖详情 142 | info = await api.GetLotteryInfo(id) 143 | if isinstance(info, ApiResults.Error): 144 | log(info, msg) 145 | return msg 146 | # 似乎朴朴压根不关心你点的哪张牌 147 | # elif info.lottery.type != LOTTERY_TYPE.DRAW: 148 | # log(f'[{info.lottery.name}] 不支持: {info.lottery.type}', msg) 149 | # return msg 150 | log(f"正在进行 [{info.lottery.name}]", msg) 151 | # 同时拉取任务列表和抽奖机会兑换列表 152 | task_groups, chance_info = await asyncio.gather( 153 | api.GetTaskGroupsData(info.lottery), api.GetChanceEntrances(info.lottery) 154 | ) 155 | if isinstance(task_groups, ApiResults.Error): 156 | log(task_groups, msg) 157 | elif not task_groups.tasks: 158 | log(" 没有配置任务") 159 | else: 160 | # 然后开始做任务 161 | for task in task_groups.tasks: 162 | if task.task_status != TaskStatus.Undone: 163 | continue 164 | if task.page_rule: 165 | co_task = api.PostPageTaskComplete(task) 166 | 167 | elif task.target_code == 3001: 168 | co_task = api.ClockInTask(info.lottery, task) 169 | else: 170 | continue 171 | # 每个任务至少间隔2~5秒的时间 172 | _, task_result = await asyncio.gather(aio_randomSleep(2, 5), co_task) 173 | if isinstance(task_result, ApiResults.Error): 174 | log(task_result) 175 | else: 176 | log(f" {task.task_name}: 已完成") 177 | 178 | # 接着尝试朴分兑换 179 | exchange_count = 0 180 | while True: 181 | if isinstance(chance_info, ApiResults.Error): 182 | # 拉取失败了 183 | if chance_info.code != ERROR_CODE.BUSY: 184 | log(chance_info, msg) 185 | # 直接尝试抽奖 186 | _, lottery_msg = await self.__Lottery(api, info) 187 | msg += lottery_msg 188 | break 189 | for entrance in chance_info.entrances: 190 | if entrance.type == CHANCE_OBTAIN_TYPE.COIN_EXCHANGE: 191 | # 目前只支持朴分兑换 192 | break 193 | else: 194 | # 没有可用的朴分兑换入口 195 | entrance = None 196 | if entrance: 197 | while ( 198 | chance_info.coin_balance >= entrance.target_value 199 | and exchange_count < self.exchange_limit 200 | ): 201 | # 朴分足够、兑换次数没超过限制 202 | _, exchange_result = await asyncio.gather( 203 | aio_randomSleep(4, 8), # 间隔4~8秒,确保朴分、抽奖机会数更新 204 | api.CoinExchange(info.lottery, entrance), 205 | ) 206 | if isinstance(exchange_result, ApiResults.Error): 207 | log(exchange_result) 208 | break 209 | exchange_count += 1 210 | log( 211 | f" 第{exchange_count}次{entrance.title}: 成功兑换{exchange_result.gain_num}次抽奖机会" 212 | ) 213 | # 更新朴分余额 214 | chance_info = await api.GetChanceEntrances(info.lottery) 215 | if isinstance(chance_info, ApiResults.Error): 216 | # 拉取失败了 217 | log(chance_info) 218 | break 219 | else: 220 | if chance_info.coin_balance < entrance.target_value: 221 | log( 222 | f" 当前朴分{chance_info.coin_balance}少于{entrance.target_value}, 放弃兑换" 223 | ) 224 | # 开始抽奖 225 | result, lottery_msg = await self.__Lottery(api, info) 226 | msg += lottery_msg 227 | if not result: 228 | # 抽奖失败 229 | break 230 | # 更新朴分余额 231 | chance_info = await api.GetChanceEntrances(info.lottery) 232 | return msg 233 | 234 | async def __Lottery(self, api: PClient, info: ApiResults.LotteryInfo): 235 | """抽奖""" 236 | msg: list[str] = [] 237 | chances_info = await api.GetUserLotteryInfo(info.lottery) 238 | if isinstance(chances_info, ApiResults.Error): 239 | log(chances_info, msg) 240 | return (False, msg) 241 | elif chances_info.remain_chances <= 0: 242 | log(" 没有抽奖机会", msg) 243 | return (False, msg) 244 | 245 | log(f" 当前有{chances_info.remain_chances}次抽奖机会", msg) 246 | for i in range(chances_info.remain_chances): 247 | # 每次抽奖至少间隔4~8秒的时间 248 | _, lottery_result = await asyncio.gather( 249 | aio_randomSleep(4, 8), api.Lottery(info.lottery) 250 | ) 251 | if isinstance(lottery_result, ApiResults.Error): 252 | log(f" 第{i+1}次抽奖: {lottery_result}", msg) 253 | else: 254 | log(f" 第{i+1}次抽奖: {lottery_result.prize.name}", msg) 255 | return (True, msg) 256 | 257 | 258 | @check(run_script_name="朴朴抽奖", run_script_expression="pupu") 259 | def main(*args, **kwargs): 260 | return asyncio.run(PUPU(check_item=kwargs.get("value")).main()) 261 | 262 | 263 | if __name__ == "__main__": 264 | main() 265 | -------------------------------------------------------------------------------- /ck_pupu_sign.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | cron: 0 1,20 * * * 4 | new Env('朴朴签到'); 5 | 6 | 微信登录朴朴app 7 | 找到请求https://cauth.pupuapi.com/clientauth/user/society/wechat/login?user_society_type=11 8 | 在json响应里有refresh_token 9 | """ 10 | import asyncio 11 | import sys 12 | from traceback import format_exc 13 | 14 | from pupu_api import Client as PClient 15 | from pupu_types import * 16 | from utils import check, log 17 | 18 | assert sys.version_info >= (3, 9) 19 | 20 | 21 | class PUPU: 22 | 23 | __slots__ = ("check_item", 24 | "device_id", 25 | "refresh_token", 26 | ) 27 | 28 | def __init__(self, check_item) -> None: 29 | self.check_item: dict = check_item 30 | 31 | async def main(self): 32 | msg: list[str] = [] 33 | try: 34 | self.device_id = self.check_item.get("device_id", "") 35 | self.refresh_token = self.check_item.get("refresh_token", "") 36 | if not self.device_id: 37 | raise SystemExit("device_id 配置有误") 38 | if not self.refresh_token: 39 | raise SystemExit("refresh_token 配置有误") 40 | 41 | msg += await self.sign() 42 | except Exception: 43 | log(f'失败: 请检查接口 {format_exc()}', msg) 44 | return "\n".join(msg) 45 | 46 | async def sign(self): 47 | msg: list[str] = [] 48 | async with PClient(self.device_id, self.refresh_token) as api: 49 | result = await api.InitializeToken(self.check_item.get("addr_filter"), 50 | force_update_receiver=False) 51 | if isinstance(result, ApiResults.Error): 52 | if api.nickname: 53 | log(f'账号: {api.nickname}', msg) 54 | log(result, msg) 55 | return msg 56 | elif isinstance(result, ApiResults.TokenRefreshed): 57 | if result.changed: 58 | log(f"refresh_token 已更新为: {result.refresh_token}") 59 | else: 60 | log(f"令牌已更新为: {api.access_token}") 61 | 62 | log(f'账号: {api.nickname}', msg) 63 | 64 | # 开始签到 65 | result = await api.SignIn() 66 | if isinstance(result, ApiResults.Error): 67 | if result.code == ERROR_CODE.kRepeatedSignIn: 68 | log("重复签到: 忽略", msg) 69 | exit() # 目前没必要执行后续的操作 70 | else: 71 | log(result, msg) 72 | else: 73 | log(f'签到成功: 奖励积分+{result.coin} {result.explanation}', msg) 74 | 75 | result = await api.GetSignPeriodInfo() 76 | if isinstance(result, ApiResults.Error): 77 | log(result, msg) 78 | else: 79 | log(f'签到信息: 本周连续签到{result.days}天', msg) 80 | return msg 81 | 82 | 83 | @check(run_script_name="朴朴签到", run_script_expression="pupu") 84 | def main(*args, **kwargs): 85 | return asyncio.run(PUPU(check_item=kwargs.get("value")).main()) 86 | 87 | 88 | if __name__ == "__main__": 89 | main() 90 | -------------------------------------------------------------------------------- /ck_rrtv.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | cron: 10 8,22 * * * 4 | new Env('多多视频'); 5 | """ 6 | from utils import check, randomSleep, log 7 | from urllib3 import disable_warnings, Retry 8 | from requests.adapters import HTTPAdapter 9 | import requests 10 | 11 | 12 | class RRTV: 13 | clientVersion = "5.21.1" 14 | clientType = "ios_jxx" # android | android_Meizu 15 | userAgent = "JuXingXing/1.2 (iPhone; iOS 15.4.1; Scale/3.00)" 16 | # activity_userAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 15_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 App/RRSPApp platform/iPhone AppVersion/1.12" 17 | api_host = "https://api.juxingclub.com" 18 | 19 | activity_url_sign = api_host + "/rrtv-activity/sign/sign" 20 | activity_url_getinfo = api_host + "/rrtv-activity/sign/getInfo" 21 | activity_url_openBag = api_host + "/rrtv-activity/sign/openBag" 22 | activity_url_listreward = api_host + "/rrtv-activity/sign/getAllBagItemMaterial" 23 | activity_url_reflashCard = api_host + "/rrtv-activity/sign/reflashUserCard" 24 | 25 | taskcenter_url_openbox = api_host + "/v3plus/taskCenter/openBox" 26 | taskcenter_url_listbox = api_host + "/v3plus/taskCenter/index" 27 | 28 | vip_url_clock = api_host + "/vip/experience/clock" 29 | 30 | """ 31 | API定义 https://img.rr.tv/rrsp/0.1.0/js/main.1641814753479.js 32 | 逻辑处理 https://img.rr.tv/rrsp/0.1.0/js/checkin.1641814753479.js 33 | """ 34 | 35 | def __init__(self, check_item): 36 | self.check_item = check_item 37 | self.session = requests.Session() 38 | self.session.verify = False 39 | adapter = HTTPAdapter() 40 | adapter.max_retries = Retry(connect=3, read=3, allowed_methods=None) 41 | self.session.mount("https://", adapter) 42 | self.session.mount("http://", adapter) 43 | 44 | def __postRequest(self, url: str, data=None, json=None): 45 | """ 46 | 发起一个POST请求 47 | 48 | :param text: body体 49 | :return: 如果成功 返回响应的JSON对象 50 | """ 51 | headers = { 52 | "Accept": "application/json, text/plain, */*", 53 | "Accept-Language": "zh-CN,zh-Hans;q=0.9", 54 | "clientVersion": self.clientVersion, 55 | "token": self.token, 56 | # "Origin": "https://mobile.rr.tv", 57 | "clientType": self.clientType, 58 | "User-Agent": self.userAgent, 59 | # "Referer": "https://mobile.rr.tv/", 60 | "Connection": "keep-alive" 61 | } 62 | response: requests.Response = self.session.post( 63 | url=url, headers=headers, data=data, json=json) 64 | return response.json() 65 | 66 | def __getRewardList(self): 67 | """ 68 | 获取礼盒中的奖品列表 69 | """ 70 | rewards = [] # {"code":"x", "name":"apple"} 71 | try: 72 | obj = self.__postRequest(self.activity_url_listreward) 73 | if obj["code"] == "0000": 74 | # isinstance(data, list) 75 | log("礼盒中的奖品列表") 76 | for reward in obj.get("data", []): 77 | code = reward.get("code", "unknown") 78 | name = f'{reward.get("text1")}{reward.get("text2")}' 79 | rewards += [{"code": code, "name": name}] 80 | log(f'- {name} ') 81 | else: 82 | log(f'获取奖品列表失败: code:{obj["code"]}, msg:{obj["msg"]}') 83 | except Exception as e: 84 | log(f'获取奖品列表异常: {e}') 85 | return rewards 86 | 87 | def __openBox(self, id: str, name: str): 88 | """ 89 | 开启宝箱 90 | 91 | :param id: 宝箱id 92 | :param name: 宝箱名 93 | """ 94 | msg = [] 95 | try: 96 | obj = self.__postRequest( 97 | self.taskcenter_url_openbox, data={"boxId": id}) 98 | if obj["code"] == "0000": 99 | box = obj["data"]["boxs"][0] 100 | reward = f'{box.get("rewardName")}+{box.get("rewardNum")}' 101 | log(f'- 开{name}: {reward} ', msg) 102 | else: 103 | log(f'- 开{name}失败: code:{obj["code"]}, msg:{obj["msg"]} ', msg) 104 | except Exception as e: 105 | log(f'- 开{name}异常: 请检查接口 {e} ', msg) 106 | return msg 107 | 108 | def openAllBoxes(self): 109 | """ 110 | 开启所有可开的宝箱 111 | """ 112 | msg = [] 113 | empty = False 114 | try: 115 | obj = self.__postRequest(self.taskcenter_url_listbox) 116 | if obj["code"] == "0000": 117 | ap = obj["data"]["activePoint"] 118 | log(f'今日活跃度: {ap}', msg) 119 | if ap is None: 120 | return msg 121 | availBoxes = [] 122 | boxes = obj["data"]["box"] 123 | for box in boxes: 124 | id = str(box["id"]) 125 | name = box.get("name", id) 126 | if not box.get("enabled", 0) == 1: 127 | log(f'- {name} 没有启用') 128 | continue 129 | if not box.get("status", 1) == 0: 130 | log(f'- {name} 已开过 忽略') 131 | continue 132 | availBoxes += [{"id": id, "name": name}] 133 | log(f'可开宝箱: {len(availBoxes)}/{len(boxes)}个', msg) 134 | for box in availBoxes: 135 | randomSleep(max=3) 136 | msg += self.__openBox(box["id"], box["name"]) 137 | empty = not availBoxes 138 | msg += ['\n'] # md缩进后需要一个换行结束缩进 139 | else: 140 | log(f'开宝箱失败: code:{obj["code"]}, msg:{obj["msg"]}', msg) 141 | except Exception as e: 142 | log(f'获取宝箱异常: 请检查接口 {e}', msg) 143 | return (msg, empty) 144 | 145 | def giftDraw(self): 146 | """ 147 | 礼盒抽奖 148 | """ 149 | msg = [] 150 | rewards = self.__getRewardList() 151 | try: 152 | obj = self.__postRequest(self.activity_url_openBag) 153 | if obj["code"] == "0000": 154 | materialCode = obj["data"]["materialCode"] 155 | for reward in rewards: 156 | if reward["code"] == materialCode: 157 | # 中奖 158 | log(f'- 礼盒抽中: {reward["name"]} 请到App中查收 ', msg) 159 | break 160 | else: 161 | log(f'- 抽奖失败: code:{obj["code"]}, msg:{obj["msg"]} ', msg) 162 | except Exception as e: 163 | log(f'- 抽奖异常: 请检查接口 {e} ', msg) 164 | return msg 165 | 166 | def __getCheckinInfo(self): 167 | try: 168 | obj = self.__postRequest(self.activity_url_getinfo) 169 | if obj["code"] == "0000": 170 | return obj["data"] 171 | else: 172 | log(f'获取签到信息失败: code:{obj["code"]}, msg:{obj["msg"]}') 173 | except Exception as e: 174 | log(f'获取签到信息异常: {e}') 175 | return {} 176 | 177 | def __resetCard(self, id): 178 | """ 179 | 重置剧本 180 | """ 181 | msg = [] 182 | try: 183 | obj = self.__postRequest( 184 | self.activity_url_reflashCard, data={"cardDetailId": id}) 185 | if obj["code"] == "0000": 186 | log(f'- 重置剧本{id}成功', msg) 187 | return True, msg 188 | else: 189 | log(f'- 重置剧本{id}失败: code:{obj["code"]}, msg:{obj["msg"]}', msg) 190 | except Exception as e: 191 | log(f'- 重置剧本{id}异常: {e}', msg) 192 | return False, msg 193 | 194 | def getSignInfo(self): 195 | """ 196 | 获取当前签到的信息 197 | 198 | :return: (msg, 是否能抽奖) 199 | """ 200 | msg = [] 201 | canDraw = False 202 | try: 203 | weekNum = -1 204 | data = self.__getCheckinInfo() 205 | signDetailList = data.get("signDetailList", []) 206 | if len(signDetailList) > 0: 207 | continueDays = str(signDetailList[0].get("continueDays", "-1")) 208 | log(f'已连续签到: {continueDays}天', msg) 209 | weekNum = int(signDetailList[0].get('weekNum', 0)) 210 | else: 211 | # 本周没有签到 212 | pass 213 | log(f'当前骰子: {data.get("diceCount")}个', msg) 214 | while weekNum == 7 and data.get("canOpenBag") == False and int(data.get("diceCount", 0)) > 0: 215 | # 剧本不满足抽奖条件,但可以用骰子重置剧本 216 | randomSleep(max=3) 217 | resetSucc = False 218 | for card in data.get("cardDetailList", []): 219 | if card.get("showDice") == True: 220 | # 这个剧本可以用骰子换一个 221 | resetSucc, resetMsg = self.__resetCard(card["id"]) 222 | msg += resetMsg 223 | break 224 | if not resetSucc: 225 | break 226 | # 重置成功 则再循环一次判断 227 | data = self.__getCheckinInfo() 228 | log(f'- 剩余骰子: {data.get("diceCount")}个', msg) 229 | canOpenBag = data.get("canOpenBag") 230 | isOpenBag = data.get("isOpenBag") 231 | if canOpenBag == True: 232 | if isOpenBag == False: 233 | # 本周礼盒可以抽奖了 234 | canDraw = True 235 | log('本周礼盒: 可以抽奖', msg) 236 | else: 237 | log('本周礼盒: 开过的旧盒子', msg) 238 | else: 239 | log('本周礼盒: 尚未获得', msg) 240 | except Exception as e: 241 | log(f'解析签到异常: 请检查接口 {e}', msg) 242 | 243 | return msg, canDraw 244 | 245 | def vipSignIn(self): 246 | """ 247 | VIP打卡 248 | 249 | """ 250 | msg = [] 251 | repeated = False 252 | try: 253 | obj = self.__postRequest(self.vip_url_clock) 254 | if obj["code"] == "0000": 255 | log(f'打卡成功: 当前V力值{obj["data"]["changedValue"]}', msg) 256 | elif obj["code"] == "9999": 257 | log('重复打卡: 忽略', msg) 258 | repeated = True 259 | else: 260 | log(f'打卡失败: code:{obj["code"]}, msg:{obj["msg"]}', msg) 261 | except Exception as e: 262 | log(f'打卡异常: 请检查接口 {e}', msg) 263 | return msg, repeated 264 | 265 | def signIn(self): 266 | """ 267 | 签到 268 | 269 | 0点容易失败 避开签到高峰 270 | """ 271 | msg = [] 272 | repeated = False 273 | try: 274 | obj = self.__postRequest(self.activity_url_sign, {"dayOffset": 0}) 275 | if obj["code"] == "0000": # 8650应该是补签成功的返回码 8751是补签条件不满足 276 | log(f'每日签到: 成功', msg) 277 | data = obj["data"] 278 | # 剧本 279 | for card in data.get("cardList", []): 280 | log(f'获得剧本: {card.get("type")} {card.get("name")}', msg) 281 | # 经验值 282 | for jyz in data.get("jyzList", []): 283 | log(f'签到奖励: 经验值+{jyz}', msg) 284 | # 每周连续签到第3、6天将分别获得一个骰子 285 | for dice in data.get("diceList", []): 286 | log(f'签到奖励: 骰子+{dice}', msg) 287 | # 这是签到获得的勋章 288 | for medal in data.get("medalList", []): 289 | # log(str(medal)) 290 | # medal == '2_7' 291 | # 至少目前客户端是这样写死只有小蜜蜂 292 | log('签到奖励: 勋章 小蜜蜂7天', msg) 293 | elif obj["code"] == "8750": 294 | log('重复签到: 忽略', msg) 295 | repeated = True 296 | else: 297 | log(f'签到失败: code:{obj["code"]}, msg:{obj["msg"]}', msg) 298 | except Exception as e: 299 | log(f'签到异常: 请检查接口 {e}', msg) 300 | return (msg, repeated) 301 | 302 | def main(self): 303 | msg = [] 304 | try: 305 | self.token: str = self.check_item.get("token", "") 306 | if not self.token.startswith('rrtv-'): 307 | raise SystemExit('token配置有误 必须rrtv-开头') 308 | sign_msg, sign_repeated = self.signIn() 309 | msg += sign_msg 310 | # 无论签到是否成功,我们继续执行,也许能抽奖 311 | info_msg, canDraw = self.getSignInfo() 312 | msg += info_msg 313 | if canDraw == True: 314 | # 可以抽奖 315 | randomSleep() 316 | msg += self.giftDraw() 317 | # 尝试开宝箱 318 | randomSleep() 319 | boxes_msg, boxes_empty = self.openAllBoxes() 320 | msg += boxes_msg 321 | # 尝试VIP打卡 322 | randomSleep() 323 | vsign_msg, vsign_repeated = self.vipSignIn() 324 | msg += vsign_msg 325 | if all([sign_repeated, vsign_repeated, not canDraw, boxes_empty]): 326 | exit() # 目前没必要执行后续的操作 327 | except Exception as e: 328 | log(f'失败: 请检查接口{e}', msg) 329 | msg = "\n".join(msg) 330 | return msg 331 | 332 | 333 | @check(run_script_name="多多视频", run_script_expression="rrtv") 334 | def main(*args, **kwargs): 335 | return RRTV(check_item=kwargs.get("value")).main() 336 | 337 | 338 | if __name__ == "__main__": 339 | disable_warnings() 340 | main() 341 | -------------------------------------------------------------------------------- /ck_wyxw.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | cron: 0 8 * * * 4 | new Env('网易新闻'); 5 | """ 6 | import asyncio 7 | from dataclasses import dataclass 8 | from traceback import format_exc 9 | 10 | from utils import check, log 11 | import json_codec 12 | from aiohttp_retry import JitterRetry, RetryClient 13 | 14 | 15 | 16 | @dataclass 17 | class SignResult: 18 | awardGoldCoin: int # 10 19 | awardScore: int # 5 20 | serialDays: int # 142 21 | subtitle: str # "签满168天得重磅好礼" 22 | 23 | 24 | class WYXW: 25 | __slots__ = ("check_item", 26 | "user_u", 27 | "data", 28 | ) 29 | 30 | def __init__(self, check_item): 31 | self.check_item = check_item 32 | 33 | async def sign(self): 34 | msg = [] 35 | headers = { 36 | "Accept-Language": "zh-CN,zh-Hans;q=0.9", 37 | "Accept": "*/*", 38 | "Content-Type": "application/x-www-form-urlencoded", # 因为data传入的是字符串,必须由调用者指定类型 39 | "User-Agent": "NewsApp/88.4 iOS/15.4.1 (iPhone14,3)", 40 | "User-U": self.user_u 41 | } 42 | try: 43 | async with RetryClient(raise_for_status=True, retry_options=JitterRetry(attempts=3)) as session: 44 | async with session.post(url="https://c.m.163.com/uc/api/sign/v3/commit", 45 | data=self.data, ssl=False, 46 | headers=headers) as response: 47 | response = await response.json() 48 | if response["code"] == 200 and "data" in response: 49 | result = json_codec.decode(response["data"], SignResult) 50 | # 再签47天得「持之以恒」3级勋章 51 | log(f'签到成功: {result.subtitle}', msg) 52 | log(f'签到奖励: 金币+{result.awardGoldCoin} 积分+{result.awardScore}', msg) 53 | log(f'连续签到: {result.serialDays}天', msg) 54 | else: 55 | log(response["msg"], msg) 56 | if response["code"] == 700: 57 | # 重复签到 58 | exit() # 目前没必要执行后续的操作 59 | except Exception: 60 | log(f'签到失败: 请检查接口 {format_exc()}', msg) 61 | return msg 62 | 63 | async def main(self): 64 | msg = [] 65 | try: 66 | name = self.check_item.get("name") 67 | self.user_u = self.check_item.get("user_u") 68 | self.data = self.check_item.get("data") 69 | if not (self.user_u and self.data): 70 | raise SystemExit('user_u和data均要配置') 71 | log(f'帐号信息: {name}', msg) 72 | msg += await self.sign() 73 | except Exception: 74 | log(f'失败: 请检查接口 {format_exc()}', msg) 75 | finally: 76 | await asyncio.sleep(0.25) 77 | msg = "\n".join(msg) 78 | return msg 79 | 80 | 81 | @check(run_script_name="网易新闻", run_script_expression="WYXW") 82 | def main(*args, **kwargs): 83 | return asyncio.run(WYXW(check_item=kwargs.get("value")).main()) 84 | 85 | 86 | if __name__ == "__main__": 87 | main() 88 | -------------------------------------------------------------------------------- /dailycheckin_scripts/README.md: -------------------------------------------------------------------------------- 1 | # 配置说明 2 | 3 | ## 参数说明 4 | 5 | ### Web 签到配置 6 | 7 | | Name | 归属 | 属性 | 说明 | 8 | | :------------------------: | :-------------------------------------------------: | :--: | :----------------------------------------------------------- | 9 | | _**IQIYI**_.cookie | [爱奇艺](https://www.iqiyi.com/) | Web | 爱奇艺 帐号的 cookie 信息 | 10 | | _**KGQQ**_.cookie | [全民K歌](https://kg.qq.com/index-pc.html) | Web | 全民K歌 帐号的 cookie 信息 | 11 | | _**VQQ**_.auth_refresh | [腾讯视频](https://v.qq.com/) | Web | 腾讯视频 搜索 带有 `auth_refresh` 的 url,填写其完整的 URL | 12 | | _**VQQ**_.cookie | [腾讯视频](https://v.qq.com/) | Web | 腾讯视频 搜索 带有 `auth_refresh` 的 url,填写其对应的 cookie | 13 | | _**YOUDAO**_.cookie | [有道云笔记](https://note.youdao.com/web/) | Web | 有道云笔记 帐号的 cookie 信息 | 14 | | _**MUSIC163**_.phone | [网易云音乐](https://music.163.com/) | 账号 | 网易云音乐 帐号的手机号 | 15 | | _**MUSIC163**_.password | [网易云音乐](https://music.163.com/) | 账号 | 网易云音乐 帐号的密码 | 16 | | _**ONEPLUSBBS**_.cookie | [一加手机社区官方论坛](https://www.oneplusbbs.com/) | Web | 一加手机社区官方论坛 账户的 cookie | 17 | | _**TIEBA**_.cookie | [百度贴吧](https://tieba.baidu.com/index.html) | Web | 百度贴吧 cookie | 18 | | _**BILIBILI**_.cookie | [Bilibili](https://www.bilibili.com) | Web | Bilibili cookie | 19 | | _**BILIBILI**_.coin_num | [Bilibili](https://www.bilibili.com) | Web | Bilibili 每日投币数量 | 20 | | _**BILIBILI**_.coin_type | [Bilibili](https://www.bilibili.com) | Web | Bilibili 投币方式 默认为 0 ;1: 为关注用户列表视频投币 0: 为随机投币。如果关注用户发布的视频不足配置的投币数,则剩余部分使用随机投币 | 21 | | _**BILIBILI**_.silver2coin | [Bilibili](https://www.bilibili.com) | Web | Bilibili 是否开启银瓜子换硬币,默认为 True 开启 | 22 | | _**V2EX**_.cookie | [V2EX](https://www.v2ex.com/) | Web | V2EX 每日签到 | 23 | | _**V2EX**_.proxy | [V2EX](https://www.v2ex.com/) | Web | V2EX 代理的信息,无密码例子: http://127.0.0.1:1080 有密码例子: http://username:password@127.0.0.1:1080 | 24 | | _**WWW2NZZ**_.cookie | [咔叽网单](https://www.2nzz.com/) | Web | 咔叽网单 每日签到 | 25 | | _**SMZDM**_.cookie | [什么值得买](https://www.smzdm.com) | Web | 什么值得买 每日签到 | 26 | | _**CLOUD189**_.phone | [天翼云盘](https://cloud.189.cn/) | Web | 天翼云盘 手机号 | 27 | | _**CLOUD189**_.password | [天翼云盘](https://cloud.189.cn/) | Web | 天翼云盘 手机号对应的密码 | 28 | | _**POJIE**_.cookie | [吾爱破解](https://www.52pojie.cn/index.php) | Web | 吾爱破解 cookie | 29 | | _**MEIZU**_.cookie | [MEIZU 社区](https://bbs.meizu.cn) | Web | MEIZU 社区 cookie | 30 | | _**MEIZU**_.draw_count | [MEIZU 社区](https://bbs.meizu.cn) | Web | MEIZU 社区 抽奖次数 | 31 | | _**ZHIYOO**_.cookie | [智友邦](http://zhizhiyoo.net/) | Web | 智友邦 WEB Cookie | 32 | | _**CSDN**_.cookie | [CSDN](https://www.csdn.net/) | Web | CSDN Cookie | 33 | | _**EVERPHOTO**_.mobile | [时光相册](https://web.everphoto.cn/) | Web | 时光相册 https://web.everphoto.cn/api/auth URL 表单内的 mobile 数据 | 34 | | _**EVERPHOTO**_.password | [时光相册](https://web.everphoto.cn/) | Web | 时光相册 https://web.everphoto.cn/api/auth URL 表单内的 password 数据 | 35 | 36 | ### 公众号签到配置 37 | 38 | | Name | 归属 | 属性 | 说明 | 39 | | :----------------------: | :--------: | :----: | :----------------------------------------------------------- | 40 | | _**WOMAIL**_.url | 联通沃邮箱 | 公众号 | 联通沃邮箱 公众号 `https://nyan.mail.wo.cn/cn/sign/index/index?mobile` 开头的 URL | 41 | | _**WOMAIL**_.pause21days | 联通沃邮箱 | 公众号 | true: 开启21天自动暂停,false: 关闭自动暂停,每天都签到。默认开启自动暂停 | 42 | | _**WOMAIL**_.phone | 联通沃邮箱 | 公众号 | 手机号 | 43 | | _**WOMAIL**_.password | 联通沃邮箱 | 公众号 | 密码 | 44 | 45 | ### APP 签到配置 46 | 47 | | Name | 归属 | 属性 | 说明 | 48 | | :----------------------: | :-----------------------------------: | :--: | :----------------------------------------------------------- | 49 | | _**FMAPP**_.token | Fa米家 | APP | Fa米家 APP headers 中的 token | 50 | | _**FMAPP**_.cookie | Fa米家 | APP | Fa米家 APP headers 中的 cookie | 51 | | _**FMAPP**_.blackbox | Fa米家 | APP | Fa米家 APP headers 中的 blackBox | 52 | | _**FMAPP**_.device_id | Fa米家 | APP | Fa米家 APP headers 中的 deviceId | 53 | | _**FMAPP**_.fmversion | Fa米家 | APP | Fa米家 APP headers 中的 fmVersion | 54 | | _**FMAPP**_.os | Fa米家 | APP | Fa米家 APP headers 中的 os | 55 | | _**FMAPP**_.useragent | Fa米家 | APP | Fa米家 APP headers 中的 User-Agent | 56 | | _**ACFUN**_.phone | [AcFun](https://www.acfun.cn/) | APP | AcFun 手机账号 | 57 | | _**ACFUN**_.password | [AcFun](https://www.acfun.cn/) | APP | AcFun 账号密码 | 58 | | _**MGTV**_.params | 芒果 TV | APP | 芒果 TV 请求参数 | 59 | | _**PICACOMIC**_.email | [哔咔漫画](https://www.picacomic.com) | APP | 哔咔漫画 账号 | 60 | | _**PICACOMIC**_.password | [哔咔漫画](https://www.picacomic.com) | APP | 哔咔漫画 密码 | 61 | | _**WEIBO**_.url | 微博 | APP | 抓取开头为 `https://api.weibo.cn/2/users/show?` 的整个 url 填入即可 | 62 | | _**DUOKAN**_.cookie | 多看阅读 | APP | 多看阅读 cookie, 抓取开头为 `https://www.duokan.com` 下的 cookie 即可 | 63 | | _**WZYD**_.data | 王者营地 | APP | 王者营地 请求体中的 data, 抓包 APP 中域名为 `https://ssl.kohsocial.qq.com` 请求内容的全部参数 | 64 | | _**HEYTAP**_.cookie | 欢太商城 | APP | 欢太商城 请求体中的 Cookie, 抓包 APP 中域名为 `https://store.oppo.com/` 请求内容的 Cookie | 65 | | _**HEYTAP**_.useragent | 欢太商城 | APP | 欢太商城 请求体中的 User-Agent, 抓包 APP 中域名为 `https://store.oppo.com/` 请求内容的 User-Agent | 66 | | _**HEYTAP**_.draw | 欢太商城 | APP | 是否开启抽奖,默认 false | 67 | | _**UNICOM**_.mobile | 联通营业厅 | APP | 联通营业厅 手机号 | 68 | | _**UNICOM**_.password | 联通营业厅 | APP | 联通营业厅 6位登录密码 | 69 | | _**UNICOM**_.app_id | 联通营业厅 | APP | 联通营业厅 请求体中的 appId, 抓包 APP 中域名为 `https://m.client.10010.com/mobileService/login.htm` 请求内容的 appId | 70 | 71 | ### 其他任务配置 72 | 73 | | Name | 归属 | 属性 | 说明 | 74 | | :---------------------: | :-------------------------------------------------------: | :--: | :-------------------------------------- | 75 | | _**MIMOTION**_.phone | 小米运动 | 其他 | 小米运动刷步数的手机账号 | 76 | | _**MIMOTION**_.password | 小米运动 | 其他 | 小米运动刷步数的手机账号密码 | 77 | | _**MIMOTION**_.min_step | 小米运动 | 其他 | 小米运动刷步数的最小步数 | 78 | | _**MIMOTION**_.max_step | 小米运动 | 其他 | 小米运动刷步数的最大步数 | 79 | | _**BAIDUT**_.data_url | [百度搜索资源平台](https://ziyuan.baidu.com/site/index#/) | 其他 | 提交网站的 URL 链接 | 80 | | _**BAIDUT**_.submit_url | [百度搜索资源平台](https://ziyuan.baidu.com/site/index#/) | 其他 | 百度搜索资源平台 提交百度网站的目标 URL | 81 | | _**BAIDUT**_.times | [百度搜索资源平台](https://ziyuan.baidu.com/site/index#/) | 其他 | 每日对同一个网站提交次数 | 82 | -------------------------------------------------------------------------------- /oc_xqz.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | cron: 2 20 * * * 4 | new Env('闲趣赚3.24'); 5 | 6 | 参考js我改成python了 以下是原文 7 | @肥皂 3.22 闲趣赚 一天0.1-0.4或者更高(根据用户等级增加任务次数) 8 | 3.24 更新加入用户余额和信息。。。。 9 | 苹果&安卓下载地址:复制链接到微信打开 https://a.jrpub.cn/3345249 10 | 新人进去直接秒到账两个0.3.。。。(微信登录)花两分钟再完成下新人任务,大概秒到微信3元左右 11 | 感觉看账号等级,我的小号进去只能做五个任务,大号可以做十个。 12 | 建议做一下里面的任务,单价还是不错的,做完等级升上来了挂脚本收益也多一点。 13 | 抓取域名 wap.quxianzhuan.com 抓取cookie的全部数据。。 14 | 青龙变量 xqzck 多账户@隔开 15 | 更新加入用户余额和信息。。。。 16 | """ 17 | from utils import check, log, randomSleep, cookie_to_dic 18 | from urllib3 import disable_warnings, Retry 19 | from requests.adapters import HTTPAdapter 20 | import requests 21 | 22 | 23 | class XQZ: 24 | userAgent = "Mozilla/5.0 (iPhone; CPU iPhone OS 15_4_1 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148 LT-APP/43/242(YM-RT)" 25 | 26 | def __init__(self, check_item): 27 | self.check_item = check_item 28 | self.session = requests.Session() 29 | self.session.verify = False 30 | adapter = HTTPAdapter() 31 | adapter.max_retries = Retry(connect=3, read=3, allowed_methods=None) 32 | self.session.mount("https://", adapter) 33 | self.session.mount("http://", adapter) 34 | self.access_token = None 35 | 36 | def __sendRequest(self, method: str, url: str, data=None, json=None): 37 | """ 38 | 发起一个POST/GET/PUT请求 39 | 40 | :param text: body体 41 | :return: requests.Response 42 | """ 43 | headers = { 44 | "Accept": "application/json, text/plain, */*", 45 | "Accept-Language": "zh-CN,zh-Hans;q=0.9", 46 | "x-app": "96c1ea5a-9a52-44c9-8ac4-8dceafa065c8", # 估计是uuid、设备id之类的 47 | "X-Requested-With": "XMLHttpRequest", 48 | "User-Agent": self.userAgent, 49 | "Referer": "https://wap.quxianzhuan.com/reward/list/?xapp-target=blank", 50 | "Connection": "keep-alive" 51 | } 52 | method = method.upper() 53 | response: requests.Response = self.session.request(method, 54 | url=url, headers=headers, data=data, json=json) 55 | return response 56 | 57 | def getTaskList(self, page=1): 58 | """ 59 | 获取任务列表 每次拉20个 60 | """ 61 | msg = [] 62 | browse_list = None 63 | try: 64 | obj = self.__sendRequest( 65 | "get", f"http://wap.quxianzhuan.com/reward/browse/index?page={page}").json() # &limit={limit} 66 | 67 | if obj["state"] != 1: 68 | log(f'获取任务列表失败 state={obj["state"]} msg={obj.get("msg", "未知错误")}', msg) 69 | # return None, msg 70 | 71 | self.formhash = self.session.cookies.get("tzb_formhash_cookie") 72 | if not self.formhash: 73 | log("无法获取 tzb_formhash_cookie", msg) 74 | return None, msg 75 | 76 | browse_list = obj.get("browse_list", []) 77 | log(f'本次获取到{len(browse_list)}个任务') 78 | except Exception as e: 79 | log(f"获取任务列表异常 请检查接口 {e}", msg) 80 | return browse_list, msg 81 | 82 | def browseTask(self, item): 83 | """ 84 | 浏览任务 如果失败将抛出StopIteration异常 85 | """ 86 | msg = [] 87 | try: 88 | id = item["reward_id"] 89 | log(f'- 任务ID:{id} {item.get("cat_name", "")}-{item.get("tags_name", "")}-{item.get("reward_title", "")}', msg) 90 | obj = self.__sendRequest("post", "https://wap.quxianzhuan.com/reward/browse/append/", 91 | data={"reward_id": id, "formhash": self.formhash, "inajax": 1}).json() 92 | if obj["state"] != 1: 93 | log(f'浏览失败 {obj.get("msg", "未知错误")}', msg) 94 | raise StopIteration 95 | log(f' {obj.get("msg", "已完成")}') 96 | except StopIteration as e: 97 | raise e 98 | except Exception as e: 99 | log(f"浏览异常 请检查接口 {e}", msg) 100 | return msg 101 | 102 | def userInfo(self): 103 | msg = [] 104 | try: 105 | text = self.__sendRequest( 106 | "get", "https://wap.quxianzhuan.com/user/").text 107 | # 返回的是html 108 | available_money = simple_match(text, '"available_money":', ',') 109 | uid = simple_match(text, 'UID:', '') 110 | log(f'用户 {uid} - 可提现余额【{available_money}】', msg) 111 | except Exception as e: 112 | log(f"查询账号异常 请检查接口 {e}", msg) 113 | return msg 114 | 115 | def main(self): 116 | msg = [] 117 | try: 118 | cookies = self.check_item.get("cookie", "") 119 | token = cookie_to_dic(cookies).get("tzb_user_cryptograph") 120 | if not token: 121 | raise SystemExit("Cookie配置有误 必须有 tzb_user_cryptograph") 122 | # Cookie只需要 tzb_user_cryptograph 即可,只有它是5天有效期 123 | self.session.cookies.set( 124 | "tzb_user_cryptograph", token, domain=".quxianzhuan.com") 125 | curr_page = 1 126 | total_task = 0 127 | succ_task = 0 128 | price = 0.0 129 | while (True): 130 | task_list, task_msg = self.getTaskList(page=curr_page) 131 | msg += task_msg 132 | if not task_list: 133 | break 134 | total_task += len(task_list) 135 | try: 136 | for task in task_list: 137 | self.browseTask(task) 138 | succ_task += 1 139 | try: 140 | unit_price = float(task.get("unit_price")) 141 | price += unit_price 142 | except Exception: 143 | pass 144 | randomSleep(11, 20) 145 | except StopIteration: 146 | break 147 | if len(task_list) < 20: 148 | # 这已经是最后一页 149 | break 150 | curr_page += 1 151 | log(f'已成功浏览{succ_task}个任务 获得{price}元', msg) 152 | msg += self.userInfo() 153 | new_token = self.session.cookies.get("tzb_user_cryptograph") 154 | if new_token != token: 155 | log(f'测试代码居然跑进来了 new_token={new_token}', msg) 156 | except Exception as e: 157 | log(f"失败: 请检查接口{e}", msg) 158 | msg = "\n".join(msg) 159 | return msg 160 | 161 | 162 | def simple_match(s: str, prefix: str, suffix: str): 163 | pos1 = s.find(prefix) 164 | if pos1 < 0: 165 | return None 166 | pos1 += len(prefix) 167 | pos2 = s.find(suffix, pos1) 168 | if pos2 < 0: 169 | return None 170 | return s[pos1:pos2] 171 | 172 | 173 | @check(run_script_name="闲趣赚", run_script_expression="XQZ") 174 | def main(*args, **kwargs): 175 | return XQZ(check_item=kwargs.get("value")).main() 176 | 177 | 178 | if __name__ == "__main__": 179 | disable_warnings() 180 | main() 181 | -------------------------------------------------------------------------------- /other_scripts/README.md: -------------------------------------------------------------------------------- 1 | ## 参数说明 2 | 3 | | Name | 归属 | 属性 | 说明 | 4 | | :-------------------: | :--------: | :--: | :------------------------------------ | 5 | | GAME163.authorization | [网易云游戏](https://cg.163.com/#/pc) | Web | 网易云游戏网站请求头authorization字段 | 6 | 7 | -------------------------------------------------------------------------------- /pupu_types.py: -------------------------------------------------------------------------------- 1 | from dataclasses import dataclass, field 2 | from enum import Enum, IntEnum 3 | from sys import _getframe as getframe 4 | from sys import version_info as py_version 5 | from traceback import format_exc 6 | from typing import Optional # 确保兼容<=Python3.9 7 | 8 | from utils import MyIntEnum 9 | 10 | assert py_version >= (3, 9) 11 | 12 | 13 | class BANNER_LINK_TYPE(MyIntEnum): 14 | RECOMMEND = -10 15 | PRODUCT_DETAIL_ACTIVITY = 0 16 | TOPIC_ACTIVITY = 10 17 | SEARCH_RESULT_ACTIVITY = 90 18 | DISCOVERY_DETAIL_ACTIVITY = 220 19 | COUPON_DETAIL_ACTIVITY = 230 20 | COUPON_LIST = 231 21 | GOOD_NEIGHBOR_ACTIVITY = 250 22 | ACTIVITY_ACTIVITY = 400 23 | INDEX_TAB_ALL_CATEGORY = 410 24 | FLASH_SALE_ACTIVITY = 650 25 | SCENE_PRODUCT_LIST = 700 26 | COOKBOOK_DETAIL = 900 27 | COOKBOOK_LIST = 901 28 | COOKBOOK_CHANNEL = 902 29 | OPEN_MP_LIVE = 903 30 | NO_JUMP = 910 31 | INDEX = 999 32 | USER_GIFT_CARD = 1000 33 | MY_USER_GIFT_CARD = 1001 34 | MY_COIN = 1010 35 | CUSTOMER_CONTACT = 1011 36 | DELIVER_ADDRESS = 1012 37 | INVOICE_EXPENSE = 1013 38 | DELIVER_BELL = 1014 39 | MY_COLLECT = 1015 40 | ABOUT_PUPU = 1016 41 | SHARE_PUPU = 1017 42 | USER_TASK = 1022 43 | CUSTOM_LOTTERY = 1023 44 | LOGIN_PAGE = 1024 45 | SHARE_SELF = 1025 46 | NOVICE = 1027 47 | GIFT_CARD_STORE_H5 = 1029 48 | ORDER_DETAIL = 1030 49 | kUnk_1032 = 1032 50 | kUnk_1034 = 1034 51 | IMPORTANT_PRODUCT_LIST = 2620 52 | WEB = 99999 53 | 54 | 55 | class DiscountType(MyIntEnum): 56 | ALL = -1 # 全部 57 | ABSOLUTE = 0 # 满减 58 | PERCENTAGE = 10 # 百分比折扣 59 | GIFT_PRODUCT = 20 # 买赠 60 | EACH_GIFT_PRODUCT = 30 # 每买赠 61 | EACH_GIFT_MONEY = 40 # 每满减 62 | TRADE_BUY = 50 # 换购 63 | 64 | 65 | class DeliveryReasonType(MyIntEnum): 66 | WEATHER = 0 67 | PEAK = 1 # 因配送高峰, 配送时间有调整, 请耐心等待 68 | OTHER = 2 69 | PROLONG = 4 70 | EXHAUSTED = 5 # 没有骑手? 71 | FUTURE_PRODUCTS = 6 72 | PROPERTY_PROBLEM = 100 73 | TRAFFIC_PROBLEM = 200 74 | LONG_DISTANCE = 300 75 | 76 | 77 | class DeliveryTimeType(MyIntEnum): 78 | IMMEDIATE = 0 79 | RESERVE = 10 80 | 81 | 82 | class LOTTERY_TYPE(MyIntEnum): 83 | SLOT = 10 84 | FLOP = 20 85 | DRAW = 30 86 | 87 | 88 | class CHANCE_OBTAIN_TYPE(MyIntEnum): 89 | RECEIVE_ORGER = 10 90 | INVITE_NEW_USER = 20 91 | COIN_EXCHANGE = 30 # 积分兑换 92 | SIGN_IN = 40 93 | INVITE_FRIEND_BOOST = 60 # 邀请助力 94 | GO_TO_BOOST = 70 95 | UNKNOWN_170 = 170 96 | 97 | 98 | class RewardType(MyIntEnum): 99 | Coupon = 10 100 | PuPoint = 20 101 | GiftCard = 30 102 | Sunrise = 40 103 | kUnk_60 = 60 104 | 105 | 106 | class ActionTYPE(MyIntEnum): 107 | BROWSE = 0 108 | SHARE = 10 109 | 110 | 111 | class TaskType(MyIntEnum): 112 | UNKNOWN = 0 113 | FLASH_SALE = 240 114 | CUSTOM_LOTTERY = 250 115 | TOPIC = 260 116 | USER_TASK = 270 117 | SCENE = 280 118 | 119 | 120 | class TaskStatus(MyIntEnum): 121 | Undone = 0 122 | Done = 10 123 | Expired = 20 124 | Receive = 30 125 | 126 | 127 | class TaskRewardType(MyIntEnum): 128 | Credit = 0 129 | Coupon = 10 130 | GiftCard = 20 131 | Lottery = 30 132 | Card = 40 133 | BetNum = 50 134 | Cash = 60 135 | 打榜积分 = 70 136 | 拼桌食材 = 80 137 | 红包 = 90 138 | NoReward = 255 139 | 140 | 141 | class SPREAD_TAG(MyIntEnum): 142 | UNKNOWN = -1 # 不限 143 | NORMAL_PRODUCT = 0 144 | NEW_PRODUCT = 10 # 新品 145 | FLASH_SALE_PRODUCT = 20 # 限时购 146 | DISCOUNT_PRODUCT = 30 # 折扣 147 | NOVICE_PRODUCT = 40 # 新手专享 148 | SPECIAL_PRODUCT = 50 # 特价 149 | HOT_PRODUCT = 60 # 热卖 150 | YIYUAN_BUTIE = 100 151 | ZERO_ORDER_EXCLUSIVE = 110 152 | ONE_ORDER_EXCLUSIVE = 120 153 | TWO_ORDER_EXCLUSIVE = 130 154 | THREE_ORDER_EXCLUSIVE = 140 155 | 156 | 157 | class PURCHASE_TYPE(MyIntEnum): 158 | ALL = -1 # 不限 159 | GENERAL = 0 # 普通 160 | RESERVE = 10 # 预定 161 | 162 | 163 | class SHARE_STATUS(MyIntEnum): 164 | UNKNOWN = 0 165 | ERROR = 1 166 | EXPIRED = 2 167 | NULL = 3 168 | NORMAL = 4 169 | 170 | 171 | class COLLECT_CARD_STATUS(MyIntEnum): 172 | NOT_START = 10 173 | PROCESSING = 20 174 | FINISHED = 30 175 | 176 | 177 | class HideTaskType(MyIntEnum): 178 | Null = 0 179 | More = 10 180 | List = 20 181 | 182 | 183 | class PAPER_CONTENT_TYPE(MyIntEnum): 184 | MANUAL_INPUT = 0 185 | REF_INPUT = 10 186 | REALATION_INPUT = 15 187 | ROBOT_GREETING = 20 188 | ROBOT_RECOM = 30 189 | ROBOT_ANSWER = 40 190 | KEYWORD_INTENTION = 50 191 | REMIND_ORDER = 60 192 | 193 | 194 | class ERROR_CODE(MyIntEnum): 195 | CODE_SUCCESS = 0 196 | 197 | NO_ENOUGH_POINT = 391 198 | POINT_LIMIT = 392 199 | NOT_IN_ACTIVITY_CITY = 393 200 | IS_LIVE = 394 201 | 202 | # COMMENT_ERROR_CODE 203 | ERR_COMMENT_PUSH = 13000 # 写评论失败 204 | ERR_COMMENT_HIT_KEYWORD_INTERCEPT = 13001 205 | ERR_COMMENT_HIT_FREQ_LIMIT = 13002 # 操作太过频繁,请1分钟后再试 206 | ERR_COMMENT_IS_BAN = 13004 207 | ERR_COMMENT_HIT_KEYWORD_BUSINESS_INTERCEPT = 13005 208 | ERR_COMMENT_HIT_COMMENT_DISCARD = 13006 209 | 210 | # LOTTERY_TEAM 211 | ERR_GET_TEAM_NO_EXIST = 32002 212 | ERR_GET_TEAM_FAIL = 33001 213 | ERR_JOIN_TEAM_FAIL = 33002 214 | ERR_CHANGE_TEAM_FAIL = 33003 215 | ERR_DO_NOT_JOIN_TEAM = 33004 216 | ERR_JOIN_LOTTERY_FAIL = 33005 217 | 218 | CITIVITYE_END = 100002 219 | ACITIVITYE_UNSTART = 100010 220 | ACITIVITYE_UNOPEN = 100011 221 | UNNORMAL = 100026 # 被限制了 222 | INVITE_LIMIT_DAILY = 100027 223 | INVITE_LIMIT = 100028 224 | ASSISTANCE_MYSELF = 100029 225 | ASSISTANCE_REPEAT = 100030 226 | ASSISTANCE_END = 100031 227 | ASSISTANCE_LIMIT_DAILY = 100032 228 | ASSISTANCE_LIMIT = 100033 229 | ASSISTANCE_ERROR = 100034 230 | 231 | # LOGIN 403, [200000, 300000), exinclude 200099 232 | kForbidden = 403 233 | OUT_TOKEN = 200001 234 | kUnauthorized = 200208 235 | EXPIRED_TOKEN = 200304 236 | 237 | kRepeatedSignIn = 350011 238 | kUnknown = -1 # 系统繁忙 239 | BUSY = 400000 # 系统繁忙 240 | 241 | ERROR_TASK_NOT_GENERATED = 400104 242 | ERROR_TASK_DOES_NOT_EXIST = 400106 243 | 244 | CODE_PRODUCT_UPDATE = 500001 245 | 246 | 247 | @dataclass 248 | class PCollectCardRule: 249 | id: str 250 | name: str 251 | time_start: int 252 | time_end: int 253 | status: COLLECT_CARD_STATUS 254 | send_status: int 255 | enabled: bool 256 | open_card_lottery: bool 257 | card_lottery_activity_id: str 258 | card_get_task_id: str 259 | 260 | 261 | @dataclass 262 | class PCollectCard: 263 | card_type: int # TODO 10普通卡 20合成卡 264 | link_type: BANNER_LINK_TYPE 265 | name: str 266 | rule_id: str 267 | sort_num: int 268 | 269 | # 以下字段仅在获取卡列表时有效 270 | rule_card_id: Optional[str] = None 271 | owner_user_id: Optional[str] = None 272 | have_count: Optional[int] = None 273 | 274 | 275 | @dataclass 276 | class PCollectCardEntity: 277 | already_get: list[PCollectCard] 278 | can_composite_count: int 279 | first_visit: bool 280 | 281 | 282 | @dataclass 283 | class PReceiverInfo: 284 | id: str 285 | address: str = "" 286 | room_num: str = "" 287 | lng_x: Optional[float] = 0 288 | lat_y: Optional[float] = 0 289 | receiver_name: str = "" 290 | phone_number: str = "" 291 | store_id: str = "" 292 | place_id: str = "" 293 | place_zip: int = 0 294 | city_zip: int = 0 295 | 296 | @property 297 | def id_empty(self): 298 | """只判断ID""" 299 | return not self.id or not self.store_id or not self.place_id 300 | 301 | 302 | @dataclass 303 | class PPrize: 304 | level: int 305 | name: str 306 | type: RewardType 307 | 308 | 309 | @dataclass 310 | class PItem: 311 | price: int 312 | product_id: str 313 | store_product_id: str 314 | remark: str 315 | spread_tag: int 316 | selected_count: int 317 | 318 | 319 | @dataclass 320 | class PPageRule: 321 | activity_id: str 322 | action_type: ActionTYPE 323 | task_type: TaskType 324 | skim_time: int # 浏览多少秒 325 | 326 | 327 | @dataclass 328 | class PAnswerRule: 329 | answer_is_done: bool 330 | 331 | 332 | @dataclass 333 | class PTask: 334 | task_id: str 335 | task_name: str # 每日打卡 336 | task_status: TaskStatus 337 | reward_type: TaskRewardType 338 | link_id: str 339 | target_code: int 340 | page_rule: Optional[PPageRule] = None # 浏览型任务 341 | answer_rule: Optional[PAnswerRule] = None # 答题型任务 342 | 343 | 344 | @dataclass 345 | class PLotteryInfo: 346 | id: str 347 | name: str 348 | type: LOTTERY_TYPE 349 | prizes: dict[int, PPrize] = field(default_factory=dict) 350 | task_system_link_id: Optional[str] = None 351 | 352 | 353 | @dataclass 354 | class PChanceEntrance: 355 | type: CHANCE_OBTAIN_TYPE 356 | title: str 357 | attend_count: int # 已获得次数 358 | limit_count: int # 最大获得次数 359 | gain_num: int # 每次完成可增加的次数 360 | target_value: int # 需要的数量 361 | 362 | 363 | @dataclass 364 | class PBatch: 365 | batch_id: str 366 | price: int # 价格 分 367 | spread_tag_desc: str 368 | 369 | 370 | @dataclass 371 | class FlashSaleInfo: 372 | store_product_id: str 373 | event_id: str 374 | quantity_each_person_limit: int 375 | progress_rate: float 376 | price: int 377 | 378 | 379 | @dataclass 380 | class PProduct: 381 | price: int # 价格 分 382 | product_id: str 383 | name: str 384 | store_product_id: str 385 | order_remarks: list[str] 386 | spread_tag: SPREAD_TAG 387 | stock_quantity: int # 库存 388 | purchase_type: PURCHASE_TYPE = PURCHASE_TYPE.GENERAL 389 | quantity_limit: Optional[int] = None # 限购数量 390 | selected_count: int = 0 # 选购几件 391 | sell_batches: Optional[list[PBatch]] = None # 该数组的最低价作为当前价格 392 | remark: Optional[str] = None # 商品备注 393 | 394 | 395 | @dataclass 396 | class PDiscountShare: 397 | # index: int # 第{index}个领取的人得最大优惠券 398 | indexes: list[int] # 朴朴新规则,一个红包可能有多个最佳(确保从小到大排序) 399 | count: int # 共{count}张优惠券 400 | share_id: str # 红包ID 401 | 402 | 403 | @dataclass 404 | class PDiscountRule: 405 | id: str 406 | type: DiscountType 407 | condition_amount: int # 6900 408 | discount_amount: int # 700 满69减7元 409 | name: Optional[str] = ( 410 | None # 230617新增用于检测是不是垃圾券,比如 “朴朴分享券(冷藏冷冻专用)” 411 | ) 412 | 413 | @property 414 | def tips(self): 415 | return f"满{self.condition_amount/100}减{self.discount_amount/100}{self.name or ''}" 416 | 417 | 418 | @dataclass 419 | class POrder: 420 | total_price: int 421 | time_create: int # 1673265913282 422 | # TODO items 423 | discount_share: Optional[PDiscountShare] = None # 红包 424 | 425 | 426 | @dataclass 427 | class PBanner: 428 | title: str 429 | link_id: str 430 | 431 | 432 | @dataclass 433 | class PShareUser: 434 | avatar: Optional[str] 435 | name: str 436 | best: bool # 是否最佳 437 | time: int # 抢包时间 438 | 439 | 440 | @dataclass 441 | class PAnswerOptions: 442 | sort: int 443 | name: str 444 | # score:Optional[int] = None 445 | selected: Optional[int] = 0 446 | 447 | 448 | @dataclass 449 | class PQuestion: 450 | id: str 451 | question_title: str 452 | question_notice: str 453 | question_type: int 454 | is_must: int 455 | is_random: int 456 | sort: int 457 | start_score_desc: str 458 | end_score_desc: str 459 | options: list[PAnswerOptions] 460 | content_type: PAPER_CONTENT_TYPE 461 | option_limit: int 462 | option_min_limit: int 463 | question_id: Optional[str] = None 464 | 465 | 466 | class ApiResults: 467 | class Error: 468 | __slots__ = ("code", "msg", "func_name") 469 | 470 | def __init__(self, json: Optional[dict], func_name: Optional[str] = None): 471 | if json: 472 | self.code = json.get("errcode") 473 | self.msg = json.get("errmsg", "") 474 | else: 475 | self.code = -1 476 | self.msg = "" 477 | 478 | self.func_name = func_name or getframe(1).f_code.co_name 479 | 480 | def __str__(self) -> str: 481 | return f"{self.func_name} 失败: code={self.code}, msg={self.msg}" 482 | 483 | class Exception(Error): 484 | __slots__ = "exception" 485 | 486 | def __init__(self, func_name: Optional[str] = None): 487 | super().__init__( 488 | json=None, func_name=func_name or getframe(1).f_code.co_name 489 | ) 490 | self.exception = format_exc() 491 | 492 | def __str__(self) -> str: 493 | return f"{self.func_name} 异常: {self.exception}" 494 | 495 | @dataclass 496 | class TokenRefreshed: 497 | refresh_token: str 498 | access_expires: int 499 | changed: bool = False 500 | 501 | @dataclass 502 | class TokenValid: 503 | pass 504 | 505 | @dataclass 506 | class SuId: 507 | id: str 508 | 509 | @dataclass 510 | class UserInfo: 511 | avatar: Optional[str] 512 | nickname: Optional[str] 513 | 514 | @dataclass 515 | class ReceiverInfo: 516 | receiver: PReceiverInfo 517 | 518 | @dataclass 519 | class SignIn: 520 | coin: int 521 | explanation: str 522 | 523 | @dataclass 524 | class SignPeriodInfo: 525 | days: int 526 | 527 | @dataclass 528 | class Banner: 529 | banners: list[PBanner] 530 | 531 | @dataclass 532 | class LotteryInfo: 533 | lottery: PLotteryInfo 534 | 535 | @dataclass 536 | class TaskGroupsData: 537 | tasks: list[PTask] 538 | 539 | @dataclass 540 | class TaskCompleted: 541 | pass 542 | 543 | @dataclass 544 | class ChanceEntrances: 545 | coin_balance: int 546 | entrances: list[PChanceEntrance] 547 | 548 | @dataclass 549 | class CoinExchanged: 550 | gain_num: int 551 | 552 | @dataclass 553 | class UserLotteryInfo: 554 | remain_chances: int 555 | 556 | @dataclass 557 | class LotteryResult: 558 | prize: PPrize 559 | 560 | @dataclass 561 | class ProductCollections: 562 | total_count: int 563 | products: list[PProduct] 564 | 565 | @dataclass 566 | class UsableCoupons: 567 | coupons: list[str] 568 | rules: list[PDiscountRule] 569 | 570 | @dataclass 571 | class DeliveryTime: 572 | type: DeliveryTimeType 573 | dtime_promise: int 574 | 575 | @dataclass 576 | class OrderCreated: 577 | id: str 578 | 579 | @dataclass 580 | class OrdersList: 581 | total_count: int 582 | orders: list[POrder] 583 | 584 | @dataclass 585 | class WxDiscountShare: 586 | best_luck: bool 587 | reentry: bool 588 | user_list: list[PShareUser] 589 | discount: Optional[PDiscountRule] 590 | available: bool 591 | 592 | @dataclass 593 | class AnswerResult: 594 | question_id: str 595 | standard: str # 标准答案 596 | result: bool # 是否答对 597 | 598 | @dataclass 599 | class Questionnaire: 600 | id: str 601 | time_start_answer: int 602 | questions: list[PQuestion] 603 | 604 | 605 | class HttpMethod(Enum): 606 | kPut = "PUT" 607 | kGet = "GET" 608 | kPost = "POST" 609 | kDelete = "DELETE" 610 | 611 | 612 | class ClientType(IntEnum): 613 | kNative = 0 614 | kWeb = 1 615 | kMicroMsg = 2 616 | 617 | 618 | if __name__ == "__main__": 619 | pass 620 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | import os 3 | import platform 4 | import random 5 | import re 6 | import sqlite3 7 | import sys 8 | import time 9 | import traceback 10 | from asyncio import sleep as aio_sleep 11 | from enum import Enum, IntEnum 12 | from functools import wraps 13 | from sys import version_info as py_version 14 | from typing import Any, Optional # 确保兼容<=Python3.9 15 | 16 | assert py_version >= (3, 9) 17 | 18 | 19 | def pip_install(): 20 | print("正在安装依赖") 21 | os.system( 22 | "pip3 install requests rsa tomli tomli_w beautifulsoup4 fasteners aiohttp aiohttp_retry typing_extensions pypushdeer" 23 | ) 24 | os.system("pip3 install git+http://github.com/cddjr/json_codec.git --user") 25 | 26 | 27 | try: 28 | import tomli 29 | import tomli_w 30 | import json_codec 31 | import aiohttp 32 | import aiohttp_retry 33 | import pypushdeer 34 | 35 | from fasteners.process_lock import InterProcessReaderWriterLock 36 | 37 | from checksendNotify import send 38 | except ModuleNotFoundError: 39 | pip_install() 40 | import tomli 41 | import tomli_w 42 | import json_codec 43 | import aiohttp 44 | import aiohttp_retry 45 | import pypushdeer 46 | from fasteners.process_lock import InterProcessReaderWriterLock 47 | 48 | from checksendNotify import send 49 | 50 | 51 | # def toml_to_json(toml_path, to_json_path): 52 | # """ 53 | # :param toml_path: 需要转换的toml文件的路径 54 | # :param to_json_path: 需要输出的json文件路径 55 | # :return: None 56 | # """ 57 | # with open(toml_path, "rb") as f: 58 | # toml_dict = tomli.load(f) 59 | # json_date = json.dumps(toml_dict, indent=4, ensure_ascii=False) 60 | # with open(to_json_path, 'w', encoding="utf8") as s: 61 | # s.write(json_date) 62 | 63 | 64 | # def json_to_toml(json_path, to_toml_path): 65 | # with open(json_path, "r", encoding="utf8") as f: 66 | # json_dict = json.load(f) 67 | # with open(to_toml_path, "wb") as f: 68 | # tomli_w.dump(json_dict, f) 69 | 70 | 71 | class config_get(object): 72 | def __init__(self, custom_path=None): 73 | """ 74 | config_path: 自定义配置文件路径 75 | config_file: 实际使用的配置文件路径 76 | config_format: 实际使用的配置文件格式 77 | """ 78 | if custom_path is None: 79 | self.config_path = self.get_config_path() 80 | self.config_file = self.get_config_file() 81 | self.config_format = self.get_config_format() 82 | else: 83 | self.config_file = custom_path 84 | self.config_format = self.get_config_format() 85 | self.lock_ = InterProcessReaderWriterLock(f"{self.config_file}.lock") 86 | 87 | def get_config_format(self): 88 | if self.config_file.endswith(".toml"): 89 | return "toml" 90 | else: 91 | return "json" 92 | 93 | @staticmethod 94 | def get_config_path(): 95 | ql_old = "/ql/config/" 96 | ql_new = "/ql/data/config/" 97 | if os.path.isdir(ql_new): 98 | print("成功 当前环境为青龙面板v2.12+ 继续执行\n") 99 | return ql_new 100 | elif os.path.isdir(ql_old): 101 | print("成功 当前环境为青龙面板v2.12- 继续执行\n") 102 | return ql_old 103 | else: 104 | if platform.system() == "Windows": 105 | return "" 106 | print("失败 请检查环境") 107 | exit(0) 108 | 109 | def get_config_file(self): 110 | toml_file = f"{self.config_path}check.toml" 111 | json_file = f"{self.config_path}check.json" 112 | if os.path.exists(toml_file): 113 | print(f"启用了toml配置文件\n路径为{toml_file}\n") 114 | return toml_file 115 | elif os.path.exists(json_file): 116 | print(f"启用了json配置文件\n路径为{json_file}\n") 117 | return json_file 118 | else: 119 | print("未找到配置文件") 120 | self.move_config_file() 121 | return toml_file 122 | 123 | def move_config_file(self): 124 | print("尝试移动配置文件到目录") 125 | if self.config_path == "/ql/config/": 126 | self.move_configuration_file_old() 127 | else: 128 | self.move_configuration_file_new() 129 | 130 | def get_real_key(self, expression): 131 | """ 132 | 从配置文件中获取,re表达式想要的KEY 133 | :return: 134 | """ 135 | pattern = re.compile(expression, re.I) 136 | real_key = "" 137 | with self.lock_.read_lock(): 138 | if self.config_format == "toml": 139 | for key in self.get_key_for_toml(self.config_file): 140 | if pattern.match(key) is not None: 141 | real_key = key 142 | else: 143 | for key in self.get_key_for_json(self.config_file): 144 | if pattern.match(key) is not None: 145 | real_key = key 146 | if real_key != "": 147 | return real_key 148 | else: 149 | print("啊哦没有找到") 150 | exit(1) 151 | 152 | def get_value(self, expression): 153 | real_key = self.get_real_key(expression) 154 | return self.get_value_2(real_key) 155 | 156 | def get_value_2(self, real_key: str): 157 | with self.lock_.read_lock(): 158 | if self.config_format == "toml": 159 | return self.get_value_for_toml(self.config_file, real_key) 160 | else: 161 | return self.get_value_for_json(self.config_file, real_key) 162 | 163 | def set_value(self, key: str, value: Any): 164 | with self.lock_.write_lock(): 165 | if self.config_format == "toml": 166 | return self.set_value_for_toml(self.config_file, key, value) 167 | else: 168 | return self.set_value_for_json(self.config_file, key, value) 169 | 170 | @staticmethod 171 | def move_configuration_file_old(): 172 | print("移动配置文件") 173 | os.system("cp /ql/repo/cddjr_check/check.sample.toml /ql/config/check.toml") 174 | 175 | @staticmethod 176 | def move_configuration_file_new(): 177 | print("移动配置文件") 178 | os.system( 179 | "cp /ql/data/repo/cddjr_check/check.sample.toml /ql/data/config/check.toml" 180 | ) 181 | 182 | @staticmethod 183 | def get_value_for_toml(toml_path, key): 184 | try: 185 | with open(toml_path, "rb") as f: 186 | try: 187 | toml_dict = tomli.load(f) 188 | return toml_dict.get(key) 189 | except tomli.TOMLDecodeError: 190 | print( 191 | f"错误:配置文件 {toml_path} 格式不对,请学习 https://toml.io/cn/v1.0.0\n错误信息:\n{traceback.format_exc()}" 192 | ) 193 | exit(1) 194 | except OSError: 195 | return None 196 | 197 | @staticmethod 198 | def set_value_for_toml(toml_path, key: str, value: Any): 199 | try: 200 | with open(toml_path, "rb") as f: 201 | try: 202 | toml_dict = tomli.load(f) 203 | except tomli.TOMLDecodeError: 204 | print( 205 | f"错误:配置文件 {toml_path} 格式不对\n{traceback.format_exc()}" 206 | ) 207 | toml_dict = {} 208 | except OSError: 209 | toml_dict = {} 210 | if isinstance(value, dict): 211 | if key not in toml_dict: 212 | toml_dict[key] = value 213 | else: 214 | toml_dict[key].update(value) 215 | elif value is not None: 216 | toml_dict[key] = value 217 | elif key in toml_dict: 218 | del toml_dict[key] 219 | try: 220 | with open(toml_path, "wb") as f: 221 | tomli_w.dump(toml_dict, f) 222 | except: 223 | print(f"修改配置文件 {toml_path} 失败\n{traceback.format_exc()}") 224 | 225 | @staticmethod 226 | def set_value_for_json(json_path, key: str, value: Any): 227 | try: 228 | with open(json_path, "r", encoding="utf8") as f: 229 | try: 230 | json_dict = json.load(f) 231 | except json.decoder.JSONDecodeError: 232 | print( 233 | f"错误:配置文件 {json_path} 格式不对,错误信息{traceback.format_exc()}" 234 | ) 235 | json_dict = {} 236 | except OSError: 237 | json_dict = {} 238 | if isinstance(value, dict): 239 | if key not in json_dict: 240 | json_dict[key] = value 241 | else: 242 | json_dict[key].update(value) 243 | else: 244 | json_dict[key] = value 245 | try: 246 | with open(json_path, "w", encoding="utf8") as f: 247 | json.dump(json_dict, f, ensure_ascii=False, indent=2) 248 | except: 249 | print(f"修改配置文件 {json_path} 失败\n{traceback.format_exc()}") 250 | 251 | @staticmethod 252 | def get_value_for_json(json_path, key): 253 | try: 254 | with open(json_path, "r", encoding="utf8") as f: 255 | try: 256 | json_dict = json.load(f) 257 | return json_dict.get(key) 258 | except json.decoder.JSONDecodeError: 259 | print( 260 | f"错误:配置文件 {json_path} 格式不对,错误信息{traceback.format_exc()}" 261 | ) 262 | except OSError: 263 | return None 264 | 265 | @staticmethod 266 | def get_key_for_toml(toml_path): 267 | try: 268 | with open(toml_path, "rb") as f: 269 | try: 270 | toml_dict = tomli.load(f) 271 | return toml_dict.keys() 272 | except tomli.TOMLDecodeError: 273 | print( 274 | f"错误:配置文件 {toml_path} 格式不对,请学习 https://toml.io/cn/v1.0.0\n错误信息:\n{traceback.format_exc()}" 275 | ) 276 | exit(1) 277 | except OSError: 278 | return [] 279 | 280 | @staticmethod 281 | def get_key_for_json(json_path): 282 | try: 283 | with open(json_path, "r", encoding="utf8") as f: 284 | try: 285 | json_dict = json.load(f) 286 | return json_dict.keys() 287 | except json.decoder.JSONDecodeError: 288 | print( 289 | f"错误:配置文件 {json_path} 格式不对,错误信息{traceback.format_exc()}" 290 | ) 291 | return [] 292 | except OSError: 293 | return [] 294 | 295 | 296 | class check(object): 297 | def __init__( 298 | self, 299 | run_script_name, 300 | run_script_expression, 301 | Configuration_flag=False, 302 | interval_min=5, 303 | interval_max=10, 304 | ): 305 | """ 306 | :param run_script_name: 执行脚本的说明 307 | :param run_script_expression: 需要获取的配置键的re表达式 308 | :param Configuration_flag: 是否只检测True或False(默认为False) 309 | :param interval_min: 多账号执行的最小间隔时间(默认为5秒) 310 | :param interval_max: 多账号执行的最大间隔时间(默认为10秒 设置0代表无间隔) 311 | """ 312 | self.run_script_name = run_script_name 313 | self.run_script_expression = run_script_expression 314 | self.Configuration_flag = Configuration_flag 315 | self.interval_min = interval_min 316 | self.interval_max = interval_max 317 | 318 | @staticmethod 319 | def other_task(): 320 | # change_db() 321 | pass 322 | 323 | def __call__(self, func): 324 | @wraps(func) 325 | def wrapper(): 326 | if not self.Configuration_flag: 327 | config = config_get() 328 | value_list = config.get_value(self.run_script_expression) or [] 329 | push_message = "" 330 | num = 0 331 | for value in value_list: 332 | num += 1 333 | print(f"<----------------账号【{num}】---------------->") 334 | username = ( 335 | value.get("username") 336 | or value.get("name") 337 | or value.get("email") 338 | or value.get("phone") 339 | ) 340 | if not username: 341 | username = str(value)[:32] + "..." 342 | print(f"获取到的账号信息为:{username}\n") 343 | try: 344 | result = func(value=value) 345 | if result: 346 | push_message += f"***\n{result}\n\n" 347 | except IndexError: 348 | print("可能是示例格式被运行\n错误信息:") 349 | print(f"{traceback.format_exc()}") 350 | push_message += "" 351 | except AttributeError: 352 | print( 353 | "可能是配置文件的键名出现问题\n" 354 | "例如:在此次更新中什么值得买的键名从smzdm_cookie变成了cookie\n" 355 | ) 356 | print(f"{traceback.format_exc()}") 357 | push_message += "" 358 | except TypeError: 359 | print(f"{traceback.format_exc()}") 360 | push_message += "" 361 | except SystemExit as e: 362 | # 脚本中执行exit不要影响其它账号的运行 363 | print(e) 364 | push_message += "" 365 | except BaseException: 366 | # 未知异常,打印调用栈,继续执行下一个账号 367 | print(f"{traceback.format_exc()}") 368 | push_message += "" 369 | if self.interval_max > 0 and num < len(value_list): 370 | randomSleep(self.interval_min, self.interval_max) 371 | send(self.run_script_name, push_message) 372 | else: 373 | config = config_get() 374 | flag = config.get_value(self.run_script_expression) 375 | if flag is not None and flag: 376 | print(f"开始执行{self.run_script_name}") 377 | func() 378 | else: 379 | print(f"设置为不执行{self.run_script_name}") 380 | 381 | return wrapper 382 | 383 | 384 | def change_cron_new( 385 | cron_file_path="/ql/data/db/database.sqlite", repositories="cddjr_check" 386 | ): 387 | print("尝试修改定时时间") 388 | os.system(f"cp {cron_file_path} {cron_file_path}.back") 389 | con = sqlite3.connect(cron_file_path) 390 | cur = con.cursor() 391 | 392 | def change_time(time_str: str): 393 | words = re.sub("\\s+", " ", time_str).split() 394 | words[0] = str(random.randrange(60)) 395 | words[1] = str(random.randrange(22)) 396 | return " ".join(words) 397 | 398 | cur.execute("select id,name,command,schedule from Crontabs") 399 | res = cur.fetchall() 400 | for line in res: 401 | if line[2].find(repositories) != -1: 402 | sql = f' UPDATE Crontabs SET schedule = "{change_time(line[3])}" WHERE id = {line[0]}' 403 | print(f"任务名称 {line[1]} 修改为{sql}") 404 | cur.execute(sql) 405 | 406 | con.commit() 407 | con.close() 408 | 409 | 410 | def change_cron_old(cron_file_path="/ql/db/crontab.db", repositories="cddjr_check"): 411 | print("尝试修改定时时间") 412 | 413 | def change_time(time_str: str): 414 | words = re.sub("\\s+", " ", time_str).split() 415 | words[0] = str(random.randrange(60)) 416 | words[1] = str(random.randrange(22)) 417 | return " ".join(words) 418 | 419 | time_str = time.strftime("%Y-%m-%d", time.localtime()) 420 | os.system(f"cp /ql/db/crontab.db /ql/db/crontab.db.{time_str}.back") 421 | lines = [] 422 | with open(cron_file_path, "r", encoding="UTF-8") as f: 423 | for i in f.readlines(): 424 | # print(record.get("command")) 425 | if i.find(repositories) != -1: 426 | record = json.loads(i) 427 | record["schedule"] = change_time(record["schedule"]) 428 | lines.append(json.dumps(record, ensure_ascii=False) + "\n") 429 | else: 430 | lines.append(i) 431 | 432 | with open(cron_file_path, "w", encoding="UTF-8") as f: 433 | f.writelines(lines) 434 | 435 | 436 | def randomSleep(min=1.0, max=6.0): 437 | delay = random.randint(int(min * 1000), int(max * 1000)) / 1000 438 | # print(f"随机等待{delay}秒...") 439 | time.sleep(delay) 440 | 441 | 442 | async def aio_randomSleep(min=1.0, max=6.0): 443 | delay = random.randint(int(min * 1000), int(max * 1000)) / 1000 444 | # print(f"随机等待{delay}秒...") 445 | await aio_sleep(delay) 446 | 447 | 448 | def log(s: object, msg_list: Optional[list[str]] = None): 449 | print(s) 450 | if msg_list is not None: 451 | msg_list += [str(s)] 452 | 453 | 454 | def GetScriptConfig(filename: str): 455 | """ 456 | 获得当前脚本对应的数据库 457 | """ 458 | try: 459 | dirname = os.path.dirname(os.path.abspath(sys.argv[0])) 460 | cache_dir = os.path.join(dirname, ".cache") 461 | try: 462 | os.makedirs(cache_dir) 463 | except OSError: 464 | if not os.path.isdir(cache_dir): 465 | raise 466 | if filename.endswith(".json"): 467 | config = config_get(os.path.join(cache_dir, filename)) 468 | else: 469 | config = config_get(os.path.join(cache_dir, f"{filename}.toml")) 470 | return config 471 | except: 472 | print(traceback.format_exc()) 473 | return None 474 | 475 | 476 | def cookie_to_dic(cookie: str): 477 | if not cookie: 478 | return {} 479 | return {item.split("=")[0]: item.split("=")[1] for item in cookie.split("; ")} 480 | 481 | 482 | class MyIntEnum(IntEnum): 483 | """在IntEnum基础上增加了自动创建枚举值的能力""" 484 | 485 | @classmethod 486 | def _missing_(cls, value): 487 | """ 488 | Returns member (possibly creating it) if one can be found for value. 489 | """ 490 | if not isinstance(value, int): 491 | raise ValueError("%r is not a valid %s" % (value, cls.__qualname__)) 492 | new_member = cls.__CreatePseudoMember(value) 493 | log(f"警告: {cls.__name__} 没有定义 '{value}', 已自动创建") 494 | return new_member 495 | 496 | @classmethod 497 | def __CreatePseudoMember(cls, value): 498 | pseudo_member = cls._value2member_map_.get(value, None) 499 | if pseudo_member is None: 500 | pseudo_member = int.__new__(cls, value) 501 | pseudo_member._name_ = str(value) # 以值作为枚举名 不会有冲突 502 | pseudo_member._value_ = value 503 | pseudo_member = cls._value2member_map_.setdefault(value, pseudo_member) 504 | return pseudo_member 505 | 506 | 507 | class MyStrEnum(str, Enum): 508 | """在StrEnum基础上增加了自动创建枚举值的能力""" 509 | 510 | @classmethod 511 | def _missing_(cls, value): 512 | """ 513 | Returns member (possibly creating it) if one can be found for value. 514 | """ 515 | if not isinstance(value, str): 516 | raise ValueError("%r is not a valid %s" % (value, cls.__qualname__)) 517 | new_member = cls.__CreatePseudoMember(value) 518 | log(f"警告: {cls.__name__} 没有定义 '{value}', 已自动创建") 519 | return new_member 520 | 521 | @classmethod 522 | def __CreatePseudoMember(cls, value): 523 | pseudo_member = cls._value2member_map_.get(value, None) 524 | if pseudo_member is None: 525 | pseudo_member = str.__new__(cls, value) 526 | pseudo_member._name_ = f"`{value}`" 527 | pseudo_member._value_ = value 528 | pseudo_member = cls._value2member_map_.setdefault(value, pseudo_member) 529 | return pseudo_member 530 | 531 | 532 | if __name__ == "__main__": 533 | pip_install() 534 | if platform.system() == "Windows": 535 | exit() 536 | config = config_get() 537 | if config.config_path == "/ql/config/": 538 | if os.path.isfile("/ql/db/database.sqlite"): 539 | change_cron_new(cron_file_path="/ql/db/database.sqlite") 540 | else: 541 | change_cron_old() 542 | else: 543 | change_cron_new() 544 | print("修改完成请重启容器") 545 | --------------------------------------------------------------------------------