diff --git a/.gitignore b/.gitignore index 9a274fb..9b823ff 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,9 @@ ocr_progress.json ocr_result.txt +# 数据库文件 - 不上传 +*.db + # 截图和图片数据 - 不上传 *.png scroll/ diff --git a/auto_send_blessing.py b/auto_send_blessing.py new file mode 100644 index 0000000..d02876e --- /dev/null +++ b/auto_send_blessing.py @@ -0,0 +1,270 @@ +# -*- coding: utf-8 -*- +""" +微信自动发送祝福语脚本 +功能: +1. 从API获取已选联系人的搜索姓名和祝福语 +2. 自动搜索联系人、发送祝福语 +3. 发送完成后更新前端状态 +4. 按 F9 停止运行 +""" +import os +import sys +import time +import json +import threading +import requests +import pyautogui +import pyperclip +from ctypes import windll, c_long + +# 坐标配置 +SEARCH_BOX_X, SEARCH_BOX_Y = 1933, 39 +SEARCH_RESULT_X, SEARCH_RESULT_Y = 2009, 100 +CHAT_INPUT_X, CHAT_INPUT_Y = 2160, 1173 + +# API配置 +API_BASE = 'http://localhost:5000/api' + +# 全局停止标志 +stop_flag = False +stop_lock = threading.Lock() + +def get_selected_contacts(): + """获取已选择的联系人""" + try: + response = requests.get(f'{API_BASE}/contacts?page=1&page_size=1000', timeout=10) + data = response.json() + selected = [c for c in data.get('contacts', []) if c.get('selected')] + return selected + except Exception as e: + print(f"获取联系人失败: {e}") + return [] + +def update_contact_sent(contact_id): + """更新联系人发送状态""" + try: + requests.put( + f'{API_BASE}/contacts/{contact_id}', + json={'selected': False}, + timeout=10 + ) + return True + except Exception as e: + print(f"更新状态失败: {e}") + return False + +def check_stop(): + """检查是否需要停止""" + with stop_lock: + return stop_flag + +def set_stop(): + """设置停止标志""" + with stop_lock: + global stop_flag + stop_flag = True + print("\n收到停止信号,正在停止...") + +def get_current_key(): + """获取当前按下的键""" + try: + # 使用windll获取按键状态 + # F9 = 0x78 (120) + if windll.user32.GetAsyncKeyState(0x78) & 0x8000: + return 'F9' + except: + pass + return None + +def wait_for_key_press(): + """监听F9按键""" + global stop_flag + import keyboard + try: + keyboard.add_hotkey('F9', set_stop) + print("提示: 按 F9 可随时停止运行") + except ImportError: + print("警告: keyboard库未安装,无法使用F9停止功能") + print("请运行: pip install keyboard") + +def send_blessing_to_contact(search_name, blessing): + """发送祝福语给单个联系人""" + if check_stop(): + return False, "已停止" + + try: + # 1. 点击搜索框 + print(f" → 点击搜索框 ({SEARCH_BOX_X}, {SEARCH_BOX_Y})") + pyautogui.moveTo(SEARCH_BOX_X, SEARCH_BOX_Y, duration=0.3) + pyautogui.click() + time.sleep(0.5) + + if check_stop(): + return False, "已停止" + + # 2. 清空搜索框并输入名字 + print(f" → 输入名字: {search_name}") + pyautogui.hotkey('ctrl', 'a') + time.sleep(0.2) + pyperclip.copy(search_name) + pyautogui.hotkey('ctrl', 'v') + time.sleep(1) + + if check_stop(): + return False, "已停止" + + # 3. 点击搜索结果 + print(f" → 点击搜索结果 ({SEARCH_RESULT_X}, {SEARCH_RESULT_Y})") + pyautogui.moveTo(SEARCH_RESULT_X, SEARCH_RESULT_Y, duration=0.3) + pyautogui.click() + time.sleep(1) + + if check_stop(): + return False, "已停止" + + # 4. 点击聊天输入框 + print(f" → 点击聊天输入框 ({CHAT_INPUT_X}, {CHAT_INPUT_Y})") + pyautogui.moveTo(CHAT_INPUT_X, CHAT_INPUT_Y, duration=0.3) + pyautogui.click() + time.sleep(0.5) + + if check_stop(): + return False, "已停止" + + # 5. 输入祝福语 + print(f" → 输入祝福语...") + pyperclip.copy(blessing) + pyautogui.hotkey('ctrl', 'v') + time.sleep(0.5) + + if check_stop(): + return False, "已停止" + + # 6. 发送 + print(f" → 发送消息") + pyautogui.press('enter') + time.sleep(0.5) + + return True, "发送成功" + + except Exception as e: + return False, f"发送失败: {str(e)}" + +def main(): + global stop_flag + + print("=" * 60) + print("微信自动发送祝福语脚本") + print("=" * 60) + print() + print("配置信息:") + print(f" 搜索框坐标: ({SEARCH_BOX_X}, {SEARCH_BOX_Y})") + print(f" 搜索结果坐标: ({SEARCH_RESULT_X}, {SEARCH_RESULT_Y})") + print(f" 聊天输入框坐标: ({CHAT_INPUT_X}, {CHAT_INPUT_Y})") + print() + + # 尝试导入keyboard库用于F9停止 + try: + import keyboard + keyboard.add_hotkey('F9', set_stop) + print("✓ F9 停止功能已启用") + except ImportError: + print("✗ keyboard库未安装,F9停止功能不可用") + print(" 请运行: pip install keyboard") + except Exception as e: + print(f"✗ keyboard库加载失败: {e}") + + print() + + # 获取已选联系人 + print("正在获取已选联系人...") + contacts = get_selected_contacts() + + if not contacts: + print("没有找到已选择的联系人!") + print("请在前端页面勾选要发送的联系人,然后重试。") + return + + print(f"找到 {len(contacts)} 个已选联系人:") + print("-" * 60) + for i, c in enumerate(contacts, 1): + print(f" {i}. {c.get('name', '未知')} (搜索名: {c.get('search_name', '')})") + print("-" * 60) + print() + + # 确认开始 + print("准备开始发送祝福语...") + print("请在 5 秒内切换到微信窗口!") + print() + + # 等待用户准备 + for i in range(5, 0, -1): + if check_stop(): + print("已取消运行") + return + print(f" {i}...") + time.sleep(1) + + print() + print("开始发送祝福语...") + print("=" * 60) + + success_count = 0 + fail_count = 0 + skip_count = 0 + + for i, contact in enumerate(contacts, 1): + if check_stop(): + print("\n用户停止运行") + break + + name = contact.get('name', '未知') + search_name = contact.get('search_name') or contact.get('name', '') + blessing = contact.get('blessing', '') + contact_id = contact.get('id') + + print(f"\n[{i}/{len(contacts)}] 发送给: {name}") + print(f" 搜索名: {search_name}") + if len(blessing) > 50: + print(f" 祝福语: {blessing[:50]}...") + else: + print(f" 祝福语: {blessing}") + + # 检查祝福语是否为空 + if not blessing: + print(f" ⚠ 跳过: 祝福语为空") + skip_count += 1 + continue + + # 发送祝福语 + success, message = send_blessing_to_contact(search_name, blessing) + + if success: + print(f" ✓ 发送成功") + success_count += 1 + + # 更新前端状态 + if update_contact_sent(contact_id): + print(f" ✓ 已更新前端状态") + else: + print(f" ✗ 更新前端状态失败") + + # 发送间隔 + time.sleep(1) + else: + print(f" ✗ {message}") + fail_count += 1 + if not check_stop(): + time.sleep(2) + + print() + print("=" * 60) + print("发送完成!") + print("=" * 60) + print(f" 成功: {success_count}") + print(f" 失败: {fail_count}") + print(f" 跳过: {skip_count}") + print() + +if __name__ == '__main__': + main() diff --git a/get_mouse_coords.py b/get_mouse_coords.py new file mode 100644 index 0000000..32d58a7 --- /dev/null +++ b/get_mouse_coords.py @@ -0,0 +1,105 @@ +# -*- coding: utf-8 -*- +""" +获取鼠标点击位置的坐标 +使用方法:运行脚本后,点击需要获取坐标的位置,程序会显示坐标 +按 ESC 键退出 +""" +import os +import sys +import time +import ctypes +from ctypes import wintypes + +# 设置控制台编码 +if sys.platform == 'win32': + os.system('chcp 65001 > nul') + +# 获取鼠标位置的函数 +def get_mouse_position(): + class POINT(ctypes.Structure): + _fields_ = [("x", ctypes.c_long), ("y", ctypes.c_long)] + + pt = POINT() + ctypes.windll.user32.GetCursorPos(ctypes.byref(pt)) + return pt.x, pt.y + +# 监听键盘事件 +def is_esc_pressed(): + if ctypes.windll.user32.GetAsyncKeyState(0x1B) & 0x8000: + return True + return False + +def main(): + print("=" * 60) + print("获取鼠标点击位置的坐标") + print("=" * 60) + print() + print("请依次点击以下位置(按 ESC 键退出):") + print("1. 搜索框位置(点击搜索框内)") + print("2. 搜索结果区域(点击第一个联系人)") + print("3. 聊天输入框位置") + print() + print("坐标记录:") + print("-" * 60) + + positions = [] + labels = ["搜索框", "搜索结果区域", "聊天输入框"] + + for i, label in enumerate(labels): + print(f"\n请点击【{label}】...") + time.sleep(0.5) + + # 等待用户点击 + clicked = False + while not clicked: + if is_esc_pressed(): + print("\n\n用户退出") + return + + # 检测鼠标左键按下 + if ctypes.windll.user32.GetAsyncKeyState(0x01) & 0x8000: + time.sleep(0.2) # 防止抖动 + x, y = get_mouse_position() + positions.append((label, x, y)) + print(f" {label}: ({x}, {y})") + clicked = True + time.sleep(0.5) # 防止重复触发 + + time.sleep(0.05) + + print("\n" + "=" * 60) + print("所有坐标获取完成!") + print("=" * 60) + print() + print("坐标汇总:") + print("-" * 60) + for label, x, y in positions: + print(f"{label}: ({x}, {y})") + print() + + # 生成代码片段 + print("=" * 60) + print("可用于代码的坐标变量:") + print("=" * 60) + print() + print("```python") + for label, x, y in positions: + # 将标签转换为变量名 + var_name = label.replace("搜索框", "search_box").replace("搜索结果区域", "search_result").replace("聊天输入框", "chat_input") + print(f"{var_name}_x = {x}") + print(f"{var_name}_y = {y}") + print("```") + + # 保存到文件 + output_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'wechat_coords.txt') + with open(output_file, 'w', encoding='utf-8') as f: + f.write("微信关键坐标位置\n") + f.write("=" * 50 + "\n\n") + for label, x, y in positions: + f.write(f"{label}: ({x}, {y})\n") + f.write("\n获取时间: " + time.strftime("%Y-%m-%d %H:%M:%S") + "\n") + + print(f"\n坐标已保存到: {output_file}") + +if __name__ == '__main__': + main() diff --git a/print_wechat_contacts.py b/print_wechat_contacts.py index 11fce1e..f44f3f1 100644 --- a/print_wechat_contacts.py +++ b/print_wechat_contacts.py @@ -3,6 +3,7 @@ 打印PC微信通讯录的所有子控件 需要以管理员权限运行Python """ +import os import uiautomation as auto import time import ctypes @@ -244,7 +245,7 @@ def main(): print() # 输出文件 - output_file = r"D:\夏骥\微信研究\wechat_controls_output.txt" + output_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'wechat_controls_output.txt') # 首先尝试标准方法 print("=" * 80) @@ -268,7 +269,7 @@ def main(): print("方式2: 使用RawViewWalker遍历控件 (包括原始控件)...") print("=" * 80) - output_file_raw = r"D:\夏骥\微信研究\wechat_controls_raw.txt" + output_file_raw = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'wechat_controls_raw.txt') try: # 加载UIAutomation类型库 @@ -317,7 +318,7 @@ def main(): hwnd = wechat_window.NativeWindowHandle - output_file_win32 = r"D:\夏骥\微信研究\wechat_controls_win32.txt" + output_file_win32 = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'wechat_controls_win32.txt') with open(output_file_win32, 'w', encoding='utf-8') as f: f.write("=" * 80 + "\n") diff --git a/send_blessing.py b/send_blessing.py new file mode 100644 index 0000000..649821a --- /dev/null +++ b/send_blessing.py @@ -0,0 +1,84 @@ +# -*- coding: utf-8 -*- +""" +发送祝福语自动化脚本 +使用pyautogui库执行鼠标和键盘操作 +""" +import pyautogui +import pyperclip +import time +import os + +# 加载坐标 +coords = {} +coords_file = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'wechat_coords.txt') +if os.path.exists(coords_file): + with open(coords_file, 'r', encoding='utf-8') as f: + for line in f: + if ':' in line: + key, value = line.strip().split(': ') + x, y = map(int, value.strip('()').split(',')) + coords[key] = (x, y) + +search_box_x, search_box_y = coords.get('搜索框', (1933, 39)) +search_result_x, search_result_y = coords.get('搜索结果区域', (2009, 100)) +chat_input_x, chat_input_y = coords.get('聊天输入框', (2160, 1173)) + +name = '夏骥' +blessing = '马年新春快乐!愿您在新的一年里,事业腾飞,马到成功!' + +print("=" * 50) +print("发送祝福语自动化脚本") +print("=" * 50) +print(f"搜索框坐标: ({search_box_x}, {search_box_y})") +print(f"搜索结果坐标: ({search_result_x}, {search_result_y})") +print(f"聊天输入框坐标: ({chat_input_x}, {chat_input_y})") +print(f"发送对象: {name}") +print(f"祝福语: {blessing}") +print("=" * 50) +print() +print("5秒后开始执行,请切换到微信窗口...") +time.sleep(5) + +# 设置pyautogui +pyautogui.PAUSE = 0.5 # 每次操作后暂停0.5秒 + +# 1. 点击搜索框 +print(f"1. 点击搜索框 ({search_box_x}, {search_box_y})") +pyautogui.moveTo(search_box_x, search_box_y, duration=0.3) +pyautogui.click() +time.sleep(0.5) + +# 2. 清空搜索框并输入名字 +print(f"2. 输入名字: {name}") +pyautogui.hotkey('ctrl', 'a') +time.sleep(0.2) +pyperclip.copy(name) +pyautogui.hotkey('ctrl', 'v') +time.sleep(1) + +# 3. 点击搜索结果 +print(f"3. 点击搜索结果 ({search_result_x}, {search_result_y})") +pyautogui.moveTo(search_result_x, search_result_y, duration=0.3) +pyautogui.click() +time.sleep(1) + +# 4. 点击聊天输入框 +print(f"4. 点击聊天输入框 ({chat_input_x}, {chat_input_y})") +pyautogui.moveTo(chat_input_x, chat_input_y, duration=0.3) +pyautogui.click() +time.sleep(0.5) + +# 5. 输入祝福语 +print(f"5. 输入祝福语") +pyperclip.copy(blessing) +pyautogui.hotkey('ctrl', 'v') +time.sleep(0.5) + +# 6. 发送 +print("6. 发送消息") +pyautogui.press('enter') + +print() +print("=" * 50) +print("操作完成!") +print("=" * 50) diff --git a/static/contacts_manager.html b/static/contacts_manager.html index c5ecc49..4567b85 100644 --- a/static/contacts_manager.html +++ b/static/contacts_manager.html @@ -382,6 +382,24 @@ document.addEventListener('DOMContentLoaded', function() { loadContacts(); updateStats(); + + // 绑定祝福语点击事件(使用事件委托) + document.getElementById('contactTable').addEventListener('click', function(e) { + // 祝福语点击 + if (e.target.classList.contains('blessing-text')) { + const id = parseInt(e.target.dataset.id); + const blessing = e.target.dataset.blessing; + const searchName = e.target.dataset.searchName; + const category = e.target.dataset.category; + openBlessingModal(id, blessing, searchName, category); + } + // 自定义内容点击 + if (e.target.classList.contains('contact-name') && e.target.dataset.customContent !== undefined) { + const id = parseInt(e.target.dataset.id); + const customContent = e.target.dataset.customContent; + openCustomContentModal(id, customContent); + } + }); }); // 搜索回车事件 @@ -454,8 +472,8 @@