添加自动发送祝福语功能和坐标配置

This commit is contained in:
2026-03-03 11:05:33 +08:00
parent 415ec18182
commit 024950e9fb
10 changed files with 514 additions and 25 deletions

3
.gitignore vendored
View File

@@ -2,6 +2,9 @@
ocr_progress.json
ocr_result.txt
# 数据库文件 - 不上传
*.db
# 截图和图片数据 - 不上传
*.png
scroll/

270
auto_send_blessing.py Normal file
View File

@@ -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()

105
get_mouse_coords.py Normal file
View File

@@ -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()

View File

@@ -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")

84
send_blessing.py Normal file
View File

@@ -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)

View File

@@ -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 @@
<td><span class="contact-name" onclick="openNameModal(${c.id}, '${escapeHtml(c.name)}', '${escapeHtml(c.search_name || '')}')">${escapeHtml(c.name)}</span></td>
<td><span class="contact-name" onclick="openNameModal(${c.id}, '${escapeHtml(c.name)}', '${escapeHtml(c.search_name || '')}')">${escapeHtml(c.search_name || '')}</span></td>
<td><div class="category-tags">${catTags}</div></td>
<td><span class="contact-name" onclick="openCustomContentModal(${c.id}, '${escapeHtml(customContent)}')">${escapeHtml(customContent)}</span></td>
<td><span class="blessing-text" onclick="openBlessingModal(${c.id}, '${escapeHtml(c.blessing)}', '${escapeHtml(c.search_name || '')}', '${escapeHtml(c.category || '')}')" title="${escapeHtml(c.blessing)}">${escapeHtml(c.blessing)}</span></td>
<td><span class="contact-name" data-id="${c.id}" data-custom-content="${escapeHtml(customContent)}" title="点击编辑">${escapeHtml(customContent)}</span></td>
<td><span class="blessing-text" data-id="${c.id}" data-blessing="${escapeHtml(c.blessing)}" data-search-name="${escapeHtml(c.search_name || '')}" data-category="${escapeHtml(c.category || '')}" title="点击编辑祝福语">${escapeHtml(c.blessing)}</span></td>
<td>${sendBadge}</td>
<td>
<button class="btn btn-sm btn-danger" onclick="deleteContact(${c.id})"><i class="bi bi-trash"></i></button>

View File

@@ -4,5 +4,5 @@
微信窗口: Name=微信, ClassName=Qt51514QWindowIcon
ControlType: WindowControl ClassName: Qt51514QWindowIcon Name: 微信 AutomationId: Rect: (-3,15,1044,1033)[1047x1018] Depth: 0
ControlType: PaneControl ClassName: MMUIRenderSubWindowHW Name: MMUIRenderSubWindowHW AutomationId: Rect: (5,15,1036,1025)[1031x1010] Depth: 1
ControlType: WindowControl ClassName: Qt51514QWindowIcon Name: 微信 AutomationId: Rect: (1727,5,3442,1402)[1715x1397] Depth: 0
ControlType: PaneControl ClassName: MMUIRenderSubWindowHW Name: MMUIRenderSubWindowHW AutomationId: Rect: (1735,5,3434,1394)[1699x1389] Depth: 1

View File

@@ -2,5 +2,5 @@
微信窗口控件树 - RawViewWalker
================================================================================
ControlType: 窗口 ClassName: Qt51514QWindowIcon Name: 微信 AutomationId: Rect: (-3,15,1044,1033) Depth: 0
ControlType: 窗格 ClassName: MMUIRenderSubWindowHW Name: MMUIRenderSubWindowHW AutomationId: Rect: (5,15,1036,1025) Depth: 1
ControlType: 窗口 ClassName: Qt51514QWindowIcon Name: 微信 AutomationId: Rect: (1727,5,3442,1402) Depth: 0
ControlType: 窗格 ClassName: MMUIRenderSubWindowHW Name: MMUIRenderSubWindowHW AutomationId: Rect: (1735,5,3434,1394) Depth: 1

View File

@@ -2,5 +2,10 @@
微信窗口控件树 - Win32 API
================================================================================
HWND: 0x401BE ClassName: Qt51514QWindowIcon Title: 微信 Rect: (-3, 15, 1044, 1033) Depth: 0
HWND: 0x1109FE ClassName: MMUIRenderSubWindowHW Title: MMUIRenderSubWindowHW Rect: (5, 15, 1036, 1025) Depth: 1
HWND: 0x10998 ClassName: Qt51514QWindowIcon Title: 微信 Rect: (1727, 5, 3442, 1402) Depth: 0
HWND: 0x206D4 ClassName: Chrome_WidgetWin_0 Title: Rect: (2053, 75, 3432, 1391) Depth: 1
HWND: 0x109A2 ClassName: Chrome_RenderWidgetHostHWND Title: Chrome Legacy Window Rect: (2053, 75, 3432, 1391) Depth: 2
HWND: 0x109A0 ClassName: Intermediate D3D Window Title: Rect: (2053, 75, 3432, 1391) Depth: 2
HWND: 0x109A2 ClassName: Chrome_RenderWidgetHostHWND Title: Chrome Legacy Window Rect: (2053, 75, 3432, 1391) Depth: 1
HWND: 0x109A0 ClassName: Intermediate D3D Window Title: Rect: (2053, 75, 3432, 1391) Depth: 1
HWND: 0x1099C ClassName: MMUIRenderSubWindowHW Title: MMUIRenderSubWindowHW Rect: (1735, 5, 3434, 1394) Depth: 1

3
wechat_coords.txt Normal file
View File

@@ -0,0 +1,3 @@
搜索框: (1933, 39)
搜索结果区域: (2009, 100)
聊天输入框: (2160, 1173)