吾爱破解 - 52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 3314|回复: 47
收起左侧

[Python 原创] 钉钉打卡提醒

[复制链接]
Tianyi731 发表于 2024-7-29 15:14
本帖最后由 Tianyi731 于 2024-7-29 17:59 编辑

小弟因为在一家手机公司做技术支持,偶尔需要值班,经常忘记打卡,故写了这个工具,发出来记录一下,顺便让大佬们看看有没有可以优化的地方。

准备工作

云服务器(本地小型服务器)、一台有xp框架的手机(没有也行,但是只能通知,无法实现全流程自动化)
服务器我用的是50块钱收的M401A电视盒子,刷的armbian。

第一步

使用python代码转换班次为json数据,表格格式以此类推

时间 班次
2024/8/1 A
2024/8/1 P
2024/8/1
#本脚本作用为读取表格数据,写入json,表格模版为schedule.xlsx
import pandas as pd
import json
from datetime import datetime

def read_excel_and_convert_to_json(excel_file):
    # 读取Excel文件
    df = pd.read_excel(excel_file, engine='openpyxl')

    # 创建一个空字典来存储JSON数据
    json_data = {}

    # 获取日期和班次列
    date_column = df['日期']
    shift_column = df['班次']

    # 遍历每一行数据
    for index, row in df.iterrows():
        date = row['日期'].strftime('%Y-%m-%d')  # 确保日期是字符串格式
        shift = row['班次']

        # 拆分日期为年、月、日,并去掉前导零
        year, month, day = int(date.split('-')[0]), int(date.split('-')[1]), int(date.split('-')[2])

        # 检查年份是否在字典中,如果不在则创建
        if year not in json_data:
            json_data[year] = {}

        # 检查月份是否在年份字典中,如果不在则创建
        if month not in json_data[year]:
            json_data[year][month] = {}

        # 将班次添加到字典中
        json_data[year][month][day] = shift

    return json_data

def update_json_file(json_data, json_file):
    # 读取现有的JSON文件,如果文件不存在,则返回空字典
    try:
        with open(json_file, 'r', encoding='utf-8') as file:
            existing_data = json.load(file)
    except (FileNotFoundError, json.JSONDecodeError):
        existing_data = {}

    # 合并新数据和旧数据
    for year, months in json_data.items():
        if str(year) not in existing_data:
            existing_data[str(year)] = {}
        for month, days in months.items():
            if str(month) not in existing_data[str(year)]:
                existing_data[str(year)][str(month)] = days
            else:
                for day, shift in days.items():
                    if str(day) in existing_data[str(year)][str(month)]:
                        print(f"Warning: Data for {year}-{month}-{day} already exists.")
                        response = input(f"是否覆盖此条数据? (y/n): ")
                        if response.lower() == 'y':
                            existing_data[str(year)][str(month)][str(day)] = shift
                    else:
                        existing_data[str(year)][str(month)][str(day)] = shift

    # 将数据按照年、月、日的顺序排序
    sorted_data = {}
    for year in sorted(existing_data.keys(), key=int):
        sorted_data[str(year)] = {}
        for month in sorted(existing_data[str(year)].keys(), key=int):
            sorted_data[str(year)][str(month)] = existing_data[str(year)][str(month)]

    # 将更新后的数据写回JSON文件
    with open(json_file, 'w', encoding='utf-8') as file:
        json.dump(sorted_data, file, ensure_ascii=False, indent=2)

# Excel文件路径
excel_file = 'schedule.xlsx'

# JSON文件路径
json_file = 'data.json'

# 读取Excel文件并转换为JSON数据
new_json_data = read_excel_and_convert_to_json(excel_file)

# 更新JSON文件
update_json_file(new_json_data, json_file)

转换的json格式

{
  "2023": {
    "12": {
      "1": "A",
      "2": "A",
      "3": "A",
      "4": "A",
      "5": "A",
      "6": "A",
      "7": "A",
      "8": "A",
      "9": "A",
      "10": "A",
      "11": "A",
      "12": "A",
      "13": "A",
      "14": "A",
      "15": "A",
      "16": "A",
      "17": "A",
      "18": "A",
      "19": "A",
      "20": "A",
      "21": "A",
      "22": "A",
      "23": "A",
      "24": "A",
      "25": "A",
      "26": "A",
      "27": "A",
      "28": "A",
      "29": "A",
      "30": "A",
      "31": "A"
    }
  },
  "2024": {
    "7": {
      "1": "A",
      "2": "A",
      "3": "A",
      "4": "A",
      "5": "A",
      "6": "A",
      "7": "A",
      "8": "A",
      "9": "A",
      "10": "A",
      "11": "A",
      "12": "A",
      "13": "A",
      "14": "A",
      "15": "A",
      "16": "A",
      "17": "A",
      "18": "A",
      "19": "A",
      "20": "A",
      "21": "A",
      "22": "A",
      "23": "A",
      "24": "A",
      "25": "A",
      "26": "A",
      "27": "A",
      "28": "A",
      "29": "A",
      "30": "A",
      "31": "A"
    },
    "8": {
      "1": "A",
      "2": "A",
      "3": "A",
      "4": "A",
      "5": "A",
      "6": "A",
      "7": "A",
      "8": "A",
      "9": "A",
      "10": "A",
      "11": "A",
      "12": "A",
      "13": "A",
      "14": "A",
      "15": "A",
      "16": "A",
      "17": "A",
      "18": "A",
      "19": "A",
      "20": "A",
      "21": "A",
      "22": "A",
      "23": "A",
      "24": "A",
      "25": "A",
      "26": "A",
      "27": "A",
      "28": "A",
      "29": "A",
      "30": "A",
      "31": "A"
    }
  }
}

云端代码

服务器代码

import json
import time
from datetime import datetime, timedelta
import requests
import os
print("Current working directory:", os.getcwd())

api_token = '你的金山文档表格令牌'

url = "你的金山文档表格webhook地址"
# 设置请求头部
headers = {
    'Content-Type': 'application/json',
    'AirScript-Token': api_token
}

def working():  # 上班通知
    url = "你的通知webhook" 
    requests.post(url)

def no_work():  # 下班通知
    url = "你的通知webhook"
    requests.post(url)

def read_schedule(file_path):
    # 读取 JSON 文件并返回解析后的数据。
    with open(file_path, 'r', encoding='utf-8') as file:
        schedule = json.load(file)
    return schedule

def get_shifts_for_date(schedule, year, month, day):
    # 根据指定的日期获取班次。
    return schedule.get(str(year), {}).get(str(month), {}).get(str(day), "休")

def print_shifts(shift):
    # 根据班次输出上班和下班的时间提示,并设置 state 变量。

    now = datetime.now()  # 获取当前时间
    current_time_obj = datetime.strptime(now.strftime("%H:%M"), "%H:%M").time()  # 当前时间转换为 time 对象

    state = "none"  # 初始化 state 变量为 "none",表示不在任何打卡时间

    if shift == "休":
        print("今天休息")
        state = "rest"
    else:
        start_time_str = "9:00" if shift == "A" else "9:30"  # 根据班次类型确定上班时间
        end_time_str = "18:00" if shift == "A" else "18:30"  # 根据班次类型确定下班时间

        start_time = datetime.strptime(start_time_str, "%H:%M").time()
        end_time = datetime.strptime(end_time_str, "%H:%M").time()

        start_time_datetime = datetime.combine(now.date(), start_time)
        end_time_datetime = datetime.combine(now.date(), end_time)
        start_15_min_later_datetime = start_time_datetime - timedelta(minutes=15)# 上班前15分钟的时间
        end_30_min_later_datetime = end_time_datetime + timedelta(minutes=30)# 在下班时间的基础上加上30分钟
        start_15_min_later = start_15_min_later_datetime.time()# 将结果转换回 time 对象
        end_30_min_later = end_30_min_later_datetime.time()# 将结果转换回 time 对象

        print(f"60-当前时间:{current_time_obj}")#当前时间
        print(f"61-上班时间:{start_time}")#上班时间
        print(f"62-上班开始时间:{start_15_min_later}")#上班开始通知时间
        print(f"63-下班时间:{end_time}")#下班时间
        print(f"64-下班最后时间:{end_30_min_later}")#下班最后通知时间
        # 如果当前时间在上班前15分钟至上班时间之间,则调用通知函数,设置 state 为 "work"
        if start_15_min_later <= current_time_obj < start_time:
            state = "work"
            print("当前在上班时间")
        # 如果当前时间在下班时间至下班后30分钟之间,则调用通知函数,设置 state 为 "worked"
        elif end_time < current_time_obj <= end_30_min_later:
            state = "worked"
            print("当前在下班时间")
        else:
            print("74-当前不在可通知时间")
    return state

if __name__ == "__main__":
    schedule = read_schedule("你的服务器json文件路径")  # 确认JSON文件
    today = datetime.now().strftime("%Y-%m-%d")  # 获取今天的日期
    year, month, day = map(int, today.split("-"))  # 分别获取年、月、日

    shifts = get_shifts_for_date(schedule, year, month, day)  # 获取今天的班次
    print(f"84-当前班次: {shifts}")
    state = print_shifts(shifts)  # 根据班次信息和当前时间确定 state
    print(f"86-当前时间状态: {state}")  # 打印当前状态

    if state == "work":
        message = "A1"
    elif state == "worked":
        message = "A2"
    else:
        message = "A3"    
    print(f"94-当前表格范围状态:{message}")
#设置请求体,包含单元格地址参数
    payload = {
        "Context": {
            "argv": {
            "message": message
            },
        }
    }
    response = requests.post(url, headers=headers, json=payload)#发出请求
    result_data = response.json()#读取请求返回json

    task_result = result_data.get('data', {}).get('result', 'No result data')#读取打卡检测值
    check_value = task_result.get('打卡检测')
    print(f"108-是否已打卡:{str(check_value)}")
    if check_value == "上班未打卡":
        working()
    elif check_value == "下班未打卡":
        no_work()
    else:
        print("114-当前不符合")

金山文档airscript代码

我们需要两个airscript脚本,第一个实现读取当前是否已经打卡,第二个实现打卡后修改表格数据为已打卡,第二个脚本需要使用金山文档定时脚本功能,定时23:30修改表格值为未打卡,为明天的打卡做准备

//此脚本的作用为读取表格值,返回到python中
const message = Context.argv.message || "A3";
//const message = "A3"
const range = ActiveSheet.Range(message)
// 读取范围内的值
const value = range.Value2 // 输出值
console.log(value)
Context.argv.X=value
return {"打卡检测": value};
//此脚本的作用是,手机打卡后,触发webhook,修改表格值为上班/下班已打卡
function A(){
  const message = "A1"
  const data = "上班未打卡"
  const range = ActiveSheet.Range(message)
  range.Value2 = data
}
function B(){
  const message = "A2"
  const data = "下班未打卡"
  const range = ActiveSheet.Range(message)
  range.Value2 = data
}
const data = Context.argv.data || "初始化";
if (data == "初始化") {
  console.log(data);
  A();
  B();
}
else if (data == "上班打卡") {
  const message = Context.argv.message
  const range = ActiveSheet.Range(message)
  range.Value2 = "上班已打卡"
  return {"打卡检测": range.Value2};
}
else if (data == "下班打卡") {
  const message = Context.argv.message
  const range = ActiveSheet.Range(message)
  range.Value2 = "下班已打卡"
  return {"打卡检测": range.Value2};
}

金山文档A1为上班打卡检测,A2为下班打卡检测。这两个格子的值为上班/下班已打卡;上班/下班未打卡。

手机端代码

打卡后使用shell脚本修改云文档数据

判断当前使用上班还是下班打卡,然后修改金山文档表格数据,逻辑交给thanox去判断,手机端代码执行的越快越好。

#!/bin/bash

# 接收thanox传入的参数
if [ "$1" = "上班打卡" ]; then
  data="上班打卡"
  message="A1"
elif [ "$1" = "下班打卡" ]; then
  data="下班打卡"
  message="A2"
fi
# 定义变量
api_token='金山文档airscript脚本令牌'
url='金山文档airscript脚本webhook地址'

# 创建请求体
payload=$(cat <<EOF
{
    "Context": {
        "argv": {
            "message": "$message",
            "data": "$data"
        }
    }
}
EOF
)

# 发送 POST 请求并捕获响应
curl -s -X POST -H "Content-Type: application/json" -H "AirScript-Token: $api_token" -d "$payload" "$url"

#response=$(curl -s -o response.txt -w "%{http_code}" -X POST -H "Content-Type: application/json" -H "AirScript-Token: $api_token" -d "$payload" "$url")

# 检查响应状态码
#if [ "$response" -eq 200 ]; then
    # 解析JSON响应
   # result_data=$(cat response.txt)
   # task_result=$(echo "$result_data" | jq -r '.data.result')

    # 打印结果
   # echo "$result_data"
   # echo "Task result: $task_result"
#else
   # echo "Failed to send request, status code: $response"
#fi

# 清理临时文件
#rm response.txt

thanox 情景模式代码

这段代码的作用是监听通知,关键字“打卡咯”,即打开钉钉。
注意,这里的通知要与上面触发提醒的通知中的关键字有重合,例如我的打卡咯,标题为打卡提醒,正文根据上下班为上班/下班打卡咯,在情景模式代码中,我就设置关键字为打卡咯,这个咯是有存在必要的。

[
  {
    "name": "钉钉打卡",
    "description": "当收到包含'打卡咯'关键词的通知时,自动打开钉钉应用",
    "priority": 1,
    "condition": "notificationAdded == true && notificationContent.contains('打卡咯')",
    "actions": [
      "ui.showShortToast('收到打卡通知,打开钉钉');",
      "activity.launchMainActivityForPackage('com.alibaba.android.rimet')"
    ]
  }
]

这个时候手机会自动打开钉钉,如果我们已经打卡了,那么手机会收到一个通知:xxx公司  考勤打卡:xxx时间 上班/下班打卡·成功,所以我们再使用这段代码,实现收到打卡成功的通知后,自动后台执行打卡成功的shell脚本,修改金山文档表格数据,这里我使用的监听关键词是“打卡·成功”
如果上面的监听关键词是"上班打卡”没有“咯”,那么打卡成功,钉钉发送的通知会导致重复触发打开钉钉。

[
  {
    "name": "上班打卡数据",
    "description": "当收到包含'上班打卡·成功'关键词的通知时,执行指定目录中的Shell脚本",
    "priority": 1,
    "condition": "notificationAdded == true && (notificationTitle.contains('上班打卡·成功') || notificationContent.contains('上班打卡·成功'))",
    "actions": [
      "ui.showShortToast('上班打卡成功,触发数据上传');",
      "su.exe('sh /sdcard/目录/打卡.sh 上班打卡')"
    ]
  }
]
[
  {
    "name": "下班打卡数据",
    "description": "当收到包含'下班打卡·成功'关键词的通知时,执行指定目录中的Shell脚本",
    "priority": 1,
    "condition": "notificationAdded == true && (notificationTitle.contains('下班打卡·成功') || notificationContent.contains('上班打卡·成功'))",
    "actions": [
      "ui.showShortToast('下班打卡成功,触发数据上传');",
      "su.exe('sh /sdcard/目录/打卡.sh 下班打卡')"
    ]
  }
]

通知webhook获取

通知我使用的是饭碗警告,新建转发规则。规则名称:打卡提醒;触发类型:webhook;变量名:message;变量来源:查询字符串;键:message;通知简述:打卡提醒;通知正文:{{message}};其他的默认即可。按照下述的webhook即可触发通知上班/下班打卡。

https://fwalert.com/你的饭碗警告webhook地址?message=上班打卡咯
https://fwalert.com/你的饭碗警告webhook地址?message=下班打卡咯

结尾

这样,我们就实现了,服务器定时循环运行打卡检测,检测到时间符合并且金山文档表格值为未打卡则发出通知,手机接收到通知自动打开钉钉,钉钉打卡后自动修改金山文档表格值。
如果你的手机没有root和xp框架,那么到提醒那一步就停止了,无法自动打卡钉钉和自动修改表格数据,其实使用adb和shizuku也可以,只是比较麻烦。

免费评分

参与人数 6吾爱币 +11 热心值 +5 收起 理由
DreamlingBig + 1 用心讨论,共获提升!
苏紫方璇 + 7 + 1 欢迎分析讨论交流,吾爱破解论坛有你更精彩!
DaShiXiong33 + 1 + 1 谢谢@Thanks!
Littlebsby + 1 热心回复!
Zhyiii + 1 + 1 收获满满
a45723929 + 1 + 1 很强大

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

纤细的企鹅 发表于 2024-8-2 17:27
大佬牛的。但是似乎有更方便的,就是手机的自带的自动任务,小米手机是有的,华为也有,其他手机不清楚。设置出发条件,比如到XX时间,连接蓝牙之后,弹出钉钉之类的,除了定位有偏差以外,总体来说,我设置任务之后没有漏过卡
zhoutong8866 发表于 2024-7-29 16:57
看着很高级的样子?一点也不懂,留给专业人士玩,
yrycw 发表于 2024-7-29 16:58
stormeidolon 发表于 2024-7-29 16:59
非程序人员看不懂,留给需要的人。
wodekuxiao1025 发表于 2024-7-29 17:12
搞这么多P用,直接远程一部公司的手机不香吗
dyk03150088 发表于 2024-7-29 17:15
钉钉打卡有时候忘了,还要被领导PUA,难顶
海是倒过来的天 发表于 2024-7-29 17:30
wodekuxiao1025 发表于 2024-7-29 17:12
搞这么多P用,直接远程一部公司的手机不香吗

这个讨论的不是远程打卡,是忘记打卡。远程的前提是你要记得远程
头像被屏蔽
exitfang 发表于 2024-7-29 17:48
提示: 作者被禁止或删除 内容自动屏蔽
jian1098 发表于 2024-7-29 17:55
手机定个日程或者闹钟不就好了吗
 楼主| Tianyi731 发表于 2024-7-29 17:58
jian1098 发表于 2024-7-29 17:55
手机定个日程或者闹钟不就好了吗

我一开始也是这么想的,但是定1个闹钟有时候不满足打卡条件,比如差几分钟到公司,还有我上班的日期不是那么有规律的,所以一个个定闹钟还麻烦,有时候顺手取消闹钟回头又忘了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则

返回列表

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-11-24 12:58

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表