1.文件内文件发生变化,则通知
2.通过指定窗口发生通知,通知内容为发生改变的文件名,增加文件或者删除等
3.指定窗口可以是QQ或者微信窗口
4.正式开始前,需要测试能否正确成功发送(字符串),因为可能发送时窗口大小不一致,或者无法前置窗口导致无法正确输入字符串。
上代码:
import os
import time
import random # 用于随机延迟
import json
import hashlib
import ctypes
import threading
import win32gui
import win32con
import win32api
import tkinter as tk
from tkinter import filedialog, messagebox, ttk
CONFIG_FILE = "folder_monitor_config.json"
class Config:
def __init__(self):
self.interval = 5 # 默认5秒
self.folder_path = ""
self.target_window = "我的手机"
self.load_config()
def load_config(self):
if os.path.exists(CONFIG_FILE):
try:
with open(CONFIG_FILE, 'r') as f:
data = json.load(f)
self.interval = data.get('interval', 5)
self.folder_path = data.get('folder_path', "")
self.target_window = data.get('target_window', "我的手机")
except:
# 如果配置文件损坏,使用默认值
self.interval = 5
self.folder_path = ""
self.target_window = "我的手机"
def save_config(self):
data = {
'interval': self.interval,
'folder_path': self.folder_path,
'target_window': self.target_window
}
with open(CONFIG_FILE, 'w') as f:
json.dump(data, f)
class WindowSender:
def __init__(self):
self.user32 = ctypes.windll.user32
self.KEYEVENTF_KEYUP = 0x0002
def force_foreground(self, hwnd):
"""强制将窗口置于前台(改进版)"""
try:
# 方法1:先尝试常规方式
win32gui.ShowWindow(hwnd, win32con.SW_RESTORE)
win32gui.SetForegroundWindow(hwnd)
time.sleep(0.1)
# 检查是否成功
if hwnd == win32gui.GetForegroundWindow():
return True
# 方法2:如果常规方式失败,使用备用方法
# 保存当前焦点窗口
foreground = win32gui.GetForegroundWindow()
# 魔法参数:使得SetForegroundWindow可以工作
self.user32.LockSetForegroundWindow(win32con.LSFW_UNLOCK)
# 将目标窗口设为最顶层
win32gui.SetWindowPos(
hwnd, win32con.HWND_TOPMOST,
0, 0, 0, 0,
win32con.SWP_NOMOVE | win32con.SWP_NOSIZE
)
win32gui.SetWindowPos(
hwnd, win32con.HWND_NOTOPMOST,
0, 0, 0, 0,
win32con.SWP_NOMOVE | win32con.SWP_NOSIZE
)
# 再次尝试获取焦点
win32gui.SetForegroundWindow(hwnd)
time.sleep(0.1)
# 恢复原焦点窗口
if foreground and foreground != hwnd:
win32gui.SetForegroundWindow(foreground)
win32gui.SetForegroundWindow(hwnd)
return hwnd == win32gui.GetForegroundWindow()
except Exception as e:
print(f"强制置前失败: {e}")
return False
def send_targeted_input(self, hwnd, text):
"""精准发送到指定窗口"""
try:
# 1. 获取目标窗口的编辑控件
edit_hwnd = win32gui.FindWindowEx(hwnd, None, "Edit", None)
if not edit_hwnd:
# 如果找不到标准Edit控件,尝试直接发送到窗口
edit_hwnd = hwnd
# 2. 激活目标控件
win32gui.SetForegroundWindow(hwnd)
win32gui.SetFocus(edit_hwnd)
time.sleep(0.1)
# 3. 清空原有内容
win32gui.SendMessage(edit_hwnd, win32con.WM_SETTEXT, 0, "")
# 4. 精准发送字符
for char in text:
win32gui.SendMessage(
edit_hwnd,
win32con.WM_CHAR,
ord(char),
0
)
time.sleep(0.01)
# 5. 发送回车
win32gui.SendMessage(
edit_hwnd,
win32con.WM_KEYDOWN,
win32con.VK_RETURN,
0
)
win32gui.SendMessage(
edit_hwnd,
win32con.WM_KEYUP,
win32con.VK_RETURN,
0
)
return True
except Exception as e:
print(f"定向发送失败: {e}")
return False
def send_to_window(self, window_title, text):
"""改进版:只发送到目标窗口"""
hwnd = win32gui.FindWindow(None, window_title)
if not hwnd:
return False
# 先尝试精准发送到编辑框
if self.send_targeted_input(hwnd, text):
return True
# 如果失败,尝试备用方法(但限制在目标窗口内)
try:
self.force_foreground(hwnd)
time.sleep(0.2)
# 获取窗口位置
rect = win32gui.GetWindowRect(hwnd)
x = (rect[0] + rect[2]) // 2
y = (rect[1] + rect[3]) // 2
x = x -200
y = y +200
# 模拟点击确保焦点
win32api.SetCursorPos((x, y))
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTDOWN, x, y, 0, 0)
win32api.mouse_event(win32con.MOUSEEVENTF_LEFTUP, x, y, 0, 0)
time.sleep(0.1)
for char in text:
win32api.SendMessage(hwnd, win32con.WM_CHAR, ord(char), 0)
time.sleep(random.uniform(0.03, 0.08))
# 发送回车
time.sleep(0.1)
win32api.keybd_event(win32con.VK_RETURN, 0, 0, 0)
win32api.keybd_event(win32con.VK_RETURN, 0, win32con.KEYEVENTF_KEYUP, 0)
return True
except Exception as e:
print(f"备用发送方法失败: {e}")
return False
class FolderMonitor:
def __init__(self, folder_path, interval, callback, target_window):
self.folder_path = folder_path
self.interval = interval
self.callback = callback
self.target_window = target_window
self.window_sender = WindowSender()
self.last_state = {}
self.running = False
def get_file_hash(self, filepath):
"""计算文件的哈希值"""
hasher = hashlib.md5()
with open(filepath, 'rb') as f:
buf = f.read()
hasher.update(buf)
return hasher.hexdigest()
def scan_folder(self):
"""扫描文件夹并返回文件状态"""
current_state = {}
for root, dirs, files in os.walk(self.folder_path):
for file in files:
filepath = os.path.join(root, file)
try:
file_stat = os.stat(filepath)
current_state[filepath] = {
'size': file_stat.st_size,
'mtime': file_stat.st_mtime,
}
except:
continue
return current_state
def compare_states(self, old_state, new_state):
"""比较两个状态,返回变化"""
changes = {
'added': [],
'deleted': [],
'modified': []
}
# 检查新增和修改的文件
for filepath in new_state:
if filepath not in old_state:
changes['added'].append(filepath)
else:
if (new_state[filepath]['size'] != old_state[filepath]['size'] or
new_state[filepath]['mtime'] != old_state[filepath]['mtime']):
changes['modified'].append(filepath)
# 检查删除的文件
for filepath in old_state:
if filepath not in new_state:
changes['deleted'].append(filepath)
return changes
def send_changes_to_window(self, changes):
"""将变化发送到目标窗口"""
if not self.target_window:
return False
# 准备要发送的消息
message_lines = []
if changes['added']:
message_lines.append("【新增文件】")
message_lines.extend([f"+ {os.path.basename(f)}" for f in changes['added']])
if changes['modified']:
message_lines.append("【修改文件】")
message_lines.extend([f"* {os.path.basename(f)}" for f in changes['modified']])
if changes['deleted']:
message_lines.append("【删除文件】")
message_lines.extend([f"- {os.path.basename(f)}" for f in changes['deleted']])
if not message_lines:
return False
message = "\n".join(message_lines)
return self.window_sender.send_to_window(self.target_window, message)
def start(self):
"""开始监控"""
self.running = True
self.last_state = self.scan_folder()
print(f"开始监控文件夹: {self.folder_path}, 检测间隔: {self.interval}秒")
while self.running:
time.sleep(self.interval)
current_state = self.scan_folder()
changes = self.compare_states(self.last_state, current_state)
if any(changes.values()):
send_result = self.send_changes_to_window(changes)
self.callback(changes, send_result)
self.last_state = current_state
def stop(self):
"""停止监控"""
self.running = False
class FolderMonitorApp:
def __init__(self, root):
self.root = root
self.root.title("文件夹监控与自动发送工具")
self.config = Config()
self.monitor = None
# 创建UI
self.create_ui()
# 加载保存的配置
self.load_settings()
def create_ui(self):
"""创建用户界面"""
# 主框架
main_frame = ttk.Frame(self.root, padding=10)
main_frame.pack(fill=tk.BOTH, expand=True)
# 文件夹选择部分
folder_frame = ttk.LabelFrame(main_frame, text="监控文件夹", padding=10)
folder_frame.pack(fill=tk.X, pady=5)
self.folder_var = tk.StringVar()
folder_entry = ttk.Entry(folder_frame, textvariable=self.folder_var, width=50)
folder_entry.pack(side=tk.LEFT, fill=tk.X, expand=True)
ttk.Button(folder_frame, text="浏览...", command=self.browse_folder).pack(side=tk.LEFT, padx=5)
# 目标窗口设置部分 - 修改为水平布局
target_frame = ttk.LabelFrame(main_frame, text="目标窗口设置", padding=10)
target_frame.pack(fill=tk.X, pady=5)
self.target_var = tk.StringVar()
# 使用Frame将输入框和按钮放在同一行
target_row = ttk.Frame(target_frame)
target_row.pack(fill=tk.X)
ttk.Label(target_row, text="窗口标题:").pack(side=tk.LEFT, padx=(0,5))
ttk.Entry(target_row, textvariable=self.target_var, width=40).pack(side=tk.LEFT, fill=tk.X, expand=True)
ttk.Button(target_row, text="测试窗口", command=self.test_window).pack(side=tk.LEFT, padx=(5,0))
# 间隔时间设置部分
interval_frame = ttk.LabelFrame(main_frame, text="检测间隔 (1-30秒)", padding=10)
interval_frame.pack(fill=tk.X, pady=5)
self.interval_var = tk.IntVar()
ttk.Scale(interval_frame, from_=1, to=30, variable=self.interval_var,
command=lambda v: self.interval_var.set(round(float(v)))).pack(fill=tk.X)
ttk.Label(interval_frame, textvariable=self.interval_var).pack()
# 控制按钮
button_frame = ttk.Frame(main_frame, padding=10)
button_frame.pack(fill=tk.X)
self.start_button = ttk.Button(button_frame, text="开始监控", command=self.threaded_start_monitoring)
self.start_button.pack(side=tk.LEFT, padx=5)
self.stop_button = ttk.Button(button_frame, text="停止监控", command=self.stop_monitoring, state=tk.DISABLED)
self.stop_button.pack(side=tk.LEFT, padx=5)
ttk.Button(button_frame, text="保存设置", command=self.save_settings).pack(side=tk.RIGHT)
ttk.Button(button_frame, text="清空日志", command=self.clear_log).pack(side=tk.RIGHT, padx=5)
# 日志输出
log_frame = ttk.LabelFrame(main_frame, text="监控日志", padding=10)
log_frame.pack(fill=tk.BOTH, expand=True)
self.log_text = tk.Text(log_frame, height=10, state=tk.DISABLED, wrap=tk.WORD)
scrollbar = ttk.Scrollbar(log_frame, command=self.log_text.yview)
self.log_text.config(yscrollcommand=scrollbar.set)
self.log_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True)
scrollbar.pack(side=tk.RIGHT, fill=tk.Y)
def browse_folder(self):
"""浏览文件夹"""
folder_path = filedialog.askdirectory()
if folder_path:
self.folder_var.set(folder_path)
def test_window(self):
"""测试目标窗口(线程化)"""
def test_window_thread():
self.start_button.config(state=tk.DISABLED)
self.stop_button.config(state=tk.DISABLED)
target_window = self.target_var.get()
if not target_window:
messagebox.showerror("错误", "请输入目标窗口标题")
self.start_button.config(state=tk.NORMAL)
return
sender = WindowSender()
hwnd = win32gui.FindWindow(None, target_window)
if hwnd:
try:
sender.force_foreground(hwnd)
self.log(f"成功找到并激活窗口: {target_window}")
if sender.send_to_window(target_window, "测试消息-文件夹监控工具"):
self.log("测试消息发送成功!")
else:
self.log("窗口找到但消息发送失败")
except Exception as e:
self.log(f"窗口测试失败: {str(e)}")
else:
self.log(f"未找到窗口: {target_window}")
self.start_button.config(state=tk.NORMAL)
# 启动线程执行测试
threading.Thread(target=test_window_thread, daemon=True).start()
def threaded_start_monitoring(self):
"""线程化的开始监控"""
def start_monitoring_thread():
self.start_button.config(state=tk.DISABLED)
self.stop_button.config(state=tk.DISABLED)
folder_path = self.folder_var.get()
interval = self.interval_var.get()
target_window = self.target_var.get()
if not folder_path:
messagebox.showerror("错误", "请先选择要监控的文件夹")
self.start_button.config(state=tk.NORMAL)
return
if not os.path.exists(folder_path):
messagebox.showerror("错误", "指定的文件夹不存在")
self.start_button.config(state=tk.NORMAL)
return
if interval < 1 or interval > 30:
messagebox.showerror("错误", "检测间隔必须在1-30秒之间")
self.start_button.config(state=tk.NORMAL)
return
# 保存当前设置
self.save_settings()
# 启动监控
self.monitor = FolderMonitor(folder_path, interval, self.handle_changes, target_window)
self.monitor_thread = threading.Thread(target=self.monitor.start, daemon=True)
self.monitor_thread.start()
self.log(f"开始监控文件夹: {folder_path}")
self.log(f"检测间隔: {interval}秒")
if target_window:
self.log(f"检测到变化将自动发送到: {target_window}")
self.stop_button.config(state=tk.NORMAL)
# 启动线程执行监控
threading.Thread(target=start_monitoring_thread, daemon=True).start()
def load_settings(self):
"""加载保存的设置"""
self.interval_var.set(self.config.interval)
self.folder_var.set(self.config.folder_path)
self.target_var.set(self.config.target_window)
def save_settings(self):
"""保存当前设置"""
self.config.interval = self.interval_var.get()
self.config.folder_path = self.folder_var.get()
self.config.target_window = self.target_var.get()
self.config.save_config()
self.log("当前设置已保存")
def stop_monitoring(self):
"""停止监控"""
if self.monitor:
self.monitor.stop()
self.monitor = None
# 更新按钮状态
self.start_button.config(state=tk.NORMAL)
self.stop_button.config(state=tk.DISABLED)
self.log("监控已停止")
def handle_changes(self, changes, send_result):
"""处理文件夹变化"""
self.log("检测到文件夹变化:")
if changes['added']:
self.log(" 新增文件:")
for file in changes['added']:
self.log(f" + {os.path.basename(file)}")
if changes['deleted']:
self.log(" 删除文件:")
for file in changes['deleted']:
self.log(f" - {os.path.basename(file)}")
if changes['modified']:
self.log(" 修改文件:")
for file in changes['modified']:
self.log(f" * {os.path.basename(file)}")
if self.target_var.get():
if send_result:
self.log(" 变化已成功发送到目标窗口")
else:
self.log(" 发送到目标窗口失败")
def log(self, message):
"""记录日志"""
timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
log_message = f"[{timestamp}] {message}\n"
self.log_text.config(state=tk.NORMAL)
self.log_text.insert(tk.END, log_message)
self.log_text.see(tk.END)
self.log_text.config(state=tk.DISABLED)
def clear_log(self):
"""清空日志"""
self.log_text.config(state=tk.NORMAL)
self.log_text.delete(1.0, tk.END)
self.log_text.config(state=tk.DISABLED)
self.log("日志已清空")
def main():
root = tk.Tk()
app = FolderMonitorApp(root)
# 设置窗口大小和位置
root.geometry("800x600+100+100")
root.minsize(600, 400)
# 启动主循环
root.mainloop()
if __name__ == "__main__":
main()
转载或参考该代码,请注明出处!多谢合作!!!