缩略图

钉钉打卡怎么能忘记

2026年03月26日 文章分类 会被自动插入 会被自动插入
本文最后更新于2026-03-26已经过去了16天请注意内容时效性
热度50 点赞 收藏0 评论0

小弟因为在一家手机公司做技术支持,偶尔需要值班,经常忘记打卡,故写了这个工具,发出来记录一下,顺便让大佬们看看有没有可以优化的地方。 准备工作 云服务器(本地小型服务器)、一台有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也可以,只是比较麻烦。

正文结束 阅读本文相关话题
相关阅读
评论框
管理员开启登录后评论
评论列表

暂时还没有任何评论,快去发表第一条评论吧~

空白列表