├── images ├── 20230804213220.jpg ├── 20230804213710.jpg ├── 20230804213711.jpg ├── 20230804222552.jpg ├── 20230804234216.jpg ├── 20230807032900.jpg ├── 微信截图_20250207112445.png └── 微信截图_20250207112506.jpg ├── __pycache__ └── notify.cpython-38.pyc ├── send_weibo ├── weibo_chaohua_sign.py ├── README.md ├── chaohua_sign.py └── notify.py /images/20230804213220.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuku3863/sign_weibo_chaohua/HEAD/images/20230804213220.jpg -------------------------------------------------------------------------------- /images/20230804213710.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuku3863/sign_weibo_chaohua/HEAD/images/20230804213710.jpg -------------------------------------------------------------------------------- /images/20230804213711.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuku3863/sign_weibo_chaohua/HEAD/images/20230804213711.jpg -------------------------------------------------------------------------------- /images/20230804222552.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuku3863/sign_weibo_chaohua/HEAD/images/20230804222552.jpg -------------------------------------------------------------------------------- /images/20230804234216.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuku3863/sign_weibo_chaohua/HEAD/images/20230804234216.jpg -------------------------------------------------------------------------------- /images/20230807032900.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuku3863/sign_weibo_chaohua/HEAD/images/20230807032900.jpg -------------------------------------------------------------------------------- /images/微信截图_20250207112445.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuku3863/sign_weibo_chaohua/HEAD/images/微信截图_20250207112445.png -------------------------------------------------------------------------------- /images/微信截图_20250207112506.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuku3863/sign_weibo_chaohua/HEAD/images/微信截图_20250207112506.jpg -------------------------------------------------------------------------------- /__pycache__/notify.cpython-38.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kuku3863/sign_weibo_chaohua/HEAD/__pycache__/notify.cpython-38.pyc -------------------------------------------------------------------------------- /send_weibo: -------------------------------------------------------------------------------- 1 | import os 2 | from urllib.parse import urlparse, parse_qs 3 | import requests 4 | 5 | def send_weibo_post(): 6 | url = "https://api.weibo.cn/2/statuses/send" 7 | headers = { 8 | "User-Agent": "Weibo/88047 (iPhone; iOS 17.3.1; Scale/3.00)", 9 | "Content-Type": "application/x-www-form-urlencoded; charset=utf-8" 10 | } 11 | 12 | # 从环境变量中获取 status_taobudiao 13 | status_taobudiao = os.getenv('status_taobudiao') 14 | if not status_taobudiao: 15 | print("环境变量 'status_taobudiao' 未设置") 16 | return 17 | 18 | # 解析 URL 并提取参数 19 | parsed_url = urlparse(status_taobudiao) 20 | params = parse_qs(parsed_url.query) 21 | params_dict = {k: v[0] for k, v in params.items()} 22 | 23 | # 调试信息 24 | print("Parsed URL:", parsed_url) 25 | print("Parsed Params:", params_dict) 26 | 27 | # 从环境变量中获取微博内容 28 | weibo_content = os.getenv('WEIBO_CONTENT', '测试微博内容') 29 | 30 | data = { 31 | "act": "add", 32 | "content": weibo_content, 33 | } 34 | 35 | response = requests.post(url, headers=headers, data=data, params=params_dict) 36 | if response.status_code == 200: 37 | response_json = response.json() 38 | username = response_json.get("user", {}).get("screen_name", "Unknown") 39 | if username != "Unknown": 40 | print(f"用户名:{username} 发送微博'{weibo_content}'成功") 41 | else: 42 | print(f"发送微博'{weibo_content}'失败: 无法获取用户名") 43 | else: 44 | print(f"发送微博'{weibo_content}'失败:", response.status_code, response.text) 45 | 46 | send_weibo_post() 47 | -------------------------------------------------------------------------------- /weibo_chaohua_sign.py: -------------------------------------------------------------------------------- 1 | import json 2 | import requests 3 | import random 4 | import time 5 | from urllib.parse import urlparse, parse_qs 6 | from notify import send 7 | import re 8 | import os 9 | 10 | API_URL = "https://api.weibo.cn/2/cardlist" 11 | SIGN_URL = "https://api.weibo.cn/2/page/button" 12 | 13 | def send_request(url, params, headers): 14 | response = requests.get(url, params=params, headers=headers) 15 | if response.status_code == 200: 16 | try: 17 | return response.json() 18 | except json.JSONDecodeError: 19 | return None 20 | return None 21 | 22 | def extract_params(url): 23 | parsed_url = urlparse(url) 24 | params_from_url = parse_qs(parsed_url.query) 25 | params_from_url = {k: v[0] for k, v in params_from_url.items()} 26 | return params_from_url 27 | 28 | def get_card_type_11(params, headers, since_id): 29 | params['since_id'] = since_id # 添加 since_id 到请求参数中 30 | data = send_request(API_URL, params, headers) 31 | if data is None: 32 | return [] 33 | cards = data.get("cards", []) 34 | card_type_11_info = [] 35 | for card in cards: 36 | if card.get("card_type") == 11: 37 | card_group = card.get("card_group", []) 38 | for item in card_group: 39 | if item.get("card_type") == 8: 40 | info = { 41 | "scheme": item.get("scheme"), 42 | "title_sub": item.get("title_sub") 43 | } 44 | card_type_11_info.append(info) 45 | return card_type_11_info 46 | 47 | def sign_in(headers, base_params, scheme, since_id): 48 | params = extract_params(scheme) 49 | request_url = f"http://i.huati.weibo.com/mobile/super/active_fcheckin?cardid=bottom_one_checkin&container_id={params['containerid']}&pageid={params['containerid']}&scheme_type=1" 50 | sign_in_params = { 51 | "aid": base_params.get("aid"), 52 | "b": base_params.get("b"), 53 | "c": base_params.get("c"), 54 | "from": base_params.get("from"), 55 | "ft": base_params.get("ft"), 56 | "gsid": base_params.get("gsid"), 57 | "lang": base_params.get("lang"), 58 | "launchid": base_params.get("launchid"), 59 | "networktype": base_params.get("networktype"), 60 | "s": base_params.get("s"), 61 | "sflag": base_params.get("sflag"), 62 | "skin": base_params.get("skin"), 63 | "ua": base_params.get("ua"), 64 | "v_f": base_params.get("v_f"), 65 | "v_p": base_params.get("v_p"), 66 | "wm": base_params.get("wm"), 67 | "fid": "232478_-_one_checkin", 68 | "lfid": base_params.get("lfid"), 69 | "luicode": base_params.get("luicode"), 70 | "moduleID": base_params.get("moduleID"), 71 | "orifid": base_params.get("orifid"), 72 | "oriuicode": base_params.get("oriuicode"), 73 | "request_url": request_url, 74 | "since_id": since_id, 75 | "source_code": base_params.get("source_code"), 76 | "sourcetype": "page", 77 | "uicode": base_params.get("uicode"), 78 | "ul_sid": base_params.get("ul_sid"), 79 | "ul_hid": base_params.get("ul_hid"), 80 | "ul_ctime": base_params.get("ul_ctime"), 81 | } 82 | data = send_request(SIGN_URL, sign_in_params, headers) 83 | return data 84 | 85 | headers = { 86 | "Accept": "*/*", 87 | "User-Agent": "Weibo/81434 (iPhone; iOS 17.0; Scale/3.00)", 88 | "SNRT": "normal", 89 | "X-Sessionid": "6AFD786D-9CFA-4E18-BD76-60D349FA8CA2", 90 | "Accept-Encoding": "gzip, deflate", 91 | "X-Validator": "QTDSOvGXzA4i8qLXMKcdkqPsamS5Ax1wCJ42jfIPrNA=", 92 | "Host": "api.weibo.cn", 93 | "x-engine-type": "cronet-98.0.4758.87", 94 | "Connection": "keep-alive", 95 | "Accept-Language": "en-US,en", 96 | "cronet_rid": "6524001", 97 | "Authorization": "", 98 | "X-Log-Uid": "5036635027", 99 | } 100 | 101 | if __name__ == "__main__": 102 | weibo_my_cookie = os.getenv("weibo_my_cookie") 103 | since_id = 1 104 | while True: 105 | params = extract_params(weibo_my_cookie) 106 | card_type_11_info = get_card_type_11(params, headers, since_id) # 传递 since_id 到函数中 107 | 108 | if not card_type_11_info: # 如果没有数据,停止循环 109 | break 110 | 111 | super_topic_list = "\n".join([f" {info['title_sub']}" for info in card_type_11_info]) 112 | print("超话列表:") 113 | print(super_topic_list) 114 | 115 | result_message = "\n签到结果:\n" 116 | for info in card_type_11_info: 117 | result = sign_in(headers, params, info['scheme'], since_id) 118 | if result and result.get('msg') == '已签到': 119 | state = '✅ 成功' 120 | else: 121 | state = '❌ 失败' 122 | result_message += f" {info['title_sub']}超话:{state}\n" 123 | time.sleep(random.randint(5, 10)) 124 | print(result_message) 125 | 126 | since_id += 1 127 | # send("微博签到结果:", f"超话列表:\n{super_topic_list}\n{result_message}") 128 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 微博超话一键签到 & 发微博工具 2 | 3 | [![访问量统计](https://komarev.com/ghpvc/?username=wd210010&style=flat-square)](https://github.com/) 4 | 5 | 本项目实现了**微博超话一键签到**和**自动发微博**功能。通过分析微博接口,我们提取了 `cardlist` 中的必需参数(如:`c`、`s`、`gsid`、`from`、`containerid`),结合 cookie 信息,实现自动签到及发微博。支持多账号管理,并内置推送通知功能,让你不错过每一次签到和发微博的结果。 6 | 7 | ## 目录 8 | 9 | - [主要功能](#主要功能) 10 | - [更新日志](#更新日志) 11 | - [环境要求](#环境要求) 12 | - [安装与使用](#安装与使用) 13 | - [方式一:本地运行](#方式一本地运行) 14 | - [方式二:青龙面板部署](#方式二青龙面板部署) 15 | - [Cookie 获取方法](#cookie-获取方法) 16 | - [cardlist 链接方式](#cardlist-链接方式) 17 | - [status 链接方式](#status-链接方式) 18 | - [运行结果展示](#运行结果展示) 19 | - [推送通知设置](#推送通知设置) 20 | - [常见问题](#常见问题) 21 | - [鸣谢与联系方式](#鸣谢与联系方式) 22 | 23 | ## 主要功能 24 | 25 | - **一键签到微博超话** 26 | 自动完成微博超话的每日签到任务,支持多账号同时签到。 27 | 28 | - **自动发微博** 29 | 利用同一 cookie 信息,自动发布微博,支持通过环境变量自定义微博内容。 30 | 31 | - **推送通知** 32 | 集成 QQ 邮箱与 Server 酱推送,确保你第一时间收到运行结果通知。 33 | 34 | - **灵活配置** 35 | 可通过环境变量或本地修改代码,自定义参数设置,满足多场景需求。 36 | 37 | ## 更新日志 38 | 39 | 40 | - **2025-02-28** 41 | - 新增操作视频 42 | - 43 | - **2025-02-26** 44 | - 修改weibo_chaohua_sign.py,添加了since_id参数,进行分页 45 | - 对 statuses 签到的图片补充。 46 | - chaohua_sign.py,单账号需要删除tianqi_url = os.getenv("status_tianqi")和("tianqi", tianqi_url) 47 | 48 | - **2025-02-07** 49 | - 修改了 statuses 签到版本逻辑,应对微博最新签到机制更新。 50 | - 新增固定值的 `内容` 参数,后续根据运行效果调整。 51 | - 支持多账号签到(配置环境变量如 `status_tianqi`、`status_taobudiao`)。 52 | 53 | - **2024-07-26** 54 | - 新增 `send_weibo.py` 发微博模块,支持使用 `WEIBO_CONTENT` 环境变量自定义微博内容。 55 | 56 | - **2024-07-20** 57 | - 新增 `chaohua_sign.py`,更新了原 `weibo_chaohua_sign.py` 的逻辑。 58 | 59 | - **2023-08-14** 60 | - 新增获取用户名功能,并推出 `multi_user_weibo_sign.py` 多用户支持(原通过 `weibo_my_cookie` 分号分隔多账号方式已弃用)。 61 | 62 | - **2023-08-09** 63 | - 新增推送通知功能,确保信息及时反馈。 64 | 65 | ## 环境要求 66 | 67 | - **Docker**(可选) 68 | - **青龙面板**(推荐用于定时任务管理) 69 | - **Python 3** 70 | 71 | ## 安装与使用 72 | 73 | ### 方式一:本地运行 74 | 75 | 1. **获取代码** 76 | 将仓库中的 `weibo_chaohua_sign.py` 文件下载或复制到本地。 77 | 78 | 2. **配置 Cookie** 79 | - 新增变量 `weibo_my_cookie`,将你通过抓包获取到的以 `https://api.weibo.cn/2/cardlist` 开头的链接赋值给它。 80 | - 注释掉代码中通过环境变量读取 Cookie 的部分(例如:`params = extract_params(os.getenv("weibo_my_cookie"))`),并启用本地变量读取(解除 `# params = extract_params(weibo_my_cookie)` 前的注释)。 81 | 82 | 3. **运行脚本** 83 | 在命令行中执行脚本,检查日志确认是否签到和发微博成功。 84 | 85 | ### 方式二:青龙面板部署 86 | 87 | 1. **依赖管理** 88 | - 在青龙面板中选择 `python3` 环境。 89 | - 点击右上角 “新建依赖”,添加 `requests` 模块。 90 | 91 | 2. **添加脚本** 92 | - 进入“脚本管理”,点击右上角 “+” 按钮,新建一个空文件(如 `weibo_sign.py`,注意保留后缀名)。 93 | - 将 `weibo_chaohua_sign.py` 的全部代码复制粘贴到文件中并保存。 94 | 95 | 3. **设置环境变量** 96 | - 在青龙面板中添加环境变量 `weibo_my_cookie`,值为抓包得到的链接(以 `https://api.weibo.cn/2/cardlist` 开头)。 97 | 98 | 4. **配置定时任务** 99 | - 进入“定时任务”,点击 “新建任务”,设置任务名称。 100 | - 在“命令/脚本”字段中填写文件名(如 `weibo_chaohua_sign.py`)。 101 | - 根据需要设置定时规则(例如:`0 10 21 * * ?` 表示每天 21:10 执行)。 102 | 103 | 5. **测试运行** 104 | - 在任务列表中找到刚添加的任务,点击运行按钮进行测试。 105 | 106 | ## Cookie 获取方法 107 | 108 | 获取 Cookie 链接是使用本项目的前提,可通过两种方式: 109 | 110 | ### cardlist 链接方式 111 | 112 | 1. **打开微博 APP** 113 | 使用手机打开微博 APP。 114 | 115 | ![打开 APP](images/20230804213711.jpg) 116 | 117 | 2. **抓包定位** 118 | 使用抓包工具搜索关键词 `cardlist`,定位到对应链接。 119 | 120 | ![抓包](images/20230804213710.jpg) 121 | 122 | 3. **复制链接** 123 | 复制以 `https://api.weibo.cn/2/cardlist` 开头的链接,用于配置 `weibo_my_cookie`。 124 | 125 | ![找到数据](images/20230804222552.jpg) 126 | 127 | ### status 链接方式 128 | 129 | 1. **打开微博 APP** 130 | 同样打开微博 APP。 131 | 132 | ![微博](https://github.com/user-attachments/assets/789233c7-7468-45d7-8b15-078e1a1f3a4c) 133 | ![f73290b7718446d31668edcaac8806d](https://github.com/user-attachments/assets/f5f70fbc-c4b7-42ce-9f33-9d87299278d9) 134 | ![b3670b296164b188f9008b7d9ce4fb5](https://github.com/user-attachments/assets/c1de0691-04e4-4992-9e2b-185becd04089) 135 | 136 | 137 | 138 | 3. **抓包定位** 139 | 使用抓包工具搜索 `状态` 关键词,找到对应链接。 140 | 141 | ![111](https://github.com/user-attachments/assets/fecbdae8-52ca-4ddd-a655-229289cb2f79) 142 | 143 | 4. **复制链接** 144 | 复制以 `https://api.weibo.cn/2/statuses` 开头的链接备用。 145 | 146 | ![222](https://github.com/user-attachments/assets/69c375e1-0b87-448e-a8e5-3d3e2ba30b67) 147 | 148 | ## 运行结果展示 149 | 150 | 工具运行成功后的示例截图如下: 151 | 152 | ![运行结果](https://github.com/user-attachments/assets/f5276d6b-6378-44dd-b188-3a30251a1564) 153 | ![运行结果](https://github.com/kuku3863/sign_weibo_chaohua/blob/master/images/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20250207112445.png) 154 | ![运行结果](https://github.com/kuku3863/sign_weibo_chaohua/blob/master/images/%E5%BE%AE%E4%BF%A1%E6%88%AA%E5%9B%BE_20250207112506.jpg) 155 | 156 | 157 | https://github.com/user-attachments/assets/109660dc-2913-48aa-b966-835d8db911c9 158 | 159 | 160 | 161 | ## 推送通知设置 162 | 163 | 本项目内置推送功能,支持以下两种方式: 164 | 165 | ### 使用 QQ 邮箱推送 166 | 167 | 请修改配置参数: 168 | 169 | - **SMTP_SERVER**:`smtp.qq.com:465` 170 | - **SMTP_SSL**:`true` 171 | - **SMTP_EMAIL**:你的 QQ 邮箱地址 172 | - **SMTP_PASSWORD**:QQ 授权码 173 | - **SMTP_NAME**:任意名称(用于标识发送者) 174 | 175 | ### 使用 Server 酱推送 176 | 177 | 配置参数: 178 | 179 | - **PUSH_KEY**:填入 Server 酱提供的 PUSH_KEY 180 | 181 | ## 常见问题 182 | 183 | 1. **如何获取最新的 Cookie 链接?** 184 | 请参考[Cookie 获取方法](#cookie-获取方法)部分。 185 | 186 | 2. **如何配置多账号签到?** 187 | 请使用多个环境变量(例如:`status_tianqi`、`status_taobudiao`),具体参考更新日志说明。 188 | 189 | 3. **运行中遇到问题怎么办?** 190 | - 请检查 Cookie 链接是否正确。 191 | - 仔细核对环境变量配置及依赖安装情况。 192 | - 如仍有疑问,可在 Issue 中反馈。 193 | 194 | ## 鸣谢与联系方式 195 | 196 | - 感谢 [青龙面板](https://github.com/whyour/qinglong) 提供的优秀定时任务管理方案。 197 | - 欢迎 Star、Fork 和提 Issue,帮助项目不断完善。 198 | - 如有疑问或建议,请在 Issue 中留言交流。 199 | -------------------------------------------------------------------------------- /chaohua_sign.py: -------------------------------------------------------------------------------- 1 | import requests 2 | import time 3 | import os 4 | from notify import send 5 | 6 | def parse_url(url): 7 | """解析URL并提取参数""" 8 | from urllib.parse import urlparse, parse_qs 9 | parsed_url = urlparse(url) 10 | params = parse_qs(parsed_url.query) 11 | return {k: v[0] for k, v in params.items()} 12 | 13 | def get_super_topics(params, payload, since_id): 14 | """获取超话列表""" 15 | url = "https://api.weibo.cn/2/statuses/container_timeline_topicsub" 16 | try: 17 | headers = { 18 | "User-Agent": "iPhone13,4__weibo__14.6.3__iphone__os17.3.1" 19 | } 20 | 21 | # 更新request_body数据中的since_id 22 | request_body = { 23 | "flowId" : "232478_-_one_checkin", 24 | "taskType" : "loadMore", 25 | "luicode" : "10001292", 26 | "max_id" : "0", 27 | "bizType" : "common|supergroup", 28 | "dynamicBundleVersion" : "1146316", 29 | "lfid" : "232590", 30 | "flowVersion" : "0.0.1", 31 | "manualType" : "scroll", 32 | "pageDataType" : "flow", 33 | "sgtotal_activity_enable" : "1", 34 | "orifid" : "232590", 35 | "mix_media_enable" : "1", 36 | "ptl" : "1", 37 | "uicode" : "10001419", 38 | "invokeType" : "manual", 39 | "moduleID" : "pagecard", 40 | "oriuicode" : "10001292", 41 | "fid" : "232478_-_one_checkin", 42 | "filterGroupStyle" : "1", 43 | "since_id" : since_id, # 使用传入的since_id 44 | "sg_tab_config" : "1", 45 | "source_code" : "10001292_232590", 46 | "feedDynamicEnable" : "1" 47 | } 48 | 49 | # 合并传入的payload和固定的request_body数据 50 | final_payload = {**payload, **request_body} 51 | 52 | response = requests.post( 53 | url, 54 | params=params, 55 | json=final_payload, # 使用合并后的payload 56 | headers=headers, 57 | timeout=15 # 添加超时设置 58 | ) 59 | 60 | response.raise_for_status() 61 | return response.json() 62 | except requests.RequestException as e: 63 | if hasattr(e, 'response') and e.response: 64 | print(f"[ERROR] 错误响应内容: {e.response.text[:500]}") 65 | return None 66 | 67 | def sign_in_super_topic(sign_url, headers, params): 68 | """签到超话""" 69 | try: 70 | 71 | response = requests.post( 72 | sign_url, 73 | params=params, 74 | headers=headers, 75 | timeout=15 76 | ) 77 | 78 | response.raise_for_status() # 如果状态码不是200,会抛出异常 79 | 80 | # 如果响应内容是JSON,直接返回其解析结果 81 | return response.json() 82 | 83 | except requests.RequestException as e: 84 | # 捕获请求中的异常,并打印详细的错误信息 85 | if hasattr(e, 'response') and e.response: 86 | print(f"[ERROR] 签到错误响应内容: {e.response.text[:500]}") 87 | return {"result": 0, "msg": "签到失败"} 88 | 89 | 90 | def process_account(account_url): 91 | """处理单个账号的签到""" 92 | params = parse_url(account_url) 93 | payload = {} 94 | 95 | super_topic_list = [] 96 | sign_in_results = [] 97 | 98 | page = 1 99 | last_since_id = "1" # 初始值 100 | 101 | while True: 102 | print(f"\n[INFO] 正在处理第 {page} 页...") 103 | 104 | data = get_super_topics(params, payload, last_since_id) # 传递since_id 105 | 106 | if data: 107 | 108 | if 'items' not in data: 109 | print("[WARNING] 响应数据中缺少 'items' 字段") 110 | break 111 | 112 | results = [] 113 | for idx, item in enumerate(data.get('items', [])): 114 | if 'items' not in item: 115 | print(f"[WARNING] 主条目 {idx+1} 缺少 'items' 字段") 116 | continue 117 | 118 | for sub_idx, sub_item in enumerate(item.get('items', [])): 119 | card_data = sub_item.get('data', {}) 120 | buttons = card_data.get('buttons', []) 121 | for btn_idx, button in enumerate(buttons): 122 | action = button.get('params', {}).get('action', '') 123 | ext_uid = button.get('params', {}).get('container_id', '') 124 | title_sub = card_data.get('title_sub', '未知超话') 125 | results.append({ 126 | 'action': action, 127 | 'ext_uid': ext_uid, 128 | 'title_sub': title_sub 129 | }) 130 | 131 | if not results: 132 | print("[INFO] 没有更多可操作条目,终止分页") 133 | break 134 | 135 | headers = { 136 | "User-Agent": "iPhone13,4__weibo__14.6.3__iphone__os17.3.1" 137 | } 138 | 139 | for result in results: 140 | print(f"\n[INFO] 处理超话: {result['title_sub']}") 141 | 142 | if not result['action']: 143 | print("[INFO] 未找到签到动作,跳过") 144 | super_topic_list.append(result['title_sub']) 145 | continue 146 | 147 | super_topic_list.append(result['title_sub']) 148 | 149 | # 调试签到参数 150 | sign_url = "https://api.weibo.cn/2/page/button" 151 | sign_params = { 152 | "aid": params['aid'], 153 | "b": params['b'], 154 | "c": params['c'], 155 | "from": params['from'], 156 | "gsid": params['gsid'], 157 | "s": params['s'], 158 | "fid": "232478_-_one_checkin", 159 | "request_url": result['action'].分屏('request_url=')[1], 160 | "ext_uid": result['ext_uid'] 161 | } 162 | sign_response = sign_in_super_topic(sign_url, headers, sign_params) 163 | status = '✅ 成功' if sign_response.get('result', 0) == 1 else '❌ 失败' 164 | sign_in_results.append(f"{result['title_sub']}超话:{状态}") 165 | print(f"[RESULT] {result['title_sub']}: {状态}") 166 | 167 | time.sleep(2) # 防止请求过频 168 | # 检查是否有更多数据,这里简化为每次循环递增since_id 169 | last_since_id = str(int(last_since_id) + 1) # 将since_id递增 170 | print(f"[DEBUG] 更新分页参数: since_id {last_since_id}") 171 | page += 1 172 | else: 173 | print("[WARNING] 获取数据为空,终止分页") 174 | break 175 | 176 | return super_topic_list, sign_in_results 177 | 178 | def main(): 179 | # 获取环境变量 180 | taobudiao_url = os.getenv("status_taobudiao") 181 | tianqi_url = os.getenv("status_tianqi") 182 | 183 | accounts = [ 184 | ("taobudiao", taobudiao_url), 185 | ("tianqi", tianqi_url) 186 | ] 187 | 188 | all_results = [] 189 | 190 | for account_name, account_url in accounts: 191 | if not account_url: 192 | continue 193 | 194 | print(f"\n{' 开始处理账号: ' + account_name + ' ':.^50}") 195 | try: 196 | super_topics, sign_in_results = process_account(account_url) 197 | account_result = ( 198 | f"\n账号:{account_name}\n" + 199 | "="*40 + 200 | "\n超话列表:\n" + 201 | "\n".join(f" - {topic}" for topic in super_topics) + 202 | "\n签到结果:\n" + 203 | "\n".join(f" - {result}" for result in sign_in_results) + 204 | "\n" + "="*40 205 | ) 206 | all_results.append(account_result) 207 | print(account_result) 208 | except Exception as e: 209 | error_msg = f"处理账号 {account_name} 时出错: {str(e)}" 210 | print(f"[CRITICAL] {error_msg}") 211 | all_results.append(f"\n账号:{account_name}\n[处理出错] {error_msg}") 212 | result_message = "\n".join(all_results) 213 | # 发送签到结果 214 | send("微博签到结果:", result_message) 215 | 216 | if __name__ == "__main__": 217 | main() 218 | -------------------------------------------------------------------------------- /notify.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 urllib.parse 12 | import smtplib 13 | from email.mime.text import MIMEText 14 | from email.header import Header 15 | from email.utils import formataddr 16 | 17 | import requests 18 | 19 | # 原先的 print 函数和主线程的锁 20 | _print = print 21 | mutex = threading.Lock() 22 | 23 | 24 | # 定义新的 print 函数 25 | def print(text, *args, **kw): 26 | """ 27 | 使输出有序进行,不出现多线程同一时间输出导致错乱的问题。 28 | """ 29 | with mutex: 30 | _print(text, *args, **kw) 31 | 32 | 33 | # 通知服务 34 | # fmt: off 35 | push_config = { 36 | 'HITOKOTO': False, # 启用一言(随机句子) 37 | 38 | 'BARK_PUSH': '', # bark IP 或设备码,例:https://api.day.app/DxHcxxxxxRxxxxxxcm/ 39 | 'BARK_ARCHIVE': '', # bark 推送是否存档 40 | 'BARK_GROUP': '', # bark 推送分组 41 | 'BARK_SOUND': '', # bark 推送声音 42 | 'BARK_ICON': '', # bark 推送图标 43 | 44 | 'CONSOLE': True, # 控制台输出 45 | 46 | 'DD_BOT_SECRET': '', # 钉钉机器人的 DD_BOT_SECRET 47 | 'DD_BOT_TOKEN': '', # 钉钉机器人的 DD_BOT_TOKEN 48 | 49 | 'FSKEY': '', # 飞书机器人的 FSKEY 50 | 51 | 'GOBOT_URL': '', # go-cqhttp 52 | # 推送到个人QQ:http://127.0.0.1/send_private_msg 53 | # 群:http://127.0.0.1/send_group_msg 54 | 'GOBOT_QQ': '', # go-cqhttp 的推送群或用户 55 | # GOBOT_URL 设置 /send_private_msg 时填入 user_id=个人QQ 56 | # /send_group_msg 时填入 group_id=QQ群 57 | 'GOBOT_TOKEN': '', # go-cqhttp 的 access_token 58 | 59 | 'GOTIFY_URL': '', # gotify地址,如https://push.example.de:8080 60 | 'GOTIFY_TOKEN': '', # gotify的消息应用token 61 | 'GOTIFY_PRIORITY': 0, # 推送消息优先级,默认为0 62 | 63 | 'IGOT_PUSH_KEY': '', # iGot 聚合推送的 IGOT_PUSH_KEY 64 | 65 | 'PUSH_KEY': '', # server 酱的 PUSH_KEY,兼容旧版与 Turbo 版 66 | 67 | 'DEER_KEY': '', # PushDeer 的 PUSHDEER_KEY 68 | 'DEER_URL': '', # PushDeer 的 PUSHDEER_URL 69 | 70 | 'CHAT_URL': '', # synology chat url 71 | 'CHAT_TOKEN': '', # synology chat token 72 | 73 | 'PUSH_PLUS_TOKEN': '', # push+ 微信推送的用户令牌 74 | 'PUSH_PLUS_USER': '', # push+ 微信推送的群组编码 75 | 76 | 'QMSG_KEY': '', # qmsg 酱的 QMSG_KEY 77 | 'QMSG_TYPE': '', # qmsg 酱的 QMSG_TYPE 78 | 79 | 'QYWX_ORIGIN': '', # 企业微信代理地址 80 | 81 | 'QYWX_AM': '', # 企业微信应用 82 | 83 | 'QYWX_KEY': '', # 企业微信机器人 84 | 85 | 'TG_BOT_TOKEN': '', # tg 机器人的 TG_BOT_TOKEN,例:1407203283:AAG9rt-6RDaaX0HBLZQq0laNOh898iFYaRQ 86 | 'TG_USER_ID': '', # tg 机器人的 TG_USER_ID,例:1434078534 87 | 'TG_API_HOST': '', # tg 代理 api 88 | 'TG_PROXY_AUTH': '', # tg 代理认证参数 89 | 'TG_PROXY_HOST': '', # tg 机器人的 TG_PROXY_HOST 90 | 'TG_PROXY_PORT': '', # tg 机器人的 TG_PROXY_PORT 91 | 92 | 'AIBOTK_KEY': '', # 智能微秘书 个人中心的apikey 文档地址:http://wechat.aibotk.com/docs/about 93 | 'AIBOTK_TYPE': '', # 智能微秘书 发送目标 room 或 contact 94 | 'AIBOTK_NAME': '', # 智能微秘书 发送群名 或者好友昵称和type要对应好 95 | 96 | 'SMTP_SERVER': '', # SMTP 发送邮件服务器,形如 smtp.exmail.qq.com:465 97 | 'SMTP_SSL': 'false', # SMTP 发送邮件服务器是否使用 SSL,填写 true 或 false 98 | 'SMTP_EMAIL': '', # SMTP 收发件邮箱,通知将会由自己发给自己 99 | 'SMTP_PASSWORD': '', # SMTP 登录密码,也可能为特殊口令,视具体邮件服务商说明而定 100 | 'SMTP_NAME': '', # SMTP 收发件人姓名,可随意填写 101 | 102 | 'PUSHME_KEY': '', # PushMe 酱的 PUSHME_KEY 103 | } 104 | notify_function = [] 105 | # fmt: on 106 | 107 | # 首先读取 面板变量 或者 github action 运行变量 108 | for k in push_config: 109 | if os.getenv(k): 110 | v = os.getenv(k) 111 | push_config[k] = v 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")}/{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 | "BARK_ICON": "icon", 133 | } 134 | params = "" 135 | for pair in filter( 136 | lambda pairs: pairs[0].startswith("BARK_") 137 | and pairs[0] != "BARK_PUSH" 138 | and pairs[1] 139 | and bark_params.get(pairs[0]), 140 | push_config.items(), 141 | ): 142 | params += f"{bark_params.get(pair[0])}={pair[1]}&" 143 | if params: 144 | url = url + "?" + params.rstrip("&") 145 | response = requests.get(url).json() 146 | 147 | if response["code"] == 200: 148 | print("bark 推送成功!") 149 | else: 150 | print("bark 推送失败!") 151 | 152 | 153 | def console(title: str, content: str) -> None: 154 | """ 155 | 使用 控制台 推送消息。 156 | """ 157 | print(f"{title}\n\n{content}") 158 | 159 | 160 | def dingding_bot(title: str, content: str) -> None: 161 | """ 162 | 使用 钉钉机器人 推送消息。 163 | """ 164 | if not push_config.get("DD_BOT_SECRET") or not push_config.get("DD_BOT_TOKEN"): 165 | print("钉钉机器人 服务的 DD_BOT_SECRET 或者 DD_BOT_TOKEN 未设置!!\n取消推送") 166 | return 167 | print("钉钉机器人 服务启动") 168 | 169 | timestamp = str(round(time.time() * 1000)) 170 | secret_enc = push_config.get("DD_BOT_SECRET").encode("utf-8") 171 | string_to_sign = "{}\n{}".format(timestamp, push_config.get("DD_BOT_SECRET")) 172 | string_to_sign_enc = string_to_sign.encode("utf-8") 173 | hmac_code = hmac.new( 174 | secret_enc, string_to_sign_enc, digestmod=hashlib.sha256 175 | ).digest() 176 | sign = urllib.parse.quote_plus(base64.b64encode(hmac_code)) 177 | url = f'https://oapi.dingtalk.com/robot/send?access_token={push_config.get("DD_BOT_TOKEN")}×tamp={timestamp}&sign={sign}' 178 | headers = {"Content-Type": "application/json;charset=utf-8"} 179 | data = {"msgtype": "text", "text": {"content": f"{title}\n\n{content}"}} 180 | response = requests.post( 181 | url=url, data=json.dumps(data), headers=headers, timeout=15 182 | ).json() 183 | 184 | if not response["errcode"]: 185 | print("钉钉机器人 推送成功!") 186 | else: 187 | print("钉钉机器人 推送失败!") 188 | 189 | 190 | def feishu_bot(title: str, content: str) -> None: 191 | """ 192 | 使用 飞书机器人 推送消息。 193 | """ 194 | if not push_config.get("FSKEY"): 195 | print("飞书 服务的 FSKEY 未设置!!\n取消推送") 196 | return 197 | print("飞书 服务启动") 198 | 199 | url = f'https://open.feishu.cn/open-apis/bot/v2/hook/{push_config.get("FSKEY")}' 200 | data = {"msg_type": "text", "content": {"text": f"{title}\n\n{content}"}} 201 | response = requests.post(url, data=json.dumps(data)).json() 202 | 203 | if response.get("StatusCode") == 0: 204 | print("飞书 推送成功!") 205 | else: 206 | print("飞书 推送失败!错误信息如下:\n", response) 207 | 208 | 209 | def go_cqhttp(title: str, content: str) -> None: 210 | """ 211 | 使用 go_cqhttp 推送消息。 212 | """ 213 | if not push_config.get("GOBOT_URL") or not push_config.get("GOBOT_QQ"): 214 | print("go-cqhttp 服务的 GOBOT_URL 或 GOBOT_QQ 未设置!!\n取消推送") 215 | return 216 | print("go-cqhttp 服务启动") 217 | 218 | url = f'{push_config.get("GOBOT_URL")}?access_token={push_config.get("GOBOT_TOKEN")}&{push_config.get("GOBOT_QQ")}&message=标题:{title}\n内容:{content}' 219 | response = requests.get(url).json() 220 | 221 | if response["status"] == "ok": 222 | print("go-cqhttp 推送成功!") 223 | else: 224 | print("go-cqhttp 推送失败!") 225 | 226 | 227 | def gotify(title: str, content: str) -> None: 228 | """ 229 | 使用 gotify 推送消息。 230 | """ 231 | if not push_config.get("GOTIFY_URL") or not push_config.get("GOTIFY_TOKEN"): 232 | print("gotify 服务的 GOTIFY_URL 或 GOTIFY_TOKEN 未设置!!\n取消推送") 233 | return 234 | print("gotify 服务启动") 235 | 236 | url = f'{push_config.get("GOTIFY_URL")}/message?token={push_config.get("GOTIFY_TOKEN")}' 237 | data = { 238 | "title": title, 239 | "message": content, 240 | "priority": push_config.get("GOTIFY_PRIORITY"), 241 | } 242 | response = requests.post(url, data=data).json() 243 | 244 | if response.get("id"): 245 | print("gotify 推送成功!") 246 | else: 247 | print("gotify 推送失败!") 248 | 249 | 250 | def iGot(title: str, content: str) -> None: 251 | """ 252 | 使用 iGot 推送消息。 253 | """ 254 | if not push_config.get("IGOT_PUSH_KEY"): 255 | print("iGot 服务的 IGOT_PUSH_KEY 未设置!!\n取消推送") 256 | return 257 | print("iGot 服务启动") 258 | 259 | url = f'https://push.hellyw.com/{push_config.get("IGOT_PUSH_KEY")}' 260 | data = {"title": title, "content": content} 261 | headers = {"Content-Type": "application/x-www-form-urlencoded"} 262 | response = requests.post(url, data=data, headers=headers).json() 263 | 264 | if response["ret"] == 0: 265 | print("iGot 推送成功!") 266 | else: 267 | print(f'iGot 推送失败!{response["errMsg"]}') 268 | 269 | 270 | def serverJ(title: str, content: str) -> None: 271 | """ 272 | 通过 serverJ 推送消息。 273 | """ 274 | if not push_config.get("PUSH_KEY"): 275 | print("serverJ 服务的 PUSH_KEY 未设置!!\n取消推送") 276 | return 277 | print("serverJ 服务启动") 278 | 279 | data = {"text": title, "desp": content.replace("\n", "\n\n")} 280 | if push_config.get("PUSH_KEY").find("SCT") != -1: 281 | url = f'https://sctapi.ftqq.com/{push_config.get("PUSH_KEY")}.send' 282 | else: 283 | url = f'https://sc.ftqq.com/{push_config.get("PUSH_KEY")}.send' 284 | response = requests.post(url, data=data).json() 285 | 286 | if response.get("errno") == 0 or response.get("code") == 0: 287 | print("serverJ 推送成功!") 288 | else: 289 | print(f'serverJ 推送失败!错误码:{response["message"]}') 290 | 291 | 292 | def pushdeer(title: str, content: str) -> None: 293 | """ 294 | 通过PushDeer 推送消息 295 | """ 296 | if not push_config.get("DEER_KEY"): 297 | print("PushDeer 服务的 DEER_KEY 未设置!!\n取消推送") 298 | return 299 | print("PushDeer 服务启动") 300 | data = { 301 | "text": title, 302 | "desp": content, 303 | "type": "markdown", 304 | "pushkey": push_config.get("DEER_KEY"), 305 | } 306 | url = "https://api2.pushdeer.com/message/push" 307 | if push_config.get("DEER_URL"): 308 | url = push_config.get("DEER_URL") 309 | 310 | response = requests.post(url, data=data).json() 311 | 312 | if len(response.get("content").get("result")) > 0: 313 | print("PushDeer 推送成功!") 314 | else: 315 | print("PushDeer 推送失败!错误信息:", response) 316 | 317 | 318 | def chat(title: str, content: str) -> None: 319 | """ 320 | 通过Chat 推送消息 321 | """ 322 | if not push_config.get("CHAT_URL") or not push_config.get("CHAT_TOKEN"): 323 | print("chat 服务的 CHAT_URL或CHAT_TOKEN 未设置!!\n取消推送") 324 | return 325 | print("chat 服务启动") 326 | data = "payload=" + json.dumps({"text": title + "\n" + content}) 327 | url = push_config.get("CHAT_URL") + push_config.get("CHAT_TOKEN") 328 | response = requests.post(url, data=data) 329 | 330 | if response.status_code == 200: 331 | print("Chat 推送成功!") 332 | else: 333 | print("Chat 推送失败!错误信息:", response) 334 | 335 | 336 | def pushplus_bot(title: str, content: str) -> None: 337 | """ 338 | 通过 push+ 推送消息。 339 | """ 340 | if not push_config.get("PUSH_PLUS_TOKEN"): 341 | print("PUSHPLUS 服务的 PUSH_PLUS_TOKEN 未设置!!\n取消推送") 342 | return 343 | print("PUSHPLUS 服务启动") 344 | 345 | url = "http://www.pushplus.plus/send" 346 | data = { 347 | "token": push_config.get("PUSH_PLUS_TOKEN"), 348 | "title": title, 349 | "content": content, 350 | "topic": push_config.get("PUSH_PLUS_USER"), 351 | } 352 | body = json.dumps(data).encode(encoding="utf-8") 353 | headers = {"Content-Type": "application/json"} 354 | response = requests.post(url=url, data=body, headers=headers).json() 355 | 356 | if response["code"] == 200: 357 | print("PUSHPLUS 推送成功!") 358 | 359 | else: 360 | url_old = "http://pushplus.hxtrip.com/send" 361 | headers["Accept"] = "application/json" 362 | response = requests.post(url=url_old, data=body, headers=headers).json() 363 | 364 | if response["code"] == 200: 365 | print("PUSHPLUS(hxtrip) 推送成功!") 366 | 367 | else: 368 | print("PUSHPLUS 推送失败!") 369 | 370 | 371 | def qmsg_bot(title: str, content: str) -> None: 372 | """ 373 | 使用 qmsg 推送消息。 374 | """ 375 | if not push_config.get("QMSG_KEY") or not push_config.get("QMSG_TYPE"): 376 | print("qmsg 的 QMSG_KEY 或者 QMSG_TYPE 未设置!!\n取消推送") 377 | return 378 | print("qmsg 服务启动") 379 | 380 | url = f'https://qmsg.zendee.cn/{push_config.get("QMSG_TYPE")}/{push_config.get("QMSG_KEY")}' 381 | payload = {"msg": f'{title}\n\n{content.replace("----", "-")}'.encode("utf-8")} 382 | response = requests.post(url=url, params=payload).json() 383 | 384 | if response["code"] == 0: 385 | print("qmsg 推送成功!") 386 | else: 387 | print(f'qmsg 推送失败!{response["reason"]}') 388 | 389 | 390 | def wecom_app(title: str, content: str) -> None: 391 | """ 392 | 通过 企业微信 APP 推送消息。 393 | """ 394 | if not push_config.get("QYWX_AM"): 395 | print("QYWX_AM 未设置!!\n取消推送") 396 | return 397 | QYWX_AM_AY = re.split(",", push_config.get("QYWX_AM")) 398 | if 4 < len(QYWX_AM_AY) > 5: 399 | print("QYWX_AM 设置错误!!\n取消推送") 400 | return 401 | print("企业微信 APP 服务启动") 402 | 403 | corpid = QYWX_AM_AY[0] 404 | corpsecret = QYWX_AM_AY[1] 405 | touser = QYWX_AM_AY[2] 406 | agentid = QYWX_AM_AY[3] 407 | try: 408 | media_id = QYWX_AM_AY[4] 409 | except IndexError: 410 | media_id = "" 411 | wx = WeCom(corpid, corpsecret, agentid) 412 | # 如果没有配置 media_id 默认就以 text 方式发送 413 | if not media_id: 414 | message = title + "\n\n" + content 415 | response = wx.send_text(message, touser) 416 | else: 417 | response = wx.send_mpnews(title, content, media_id, touser) 418 | 419 | if response == "ok": 420 | print("企业微信推送成功!") 421 | else: 422 | print("企业微信推送失败!错误信息如下:\n", response) 423 | 424 | 425 | class WeCom: 426 | def __init__(self, corpid, corpsecret, agentid): 427 | self.CORPID = corpid 428 | self.CORPSECRET = corpsecret 429 | self.AGENTID = agentid 430 | self.ORIGIN = "https://qyapi.weixin.qq.com" 431 | if push_config.get("QYWX_ORIGIN"): 432 | self.ORIGIN = push_config.get("QYWX_ORIGIN") 433 | 434 | def get_access_token(self): 435 | url = f"{self.ORIGIN}/cgi-bin/gettoken" 436 | values = { 437 | "corpid": self.CORPID, 438 | "corpsecret": self.CORPSECRET, 439 | } 440 | req = requests.post(url, params=values) 441 | data = json.loads(req.text) 442 | return data["access_token"] 443 | 444 | def send_text(self, message, touser="@all"): 445 | send_url = f"{self.ORIGIN}/cgi-bin/message/send?access_token={self.get_access_token()}" 446 | send_values = { 447 | "touser": touser, 448 | "msgtype": "text", 449 | "agentid": self.AGENTID, 450 | "text": {"content": message}, 451 | "safe": "0", 452 | } 453 | send_msges = bytes(json.dumps(send_values), "utf-8") 454 | respone = requests.post(send_url, send_msges) 455 | respone = respone.json() 456 | return respone["errmsg"] 457 | 458 | def send_mpnews(self, title, message, media_id, touser="@all"): 459 | send_url = f"https://{self.HOST}/cgi-bin/message/send?access_token={self.get_access_token()}" 460 | send_values = { 461 | "touser": touser, 462 | "msgtype": "mpnews", 463 | "agentid": self.AGENTID, 464 | "mpnews": { 465 | "articles": [ 466 | { 467 | "title": title, 468 | "thumb_media_id": media_id, 469 | "author": "Author", 470 | "content_source_url": "", 471 | "content": message.replace("\n", "
"), 472 | "digest": message, 473 | } 474 | ] 475 | }, 476 | } 477 | send_msges = bytes(json.dumps(send_values), "utf-8") 478 | respone = requests.post(send_url, send_msges) 479 | respone = respone.json() 480 | return respone["errmsg"] 481 | 482 | 483 | def wecom_bot(title: str, content: str) -> None: 484 | """ 485 | 通过 企业微信机器人 推送消息。 486 | """ 487 | if not push_config.get("QYWX_KEY"): 488 | print("企业微信机器人 服务的 QYWX_KEY 未设置!!\n取消推送") 489 | return 490 | print("企业微信机器人服务启动") 491 | 492 | origin = "https://qyapi.weixin.qq.com" 493 | if push_config.get("QYWX_ORIGIN"): 494 | origin = push_config.get("QYWX_ORIGIN") 495 | 496 | url = f"{origin}/cgi-bin/webhook/send?key={push_config.get('QYWX_KEY')}" 497 | headers = {"Content-Type": "application/json;charset=utf-8"} 498 | data = {"msgtype": "text", "text": {"content": f"{title}\n\n{content}"}} 499 | response = requests.post( 500 | url=url, data=json.dumps(data), headers=headers, timeout=15 501 | ).json() 502 | 503 | if response["errcode"] == 0: 504 | print("企业微信机器人推送成功!") 505 | else: 506 | print("企业微信机器人推送失败!") 507 | 508 | 509 | def telegram_bot(title: str, content: str) -> None: 510 | """ 511 | 使用 telegram 机器人 推送消息。 512 | """ 513 | if not push_config.get("TG_BOT_TOKEN") or not push_config.get("TG_USER_ID"): 514 | print("tg 服务的 bot_token 或者 user_id 未设置!!\n取消推送") 515 | return 516 | print("tg 服务启动") 517 | 518 | if push_config.get("TG_API_HOST"): 519 | url = f"https://{push_config.get('TG_API_HOST')}/bot{push_config.get('TG_BOT_TOKEN')}/sendMessage" 520 | else: 521 | url = ( 522 | f"https://api.telegram.org/bot{push_config.get('TG_BOT_TOKEN')}/sendMessage" 523 | ) 524 | headers = {"Content-Type": "application/x-www-form-urlencoded"} 525 | payload = { 526 | "chat_id": str(push_config.get("TG_USER_ID")), 527 | "text": f"{title}\n\n{content}", 528 | "disable_web_page_preview": "true", 529 | } 530 | proxies = None 531 | if push_config.get("TG_PROXY_HOST") and push_config.get("TG_PROXY_PORT"): 532 | if push_config.get("TG_PROXY_AUTH") is not None and "@" not in push_config.get( 533 | "TG_PROXY_HOST" 534 | ): 535 | push_config["TG_PROXY_HOST"] = ( 536 | push_config.get("TG_PROXY_AUTH") 537 | + "@" 538 | + push_config.get("TG_PROXY_HOST") 539 | ) 540 | proxyStr = "http://{}:{}".format( 541 | push_config.get("TG_PROXY_HOST"), push_config.get("TG_PROXY_PORT") 542 | ) 543 | proxies = {"http": proxyStr, "https": proxyStr} 544 | response = requests.post( 545 | url=url, headers=headers, params=payload, proxies=proxies 546 | ).json() 547 | 548 | if response["ok"]: 549 | print("tg 推送成功!") 550 | else: 551 | print("tg 推送失败!") 552 | 553 | 554 | def aibotk(title: str, content: str) -> None: 555 | """ 556 | 使用 智能微秘书 推送消息。 557 | """ 558 | if ( 559 | not push_config.get("AIBOTK_KEY") 560 | or not push_config.get("AIBOTK_TYPE") 561 | or not push_config.get("AIBOTK_NAME") 562 | ): 563 | print("智能微秘书 的 AIBOTK_KEY 或者 AIBOTK_TYPE 或者 AIBOTK_NAME 未设置!!\n取消推送") 564 | return 565 | print("智能微秘书 服务启动") 566 | 567 | if push_config.get("AIBOTK_TYPE") == "room": 568 | url = "https://api-bot.aibotk.com/openapi/v1/chat/room" 569 | data = { 570 | "apiKey": push_config.get("AIBOTK_KEY"), 571 | "roomName": push_config.get("AIBOTK_NAME"), 572 | "message": {"type": 1, "content": f"【青龙快讯】\n\n{title}\n{content}"}, 573 | } 574 | else: 575 | url = "https://api-bot.aibotk.com/openapi/v1/chat/contact" 576 | data = { 577 | "apiKey": push_config.get("AIBOTK_KEY"), 578 | "name": push_config.get("AIBOTK_NAME"), 579 | "message": {"type": 1, "content": f"【青龙快讯】\n\n{title}\n{content}"}, 580 | } 581 | body = json.dumps(data).encode(encoding="utf-8") 582 | headers = {"Content-Type": "application/json"} 583 | response = requests.post(url=url, data=body, headers=headers).json() 584 | print(response) 585 | if response["code"] == 0: 586 | print("智能微秘书 推送成功!") 587 | else: 588 | print(f'智能微秘书 推送失败!{response["error"]}') 589 | 590 | 591 | def smtp(title: str, content: str) -> None: 592 | """ 593 | 使用 SMTP 邮件 推送消息。 594 | """ 595 | if ( 596 | not push_config.get("SMTP_SERVER") 597 | or not push_config.get("SMTP_SSL") 598 | or not push_config.get("SMTP_EMAIL") 599 | or not push_config.get("SMTP_PASSWORD") 600 | or not push_config.get("SMTP_NAME") 601 | ): 602 | print( 603 | "SMTP 邮件 的 SMTP_SERVER 或者 SMTP_SSL 或者 SMTP_EMAIL 或者 SMTP_PASSWORD 或者 SMTP_NAME 未设置!!\n取消推送" 604 | ) 605 | return 606 | print("SMTP 邮件 服务启动") 607 | 608 | message = MIMEText(content, "plain", "utf-8") 609 | message["From"] = formataddr( 610 | ( 611 | Header(push_config.get("SMTP_NAME"), "utf-8").encode(), 612 | push_config.get("SMTP_EMAIL"), 613 | ) 614 | ) 615 | message["To"] = formataddr( 616 | ( 617 | Header(push_config.get("SMTP_NAME"), "utf-8").encode(), 618 | push_config.get("SMTP_EMAIL"), 619 | ) 620 | ) 621 | message["Subject"] = Header(title, "utf-8") 622 | 623 | try: 624 | smtp_server = ( 625 | smtplib.SMTP_SSL(push_config.get("SMTP_SERVER")) 626 | if push_config.get("SMTP_SSL") == "true" 627 | else smtplib.SMTP(push_config.get("SMTP_SERVER")) 628 | ) 629 | smtp_server.login( 630 | push_config.get("SMTP_EMAIL"), push_config.get("SMTP_PASSWORD") 631 | ) 632 | smtp_server.sendmail( 633 | push_config.get("SMTP_EMAIL"), 634 | push_config.get("SMTP_EMAIL"), 635 | message.as_bytes(), 636 | ) 637 | smtp_server.close() 638 | print("SMTP 邮件 推送成功!") 639 | except Exception as e: 640 | print(f"SMTP 邮件 推送失败!{e}") 641 | 642 | 643 | def pushme(title: str, content: str) -> None: 644 | """ 645 | 使用 PushMe 推送消息。 646 | """ 647 | if not push_config.get("PUSHME_KEY"): 648 | print("PushMe 服务的 PUSHME_KEY 未设置!!\n取消推送") 649 | return 650 | print("PushMe 服务启动") 651 | 652 | url = f'https://push.i-i.me/?push_key={push_config.get("PUSHME_KEY")}' 653 | data = { 654 | "title": title, 655 | "content": content, 656 | } 657 | response = requests.post(url, data=data) 658 | 659 | if response.status_code == 200 and response.text == "success": 660 | print("PushMe 推送成功!") 661 | else: 662 | print(f"PushMe 推送失败!{response.status_code} {response.text}") 663 | 664 | 665 | def one() -> str: 666 | """ 667 | 获取一条一言。 668 | :return: 669 | """ 670 | url = "https://v1.hitokoto.cn/" 671 | res = requests.get(url).json() 672 | return res["hitokoto"] + " ----" + res["from"] 673 | 674 | 675 | if push_config.get("BARK_PUSH"): 676 | notify_function.append(bark) 677 | if push_config.get("CONSOLE"): 678 | notify_function.append(console) 679 | if push_config.get("DD_BOT_TOKEN") and push_config.get("DD_BOT_SECRET"): 680 | notify_function.append(dingding_bot) 681 | if push_config.get("FSKEY"): 682 | notify_function.append(feishu_bot) 683 | if push_config.get("GOBOT_URL") and push_config.get("GOBOT_QQ"): 684 | notify_function.append(go_cqhttp) 685 | if push_config.get("GOTIFY_URL") and push_config.get("GOTIFY_TOKEN"): 686 | notify_function.append(gotify) 687 | if push_config.get("IGOT_PUSH_KEY"): 688 | notify_function.append(iGot) 689 | if push_config.get("PUSH_KEY"): 690 | notify_function.append(serverJ) 691 | if push_config.get("DEER_KEY"): 692 | notify_function.append(pushdeer) 693 | if push_config.get("CHAT_URL") and push_config.get("CHAT_TOKEN"): 694 | notify_function.append(chat) 695 | if push_config.get("PUSH_PLUS_TOKEN"): 696 | notify_function.append(pushplus_bot) 697 | if push_config.get("QMSG_KEY") and push_config.get("QMSG_TYPE"): 698 | notify_function.append(qmsg_bot) 699 | if push_config.get("QYWX_AM"): 700 | notify_function.append(wecom_app) 701 | if push_config.get("QYWX_KEY"): 702 | notify_function.append(wecom_bot) 703 | if push_config.get("TG_BOT_TOKEN") and push_config.get("TG_USER_ID"): 704 | notify_function.append(telegram_bot) 705 | if ( 706 | push_config.get("AIBOTK_KEY") 707 | and push_config.get("AIBOTK_TYPE") 708 | and push_config.get("AIBOTK_NAME") 709 | ): 710 | notify_function.append(aibotk) 711 | if ( 712 | push_config.get("SMTP_SERVER") 713 | and push_config.get("SMTP_SSL") 714 | and push_config.get("SMTP_EMAIL") 715 | and push_config.get("SMTP_PASSWORD") 716 | and push_config.get("SMTP_NAME") 717 | ): 718 | notify_function.append(smtp) 719 | if push_config.get("PUSHME_KEY"): 720 | notify_function.append(pushme) 721 | 722 | 723 | def send(title: str, content: str) -> None: 724 | if not content: 725 | print(f"{title} 推送内容为空!") 726 | return 727 | 728 | # 根据标题跳过一些消息推送,环境变量:SKIP_PUSH_TITLE 用回车分隔 729 | skipTitle = os.getenv("SKIP_PUSH_TITLE") 730 | if skipTitle: 731 | if title in re.split("\n", skipTitle): 732 | print(f"{title} 在SKIP_PUSH_TITLE环境变量内,跳过推送!") 733 | return 734 | 735 | hitokoto = push_config.get("HITOKOTO") 736 | 737 | text = one() if hitokoto else "" 738 | content += "\n\n" + text 739 | 740 | ts = [ 741 | threading.Thread(target=mode, args=(title, content), name=mode.__name__) 742 | for mode in notify_function 743 | ] 744 | [t.start() for t in ts] 745 | [t.join() for t in ts] 746 | 747 | 748 | def main(): 749 | send("title", "content") 750 | 751 | 752 | if __name__ == "__main__": 753 | main() 754 | --------------------------------------------------------------------------------