代码拉取完成,页面将自动刷新
from threading import Thread, Lock
from time import sleep
from tkinter import filedialog, Tk
from pandas import read_excel
from pynput import keyboard
from win32api import SetCursorPos, mouse_event
from win32clipboard import OpenClipboard, EmptyClipboard, SetClipboardData, CloseClipboard
from win32gui import FindWindow, GetWindowPlacement, ShowWindow, SetWindowPos, PostMessage
from win32con import MOUSEEVENTF_LEFTDOWN, MOUSEEVENTF_LEFTUP, CF_UNICODETEXT, SW_SHOWDEFAULT, HWND_TOPMOST, \
HWND_NOTOPMOST, SWP_SHOWWINDOW, SW_SHOWNORMAL, WM_CLOSE
from pykeyboard import PyKeyboard
# pykeyboard==0.1.2 需要先安装PyUserInput,pyHook, PyWin32(这个应该有自带)
from pyperclip import copy
from argparse import ArgumentParser
from datetime import datetime
from sys import setrecursionlimit, exit
from os import system
setrecursionlimit(100000) # 设置递归深度
__author__ = '广大菜鸟'
isEnd = False
listener = None
def on_activate():
print('中断热键 <shift> 已启动.在此为本次程序异常对您造成的不便感到十分抱歉!!!')
global isEnd
isEnd = True
return False
def for_canonical(f):
global listener
return lambda k: f(listener.canonical(k))
hotkey = keyboard.HotKey(
keyboard.HotKey.parse('<shift>'),
on_activate)
def closeWrongWindow() -> bool:
sleep(0.2)
windowHandle = FindWindow("MsgManagerWindow", u"消息管理器") # 可以通过winSpy获得
max_iter = 4
while windowHandle == 0 and max_iter > 0: # 设置1秒内5次检查是否有消息管理器这个错误窗口
sleep(0.2)
windowHandle = FindWindow("MsgManagerWindow", u"消息管理器")
max_iter -= 1
if windowHandle != 0:
PostMessage(windowHandle, WM_CLOSE, 0, 0)
return True # 表示目前是关闭了错误弹窗
return False
def getWorkingLocation():
window = FindWindow("WeWorkWindow", u"企业微信") # 可以通过winSpy获得
max_iter = 100
while window == 0 and max_iter > 0:
sleep(0.2)
print("在定位窗口句柄时,发现为无效的窗口句柄。请检查是否打开企业微信的窗口后再重试此软件")
window = FindWindow("WeWorkWindow", u"企业微信") # 可以通过winSpy获得
max_iter -= 1
ShowWindow(window, SW_SHOWDEFAULT) # 设置大小为正常模式,处理开启但没显示,最小化和最大化的情况
return window, GetWindowPlacement(window)
# 输入学生的学号,前提:需要打开电脑的企业微信
def openUser(info, lang=None, time=None):
k = PyKeyboard()
# 0、 选择搜索页面
window, loginid = getWorkingLocation()
[left, top, _, _] = loginid[4]
# [left, top, right, bottom] = loginid[4]
if left < 0: # 处理左侧被挡住
left = 10
width, height = 739, 635 # 防止有的人拉太大
SetWindowPos(window, HWND_TOPMOST, left, top, width, height, SWP_SHOWWINDOW) # 先设置为置顶,不可以改变位置
SetWindowPos(window, HWND_NOTOPMOST, left, top, width, height,
SWP_SHOWWINDOW) # 修改为可改变在前面的软件界面,考虑到在遇到意外需要手动关闭终端
# 通过像素获取左侧聊天界面按钮,同时暂时覆盖其他软件
foreX, foreY = int(left + 31 / 897 * width), int(top + height * 110 / 712) # 比值是通过图片像素比值获得聊天首页面
SetCursorPos((foreX, foreY))
mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0) # press mouse
mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0) # release mouse
waitForReactionTime = 1 if time is None or time < 1 else time
firstX, firstY = int(left + 154 / 919 * width), int(top + height * 48 / 650) # 比值是通过图片像素比值获得搜索框
secondX, secondY = int(left + 154 / 919 * width), int(top + height * 108 / 650) # 比值是通过图片像素比值获得名片选择框
thirdX, thirdY = int(left + 555 / 858 * width), int(top + height * 591 / 650) # 比值是通过图片像素比值获得对话框
global isEnd
global listener
listener = keyboard.Listener(
on_press=for_canonical(hotkey.press))
listener.daemon = 1
# 创建、启动鼠标监听线程
t2 = Thread(target=sendInfo,
args=(info, k, firstX, firstY, secondX, secondY, thirdX, thirdY, waitForReactionTime, lang))
t2.daemon = 1
listener.start()
t2.start()
while True:
if isEnd:
return
lock = Lock() # 申请一把锁
def sendInfo(info, k, firstX, firstY, secondX, secondY, thirdX, thirdY, waitForReactionTime, lang):
global isEnd
for (sno, sname) in info:
if isEnd:
return
k.press_key(k.control_key) # ctrl+f
k.tap_key('f')
k.release_key(k.control_key)
# 1、在输入框输入用户学号 :发现快捷键ctrl-f可以定位到搜索框,但为了可靠,还是手动点下位置
SetCursorPos((firstX, firstY))
mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0) # press mouse
mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0) # release mouse
lock.acquire() # 加锁
copy(sno) # 复制到剪贴板
k.press_key(k.control_key) # ctrl+a
k.tap_key('a')
k.release_key(k.control_key)
k.press_key(k.control_key) # ctrl+v
k.tap_key('v')
k.release_key(k.control_key)
k.press_key(k.enter_key) # 发送
k.release_key(k.enter_key)
lock.release() # 释放锁
sleep(waitForReactionTime) # 给等待响应时间
# 2、选中用户下拉框第一个元素
SetCursorPos((secondX, secondY))
mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0) # press mouse
mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0) # release mouse
if closeWrongWindow(): # 表示目前光标进入了错误的对话框
print('没有通知到位的学生:' + sno + " " + sname)
continue
# 3、输入信息
msg = "你好," + sname + "同学。"
if lang is not None:
msg = msg + lang
else:
msg += "温馨提醒,疫情防控,从我做起。截止于下午3点半,系统显示您今日还没有上报健康信息,请尽快上报。"
SetCursorPos((thirdX, thirdY))
mouse_event(MOUSEEVENTF_LEFTDOWN, 0, 0, 0, 0) # press mouse
mouse_event(MOUSEEVENTF_LEFTUP, 0, 0, 0, 0) # release mouse
# 打开剪贴板
OpenClipboard()
# 清空剪贴板
EmptyClipboard()
# 设置剪贴板内容
lock.acquire() # 加锁
SetClipboardData(CF_UNICODETEXT, msg)
CloseClipboard()
k.press_key(k.control_key) # ctrl+v
k.tap_key('v')
k.release_key(k.control_key)
k.press_key(k.enter_key) # 发送
k.release_key(k.enter_key)
lock.release() # 释放锁
sleep(0.1) # 给检查和处理异常的有效时间,线程可以切换检查是否有按下快捷键
isEnd = True
def readFromExcelByPandas(filename):
try:
df = read_excel(filename, header=1, index_col=0, dtype=str)
return df
except Exception as e:
print(str(e))
return None
def informUsers(src, classno=None, lang=None, waitTime=None, delGrade=None):
itemDataFrame = readFromExcelByPandas(src)
if itemDataFrame is None:
return
itemDataFrame.fillna(' ', inplace=True)
if classno is not None and '班级' in itemDataFrame.columns:
target = itemDataFrame.loc[itemDataFrame['班级'] == classno] # 建立在表格的用户的sno都存在
else:
target = itemDataFrame
# 默认删除17级
if '班级' in target.columns:
target = target.loc[~ target['班级'].str.contains('[^0-9]+' + str(17) + '\d')] # 默认先删除17级
if delGrade is not None and 10 < delGrade < 100: # 目前可见年级有17-20,所以合理范围为2位数 去除
target = target.loc[~ target['班级'].str.contains('[^0-9]+' + str(delGrade) + '\d')] # 正则表达式匹配 如"计科214"的21
print("本次需要选择的对象数目有:", len(target))
# openUser(list(target.groupby(['学号', '姓名']).groups.keys()), lang) # 按照学号排序
if len(target) > 0:
openUser(target[['学号', '姓名']].values.tolist(), lang, waitTime) # 按原来的顺序
def informUser(sno, sname, lang=None, waitTime=None):
openUser([(sno, sname)], lang, waitTime)
# 选择文件
def selectFilename():
root = Tk()
root.withdraw()
filename = filedialog.askopenfilename()
if filename != '':
print("您选择的文件是:" + filename)
return filename
else:
raise Exception("您没有选择任何文件")
def main():
parser = ArgumentParser()
parser.description = '本软件功能是企业微信打卡,可以选择文件/个人模式(注:确定企业微信最新版,在启动企业微信和该程序后,不要移动鼠标和点击键盘)\n' \
'\n默认发送消息格式为\'你好,xxx同学。\'\n' \
'简单使用案例:企业微信打卡提醒 -c * -r 21 (意思是删除表中21届的学生,不给于通知)'
parser.add_argument("-c", "--classno", help="这个参数是班级名称(当*时,为操作所有班级数据)", type=str)
parser.add_argument("-o", "--sno", help="这个参数是学生学号", type=str)
parser.add_argument("-a", "--sname", help="这个参数是学生名字", type=str)
parser.add_argument("-l", "--lang", help="这个参数是在基本发送格式[你好,xxx同学。]增加的文字。如不设置,则输出自定的格式。", type=str)
parser.add_argument("-r", "--delGrade", help="这个参数是删除年级(2位数字,如17)", type=int, default=17)
parser.add_argument("-t", "--time", help="这个参数是等待响应时间(可以根据网速增加,默认1(单位:秒),只能多不能少,少了肯定有异常)", type=float)
args = parser.parse_args()
if args.sname is not None and args.sno is not None:
oldtime = datetime.now()
try:
informUser(args.sno, args.sname, args.lang, args.time)
except Exception as e:
print(str(e))
finally:
newtime = datetime.now()
print("本次通知使用时间为%.2fs" % ((newtime - oldtime).total_seconds()))
elif args.classno is not None: # 文件+班级模式
classno = args.classno if args.classno != '*' else None
oldtime = datetime.now() # 包括选择文件的时间
try:
srcFilePath = selectFilename()
informUsers(srcFilePath, classno, args.lang, args.time, args.delGrade)
except Exception as e:
print(str(e))
finally:
newtime = datetime.now()
print("本次通知使用时间为%.2fs" % ((newtime - oldtime).total_seconds()))
else:
try:
parser.error('\n请使用终端命令行启动本软件,并输入参数\n选择文件模式时,需要指定班级名称或者用*号代表所有班级;\n选择学生模式时,需要指定学生学号和学生名字')
except Exception as e:
print(str(e))
finally:
system("pause")
if __name__ == '__main__':
main()
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。