前言:
某公司可以使用OA系统进行打卡,前提是要在公司内网,也就是必须要在公司才能打卡,实际工作中经常会遇到忘记打卡或者迟到早退的情况(嘿嘿你懂的),于是就想写个小程序来实现自动打卡。其实这也可以视为OA系统的一个跨站请求伪造(CSRF)漏洞Python画流程图python的皮卡丘如何写代码。要堵住这个漏洞可以使用四种方法:
1、随机TOKEN,打卡请求时携带一个随机TOKEN
2、图形验证码,打卡请求时输入一个随机验证码
3、Referrer头验证,打卡请求时验证Referrer头已判断来源
4、输入密码,打卡请求时输入自己的密码
代码逻辑说明:
由于tkinter不能使用类似schedule的调度模块,也不能使用time.sleep,主要原因是tkinter的GUI是靠window.mainloop()的循环实现,当使用schedule或time.sleep时,函数会暂停跳不到window.mainloop()导致界面会卡死。tkinter为了实现定时任务提供了自己的after方法。其功能是指定一定的时间(毫秒)后执行某函数。因此这个定时打卡退卡程序反复使用tkinter的after来实现。思路如下:
一、当点击button按钮进入start_time函数,主要功能是无论何时启动打卡,都能在设置的打卡时间进行打卡,退卡时间进行退卡。实现如下:
1、 先获取当前时间,再设置一个打卡时间workon_time和一个退卡时间workoff_time;
2、 分别计算打卡时间到当前时间之间的打卡毫秒数minsecond和退卡时间到当前时间的退卡毫秒数minsecond1;
3、 设置两个window.after定时任务,一个是minsecond毫秒后启动start_time1函数;另一个是minsecond1毫秒后启动start_time1函数;
二、start_time1函数的作用是实现循环打卡,也就是本次执行后每24小时后再执行一次,实现如下:
1、 该函数启动先执行一次randomtimes函数,24小时(86400000毫秒)后再执行一次自己(同时再次先执行一次randomtimes),以实现循环执行;
2、 start_time2和start_time1一样,只不过一个在打卡时间时启动循环,另一个在退卡时间启动循环
三、randomtimes函数的主要功能是随机时间打卡,以防止每次打开时间相同让人起疑,实现如下:
1、 在一定的范围内(这里设置的是360秒)随机一个秒数s;
2、 在s秒后开始执行真正的打卡函数work_on;
3、 randomtimes1函数和randomtimes功能相似;
四、work_on函数的作用是模拟登陆后打卡,都是由requests模块实现,不再详说;
举例(以打卡为例):
假如打卡时间是上午8:30,当前时间是上午8:00,我启动了此程序,点击了‘按钮’,程序先执行了start_time函数;
start_time函数获取了当前时间8:00,计算出30分钟后(也就是8:30)开始执行start_time1函数;
start_time1函数先执行了randomtimes函数,然后再执行一个计划任务——24小时后再执行一次自己;
randomtimes函数随机了一个秒数60秒,并在60秒后执行work_on函数;
60秒后(8:31时)work_on正式启动,第1次打卡;
24小时后(第二天8:30)…
start_time1再次启动了自己,执行了randomtimes函数,同时又执行一个计划任务——24小时后再执行一次自己;
randomtimes函数这次随机120秒,并在120秒后执行work_on函数;
120秒后(8:32时)work_on正式启动,第2次打卡;
24小时后(第三天8:30)…
…
代码:
#!/usr/bin/env python
# -*- coding: UTF-8 -*-
# @Time :2020/6/28 10:43
# @Author :Donvin.li
# @File :autologin_v1.py
import requests
import rsa
import time
import random
import tkinter as tk
from tkinter import ttk
import datetime
import json
def basedesk(root):
root.config()
root.title('江波龙自动打卡')
root.geometry('355x355')
interface(root)
# 主窗口,主要实现登录功能,成功后跳转到框架窗口
def interface(root):
master=root
master.config(bg='green')
interface=tk.Frame(master)
interface.pack(expand='yes', fill='both')
#interface.place(x=0,y=0)
l1 = tk.Label(interface, text='用户名:', font=('Arial', 10))
l1.place(x=50,y=50)
e1 = tk.Entry(interface, width=20, show=None) # 显示成明文形式
e1.place(x=130, y=50)
l2 = tk.Label(interface, text='密 码:', font=('Arial', 10))
l2.place(x=50, y=80)
e2 = tk.Entry(interface, width=20, show='*') # 显示成密文形式
e2.place(x=130, y=80)
# RSA加密函数,实现模拟前端RSA加密,加密后的字符串(密码)被logintest函数调用
def get_rsa_result(content):
"""
根据 模量与指数 生成公钥,并利用公钥对内容 rsa 加密返回结果
:param e:指数
:param n: 模量
:param content:待加密字符串
:return: 加密后结果
"""
n = "8bcbceb956d3d6c0da8cd8847e50796eac0fb3d67d4901820fa85dcd8edbb30bd25966eb18223e1ace1308da181897df4559bf97cca6ae9a33a0baf6f53324334a385d2a7cbc186fb5070045080b6c948423e7ddcd795ac9eaa438317772f4a948409ecec92dfe222a10b4c327e8d0e494cc0aa42ebc786030a105da0637049d"
e = "10001"
e = int(e, 16)
n = int(n, 16)
pub_key = rsa.PublicKey(e=e, n=n)
m = rsa.encrypt(content.encode(), pub_key)
# print(m.hex())
return m.hex()
def logintest1():
usr = e1.get()
psd = e2.get()
change(usr, psd)
# 登录函数,主要实现验证账号密码功能。
def logintest():
usr = e1.get()
psd = e2.get()
passd = get_rsa_result(psd)
s = requests.session()
headers = {'Host': 'e.xxxx.com',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:72.0) Gecko/20100101 Firefox/72.0',
'Accept': 'application/json, text/java, */*; q=0.01',
'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
'Accept-Encoding': 'gzip, deflate',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-Requested-With': 'XMLHttpRequest',
'Origin': 'http://e.xxxx.com',
'Connection': 'close',
'Referer': 'http://e.xxxx.com/portal/',
}
postdata = {'lang': 'cn', 'userid': usr, 'pwd': passd, 'cmd': 'CLIENT_USER_LOGIN', 'sid': '',
'deviceType': 'pc', '_CACHE_LOGIN_TIME_': '1579162050273', 'pwdEncode': 'RSA', 'timeZone': '-8'}
url = 'http://e.xxxx.com/portal/r/jd'
rs = s.post(url, postdata, headers=headers)
result = rs.text
if 'error' not in result:
ts = '登录成功,开始自动打卡守护,请勿关闭程序\n'
t1.insert(tk.INSERT, ts)
change(usr,psd) #登录执行此函数
return 'OK'
else:
tishi = '登录失败,请重新登录\n'
t1.insert(tk.INSERT, tishi)
# 跳转函数,实现跳转到窗口1功能
def change(usr,psd):
interface.destroy()
interface1(usr,psd) #窗口1函数
b1 = tk.Button(interface, text='登 录', width=10, height=1, command=logintest)
b1.place(x=130, y=120)
t1 = tk.Text(interface, bg='green', fg='white', width=47, height=9)
# 说明: bg为背景,fg为字体颜色,font为字体,width为长,height为高,这里的长和高是字符的长和高,比如height=2,就是标签有2个字符这么高
t1.place(x=10, y=180)
l3 = tk.Label(interface, text='作者:沙漠网管', font=('Arial', 10))
l3.place(x=130, y=320)
# 窗口1函数,实现打卡功能
def interface1(usr,psd):
master = root
master.config(bg='blue')
interface1 = tk.Frame(master)
interface1.pack(expand='yes', fill='both')
xVariable = tk.StringVar() # #创建变量,便于取值
l1 = tk.Label(interface1, text='打卡时间', font=('Arial', 10))
l1.place(x=20,y=20)
com = ttk.Combobox(master, textvariable=xVariable,width=3) # #创建下拉菜单
#com.pack() # #将下拉菜单绑定到窗体
com.place(x=20, y=50)
com["value"] = ("7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22") # #给下拉菜单设定值
com.current(1) # #设定下拉菜单的默认值为第2个
def xFunc(event):
#print(com.get()) #获取选中的值方法1
xVariable.get() #获取选中的值方法2
com.bind("<<ComboboxSelected>>", xFunc) # #给下拉菜单绑定事件
xVariable1 = tk.StringVar()
com1 = ttk.Combobox(master, textvariable=xVariable1, width=3) # #创建下拉菜单
# com.pack() # #将下拉菜单绑定到窗体
com1.place(x=60, y=50)
com1["value"] = ("00","05","10","15","20","25","30","35","40","45","50","55") # #给下拉菜单设定值
com1.current(10) # #设定下拉菜单的默认值为第2个
def xFunc1(event):
#print(com1.get()) # #获取选中的值方法1
xVariable1.get() # #获取选中的值方法2
com1.bind("<<ComboboxSelected>>", xFunc1) # #给下拉菜单绑定事件
l2 = tk.Label(interface1, text='退卡时间', font=('Arial', 10))
l2.place(x=220,y=20)
xVariable2 = tk.StringVar()
com2 = ttk.Combobox(master, textvariable=xVariable2,width=3) # #创建下拉菜单
#com.pack() # #将下拉菜单绑定到窗体
com2.place(x=220, y=50)
com2["value"] = ("7","8","9","10","11","12","13","14","15","16","17","18","19","20","21","22") # #给下拉菜单设定值
com2.current(11) # #设定下拉菜单的默认值为第2个
def xFunc2(event):
#print(com2.get()) # #获取选中的值方法1
xVariable2.get() # #获取选中的值方法2
com2.bind("<<ComboboxSelected>>", xFunc2) # #给下拉菜单绑定事件
xVariable3 = tk.StringVar()
com3 = ttk.Combobox(master, textvariable=xVariable3, width=3) # #创建下拉菜单
# com.pack() # #将下拉菜单绑定到窗体
com3.place(x=260, y=50)
com3["value"] = ("00","05","10","15","20","25","30","35","40","45","50","55") # #给下拉菜单设定值
com3.current(0) # #设定下拉菜单的默认值为第2个
def xFunc3(event):
#print(com3.get()) # #获取选中的值方法1
xVariable3.get() # #获取选中的值方法2
com3.bind("<<ComboboxSelected>>", xFunc3) # #给下拉菜单绑定事件
# 首次打卡函数,无论何时启动打卡,都能在设置的打卡时间进行打卡,退卡时间进行退卡
def start_time():
now = datetime.datetime.now() # 当前时间,即:程序启动的时间
workon_time = com.get()+':'+com1.get() # 打卡时间字符串
output_1='当前打卡时间:'+workon_time+'\n'
t2.insert(tk.INSERT, output_1)
workon_time1 = datetime.datetime.strptime(workon_time, '%H:%M') # 将打卡时间字符串转换成时间格式
second = (workon_time1 - now).seconds # 计算现在到打卡时间之间的秒数
minsecond = second * 1000 # 转换成毫秒,供window.after使用
h = minsecond / 3600000 # 转换成小时,供输出
ts = str(h) + '小时后开始打卡\n'
t2.insert(tk.INSERT, ts) # 输出到t1
workoff_time = com2.get()+':'+com3.get()
output_2='当前退卡时间:'+workoff_time+'\n'
t2.insert(tk.INSERT, output_2)
workoff_time1 = datetime.datetime.strptime(workoff_time, '%H:%M')
second1 = (workoff_time1 - now).seconds # 现在到打卡时间之间的秒数
minsecond1 = second1 * 1000
h1 = minsecond1 / 3600000
ts1 = str(h1) + '小时后开始退卡\n'
t2.insert(tk.INSERT, ts1)
master.after(minsecond, start_time1) # second秒后执行循环打卡函数
master.after(minsecond1, start_time2) # second1秒后执行循环退卡函数
# 循环打卡函数,每24小时执行一次
def start_time1():
ts = '打卡启动\n'
t2.insert(tk.INSERT, ts)
randomtimes() # 执行一次随机打卡函数
master.after(86400000, start_time1) # 每24小时执行一次自己,实现循环
# 循环退卡函数,每24小时执行一次
def start_time2():
ts = '退卡启动\n'
t2.insert(tk.INSERT, ts)
randomtimes1() # 执行一次随机退卡函数
master.after(86400000, start_time2) # 每24小时执行一次自己,实现循环
# 随机打卡函数,用于让打卡时间随机
def randomtimes():
s = random.randint(0, 360) # 随机一个360秒内的秒数
t2.insert(tk.INSERT, str(s) + '秒后开始打卡......\n')
mins = s * 1000 # 转换成毫秒
master.after(mins, work_on) # mins毫秒后执行打卡函数
# 随机退卡函数,用于让退卡时间随机
def randomtimes1():
s = random.randint(0, 360)
t2.insert(tk.INSERT, str(s) + '秒后开始退卡......\n')
mins = s * 1000
master.after(mins, work_off) # mins毫秒后执行退卡函数
def get_rsa_result(content):
"""
根据 模量与指数 生成公钥,并利用公钥对内容 rsa 加密返回结果
:param e:指数
:param n: 模量
:param content:待加密字符串
:return: 加密后结果
"""
n = "8bcbceb956d3d6c0da8cd8847e50796eac0fb3d67d4901820fa85dcd8edbb30bd25966eb18223e1ace1308da181897df4559bf97cca6ae9a33a0baf6f53324334a385d2a7cbc186fb5070045080b6c948423e7ddcd795ac9eaa438317772f4a948409ecec92dfe222a10b4c327e8d0e494cc0aa42ebc786030a105da0637049d"
e = "10001"
e = int(e, 16)
n = int(n, 16)
pub_key = rsa.PublicKey(e=e, n=n)
m = rsa.encrypt(content.encode(), pub_key)
# print(m.hex())
return m.hex()
# 打卡函数,用于打卡
def work_on():
passd = get_rsa_result(psd)
s = requests.session()
headers = {'Host': 'e.xxxx.com',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:72.0) Gecko/20100101 Firefox/72.0',
'Accept': 'application/json, text/java, */*; q=0.01',
'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
'Accept-Encoding': 'gzip, deflate',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-Requested-With': 'XMLHttpRequest',
'Origin': 'http://e.xxxx.com',
'Connection': 'close',
'Referer': 'http://e.xxxx.com/portal/',
}
postdata = {'lang': 'cn', 'userid': usr, 'pwd': passd, 'cmd': 'CLIENT_USER_LOGIN', 'sid': '',
'deviceType': 'pc', '_CACHE_LOGIN_TIME_': '1579162050273', 'pwdEncode': 'RSA', 'timeZone': '-8'}
url = 'http://e.xxxx.com/portal/r/jd'
rs = s.post(url, postdata, headers=headers)
result = rs.text
if 'error' not in result:
sid = json.loads(result)['data']['sid']
# print('Login sucessful:'+usr+':'+psd)
work_url = 'http://e.xxxx.com/portal/r/jd?sid=' + sid + '&cmd=com.hrpaas.apps.atnd.sign'
work_postdata = {'signtype': 1, 'type': 1}
work_rs = s.post(work_url, work_postdata, headers=headers)
if 'error' not in work_rs.text:
now = time.strftime("%Y-%m-%d %H:%M:%S")
output_sucessful = '打卡成功!'
t2.insert(tk.INSERT, str(now) + ':')
t2.insert(tk.INSERT, output_sucessful)
elif 'WIFI' in work_rs.text:
now = time.strftime("%Y-%m-%d %H:%M:%S")
output_err = '打卡失败,你当前网络不在打卡范围\n'
t2.insert(tk.INSERT, str(now) + ':')
t2.insert(tk.INSERT, output_err)
else:
now = time.strftime("%Y-%m-%d %H:%M:%S")
output_err1 = '打卡失败,未知原因\n'
t2.insert(tk.INSERT, str(now) + ':')
t2.insert(tk.INSERT, output_err1)
else:
now = time.strftime("%Y-%m-%d %H:%M:%S")
ts = '账户异常\n'
t2.insert(tk.INSERT, str(now) + ':')
t2.insert(tk.INSERT, ts)
# 退卡函数,用于退卡
def work_off():
passd = get_rsa_result(psd)
s = requests.session()
headers = {'Host': 'e.xxxx.com',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:72.0) Gecko/20100101 Firefox/72.0',
'Accept': 'application/json, text/java, */*; q=0.01',
'Accept-Language': 'zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2',
'Accept-Encoding': 'gzip, deflate',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
'X-Requested-With': 'XMLHttpRequest',
'Origin': 'http://e.xxxx.com',
'Connection': 'close',
'Referer': 'http://e.xxxx.com/portal/',
}
postdata = {'lang': 'cn', 'userid': usr, 'pwd': passd, 'cmd': 'CLIENT_USER_LOGIN', 'sid': '',
'deviceType': 'pc',
'_CACHE_LOGIN_TIME_': '1579162050273', 'pwdEncode': 'RSA', 'timeZone': '-8'}
url = 'http://e.xxxx.com/portal/r/jd'
rs = s.post(url, postdata, headers=headers)
result = rs.text
if 'error' not in result:
sid = json.loads(result)['data']['sid']
# print('Login sucessful:'+usr+':'+psd)
work_url = 'http://e.xxxx.com/portal/r/jd?sid=' + sid + '&cmd=com.hrpaas.apps.atnd.sign'
work_postdata = {'signtype': 1, 'type': 2}
work_rs = s.post(work_url, work_postdata, headers=headers)
if 'error' not in work_rs.text:
now = time.strftime("%Y-%m-%d %H:%M:%S")
output_sucessful = '退卡成功!'
t2.insert(tk.INSERT, str(now) + ':')
t2.insert(tk.INSERT, output_sucessful)
elif 'WIFI' in work_rs.text:
now = time.strftime("%Y-%m-%d %H:%M:%S")
output_err = '退卡失败,你当前网络不在打卡范围\n'
t2.insert(tk.INSERT, str(now) + ':')
t2.insert(tk.INSERT, output_err)
else:
now = time.strftime("%Y-%m-%d %H:%M:%S")
output_err1 = '退卡失败,未知原因\n'
t2.insert(tk.INSERT, str(now) + ':')
t2.insert(tk.INSERT, output_err1)
else:
now = time.strftime("%Y-%m-%d %H:%M:%S")
ts = '账户异常\n'
t2.insert(tk.INSERT, str(now) + ':')
t2.insert(tk.INSERT, ts)
b1 = tk.Button(interface1, text='启 动', width=10, height=1, command=start_time)
b1.place(x=130, y=80)
t2 = tk.Text(interface1, bg='green', fg='white', width=47, height=14)
# 说明: bg为背景,fg为字体颜色,font为字体,width为长,height为高,这里的长和高是字符的长和高,比如height=2,就是标签有2个字符这么高
t2.place(x=10, y=120)
output_0 = '登录成功!请设置打卡时间和退卡时间\n'
t2.insert(tk.INSERT, output_0)
l3 = tk.Label(interface1, text='作者:沙漠网管', font=('Arial', 10))
l3.place(x=130, y=320)
if __name__=='__main__':
root = tk.Tk()
basedesk(root)
root.mainloop()
使用:
使用pyinstaller打包成exe直接双击执行即可使用:
pyinstaller -F -w autologin.py