修改通知时间为晚上10点,添加cookie保存功能,按用户和域名区分保存

This commit is contained in:
2026-03-04 22:45:31 +08:00
parent 918ac92230
commit 7a896f56ba
8 changed files with 564 additions and 412 deletions
+12 -4
View File
@@ -9,7 +9,8 @@
},
"notification": {
"iyuu_token": "IYUU37629Tc1d371c7ce99a49ff9778e196286b7e4592be191",
"max_fail_count": 3
"max_fail_count": 3,
"notify_on_success": true
},
"browser": {
"type": "edge",
@@ -33,7 +34,8 @@
},
"notification": {
"iyuu_token": "IYUU37629Tc1d371c7ce99a49ff9778e196286b7e4592be191",
"max_fail_count": 3
"max_fail_count": 3,
"notify_on_success": true
},
"browser": {
"type": "edge",
@@ -44,9 +46,15 @@
"name": "网站A",
"url": "https://lmkbi.95155.com/bi-system/#/carUnpetrifiedComAll",
"login_check_selector": "",
"success_text": ""
"success_text": "彭峰"
},
{
"name": "网站B",
"url": "https://192.168.1.200:8006/",
"login_check_selector": "",
"success_text": "彭峰"
}
]
}
]
}
}
+50 -157
View File
@@ -1,160 +1,53 @@
{
"cookies": [
{
"name": "JSESSIONID",
"value": "DD8A9B4F6E06DF35AE1EC2339DF0FA36",
"domain": "10.190.1.205"
},
{
"name": "__rubyUX",
"value": "false",
"domain": "ntp.msn.cn"
},
{
"name": "pglt-edgeChromium-dhp",
"value": "2083",
"domain": ".msn.cn"
},
{
"name": "pglt-edgeChromium-ntp",
"value": "2083",
"domain": ".msn.cn"
},
{
"name": "_C_Auth",
"value": "",
"domain": "ntp.msn.cn"
},
{
"name": "MicrosoftApplicationsTelemetryDeviceId",
"value": "da0bf04e-2115-4952-a012-fc29b4954ec0",
"domain": "ntp.msn.cn"
},
{
"name": "USRLOC",
"value": "",
"domain": ".msn.cn"
},
{
"name": "MUID",
"value": "15CB8BCF863F643B03449CDE8778658F",
"domain": ".msn.cn"
},
{
"name": "v1",
"value": "ExJ_flr=b!>Rb_?o_sb/",
"domain": ".mediav.com"
},
{
"name": "MUID",
"value": "15CB8BCF863F643B03449CDE8778658F",
"domain": ".bing.com"
},
{
"name": "MR",
"value": "0",
"domain": ".c.bing.com"
},
{
"name": "SRM_B",
"value": "15CB8BCF863F643B03449CDE8778658F",
"domain": ".c.bing.com"
},
{
"name": "MR",
"value": "0",
"domain": ".c.msn.cn"
},
{
"name": "MUIDB",
"value": "15CB8BCF863F643B03449CDE8778658F",
"domain": "ntp.msn.cn"
},
{
"name": ".ASPXANONYMOUS",
"value": "21JPwTAtPyn84bHA6aNjbcWhPsVrLyiIZ-4mnJ6_dMtvLzgXbw3J9nyy0dxCZj0b8vOEHXZpgfYoPylWhp5s3AY2GwcwdYg9JI6VqZ6YK3uRkwMf0",
"domain": "i3.sinopec.com"
},
{
"name": "x",
"value": "x",
"domain": "auth.siam.sinopec.com"
},
{
"name": "msnup",
"value": "%7B%22cnex%22%3A%22no%22%7D",
"domain": ".msn.cn"
},
{
"name": "msaoptout",
"value": "0",
"domain": "ntp.msn.cn"
},
{
"name": "OptanonAlertBoxClosed",
"value": "2026-03-04T08:18:01.713Z",
"domain": ".msn.cn"
},
{
"name": "eupubconsent-v2",
"value": "CQghjcAQghjcAAcABBZHBfFsAP_gAELgACiQK4tX_G__bXlr8X73aftkeY1f99h77sQxBhaJk-4FzJvW_JwX32E7NAz6tqYKmRIAu3TBAQNlHJDURVCgaIgVqSDMaEyUoTNKJ6BkiFMRY2dYCFxvm4tjeQCY5vr991d52B-t7dr83dzyy4hHv3a5_0S1WAAAAYCNDfv9bROb-9IO9_x8v4v4_F7pE2_eS1l_tWvp7D9-cts_9XW99_bbff9Pn_-uF_-_X_vf_H37v9oK5AACAAAARgkCAAAgAAAAABAAAAAQAAAAAQBgAAARBAEAAgAQGEgAEAAAAIAAAAAgSAAAAAJAAgAAAABQAAAIBAAAAwAIBgAAGAAACABACAAAAgEgYJgQQCBAABCYBAAgQgAAAACwECAABAgiACEQcAQAAAAAAAAEAAoAAEAgDAQkkBCxIIAuIJoAACAAAIIAChFJ2YAggDNlqrwZNoytMCwfMFzymAZAEQRk5JsQmsAoJA1AAQAAuACgAKgAcAA8ACCAGQAagA8ACIAEwAKoAbwA9AB-AEJAIYAiQBHACWAE0AK0AYYAywBsgDvgHsAfEA-wD9AIBARcBGACNAFBAKgAVcAuYBigDaAG4AOIAkQBOwChwFHgKRAWwAuQBd4DDQGSAMnAZcAzmBrAGsgNvAeOEAMAAOAHOAQcAn4CPQEigJWATaAsIBeQDEAGLQMhAyMBowDUwG0ANuAboA8oB8gD9wICAQMAgiOATgAIgAcAB4AFwAfgBoAHOAO4AgEBBwEIAJ-AVAAvQB0gEegJFASsAmIBMoCbQFIAKTAWoAvoBiADFgGQgMmAaMA00BqYDXgG0ANuAeUA-IB9sD9gP3AgeBBEdBAAAXABQAFQAOAAgABdADIANQAeABEACYAFWALgAugBiADeAHoAP0AhgCJAEsAJoAUYArQBhgDKAGiANkAd4A9oB9gH7ARYBGACggFXALEAXMAvIBigDaAG4AOIAdQBF4CRAEyAJ2AUOAo8BTQCxQFsALgAXIAu0Bd4DDQGPAMkAZOAyqBlgGXAM5AaqA1gBt4DxwH1gQBIAHAAEABoAHOAWIBB4CPQE2gKTAVKAvIBqYDbAG3APKAfEA_YCB4EGAINgQrAimBGkCN8EkQSSAqmBWkCtsFcQVyIQIwAFgAUABcAFUALgAYgA3gB6AEcAO8AigBKQCggFXALmAYoA2gB1IFNAU2AqwBYoC0QFwALkAZOAzkBqoDxwH9gQtAh6BIoCSAFSgKvkoD4ACAAFgAUAA4ADwAIgATAAqgBcADFAIYAiQBHACjAFaANkAd4A_ACrgGKAOoAi8BIgCjwFigLYAZOAywBnIDWAG3gQPJADQALgDuAIAAVABHoCRQErAJtAUmAxYB5QD9wIIlIFwAC4AKAAqABwAEEAMgA0AB4AEQAJgAVQAxAB-gEMARIAowBWgDKAGiANkAd8A-wD9AIsARgAoIBVwC5gF5AMUAbQA3ACLwEiAJ2AUOApoBYoC2AFwALkAXaAw0BkgDJwGXAM5gawBrIDbwHjlADYAFwBHADnAHcAQAAkQBYgDXgHbAP-Aj0BIoCYgE2gKQAU-AvIBfQDFgGTANTAa8A8oB8UD9gP3AgYBA8.f_wACFwAAAAA",
"domain": ".msn.cn"
},
{
"name": "MSFPC",
"value": "GUID=139384d4545d4f4583ff3ef161faee39&HASH=1393&LV=202603&V=4&LU=1772612281994",
"domain": "ntp.msn.cn"
},
{
"name": "UID",
"value": "1862498edfbe5a24e12c8d21772613043",
"domain": ".scorecardresearch.com"
},
{
"name": "XID",
"value": "1862498edfbe5a24e12c8d21772613043",
"domain": ".scorecardresearch.com"
},
{
"name": "SM",
"value": "C",
"domain": ".c.msn.cn"
},
{
"name": "JSESSIONID",
"value": "DD8A9B4F6E06DF35AE1EC2339DF0FA36",
"domain": "10.190.1.205"
},
{
"name": "language",
"value": "zh-CN",
"domain": "192.168.8.147"
},
{
"name": "fnos-token",
"value": "n4fYErf2p2lHSucAt8uYAo+M+JDy354smCjF98dHtJM=",
"domain": "192.168.8.147"
},
{
"name": "fnos-long-token",
"value": "07MRUOgDAAC3g89pAAAAAMrPwB8hdcC8nqwi0bqBWfNDO0uPBM7fPQ==",
"domain": "192.168.8.147"
},
{
"name": "_C_ETH",
"value": "1",
"domain": ".msn.cn"
},
{
"name": "_EDGE_S",
"value": "\"SID=217608637BE1667B39501F717A966764\"",
"domain": ".msn.cn"
},
{
"name": "OptanonConsent",
"value": "isGpcEnabled=0&datestamp=Wed+Mar+04+2026+17%3A09%3A17+GMT%2B0800+(%E4%B8%AD%E5%9B%BD%E6%A0%87%E5%87%86%E6%97%B6%E9%97%B4)&version=202501.2.0&browserGpcFlag=0&isIABGlobal=false&hosts=&consentId=3599d387-f02a-409c-910d-0a6caea03c05&interactionCount=1&isAnonUser=1&landingPath=NotLandingPage&groups=C0001%3A1%2CC0002%3A1%2CC0004%3A1%2CC0008%3A1%2CV2STACK42%3A1&AwaitingReconsent=false&intType=1&geolocation=%3B",
"domain": ".msn.cn"
"用户A": {
"lmkbi.95155.com": {
"cookies": [],
"url": "https://lmkbi.95155.com/bi-system/#/carUnpetrifiedComAll",
"website_name": "网站A"
}
],
"url": "http://192.168.8.147:5666/login"
},
"用户B": {
"lmkbi.95155.com": {
"cookies": [
{
"name": "_qimei_uuid42",
"value": "1a302130f16100ca18af370a47cb8afce807c4a076",
"domain": ".95155.com"
},
{
"name": "_qimei_fingerprint",
"value": "50aa79f919511142818459f44ac38dee",
"domain": ".95155.com"
},
{
"name": "_qimei_i_3",
"value": "64c844d0c35e05dc9796fc355d8470e5a3eba5f0410e00d3e78c2d0e2f95293d306031943c89e29eb6a6",
"domain": ".95155.com"
},
{
"name": "_qimei_h38",
"value": "f8e0750618af370a47cb8afc0200000c91a302",
"domain": ".95155.com"
},
{
"name": "_qimei_i_1",
"value": "42d946d49d0f50dcc497f83153d525e3f0eef5f51508518ae5d97b582493206c6163649c39d8e1dcd4a9dffb",
"domain": ".95155.com"
}
],
"url": "https://lmkbi.95155.com/bi-system/#/carUnpetrifiedComAll",
"website_name": "网站A"
},
"192.168.1.200:8006": {
"cookies": [
{
"name": "PVELangCookie",
"value": "zh_CN",
"domain": "192.168.1.200"
}
],
"url": "https://192.168.1.200:8006/",
"website_name": "网站B"
}
}
}
+73 -49
View File
@@ -4,7 +4,7 @@ from tkinter import ttk, messagebox
import json
import os
import logging
from DrissionPage import Chromium, ChromiumOptions
from DrissionPage import ChromiumPage, ChromiumOptions
logging.basicConfig(
level=logging.INFO,
@@ -25,8 +25,7 @@ class CookieExtractorApp:
self.root.geometry("600x250")
self.root.resizable(True, True)
self.browser = None
self.tab = None
self.page = None
self._create_widgets()
@@ -64,6 +63,18 @@ class CookieExtractorApp:
ttk.Label(status_frame, text="状态:").pack(side=tk.LEFT, padx=5)
ttk.Label(status_frame, textvariable=self.status_var, foreground="blue").pack(side=tk.LEFT, padx=5)
def _init_browser(self):
"""初始化浏览器"""
co = ChromiumOptions()
co.set_browser_path(r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe")
self.page = ChromiumPage(addr_or_opts=co)
def _wait_page_loaded(self, timeout=30):
"""等待页面加载完成"""
if self.page:
self.page.wait.doc_loaded(timeout=timeout)
self.page.ele('tag:body', timeout=timeout)
def open_website(self):
"""打开指定网站"""
url = self.url_var.get().strip()
@@ -82,24 +93,13 @@ class CookieExtractorApp:
self.status_var.set("正在打开浏览器...")
self.root.update()
co = ChromiumOptions()
co.set_browser_path(r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe")
co.set_argument('--no-sandbox')
co.set_argument('--disable-dev-shm-usage')
self.browser = Chromium(co)
self.tab = self.browser.new_tab()
self._init_browser()
self.status_var.set(f"正在访问: {url}")
self.root.update()
self.tab.get(url)
self.status_var.set("等待页面加载...")
self.root.update()
self.tab.wait.load_start()
self.tab.wait.doc_loaded()
self.page.get(url)
self._wait_page_loaded()
self.status_var.set(f"已打开: {url}")
self.save_button.config(state=tk.NORMAL)
@@ -113,16 +113,15 @@ class CookieExtractorApp:
def save_cookies(self):
"""保存当前的cookie信息,并强制关闭浏览器"""
if not self.browser or not self.tab:
if not self.page:
messagebox.showerror("错误", "请先打开网站")
return
try:
self.status_var.set("正在提取cookies...")
self.status_var.set("正在保存Cookies...")
self.root.update()
cookies = self.browser.cookies()
logger.info(f"成功提取 {len(cookies)} 个 cookies")
cookies = self.page.cookies()
data = {
"cookies": cookies,
@@ -133,13 +132,12 @@ class CookieExtractorApp:
with open(output_file, 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2, ensure_ascii=False)
self.status_var.set("正在关闭浏览器...")
self.root.update()
self.page.quit()
self.page = None
self.save_button.config(state=tk.DISABLED)
self._cleanup_browser()
self.status_var.set(f"数据已保存到: {output_file}")
messagebox.showinfo("成功", f"已成功保存 {len(cookies)} 个cookie到: {output_file}\n\n浏览器已关闭")
self.status_var.set(f"Cookies已保存到 {output_file}")
messagebox.showinfo('成功', 'Cookies保存成功,浏览器已关闭')
except Exception as e:
logger.error(f"保存cookies失败: {e}")
@@ -148,7 +146,8 @@ class CookieExtractorApp:
self._cleanup_browser()
def import_cookies(self):
"""打开浏览器,导入cookie信息,然后打开指定网站"""
"""打开浏览器,导入cookie和localStorage信息,然后打开指定网站"""
import time
try:
output_file = "cookie_data.json"
if not os.path.exists(output_file):
@@ -163,6 +162,7 @@ class CookieExtractorApp:
url = data.get("url", "")
cookies = data.get("cookies", [])
local_storage = data.get("local_storage", {})
if not url:
url = self.url_var.get().strip()
@@ -179,17 +179,16 @@ class CookieExtractorApp:
self.status_var.set("正在打开浏览器...")
self.root.update()
co = ChromiumOptions()
co.set_browser_path(r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe")
co.set_argument('--no-sandbox')
co.set_argument('--disable-dev-shm-usage')
self._init_browser()
self.browser = Chromium(co)
self.tab = self.browser.new_tab()
self.status_var.set("正在准备环境...")
self.status_var.set("等待浏览器就绪...")
self.root.update()
self.tab.get("about:blank")
self.page.get("about:blank")
time.sleep(1)
from urllib.parse import urlparse
parsed = urlparse(url)
domain = parsed.netloc
if cookies:
self.status_var.set("正在导入cookies...")
@@ -201,25 +200,51 @@ class CookieExtractorApp:
if not cookie.get('name') or not cookie.get('value'):
continue
cookie.setdefault('path', '/')
cookie.setdefault('httpOnly', False)
cookie.setdefault('secure', False)
cookie_dict = {
'name': cookie.get('name'),
'value': cookie.get('value'),
'domain': cookie.get('domain', domain),
'path': cookie.get('path', '/')
}
self.browser.set.cookies([cookie])
self.page.set.cookies(cookie_dict)
success_count += 1
except Exception as e:
logger.error(f"导入cookie失败: {cookie.get('name')} - {e}")
logger.info(f"成功导入 {success_count} 个 cookies")
if local_storage:
self.status_var.set("正在导入localStorage...")
self.root.update()
ls_count = 0
for ls_domain, items in local_storage.items():
try:
for key, value in items.items():
try:
self.page.set.local_storage(key, value)
ls_count += 1
except Exception as e:
logger.error(f"导入localStorage失败: {key} - {e}")
except Exception as e:
logger.error(f"导入localStorage域失败: {ls_domain} - {e}")
logger.info(f"成功导入 {ls_count} 个 localStorage")
self.status_var.set(f"正在访问: {url}")
self.root.update()
self.tab.get(url)
self.page.get(url)
self._wait_page_loaded()
self.status_var.set("等待页面加载...")
self.status_var.set("刷新页面验证登录状态...")
self.root.update()
self.tab.wait.load_start()
self.tab.wait.doc_loaded()
for i in range(3):
self.page.refresh()
self._wait_page_loaded()
time.sleep(1)
self.status_var.set(f"刷新第 {i+1} 次...")
self.root.update()
self.url_var.set(url)
self.status_var.set(f"已导入并打开: {url}")
@@ -237,14 +262,13 @@ class CookieExtractorApp:
self.root.destroy()
def _cleanup_browser(self):
if self.browser:
if self.page:
try:
self.browser.quit()
self.page.quit()
except Exception as e:
logger.error(f"关闭浏览器失败: {e}")
finally:
self.browser = None
self.tab = None
self.page = None
self.save_button.config(state=tk.DISABLED)
self.status_var.set("就绪")
+300 -52
View File
@@ -1,13 +1,166 @@
"""Cookie 监控主程序"""
import json
from datetime import datetime
import time
import os
from datetime import datetime, time as dt_time
from urllib.parse import urlparse
from cookie_cloud import CookieCloud
from notifier import IYUUNotifier, FailureTracker
from browser_login import BrowserLogin, CookieManager
from notifier import IYUUNotifier
from applogger import setup_logging, get_logger
from DrissionPage import ChromiumPage, ChromiumOptions
logger = get_logger(__name__)
NOTIFY_TIMES = [6, 22]
STATE_FILE = "notify_state.json"
def load_notify_state() -> dict:
if os.path.exists(STATE_FILE):
try:
with open(STATE_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
except:
pass
return {}
def save_notify_state(state: dict):
try:
with open(STATE_FILE, 'w', encoding='utf-8') as f:
json.dump(state, f, indent=2, ensure_ascii=False)
except Exception as e:
logger.error(f"保存通知状态失败: {e}")
def should_send_notification(user_name: str) -> bool:
return False
def mark_notification_sent(user_name: str):
now = datetime.now()
state = load_notify_state()
if user_name not in state:
state[user_name] = {}
state[user_name]['date'] = now.strftime('%Y-%m-%d')
state[user_name]['hour'] = now.hour
save_notify_state(state)
class BrowserManager:
"""浏览器管理器"""
def __init__(self, browser_type: str = 'edge', headless: bool = False):
self.browser_type = browser_type
self.headless = headless
self.page = None
def init_browser(self):
"""初始化浏览器"""
co = ChromiumOptions()
if self.browser_type == 'edge':
co.set_browser_path(r"C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe")
co.set_argument('--no-sandbox')
co.set_argument('--disable-dev-shm-usage')
if self.headless:
co.set_argument('--headless')
self.page = ChromiumPage(addr_or_opts=co)
def wait_page_loaded(self, timeout=30):
"""等待页面加载完成"""
if self.page:
self.page.wait.doc_loaded(timeout=timeout)
self.page.ele('tag:body', timeout=timeout)
def ensure_connection(self):
"""确保浏览器连接正常"""
try:
if self.page:
self.page.get("about:blank")
return True
except:
pass
try:
self.init_browser()
self.page.get("about:blank")
return True
except Exception as e:
logger.error(f"无法建立浏览器连接: {e}")
return False
def import_cookies(self, cookies: list, domain: str):
"""导入cookies"""
if not self.ensure_connection():
return 0
success_count = 0
for cookie in cookies:
try:
if not cookie.get('name') or not cookie.get('value'):
continue
cookie_dict = {
'name': cookie.get('name'),
'value': cookie.get('value'),
'domain': cookie.get('domain', domain),
'path': cookie.get('path', '/')
}
self.page.set.cookies(cookie_dict)
success_count += 1
except Exception as e:
pass
return success_count
def verify_login(self, url: str, check_selector: str = "", success_text: str = "") -> bool:
"""验证登录状态"""
if not self.ensure_connection():
return False
try:
self.page.get(url)
self.wait_page_loaded()
for i in range(3):
time.sleep(1)
self.page.refresh()
self.wait_page_loaded()
if not check_selector:
return True
try:
element = self.page.ele(check_selector, timeout=10)
if element:
element_text = element.text or ""
if not success_text:
return True
if success_text in element_text:
return True
except:
pass
return False
except Exception as e:
logger.error(f"验证登录状态失败: {e}")
return False
def close(self):
"""关闭浏览器"""
if self.page:
try:
self.page.quit()
except:
pass
finally:
self.page = None
class CookieMonitor:
"""Cookie 监控器"""
@@ -15,8 +168,7 @@ class CookieMonitor:
def __init__(self, config_file: str = "config.json"):
self.config_file = config_file
self.config = self._load_config()
self.cookie_manager = CookieManager("cookies.json")
self.failure_tracker = FailureTracker("state.json")
self.all_results = []
def _load_config(self) -> dict:
try:
@@ -26,14 +178,8 @@ class CookieMonitor:
logger.error(f"加载配置文件失败: {e}")
return {}
def _get_domain_from_url(self, url: str) -> str:
from urllib.parse import urlparse
return urlparse(url).netloc
def process_user(self, user_config: dict):
"""
处理单个用户:一次性导入所有cookies,然后验证各网站登录状态
"""
"""处理单个用户:依次验证各网站登录状态"""
user_name = user_config.get('name', '未知用户')
logger.info(f"{'='*20} 开始处理用户: {user_name} {'='*20}")
@@ -46,10 +192,6 @@ class CookieMonitor:
logger.info(f"用户 {user_name} 没有配置网站,跳过")
return
iyuu_token = notification_config.get('iyuu_token', '')
max_fail_count = notification_config.get('max_fail_count', 3)
notifier = IYUUNotifier(iyuu_token) if iyuu_token else None
cookie_cloud = CookieCloud(
api_url=cookie_cloud_config.get('api_url', ''),
uuid=cookie_cloud_config.get('uuid', ''),
@@ -62,46 +204,44 @@ class CookieMonitor:
total_cookies = sum(len(c) for c in all_cookies.values())
logger.info(f"获取到 {total_domains} 个域名,共 {total_cookies} 个 cookie")
browser = BrowserLogin(
browser = BrowserManager(
browser_type=browser_config.get('type', 'edge'),
headless=browser_config.get('headless', False)
)
results = []
user_results = {
'user_name': user_name,
'notification_config': notification_config,
'websites': [],
'success_count': 0,
'fail_count': 0
}
try:
logger.info(f"步骤2: 一次性导入所有 cookies 到浏览器")
all_cookies_list = []
for cookies in all_cookies.values():
all_cookies_list.extend(cookies)
import_success = browser.import_all_cookies(all_cookies_list)
if import_success:
logger.info(f"成功导入 {len(all_cookies_list)} 个 cookie")
else:
logger.warning(f"部分 cookie 导入失败")
logger.info(f"步骤2: 初始化浏览器")
browser.init_browser()
logger.info(f"步骤3: 依次验证各网站登录状态")
for website in websites:
result = self.process_website(
user_name=user_name,
website_config=website,
browser=browser
browser=browser,
all_cookies=all_cookies
)
results.append(result)
if not result['success']:
self.failure_tracker.check_and_notify(
user_name=user_name,
website_name=result['name'],
max_fail_count=max_fail_count,
notifier=notifier,
error_msg=result['error']
)
user_results['websites'].append(result)
if result['success']:
user_results['success_count'] += 1
else:
self.failure_tracker.reset_fail_count(user_name, result['name'])
user_results['fail_count'] += 1
self._print_summary(user_name, results)
self.all_results.append(user_results)
self._print_summary(user_name, user_results['websites'])
logger.info(f"\n检查用户 {user_name} 是否需要发送通知")
if should_send_notification(user_name):
self._send_user_notification(user_results)
finally:
browser.close()
@@ -110,14 +250,10 @@ class CookieMonitor:
self,
user_name: str,
website_config: dict,
browser: BrowserLogin
browser: BrowserManager,
all_cookies: dict
) -> dict:
"""
验证网站登录状态(cookies已预先导入)
Returns:
dict: {'name': 网站名, 'url': URL, 'success': 是否成功, 'error': 错误信息}
"""
"""验证网站登录状态"""
website_name = website_config.get('name', '未知网站')
url = website_config.get('url', '')
check_selector = website_config.get('login_check_selector', '')
@@ -139,6 +275,22 @@ class CookieMonitor:
return result
try:
parsed = urlparse(url)
domain = parsed.netloc
domain_cookies = []
for cookie_domain, cookies in all_cookies.items():
clean_domain = cookie_domain.lstrip('.')
if domain in clean_domain or clean_domain in domain:
domain_cookies.extend(cookies)
if domain_cookies:
logger.info(f" 找到 {len(domain_cookies)} 个相关 cookie")
success_count = browser.import_cookies(domain_cookies, domain)
logger.info(f" 成功导入 {success_count} 个 cookie")
else:
logger.warning(f" 未找到相关 cookie")
login_success = browser.verify_login(
url=url,
check_selector=check_selector,
@@ -149,10 +301,7 @@ class CookieMonitor:
result['success'] = True
logger.info(f" ✓ 登录验证成功")
new_cookies = browser.refresh_and_save_cookies(url)
if new_cookies:
self.cookie_manager.save_cookies(user_name, website_name, new_cookies)
logger.info(f" 已保存 {len(new_cookies)} 个新 cookie")
self._save_cookies_to_file(domain_cookies, url, website_name, user_name)
else:
result['error'] = '登录状态验证失败(cookies可能已过期或选择器配置错误)'
logger.warning(f"{result['error']}")
@@ -163,6 +312,49 @@ class CookieMonitor:
return result
def _save_cookies_to_file(self, cookies: list, url: str, website_name: str, user_name: str):
"""保存 cookies 到文件(按用户和域名区分)"""
try:
parsed = urlparse(url)
domain = parsed.netloc
cookie_entry = {
"cookies": [],
"url": url,
"website_name": website_name
}
for cookie in cookies:
if cookie.get('name') and cookie.get('value'):
cookie_entry["cookies"].append({
"name": cookie.get('name'),
"value": cookie.get('value'),
"domain": cookie.get('domain', domain)
})
filename = "cookie_data.json"
cookie_data = {}
if os.path.exists(filename):
with open(filename, 'r', encoding='utf-8') as f:
try:
cookie_data = json.load(f)
except:
cookie_data = {}
if user_name not in cookie_data:
cookie_data[user_name] = {}
cookie_data[user_name][domain] = cookie_entry
with open(filename, 'w', encoding='utf-8') as f:
json.dump(cookie_data, f, indent=2, ensure_ascii=False)
logger.info(f" 已保存 {len(cookie_entry['cookies'])} 个 cookie 到 {filename} ({user_name} -> {domain})")
except Exception as e:
logger.error(f" 保存 cookie 失败: {e}")
def _print_summary(self, user_name: str, results: list):
"""打印处理结果汇总"""
success_count = sum(1 for r in results if r['success'])
@@ -178,6 +370,59 @@ class CookieMonitor:
if not r['success']:
logger.info(f" - {r['name']}: {r['error']}")
def _send_user_notification(self, user_result: dict):
"""发送单个用户的通知"""
user_name = user_result['user_name']
notification_config = user_result.get('notification_config', {})
iyuu_token = notification_config.get('iyuu_token', '')
if not iyuu_token:
return
notifier = IYUUNotifier(iyuu_token)
success_count = user_result['success_count']
fail_count = user_result['fail_count']
title = f"【Cookie监控】{user_name} - 成功{success_count} 失败{fail_count}"
content_lines = [
f"**{user_name} Cookie监控报告**",
f"",
f"监控时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}",
f"成功: {success_count} 个网站",
f"失败: {fail_count} 个网站",
f"",
]
for website in user_result['websites']:
status = "✓ 成功" if website['success'] else f"✗ 失败: {website['error']}"
content_lines.append(f" {website['name']}: {status}")
content = "\n".join(content_lines)
print(f"\n{'='*60}")
print(f"发送用户通知: {user_name}")
print(f"{'='*60}")
print(f"\n发送内容:")
print(f"Title: {title}")
print(f"Content:\n{content}")
print(f"\n发送方式: 爱语飞飞 API (GET 请求)")
print(f"API URL: https://iyuu.cn/{iyuu_token}.send")
print(f"参数: text={title}, desp={content[:50]}...")
print(f"\n正在发送...")
logger.info(f"\n发送用户通知: {user_name}")
logger.info(f"Title: {title}")
if notifier.send(title, content):
print(f"\n发送结果: ✓ 成功")
logger.info(f"✓ 用户 {user_name} 通知发送成功")
mark_notification_sent(user_name)
else:
print(f"\n发送结果: ✗ 失败")
logger.error(f"✗ 用户 {user_name} 通知发送失败")
def run(self):
"""运行监控"""
logger.info("="*50)
@@ -197,6 +442,9 @@ class CookieMonitor:
except Exception as e:
logger.error(f"处理用户失败: {e}")
for user_result in self.all_results:
pass
logger.info("\n" + "="*50)
logger.info("Cookie 监控结束")
logger.info("="*50)
+15 -150
View File
@@ -1,6 +1,5 @@
"""消息通知模块 - 通过爱语飞飞发送通知"""
import requests
from typing import Optional
from applogger import get_logger
logger = get_logger(__name__)
@@ -10,26 +9,17 @@ class IYUUNotifier:
"""爱语飞飞通知客户端"""
def __init__(self, token: str):
"""
初始化爱语飞飞通知客户端
Args:
token: 爱语飞飞令牌
"""
self.token = token
self.api_url = f"https://iyuu.cn/{token}.send"
def send(self, title: str, content: str) -> bool:
"""
发送通知
Args:
title: 通知标题
content: 通知内容
Returns:
是否发送成功
"""
print(f"\n{'='*50}")
print(f"[测试] 准备发送通知")
print(f"[测试] API URL: {self.api_url}")
print(f"[测试] Title: {title}")
print(f"[测试] Content: {content[:100]}...")
print(f"{'='*50}\n")
try:
response = requests.get(
self.api_url,
@@ -39,12 +29,20 @@ class IYUUNotifier:
},
timeout=10
)
print(f"[测试] 响应状态码: {response.status_code}")
print(f"[测试] 响应内容: {response.text}")
response.raise_for_status()
result = response.json()
print(f"[测试] 解析结果: {result}")
if result.get('errcode') == 0:
print(f"[测试] ✓ 发送成功!")
return True
else:
print(f"[测试] ✗ 发送失败: {result.get('errmsg')}")
logger.error(f"发送通知失败: {result.get('errmsg')}")
return False
@@ -54,136 +52,3 @@ class IYUUNotifier:
except Exception as e:
logger.error(f"发送通知异常: {e}")
return False
class FailureTracker:
"""失败次数追踪器"""
def __init__(self, state_file: str):
"""
初始化失败次数追踪器
Args:
state_file: 状态文件路径
"""
self.state_file = state_file
self.state = self._load_state()
def _load_state(self) -> dict:
"""加载状态文件"""
import json
import os
if os.path.exists(self.state_file):
try:
with open(self.state_file, 'r', encoding='utf-8') as f:
return json.load(f)
except Exception as e:
logger.error(f"加载状态文件失败: {e}")
return {}
def _save_state(self):
"""保存状态文件"""
import json
try:
with open(self.state_file, 'w', encoding='utf-8') as f:
json.dump(self.state, f, indent=2, ensure_ascii=False)
except Exception as e:
logger.error(f"保存状态文件失败: {e}")
def get_fail_count(self, user_name: str, website_name: str) -> int:
"""
获取指定网站的失败次数
Args:
user_name: 用户名
website_name: 网站名
Returns:
失败次数
"""
key = f"{user_name}_{website_name}"
return self.state.get(key, 0)
def increment_fail_count(self, user_name: str, website_name: str) -> int:
"""
增加失败次数
Args:
user_name: 用户名
website_name: 网站名
Returns:
增加后的失败次数
"""
key = f"{user_name}_{website_name}"
self.state[key] = self.state.get(key, 0) + 1
self._save_state()
return self.state[key]
def reset_fail_count(self, user_name: str, website_name: str):
"""
重置失败次数
Args:
user_name: 用户名
website_name: 网站名
"""
key = f"{user_name}_{website_name}"
self.state[key] = 0
self._save_state()
def check_and_notify(
self,
user_name: str,
website_name: str,
max_fail_count: int,
notifier: Optional[IYUUNotifier],
error_msg: str
) -> bool:
"""
检查失败次数并在达到阈值时发送通知
Args:
user_name: 用户名
website_name: 网站名
max_fail_count: 最大失败次数
notifier: 爱语飞飞通知客户端
error_msg: 错误信息
Returns:
是否应该停止重试
"""
fail_count = self.increment_fail_count(user_name, website_name)
if fail_count >= max_fail_count:
# 发送通知
if notifier:
title = f"【Cookie监控】{user_name} - {website_name} 登录失败"
content = f"网站: {website_name}\n用户: {user_name}\n连续失败次数: {fail_count}\n错误信息: {error_msg}"
if notifier.send(title, content):
logger.info(f"已发送失败通知: {user_name} - {website_name}")
else:
logger.error(f"发送失败通知失败: {user_name} - {website_name}")
# 重置失败计数
self.reset_fail_count(user_name, website_name)
return True
return False
if __name__ == "__main__":
# 测试代码
tracker = FailureTracker("state.json")
# 增加失败次数
count = tracker.increment_fail_count("测试用户", "测试网站")
logger.info(f"当前失败次数: {count}")
# 检查并发送通知(需要真实的爱语飞飞令牌)
# notifier = IYUUNotifier("您的爱语飞飞令牌")
# tracker.check_and_notify("测试用户", "测试网站", 3, notifier, "测试错误信息")
+10
View File
@@ -0,0 +1,10 @@
{
"用户A": {
"date": "2026-03-04",
"hour": 22
},
"用户B": {
"date": "2026-03-04",
"hour": 22
}
}
+62
View File
@@ -0,0 +1,62 @@
"""直接发送测试消息给两个用户"""
from notifier import IYUUNotifier
from datetime import datetime
iyuu_token = "IYUU37629Tc1d371c7ce99a49ff9778e196286b7e4592be191"
notifier = IYUUNotifier(iyuu_token)
print("="*60)
print("发送测试消息给用户A")
print("="*60)
title_a = "【测试】用户A - Cookie监控测试"
content_a = f"""**用户A Cookie监控测试报告**
测试时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
测试类型: 直接发送测试消息
这是一条测试消息,用于验证爱语飞飞通知功能是否正常工作。
如果您收到此消息,说明通知功能正常。"""
print(f"\n发送内容:")
print(f"Title: {title_a}")
print(f"Content:\n{content_a}")
print(f"\n发送方式: 爱语飞飞 API (GET 请求)")
print(f"API URL: https://iyuu.cn/{iyuu_token}.send")
print(f"参数: text={title_a}, desp={content_a}")
print("\n正在发送...")
result_a = notifier.send(title_a, content_a)
print(f"\n发送结果: {'✓ 成功' if result_a else '✗ 失败'}")
print("\n" + "="*60)
print("发送测试消息给用户B")
print("="*60)
title_b = "【测试】用户B - Cookie监控测试"
content_b = f"""**用户B Cookie监控测试报告**
测试时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
测试类型: 直接发送测试消息
这是一条测试消息,用于验证爱语飞飞通知功能是否正常工作。
如果您收到此消息,说明通知功能正常。"""
print(f"\n发送内容:")
print(f"Title: {title_b}")
print(f"Content:\n{content_b}")
print(f"\n发送方式: 爱语飞飞 API (GET 请求)")
print(f"API URL: https://iyuu.cn/{iyuu_token}.send")
print(f"参数: text={title_b}, desp={content_b}")
print("\n正在发送...")
result_b = notifier.send(title_b, content_b)
print(f"\n发送结果: {'✓ 成功' if result_b else '✗ 失败'}")
print("\n" + "="*60)
print("测试完成")
print("="*60)
print(f"用户A: {'✓ 发送成功' if result_a else '✗ 发送失败'}")
print(f"用户B: {'✓ 发送成功' if result_b else '✗ 发送失败'}")
+42
View File
@@ -0,0 +1,42 @@
"""测试爱语飞飞通知"""
import requests
iyuu_token = "IYUU37629Tc1d371c7ce99a49ff9778e196286b7e4592be191"
api_url = f"https://iyuu.cn/{iyuu_token}.send"
title = "【测试】Cookie监控通知测试"
content = """这是一条测试消息。
测试时间: 2026-03-04
测试内容: 验证爱语飞飞通知是否正常工作
如果您收到此消息,说明通知功能正常。"""
print(f"API URL: {api_url}")
print(f"Title: {title}")
print(f"Content: {content}")
print("\n正在发送请求...")
try:
response = requests.get(
api_url,
params={
'text': title,
'desp': content
},
timeout=10
)
print(f"状态码: {response.status_code}")
print(f"响应内容: {response.text}")
result = response.json()
print(f"\n解析结果: {result}")
if result.get('errcode') == 0:
print("\n✓ 发送成功!")
else:
print(f"\n✗ 发送失败: {result.get('errmsg')}")
except Exception as e:
print(f"\n✗ 请求异常: {e}")