├── .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 | 
6 | 
7 | 
8 | 
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 | [](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 |
--------------------------------------------------------------------------------