diff --git a/config.json b/config.json index eb08b57..64f9a7b 100644 --- a/config.json +++ b/config.json @@ -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": "彭峰" } ] } ] -} \ No newline at end of file +} diff --git a/cookie_data.json b/cookie_data.json index 5940782..d2b7e01 100644 --- a/cookie_data.json +++ b/cookie_data.json @@ -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" + } + } } \ No newline at end of file diff --git a/cookie_extractor.py b/cookie_extractor.py index 046ca20..e26619b 100644 --- a/cookie_extractor.py +++ b/cookie_extractor.py @@ -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("就绪") diff --git a/monitor.py b/monitor.py index 968252d..a577c57 100644 --- a/monitor.py +++ b/monitor.py @@ -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) diff --git a/notifier.py b/notifier.py index 6754f36..f428885 100644 --- a/notifier.py +++ b/notifier.py @@ -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, "测试错误信息") \ No newline at end of file diff --git a/notify_state.json b/notify_state.json new file mode 100644 index 0000000..b86f152 --- /dev/null +++ b/notify_state.json @@ -0,0 +1,10 @@ +{ + "用户A": { + "date": "2026-03-04", + "hour": 22 + }, + "用户B": { + "date": "2026-03-04", + "hour": 22 + } +} \ No newline at end of file diff --git a/send_test.py b/send_test.py new file mode 100644 index 0000000..465f265 --- /dev/null +++ b/send_test.py @@ -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 '✗ 发送失败'}") diff --git a/test_notify.py b/test_notify.py new file mode 100644 index 0000000..2a16f0c --- /dev/null +++ b/test_notify.py @@ -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}")