from app.service.photo import get_photo
from datetime import timedelta, datetime
from flask_apscheduler import APScheduler
from apscheduler.schedulers.gevent import GeventScheduler

import os
import logging
from flask_jwt_extended.utils import create_access_token
from werkzeug.security import safe_str_cmp
import fcntl
from concurrent.futures import ThreadPoolExecutor
from flask import Flask, request, send_file, jsonify
from flask_jwt_extended import JWTManager, jwt_required, current_user
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
import atexit
from flask_cors import CORS
from app.service.daka import dakala
from app.service.record import dkrecords
from app.config import APP_ADMIN_KEY, APP_SECRET_KEY
import app.userdb as userdb
from flask import render_template

app = Flask(__name__)

jwt = JWTManager(app)


@jwt.user_lookup_loader
def user_lookup(header, data):
    stuid = data["sub"]
    return userdb.db_get_user_by_stuid(stuid)


# gunicorn logger intergation
if __name__ != "__main__":
    gunicorn_logger = logging.getLogger("gunicorn.error")
    app.logger.handlers = gunicorn_logger.handlers
    app.logger.setLevel(gunicorn_logger.level)

CORS(app, resources={r"/*": {"origins": "*"}})

# jwt auth init
app.config["JWT_SECRET_KEY"] = APP_SECRET_KEY  # jwt secret key
app.config["JWT_ACCESS_TOKEN_EXPIRES"] = timedelta(days=1)  # jwt expire time
# jsonify config
app.config["JSON_AS_ASCII"] = False

scheduler = APScheduler(scheduler=GeventScheduler())

# thread_pool
thread_pool = ThreadPoolExecutor(os.cpu_count() + 2)

# scheduler init
JOBS_STORE_LOCATION = os.path.abspath("./data/db/job.db")
app.logger.info(f"定时任务文件位置: {JOBS_STORE_LOCATION}")


class SchedulerConfig:
    SCHEDULER_JOBSTORES = {
        "default": SQLAlchemyJobStore(url=f"sqlite:///{JOBS_STORE_LOCATION}")
    }
    SCHEDULER_TIMEZONE = "Asia/Shanghai"


app.config.from_object(SchedulerConfig())


SCHEDULE_LOCK_FILE = os.path.abspath("./data/db/schedule.lock")
if os.path.exists(SCHEDULE_LOCK_FILE):
    os.remove(SCHEDULE_LOCK_FILE)
app.logger.info(f"锁文件位置: {SCHEDULE_LOCK_FILE}")


def register():
    f = open(SCHEDULE_LOCK_FILE, "wb")
    try:
        fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
        scheduler.init_app(app)
        scheduler.start()
        app.logger.info("定时任务启动")
    except:
        pass

    def unlock():
        fcntl.flock(f, fcntl.LOCK_UN)
        f.close()

    atexit.register(unlock)


register()


@app.route("/stu/login", methods=["POST"])
def user_login():
    rf = request.json
    stuid = rf.get("stuid", None)
    password = rf.get("password", None)
    if not stuid or not password:
        return jsonify({"msg": "用户名或密码错误", "code": 403})
    stu = userdb.db_get_user_by_stuid(stuid)
    if not stu:
        return jsonify({"msg": "用户名或密码错误", "code": 403})
    if not safe_str_cmp(stu["password"], password):
        return jsonify({"msg": "用户名或密码错误", "code": 403})
    token = create_access_token(identity=stuid)
    config = userdb.db_get_user_config(stuid)
    trigger = userdb.db_get_user_daka_trigger(stuid)
    if trigger is None:
        trigger = True
    ck_info = userdb.db_get_dk_callback_info(stuid)
    return jsonify(
        {
            "msg": "登录成功",
            "code": 200,
            "trigger": trigger,
            "ck": ck_info,
            "token": token,
            "stuid": stuid,
            "config": config,
        }
    )


@app.route("/stu/register", methods=["POST"])
def user_register():
    rf = request.json
    required = [
        "stuid",
        "password",
        "cityStatus",
        "workingPlace",
        "healthStatus",
        "livingStatus",
        "homeStatus",
    ]
    if not rf or not all(k in rf for k in required):
        return jsonify({"msg": "必要信息未填写", "code": 403})
    password = rf.get("password", None)
    stuid = rf.get("stuid", None)
    if not stuid.strip() or not password.strip():
        return jsonify({"msg": "学号或密码不正确", "code": 403})
    if len(stuid) < 10:
        return jsonify({"msg": "学号长度不正确", "code": 403})
    if userdb.db_get_user_by_stuid(stuid):
        return jsonify({"msg": "已经被注册", "code": 403})

    config = {
        "homeStatus": rf.get("homeStatus", None),
        "livingStatus": rf.get("livingStatus", None),
        "healthStatus": rf.get("healthStatus", None),
        "workingPlace": rf.get("workingPlace", None),
        "cityStatus": rf.get("cityStatus", None),
        "application_location": rf.get("application_location", None),
        "application_reason": rf.get("application_reason", None),
        "application_start_time": rf.get("application_start_time", None),
        "application_start_day": rf.get("application_start_day", None),
        "application_end_day": rf.get("application_end_day", None),
        "application_end_time": rf.get("application_end_time", None),
        "scheduler_time_segment": rf.get("scheduler_time_segment", None),
        "scheduler_start_time": rf.get("scheduler_start_time", None),
    }
    userdb.db_put_user_info(stuid, password)
    userdb.db_put_user_config(stuid, config)

    app.logger.info(f"学号 {stuid} , 移动端注册成功")
    return jsonify({"msg": "注册成功", "code": 200})


@app.route("/stu/<stuid>/info", methods=["GET"])
@jwt_required()
def user_info(stuid):
    if not safe_str_cmp(stuid, current_user["stuid"]):
        return jsonify({"msg": "不允许访问", "code": 401})
    stu_config = userdb.db_get_user_config(stuid)
    trigger = userdb.db_get_user_daka_trigger(stuid)
    ck_info = userdb.db_get_dk_callback_info(stuid)
    if trigger is None:
        trigger = True
    return jsonify(
        {
            "stuid": stuid,
            "trigger": trigger,
            "ck": ck_info,
            "config": stu_config,
            "code": 200,
        }
    )


@app.route("/stu/<stuid>/daka", methods=["POST"])
@jwt_required()
def user_daka(stuid):
    app.logger.debug(stuid)
    app.logger.debug(current_user["stuid"])
    if not safe_str_cmp(stuid, current_user["stuid"]):
        return jsonify({"msg": "不允许访问", "code": 401})
    app.logger.info(
        f"学号 {stuid},执行了手动打卡,时间为{datetime.now().strftime('%m/%d/%Y, %H:%M:%S')}"
    )
    student = userdb.db_get_user_by_stuid(stuid)
    config = userdb.db_get_user_config(stuid)
    thread_pool.submit(daka_task, student, config)
    return jsonify({"msg": "打卡命令执行成功", "code": 200})


@app.route("/stu/<stuid>/info/update", methods=["POST"])
@jwt_required()
def update_config(stuid):
    if not safe_str_cmp(stuid, current_user["stuid"]):
        return jsonify({"msg": "不允许访问", "code": 401})
    required = ["stuid"]
    rf = request.json
    if not rf or not all(k in rf for k in required):
        return jsonify({"msg": "必要信息缺失", "code": 403})
    origin_conf = userdb.db_get_user_config(rf.get("stuid"))

    config = {
        "homeStatus": rf.get("homeStatus", origin_conf["homeStatus"]),
        "livingStatus": rf.get("livingStatus", origin_conf["livingStatus"]),
        "healthStatus": rf.get("healthStatus", origin_conf["healthStatus"]),
        "workingPlace": rf.get("workingPlace", origin_conf["workingPlace"]),
        "cityStatus": rf.get("cityStatus", origin_conf["cityStatus"]),
        "application_location": rf.get(
            "application_location", origin_conf["application_location"]
        ),
        "application_reason": rf.get(
            "application_reason", origin_conf["application_reason"]
        ),
        "application_start_time": rf.get(
            "application_start_time", origin_conf["application_start_time"]
        ),
        "application_start_day": rf.get(
            "application_start_day", origin_conf["application_start_day"]
        ),
        "application_end_day": rf.get(
            "application_end_day", origin_conf["application_end_day"]
        ),
        "application_end_time": rf.get(
            "application_end_time", origin_conf["application_end_time"]
        ),
        "scheduler_time_segment": rf.get(
            "scheduler_time_segment", origin_conf["scheduler_time_segment"]
        ),
        "scheduler_start_time": rf.get(
            "scheduler_start_time", origin_conf["scheduler_start_time"]
        ),
    }
    userdb.db_put_user_config(stuid, config)

    if rf.get("password", None):
        userdb.db_put_user_info(stuid, rf.get("password"))

    return jsonify({"msg": "更新成功", "config ": config, "code": 200})


@app.route("/stu/<stuid>/photo", methods=["GET"])
@jwt_required()
def user_photo(stuid):
    if not safe_str_cmp(stuid, current_user["stuid"]):
        return jsonify({"msg": "不允许访问", "code": 401})
    pic_path = os.path.abspath(f"./data/pic/{stuid}_img.png")
    if os.path.exists(pic_path):
        return send_file(pic_path)
    return bytes(0)


@app.route("/stu/<stuid>/callback", methods=["GET"])
@jwt_required()
def user_callback(stuid):
    if not safe_str_cmp(stuid, current_user["stuid"]):
        return jsonify({"msg": "不允许访问", "code": 401})
    ck_info = userdb.db_get_dk_callback_info(stuid)
    return jsonify({"ck": ck_info, "code": 200})


@app.route("/stu/<stuid>/del", methods=["POST"])
@jwt_required()
def user_del(stuid):
    if not safe_str_cmp(stuid, current_user["stuid"]):
        return jsonify({"msg": "不允许访问", "code": 401})
    userdb.db_delete_user_info(stuid)
    pic_path = os.path.abspath(f"./data/pic/{stuid}_img.png")
    if os.path.exists(pic_path):
        os.remove(pic_path)
    return jsonify({"msg": "删除成功", "code": 401})


@app.route("/stu/<stuid>/dakatrigger/update", methods=["POST"])
@jwt_required()
def trigger_daka_update(stuid):
    if not safe_str_cmp(stuid, current_user["stuid"]):
        return jsonify({"msg": "不允许访问", "code": 401})
    rf = request.json
    required = ["daka_trigger"]
    if not rf or not all(k in rf for k in required):
        return jsonify({"msg": "必要信息缺失", "code": 403})
    trigger = rf.get("daka_trigger", True)
    userdb.db_put_user_daka_trigger(stuid, trigger)
    return jsonify({"msg": "更新成功", "code": 200, "trigger": trigger})


@app.route("/stu/<stuid>/dakatrigger/info", methods=["GET"])
@jwt_required()
def daka_trigger_info(stuid):
    if not safe_str_cmp(stuid, current_user["stuid"]):
        return jsonify({"msg": "不允许访问", "code": 401})
    flag = userdb.db_get_user_daka_trigger(stuid)
    if flag is None:
        # 默认trigger开启
        flag = True
    return jsonify({"msg": "查询成功", "code": 200, "trigger": flag})


@app.route("/stu/<stuid>/dkrecords/info", methods=["GET"])
@jwt_required()
def get_dk_record(stuid):
    if not safe_str_cmp(stuid, current_user["stuid"]):
        return jsonify({"msg": "不允许访问", "code": 401})
    combo = userdb.db_get_user_daka_combo(stuid)
    records = userdb.db_get_user_daka_records(stuid)
    return jsonify({"msg": "查询成功", "code": 200, "combo": combo, "records": records})


@app.route("/stu/<stuid>/dkrecords/reflush", methods=["POST"])
@jwt_required()
def reflush_dk_record(stuid):
    if not safe_str_cmp(stuid, current_user["stuid"]):
        return jsonify({"msg": "不允许访问", "code": 401})
    lasttime = userdb.db_get_user_relfush_daka_record_time(stuid)
    combo = userdb.db_get_user_daka_combo(stuid)
    records = userdb.db_get_user_daka_records(stuid)
    if lasttime + 60 > int(datetime.now().timestamp()):
        return jsonify({"msg": "刷新成功", "code": 200, "records": records, "combo": combo})
    student = userdb.db_get_user_by_stuid(stuid)
    userdb.db_put_user_reflush_daka_record_time(stuid, datetime.now())
    thread_pool.submit(dkrecords, student)
    return jsonify({"msg": "刷新成功", "code": 200, "records": records, "combo": combo})


@app.route("/stu/<stuid>/dknotify", methods=["POST"])
@jwt_required()
def set_push_setting(stuid):
    if not safe_str_cmp(stuid, current_user["stuid"]):
        return jsonify({"msg": "不允许访问", "code": 401})
    required = ["push_type", "key"]
    rf = request.json
    if not rf or not all(k in rf for k in required):
        return jsonify({"msg": "必要信息缺失", "code": 403})
    ptype = rf.get("push_type", None)
    key = rf.get("key", None)
    userdb.db_put_push_type(stuid, ptype)
    if ptype == "serverchan":
        userdb.db_put_server_chan_key(stuid, key)
    elif ptype == "qmsg":
        userdb.db_put_qmsg_key(stuid, key)
    elif ptype == "email":
        userdb.db_put_user_email(stuid, key)
    return jsonify({"code": 200, "msg": "设置成功", "push_type": ptype, "key": key})


@app.route("/stu/<stuid>/dknotify/info", methods=["POST"])
@jwt_required()
def push_setting_info(stuid):
    if not safe_str_cmp(stuid, current_user["stuid"]):
        return jsonify({"msg": "不允许访问", "code": 401})
    ptype = userdb.db_get_push_type(stuid)
    key = userdb.db_get_push_key_by_type(stuid, ptype)
    return jsonify({"msg": "查询成功", "key": key, "ptype": ptype, "code": 200})


@app.route("/admin/daka/all", methods=["GET"])
def admin_daka_for_all():
    supercode = request.args.get("supercode", None)
    if not supercode:
        return jsonify({"msg": "缺少参数", "code": 403})
    if not APP_ADMIN_KEY:
        return jsonify({"msg": "拒绝访问", "code": 403})
    if not safe_str_cmp(supercode, APP_ADMIN_KEY):
        return jsonify({"msg": "拒绝访问", "code": 401})
    app.logger.info(f'admin daka exec {datetime.now().strftime("%Y/%m/%d")}')
    thread_pool.submit(daka_worker)
    return jsonify({"msg": "执行成功", "code": 200})


@app.route("/stu/<stuid>/dkphoto/reflush",methods=["POST"])
@jwt_required()
def reflush_dk_photo(stuid):
    if not safe_str_cmp(stuid, current_user["stuid"]):
        return jsonify({"msg": "不允许访问", "code": 401})
    app.logger.info(f'{stuid}执行了刷新截图 {datetime.now().strftime("%Y/%m/%d")}')
    student = userdb.db_get_user_by_stuid(stuid)
    thread_pool.submit(get_photo, student)
    return jsonify({"msg": "执行成功", "code": 200})


def daka_task(student, config):
    dakala(student, config)
    dkrecords(student)
    userdb.db_put_user_reflush_daka_record_time(student["stuid"], datetime.now())


def daka_worker():
    all_stu = userdb.find_all_user()
    for stu in all_stu:
        trigger = userdb.db_get_user_daka_trigger(stu["stuid"])
        if trigger is None or trigger is True:
            config = userdb.db_get_user_config(stu["stuid"])
            thread_pool.submit(daka_task, stu, config)
        else:
            sid = stu["stuid"]
            userdb.db_put_dk_callback_info(
                sid, f'{datetime.now().strftime("%Y/%m/%d")} 已暂停打卡'
            )
            app.logger.info(f"{sid} 关闭了每日打卡")


@scheduler.task(
    "cron",
    id="interval_daka",
    timezone="Asia/Shanghai",
    day_of_week="0-6",
    hour=8,
    minute=10,
    misfire_grace_time=36000,
)
# @scheduler.task('cron', id="d", minute='*')  # for test
def interval_daka():
    app.logger.info(f'每日打卡任务执行 {datetime.now().strftime("%Y/%m/%d")}')
    thread_pool.submit(daka_worker)

745ad20829ed0cfb0a825458a69eff2450d8438d
<mask token>


class SchedulerConfig:
    SCHEDULER_JOBSTORES = {'default': SQLAlchemyJobStore(url=
        f'sqlite:///{JOBS_STORE_LOCATION}')}
    SCHEDULER_TIMEZONE = 'Asia/Shanghai'


<mask token>


def register():
    f = open(SCHEDULE_LOCK_FILE, 'wb')
    try:
        fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
        scheduler.init_app(app)
        scheduler.start()
        app.logger.info('定时任务启动')
    except:
        pass

    def unlock():
        fcntl.flock(f, fcntl.LOCK_UN)
        f.close()
    atexit.register(unlock)


<mask token>


@app.route('/stu/login', methods=['POST'])
def user_login():
    rf = request.json
    stuid = rf.get('stuid', None)
    password = rf.get('password', None)
    if not stuid or not password:
        return jsonify({'msg': '用户名或密码错误', 'code': 403})
    stu = userdb.db_get_user_by_stuid(stuid)
    if not stu:
        return jsonify({'msg': '用户名或密码错误', 'code': 403})
    if not safe_str_cmp(stu['password'], password):
        return jsonify({'msg': '用户名或密码错误', 'code': 403})
    token = create_access_token(identity=stuid)
    config = userdb.db_get_user_config(stuid)
    trigger = userdb.db_get_user_daka_trigger(stuid)
    if trigger is None:
        trigger = True
    ck_info = userdb.db_get_dk_callback_info(stuid)
    return jsonify({'msg': '登录成功', 'code': 200, 'trigger': trigger, 'ck':
        ck_info, 'token': token, 'stuid': stuid, 'config': config})


<mask token>


@app.route('/stu/<stuid>/daka', methods=['POST'])
@jwt_required()
def user_daka(stuid):
    app.logger.debug(stuid)
    app.logger.debug(current_user['stuid'])
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    app.logger.info(
        f"学号 {stuid},执行了手动打卡,时间为{datetime.now().strftime('%m/%d/%Y, %H:%M:%S')}"
        )
    student = userdb.db_get_user_by_stuid(stuid)
    config = userdb.db_get_user_config(stuid)
    thread_pool.submit(daka_task, student, config)
    return jsonify({'msg': '打卡命令执行成功', 'code': 200})


<mask token>


@app.route('/stu/<stuid>/photo', methods=['GET'])
@jwt_required()
def user_photo(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    pic_path = os.path.abspath(f'./data/pic/{stuid}_img.png')
    if os.path.exists(pic_path):
        return send_file(pic_path)
    return bytes(0)


@app.route('/stu/<stuid>/callback', methods=['GET'])
@jwt_required()
def user_callback(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    ck_info = userdb.db_get_dk_callback_info(stuid)
    return jsonify({'ck': ck_info, 'code': 200})


@app.route('/stu/<stuid>/del', methods=['POST'])
@jwt_required()
def user_del(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    userdb.db_delete_user_info(stuid)
    pic_path = os.path.abspath(f'./data/pic/{stuid}_img.png')
    if os.path.exists(pic_path):
        os.remove(pic_path)
    return jsonify({'msg': '删除成功', 'code': 401})


@app.route('/stu/<stuid>/dakatrigger/update', methods=['POST'])
@jwt_required()
def trigger_daka_update(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    rf = request.json
    required = ['daka_trigger']
    if not rf or not all(k in rf for k in required):
        return jsonify({'msg': '必要信息缺失', 'code': 403})
    trigger = rf.get('daka_trigger', True)
    userdb.db_put_user_daka_trigger(stuid, trigger)
    return jsonify({'msg': '更新成功', 'code': 200, 'trigger': trigger})


<mask token>


@app.route('/stu/<stuid>/dkrecords/reflush', methods=['POST'])
@jwt_required()
def reflush_dk_record(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    lasttime = userdb.db_get_user_relfush_daka_record_time(stuid)
    combo = userdb.db_get_user_daka_combo(stuid)
    records = userdb.db_get_user_daka_records(stuid)
    if lasttime + 60 > int(datetime.now().timestamp()):
        return jsonify({'msg': '刷新成功', 'code': 200, 'records': records,
            'combo': combo})
    student = userdb.db_get_user_by_stuid(stuid)
    userdb.db_put_user_reflush_daka_record_time(stuid, datetime.now())
    thread_pool.submit(dkrecords, student)
    return jsonify({'msg': '刷新成功', 'code': 200, 'records': records, 'combo':
        combo})


@app.route('/stu/<stuid>/dknotify', methods=['POST'])
@jwt_required()
def set_push_setting(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    required = ['push_type', 'key']
    rf = request.json
    if not rf or not all(k in rf for k in required):
        return jsonify({'msg': '必要信息缺失', 'code': 403})
    ptype = rf.get('push_type', None)
    key = rf.get('key', None)
    userdb.db_put_push_type(stuid, ptype)
    if ptype == 'serverchan':
        userdb.db_put_server_chan_key(stuid, key)
    elif ptype == 'qmsg':
        userdb.db_put_qmsg_key(stuid, key)
    elif ptype == 'email':
        userdb.db_put_user_email(stuid, key)
    return jsonify({'code': 200, 'msg': '设置成功', 'push_type': ptype, 'key': key}
        )


<mask token>


@app.route('/stu/<stuid>/dkphoto/reflush', methods=['POST'])
@jwt_required()
def reflush_dk_photo(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    app.logger.info(f"{stuid}执行了刷新截图 {datetime.now().strftime('%Y/%m/%d')}")
    student = userdb.db_get_user_by_stuid(stuid)
    thread_pool.submit(get_photo, student)
    return jsonify({'msg': '执行成功', 'code': 200})


<mask token>


def daka_worker():
    all_stu = userdb.find_all_user()
    for stu in all_stu:
        trigger = userdb.db_get_user_daka_trigger(stu['stuid'])
        if trigger is None or trigger is True:
            config = userdb.db_get_user_config(stu['stuid'])
            thread_pool.submit(daka_task, stu, config)
        else:
            sid = stu['stuid']
            userdb.db_put_dk_callback_info(sid,
                f"{datetime.now().strftime('%Y/%m/%d')} 已暂停打卡")
            app.logger.info(f'{sid} 关闭了每日打卡')


@scheduler.task('cron', id='interval_daka', timezone='Asia/Shanghai',
    day_of_week='0-6', hour=8, minute=10, misfire_grace_time=36000)
def interval_daka():
    app.logger.info(f"每日打卡任务执行 {datetime.now().strftime('%Y/%m/%d')}")
    thread_pool.submit(daka_worker)

<mask token>


class SchedulerConfig:
    SCHEDULER_JOBSTORES = {'default': SQLAlchemyJobStore(url=
        f'sqlite:///{JOBS_STORE_LOCATION}')}
    SCHEDULER_TIMEZONE = 'Asia/Shanghai'


<mask token>


def register():
    f = open(SCHEDULE_LOCK_FILE, 'wb')
    try:
        fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
        scheduler.init_app(app)
        scheduler.start()
        app.logger.info('定时任务启动')
    except:
        pass

    def unlock():
        fcntl.flock(f, fcntl.LOCK_UN)
        f.close()
    atexit.register(unlock)


<mask token>


@app.route('/stu/login', methods=['POST'])
def user_login():
    rf = request.json
    stuid = rf.get('stuid', None)
    password = rf.get('password', None)
    if not stuid or not password:
        return jsonify({'msg': '用户名或密码错误', 'code': 403})
    stu = userdb.db_get_user_by_stuid(stuid)
    if not stu:
        return jsonify({'msg': '用户名或密码错误', 'code': 403})
    if not safe_str_cmp(stu['password'], password):
        return jsonify({'msg': '用户名或密码错误', 'code': 403})
    token = create_access_token(identity=stuid)
    config = userdb.db_get_user_config(stuid)
    trigger = userdb.db_get_user_daka_trigger(stuid)
    if trigger is None:
        trigger = True
    ck_info = userdb.db_get_dk_callback_info(stuid)
    return jsonify({'msg': '登录成功', 'code': 200, 'trigger': trigger, 'ck':
        ck_info, 'token': token, 'stuid': stuid, 'config': config})


<mask token>


@app.route('/stu/<stuid>/info', methods=['GET'])
@jwt_required()
def user_info(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    stu_config = userdb.db_get_user_config(stuid)
    trigger = userdb.db_get_user_daka_trigger(stuid)
    ck_info = userdb.db_get_dk_callback_info(stuid)
    if trigger is None:
        trigger = True
    return jsonify({'stuid': stuid, 'trigger': trigger, 'ck': ck_info,
        'config': stu_config, 'code': 200})


@app.route('/stu/<stuid>/daka', methods=['POST'])
@jwt_required()
def user_daka(stuid):
    app.logger.debug(stuid)
    app.logger.debug(current_user['stuid'])
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    app.logger.info(
        f"学号 {stuid},执行了手动打卡,时间为{datetime.now().strftime('%m/%d/%Y, %H:%M:%S')}"
        )
    student = userdb.db_get_user_by_stuid(stuid)
    config = userdb.db_get_user_config(stuid)
    thread_pool.submit(daka_task, student, config)
    return jsonify({'msg': '打卡命令执行成功', 'code': 200})


<mask token>


@app.route('/stu/<stuid>/photo', methods=['GET'])
@jwt_required()
def user_photo(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    pic_path = os.path.abspath(f'./data/pic/{stuid}_img.png')
    if os.path.exists(pic_path):
        return send_file(pic_path)
    return bytes(0)


@app.route('/stu/<stuid>/callback', methods=['GET'])
@jwt_required()
def user_callback(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    ck_info = userdb.db_get_dk_callback_info(stuid)
    return jsonify({'ck': ck_info, 'code': 200})


@app.route('/stu/<stuid>/del', methods=['POST'])
@jwt_required()
def user_del(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    userdb.db_delete_user_info(stuid)
    pic_path = os.path.abspath(f'./data/pic/{stuid}_img.png')
    if os.path.exists(pic_path):
        os.remove(pic_path)
    return jsonify({'msg': '删除成功', 'code': 401})


@app.route('/stu/<stuid>/dakatrigger/update', methods=['POST'])
@jwt_required()
def trigger_daka_update(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    rf = request.json
    required = ['daka_trigger']
    if not rf or not all(k in rf for k in required):
        return jsonify({'msg': '必要信息缺失', 'code': 403})
    trigger = rf.get('daka_trigger', True)
    userdb.db_put_user_daka_trigger(stuid, trigger)
    return jsonify({'msg': '更新成功', 'code': 200, 'trigger': trigger})


@app.route('/stu/<stuid>/dakatrigger/info', methods=['GET'])
@jwt_required()
def daka_trigger_info(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    flag = userdb.db_get_user_daka_trigger(stuid)
    if flag is None:
        flag = True
    return jsonify({'msg': '查询成功', 'code': 200, 'trigger': flag})


@app.route('/stu/<stuid>/dkrecords/info', methods=['GET'])
@jwt_required()
def get_dk_record(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    combo = userdb.db_get_user_daka_combo(stuid)
    records = userdb.db_get_user_daka_records(stuid)
    return jsonify({'msg': '查询成功', 'code': 200, 'combo': combo, 'records':
        records})


@app.route('/stu/<stuid>/dkrecords/reflush', methods=['POST'])
@jwt_required()
def reflush_dk_record(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    lasttime = userdb.db_get_user_relfush_daka_record_time(stuid)
    combo = userdb.db_get_user_daka_combo(stuid)
    records = userdb.db_get_user_daka_records(stuid)
    if lasttime + 60 > int(datetime.now().timestamp()):
        return jsonify({'msg': '刷新成功', 'code': 200, 'records': records,
            'combo': combo})
    student = userdb.db_get_user_by_stuid(stuid)
    userdb.db_put_user_reflush_daka_record_time(stuid, datetime.now())
    thread_pool.submit(dkrecords, student)
    return jsonify({'msg': '刷新成功', 'code': 200, 'records': records, 'combo':
        combo})


@app.route('/stu/<stuid>/dknotify', methods=['POST'])
@jwt_required()
def set_push_setting(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    required = ['push_type', 'key']
    rf = request.json
    if not rf or not all(k in rf for k in required):
        return jsonify({'msg': '必要信息缺失', 'code': 403})
    ptype = rf.get('push_type', None)
    key = rf.get('key', None)
    userdb.db_put_push_type(stuid, ptype)
    if ptype == 'serverchan':
        userdb.db_put_server_chan_key(stuid, key)
    elif ptype == 'qmsg':
        userdb.db_put_qmsg_key(stuid, key)
    elif ptype == 'email':
        userdb.db_put_user_email(stuid, key)
    return jsonify({'code': 200, 'msg': '设置成功', 'push_type': ptype, 'key': key}
        )


<mask token>


@app.route('/stu/<stuid>/dkphoto/reflush', methods=['POST'])
@jwt_required()
def reflush_dk_photo(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    app.logger.info(f"{stuid}执行了刷新截图 {datetime.now().strftime('%Y/%m/%d')}")
    student = userdb.db_get_user_by_stuid(stuid)
    thread_pool.submit(get_photo, student)
    return jsonify({'msg': '执行成功', 'code': 200})


def daka_task(student, config):
    dakala(student, config)
    dkrecords(student)
    userdb.db_put_user_reflush_daka_record_time(student['stuid'], datetime.
        now())


def daka_worker():
    all_stu = userdb.find_all_user()
    for stu in all_stu:
        trigger = userdb.db_get_user_daka_trigger(stu['stuid'])
        if trigger is None or trigger is True:
            config = userdb.db_get_user_config(stu['stuid'])
            thread_pool.submit(daka_task, stu, config)
        else:
            sid = stu['stuid']
            userdb.db_put_dk_callback_info(sid,
                f"{datetime.now().strftime('%Y/%m/%d')} 已暂停打卡")
            app.logger.info(f'{sid} 关闭了每日打卡')


@scheduler.task('cron', id='interval_daka', timezone='Asia/Shanghai',
    day_of_week='0-6', hour=8, minute=10, misfire_grace_time=36000)
def interval_daka():
    app.logger.info(f"每日打卡任务执行 {datetime.now().strftime('%Y/%m/%d')}")
    thread_pool.submit(daka_worker)

<mask token>


@jwt.user_lookup_loader
def user_lookup(header, data):
    stuid = data['sub']
    return userdb.db_get_user_by_stuid(stuid)


if __name__ != '__main__':
    gunicorn_logger = logging.getLogger('gunicorn.error')
    app.logger.handlers = gunicorn_logger.handlers
    app.logger.setLevel(gunicorn_logger.level)
CORS(app, resources={'/*': {'origins': '*'}})
<mask token>
app.logger.info(f'定时任务文件位置: {JOBS_STORE_LOCATION}')


class SchedulerConfig:
    SCHEDULER_JOBSTORES = {'default': SQLAlchemyJobStore(url=
        f'sqlite:///{JOBS_STORE_LOCATION}')}
    SCHEDULER_TIMEZONE = 'Asia/Shanghai'


app.config.from_object(SchedulerConfig())
<mask token>
if os.path.exists(SCHEDULE_LOCK_FILE):
    os.remove(SCHEDULE_LOCK_FILE)
app.logger.info(f'锁文件位置: {SCHEDULE_LOCK_FILE}')


def register():
    f = open(SCHEDULE_LOCK_FILE, 'wb')
    try:
        fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
        scheduler.init_app(app)
        scheduler.start()
        app.logger.info('定时任务启动')
    except:
        pass

    def unlock():
        fcntl.flock(f, fcntl.LOCK_UN)
        f.close()
    atexit.register(unlock)


register()


@app.route('/stu/login', methods=['POST'])
def user_login():
    rf = request.json
    stuid = rf.get('stuid', None)
    password = rf.get('password', None)
    if not stuid or not password:
        return jsonify({'msg': '用户名或密码错误', 'code': 403})
    stu = userdb.db_get_user_by_stuid(stuid)
    if not stu:
        return jsonify({'msg': '用户名或密码错误', 'code': 403})
    if not safe_str_cmp(stu['password'], password):
        return jsonify({'msg': '用户名或密码错误', 'code': 403})
    token = create_access_token(identity=stuid)
    config = userdb.db_get_user_config(stuid)
    trigger = userdb.db_get_user_daka_trigger(stuid)
    if trigger is None:
        trigger = True
    ck_info = userdb.db_get_dk_callback_info(stuid)
    return jsonify({'msg': '登录成功', 'code': 200, 'trigger': trigger, 'ck':
        ck_info, 'token': token, 'stuid': stuid, 'config': config})


@app.route('/stu/register', methods=['POST'])
def user_register():
    rf = request.json
    required = ['stuid', 'password', 'cityStatus', 'workingPlace',
        'healthStatus', 'livingStatus', 'homeStatus']
    if not rf or not all(k in rf for k in required):
        return jsonify({'msg': '必要信息未填写', 'code': 403})
    password = rf.get('password', None)
    stuid = rf.get('stuid', None)
    if not stuid.strip() or not password.strip():
        return jsonify({'msg': '学号或密码不正确', 'code': 403})
    if len(stuid) < 10:
        return jsonify({'msg': '学号长度不正确', 'code': 403})
    if userdb.db_get_user_by_stuid(stuid):
        return jsonify({'msg': '已经被注册', 'code': 403})
    config = {'homeStatus': rf.get('homeStatus', None), 'livingStatus': rf.
        get('livingStatus', None), 'healthStatus': rf.get('healthStatus',
        None), 'workingPlace': rf.get('workingPlace', None), 'cityStatus':
        rf.get('cityStatus', None), 'application_location': rf.get(
        'application_location', None), 'application_reason': rf.get(
        'application_reason', None), 'application_start_time': rf.get(
        'application_start_time', None), 'application_start_day': rf.get(
        'application_start_day', None), 'application_end_day': rf.get(
        'application_end_day', None), 'application_end_time': rf.get(
        'application_end_time', None), 'scheduler_time_segment': rf.get(
        'scheduler_time_segment', None), 'scheduler_start_time': rf.get(
        'scheduler_start_time', None)}
    userdb.db_put_user_info(stuid, password)
    userdb.db_put_user_config(stuid, config)
    app.logger.info(f'学号 {stuid} , 移动端注册成功')
    return jsonify({'msg': '注册成功', 'code': 200})


@app.route('/stu/<stuid>/info', methods=['GET'])
@jwt_required()
def user_info(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    stu_config = userdb.db_get_user_config(stuid)
    trigger = userdb.db_get_user_daka_trigger(stuid)
    ck_info = userdb.db_get_dk_callback_info(stuid)
    if trigger is None:
        trigger = True
    return jsonify({'stuid': stuid, 'trigger': trigger, 'ck': ck_info,
        'config': stu_config, 'code': 200})


@app.route('/stu/<stuid>/daka', methods=['POST'])
@jwt_required()
def user_daka(stuid):
    app.logger.debug(stuid)
    app.logger.debug(current_user['stuid'])
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    app.logger.info(
        f"学号 {stuid},执行了手动打卡,时间为{datetime.now().strftime('%m/%d/%Y, %H:%M:%S')}"
        )
    student = userdb.db_get_user_by_stuid(stuid)
    config = userdb.db_get_user_config(stuid)
    thread_pool.submit(daka_task, student, config)
    return jsonify({'msg': '打卡命令执行成功', 'code': 200})


@app.route('/stu/<stuid>/info/update', methods=['POST'])
@jwt_required()
def update_config(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    required = ['stuid']
    rf = request.json
    if not rf or not all(k in rf for k in required):
        return jsonify({'msg': '必要信息缺失', 'code': 403})
    origin_conf = userdb.db_get_user_config(rf.get('stuid'))
    config = {'homeStatus': rf.get('homeStatus', origin_conf['homeStatus']),
        'livingStatus': rf.get('livingStatus', origin_conf['livingStatus']),
        'healthStatus': rf.get('healthStatus', origin_conf['healthStatus']),
        'workingPlace': rf.get('workingPlace', origin_conf['workingPlace']),
        'cityStatus': rf.get('cityStatus', origin_conf['cityStatus']),
        'application_location': rf.get('application_location', origin_conf[
        'application_location']), 'application_reason': rf.get(
        'application_reason', origin_conf['application_reason']),
        'application_start_time': rf.get('application_start_time',
        origin_conf['application_start_time']), 'application_start_day': rf
        .get('application_start_day', origin_conf['application_start_day']),
        'application_end_day': rf.get('application_end_day', origin_conf[
        'application_end_day']), 'application_end_time': rf.get(
        'application_end_time', origin_conf['application_end_time']),
        'scheduler_time_segment': rf.get('scheduler_time_segment',
        origin_conf['scheduler_time_segment']), 'scheduler_start_time': rf.
        get('scheduler_start_time', origin_conf['scheduler_start_time'])}
    userdb.db_put_user_config(stuid, config)
    if rf.get('password', None):
        userdb.db_put_user_info(stuid, rf.get('password'))
    return jsonify({'msg': '更新成功', 'config ': config, 'code': 200})


@app.route('/stu/<stuid>/photo', methods=['GET'])
@jwt_required()
def user_photo(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    pic_path = os.path.abspath(f'./data/pic/{stuid}_img.png')
    if os.path.exists(pic_path):
        return send_file(pic_path)
    return bytes(0)


@app.route('/stu/<stuid>/callback', methods=['GET'])
@jwt_required()
def user_callback(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    ck_info = userdb.db_get_dk_callback_info(stuid)
    return jsonify({'ck': ck_info, 'code': 200})


@app.route('/stu/<stuid>/del', methods=['POST'])
@jwt_required()
def user_del(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    userdb.db_delete_user_info(stuid)
    pic_path = os.path.abspath(f'./data/pic/{stuid}_img.png')
    if os.path.exists(pic_path):
        os.remove(pic_path)
    return jsonify({'msg': '删除成功', 'code': 401})


@app.route('/stu/<stuid>/dakatrigger/update', methods=['POST'])
@jwt_required()
def trigger_daka_update(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    rf = request.json
    required = ['daka_trigger']
    if not rf or not all(k in rf for k in required):
        return jsonify({'msg': '必要信息缺失', 'code': 403})
    trigger = rf.get('daka_trigger', True)
    userdb.db_put_user_daka_trigger(stuid, trigger)
    return jsonify({'msg': '更新成功', 'code': 200, 'trigger': trigger})


@app.route('/stu/<stuid>/dakatrigger/info', methods=['GET'])
@jwt_required()
def daka_trigger_info(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    flag = userdb.db_get_user_daka_trigger(stuid)
    if flag is None:
        flag = True
    return jsonify({'msg': '查询成功', 'code': 200, 'trigger': flag})


@app.route('/stu/<stuid>/dkrecords/info', methods=['GET'])
@jwt_required()
def get_dk_record(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    combo = userdb.db_get_user_daka_combo(stuid)
    records = userdb.db_get_user_daka_records(stuid)
    return jsonify({'msg': '查询成功', 'code': 200, 'combo': combo, 'records':
        records})


@app.route('/stu/<stuid>/dkrecords/reflush', methods=['POST'])
@jwt_required()
def reflush_dk_record(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    lasttime = userdb.db_get_user_relfush_daka_record_time(stuid)
    combo = userdb.db_get_user_daka_combo(stuid)
    records = userdb.db_get_user_daka_records(stuid)
    if lasttime + 60 > int(datetime.now().timestamp()):
        return jsonify({'msg': '刷新成功', 'code': 200, 'records': records,
            'combo': combo})
    student = userdb.db_get_user_by_stuid(stuid)
    userdb.db_put_user_reflush_daka_record_time(stuid, datetime.now())
    thread_pool.submit(dkrecords, student)
    return jsonify({'msg': '刷新成功', 'code': 200, 'records': records, 'combo':
        combo})


@app.route('/stu/<stuid>/dknotify', methods=['POST'])
@jwt_required()
def set_push_setting(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    required = ['push_type', 'key']
    rf = request.json
    if not rf or not all(k in rf for k in required):
        return jsonify({'msg': '必要信息缺失', 'code': 403})
    ptype = rf.get('push_type', None)
    key = rf.get('key', None)
    userdb.db_put_push_type(stuid, ptype)
    if ptype == 'serverchan':
        userdb.db_put_server_chan_key(stuid, key)
    elif ptype == 'qmsg':
        userdb.db_put_qmsg_key(stuid, key)
    elif ptype == 'email':
        userdb.db_put_user_email(stuid, key)
    return jsonify({'code': 200, 'msg': '设置成功', 'push_type': ptype, 'key': key}
        )


@app.route('/stu/<stuid>/dknotify/info', methods=['POST'])
@jwt_required()
def push_setting_info(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    ptype = userdb.db_get_push_type(stuid)
    key = userdb.db_get_push_key_by_type(stuid, ptype)
    return jsonify({'msg': '查询成功', 'key': key, 'ptype': ptype, 'code': 200})


@app.route('/admin/daka/all', methods=['GET'])
def admin_daka_for_all():
    supercode = request.args.get('supercode', None)
    if not supercode:
        return jsonify({'msg': '缺少参数', 'code': 403})
    if not APP_ADMIN_KEY:
        return jsonify({'msg': '拒绝访问', 'code': 403})
    if not safe_str_cmp(supercode, APP_ADMIN_KEY):
        return jsonify({'msg': '拒绝访问', 'code': 401})
    app.logger.info(f"admin daka exec {datetime.now().strftime('%Y/%m/%d')}")
    thread_pool.submit(daka_worker)
    return jsonify({'msg': '执行成功', 'code': 200})


@app.route('/stu/<stuid>/dkphoto/reflush', methods=['POST'])
@jwt_required()
def reflush_dk_photo(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    app.logger.info(f"{stuid}执行了刷新截图 {datetime.now().strftime('%Y/%m/%d')}")
    student = userdb.db_get_user_by_stuid(stuid)
    thread_pool.submit(get_photo, student)
    return jsonify({'msg': '执行成功', 'code': 200})


def daka_task(student, config):
    dakala(student, config)
    dkrecords(student)
    userdb.db_put_user_reflush_daka_record_time(student['stuid'], datetime.
        now())


def daka_worker():
    all_stu = userdb.find_all_user()
    for stu in all_stu:
        trigger = userdb.db_get_user_daka_trigger(stu['stuid'])
        if trigger is None or trigger is True:
            config = userdb.db_get_user_config(stu['stuid'])
            thread_pool.submit(daka_task, stu, config)
        else:
            sid = stu['stuid']
            userdb.db_put_dk_callback_info(sid,
                f"{datetime.now().strftime('%Y/%m/%d')} 已暂停打卡")
            app.logger.info(f'{sid} 关闭了每日打卡')


@scheduler.task('cron', id='interval_daka', timezone='Asia/Shanghai',
    day_of_week='0-6', hour=8, minute=10, misfire_grace_time=36000)
def interval_daka():
    app.logger.info(f"每日打卡任务执行 {datetime.now().strftime('%Y/%m/%d')}")
    thread_pool.submit(daka_worker)

from app.service.photo import get_photo
from datetime import timedelta, datetime
from flask_apscheduler import APScheduler
from apscheduler.schedulers.gevent import GeventScheduler
import os
import logging
from flask_jwt_extended.utils import create_access_token
from werkzeug.security import safe_str_cmp
import fcntl
from concurrent.futures import ThreadPoolExecutor
from flask import Flask, request, send_file, jsonify
from flask_jwt_extended import JWTManager, jwt_required, current_user
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
import atexit
from flask_cors import CORS
from app.service.daka import dakala
from app.service.record import dkrecords
from app.config import APP_ADMIN_KEY, APP_SECRET_KEY
import app.userdb as userdb
from flask import render_template
app = Flask(__name__)
jwt = JWTManager(app)


@jwt.user_lookup_loader
def user_lookup(header, data):
    stuid = data['sub']
    return userdb.db_get_user_by_stuid(stuid)


if __name__ != '__main__':
    gunicorn_logger = logging.getLogger('gunicorn.error')
    app.logger.handlers = gunicorn_logger.handlers
    app.logger.setLevel(gunicorn_logger.level)
CORS(app, resources={'/*': {'origins': '*'}})
app.config['JWT_SECRET_KEY'] = APP_SECRET_KEY
app.config['JWT_ACCESS_TOKEN_EXPIRES'] = timedelta(days=1)
app.config['JSON_AS_ASCII'] = False
scheduler = APScheduler(scheduler=GeventScheduler())
thread_pool = ThreadPoolExecutor(os.cpu_count() + 2)
JOBS_STORE_LOCATION = os.path.abspath('./data/db/job.db')
app.logger.info(f'定时任务文件位置: {JOBS_STORE_LOCATION}')


class SchedulerConfig:
    SCHEDULER_JOBSTORES = {'default': SQLAlchemyJobStore(url=
        f'sqlite:///{JOBS_STORE_LOCATION}')}
    SCHEDULER_TIMEZONE = 'Asia/Shanghai'


app.config.from_object(SchedulerConfig())
SCHEDULE_LOCK_FILE = os.path.abspath('./data/db/schedule.lock')
if os.path.exists(SCHEDULE_LOCK_FILE):
    os.remove(SCHEDULE_LOCK_FILE)
app.logger.info(f'锁文件位置: {SCHEDULE_LOCK_FILE}')


def register():
    f = open(SCHEDULE_LOCK_FILE, 'wb')
    try:
        fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
        scheduler.init_app(app)
        scheduler.start()
        app.logger.info('定时任务启动')
    except:
        pass

    def unlock():
        fcntl.flock(f, fcntl.LOCK_UN)
        f.close()
    atexit.register(unlock)


register()


@app.route('/stu/login', methods=['POST'])
def user_login():
    rf = request.json
    stuid = rf.get('stuid', None)
    password = rf.get('password', None)
    if not stuid or not password:
        return jsonify({'msg': '用户名或密码错误', 'code': 403})
    stu = userdb.db_get_user_by_stuid(stuid)
    if not stu:
        return jsonify({'msg': '用户名或密码错误', 'code': 403})
    if not safe_str_cmp(stu['password'], password):
        return jsonify({'msg': '用户名或密码错误', 'code': 403})
    token = create_access_token(identity=stuid)
    config = userdb.db_get_user_config(stuid)
    trigger = userdb.db_get_user_daka_trigger(stuid)
    if trigger is None:
        trigger = True
    ck_info = userdb.db_get_dk_callback_info(stuid)
    return jsonify({'msg': '登录成功', 'code': 200, 'trigger': trigger, 'ck':
        ck_info, 'token': token, 'stuid': stuid, 'config': config})


@app.route('/stu/register', methods=['POST'])
def user_register():
    rf = request.json
    required = ['stuid', 'password', 'cityStatus', 'workingPlace',
        'healthStatus', 'livingStatus', 'homeStatus']
    if not rf or not all(k in rf for k in required):
        return jsonify({'msg': '必要信息未填写', 'code': 403})
    password = rf.get('password', None)
    stuid = rf.get('stuid', None)
    if not stuid.strip() or not password.strip():
        return jsonify({'msg': '学号或密码不正确', 'code': 403})
    if len(stuid) < 10:
        return jsonify({'msg': '学号长度不正确', 'code': 403})
    if userdb.db_get_user_by_stuid(stuid):
        return jsonify({'msg': '已经被注册', 'code': 403})
    config = {'homeStatus': rf.get('homeStatus', None), 'livingStatus': rf.
        get('livingStatus', None), 'healthStatus': rf.get('healthStatus',
        None), 'workingPlace': rf.get('workingPlace', None), 'cityStatus':
        rf.get('cityStatus', None), 'application_location': rf.get(
        'application_location', None), 'application_reason': rf.get(
        'application_reason', None), 'application_start_time': rf.get(
        'application_start_time', None), 'application_start_day': rf.get(
        'application_start_day', None), 'application_end_day': rf.get(
        'application_end_day', None), 'application_end_time': rf.get(
        'application_end_time', None), 'scheduler_time_segment': rf.get(
        'scheduler_time_segment', None), 'scheduler_start_time': rf.get(
        'scheduler_start_time', None)}
    userdb.db_put_user_info(stuid, password)
    userdb.db_put_user_config(stuid, config)
    app.logger.info(f'学号 {stuid} , 移动端注册成功')
    return jsonify({'msg': '注册成功', 'code': 200})


@app.route('/stu/<stuid>/info', methods=['GET'])
@jwt_required()
def user_info(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    stu_config = userdb.db_get_user_config(stuid)
    trigger = userdb.db_get_user_daka_trigger(stuid)
    ck_info = userdb.db_get_dk_callback_info(stuid)
    if trigger is None:
        trigger = True
    return jsonify({'stuid': stuid, 'trigger': trigger, 'ck': ck_info,
        'config': stu_config, 'code': 200})


@app.route('/stu/<stuid>/daka', methods=['POST'])
@jwt_required()
def user_daka(stuid):
    app.logger.debug(stuid)
    app.logger.debug(current_user['stuid'])
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    app.logger.info(
        f"学号 {stuid},执行了手动打卡,时间为{datetime.now().strftime('%m/%d/%Y, %H:%M:%S')}"
        )
    student = userdb.db_get_user_by_stuid(stuid)
    config = userdb.db_get_user_config(stuid)
    thread_pool.submit(daka_task, student, config)
    return jsonify({'msg': '打卡命令执行成功', 'code': 200})


@app.route('/stu/<stuid>/info/update', methods=['POST'])
@jwt_required()
def update_config(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    required = ['stuid']
    rf = request.json
    if not rf or not all(k in rf for k in required):
        return jsonify({'msg': '必要信息缺失', 'code': 403})
    origin_conf = userdb.db_get_user_config(rf.get('stuid'))
    config = {'homeStatus': rf.get('homeStatus', origin_conf['homeStatus']),
        'livingStatus': rf.get('livingStatus', origin_conf['livingStatus']),
        'healthStatus': rf.get('healthStatus', origin_conf['healthStatus']),
        'workingPlace': rf.get('workingPlace', origin_conf['workingPlace']),
        'cityStatus': rf.get('cityStatus', origin_conf['cityStatus']),
        'application_location': rf.get('application_location', origin_conf[
        'application_location']), 'application_reason': rf.get(
        'application_reason', origin_conf['application_reason']),
        'application_start_time': rf.get('application_start_time',
        origin_conf['application_start_time']), 'application_start_day': rf
        .get('application_start_day', origin_conf['application_start_day']),
        'application_end_day': rf.get('application_end_day', origin_conf[
        'application_end_day']), 'application_end_time': rf.get(
        'application_end_time', origin_conf['application_end_time']),
        'scheduler_time_segment': rf.get('scheduler_time_segment',
        origin_conf['scheduler_time_segment']), 'scheduler_start_time': rf.
        get('scheduler_start_time', origin_conf['scheduler_start_time'])}
    userdb.db_put_user_config(stuid, config)
    if rf.get('password', None):
        userdb.db_put_user_info(stuid, rf.get('password'))
    return jsonify({'msg': '更新成功', 'config ': config, 'code': 200})


@app.route('/stu/<stuid>/photo', methods=['GET'])
@jwt_required()
def user_photo(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    pic_path = os.path.abspath(f'./data/pic/{stuid}_img.png')
    if os.path.exists(pic_path):
        return send_file(pic_path)
    return bytes(0)


@app.route('/stu/<stuid>/callback', methods=['GET'])
@jwt_required()
def user_callback(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    ck_info = userdb.db_get_dk_callback_info(stuid)
    return jsonify({'ck': ck_info, 'code': 200})


@app.route('/stu/<stuid>/del', methods=['POST'])
@jwt_required()
def user_del(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    userdb.db_delete_user_info(stuid)
    pic_path = os.path.abspath(f'./data/pic/{stuid}_img.png')
    if os.path.exists(pic_path):
        os.remove(pic_path)
    return jsonify({'msg': '删除成功', 'code': 401})


@app.route('/stu/<stuid>/dakatrigger/update', methods=['POST'])
@jwt_required()
def trigger_daka_update(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    rf = request.json
    required = ['daka_trigger']
    if not rf or not all(k in rf for k in required):
        return jsonify({'msg': '必要信息缺失', 'code': 403})
    trigger = rf.get('daka_trigger', True)
    userdb.db_put_user_daka_trigger(stuid, trigger)
    return jsonify({'msg': '更新成功', 'code': 200, 'trigger': trigger})


@app.route('/stu/<stuid>/dakatrigger/info', methods=['GET'])
@jwt_required()
def daka_trigger_info(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    flag = userdb.db_get_user_daka_trigger(stuid)
    if flag is None:
        flag = True
    return jsonify({'msg': '查询成功', 'code': 200, 'trigger': flag})


@app.route('/stu/<stuid>/dkrecords/info', methods=['GET'])
@jwt_required()
def get_dk_record(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    combo = userdb.db_get_user_daka_combo(stuid)
    records = userdb.db_get_user_daka_records(stuid)
    return jsonify({'msg': '查询成功', 'code': 200, 'combo': combo, 'records':
        records})


@app.route('/stu/<stuid>/dkrecords/reflush', methods=['POST'])
@jwt_required()
def reflush_dk_record(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    lasttime = userdb.db_get_user_relfush_daka_record_time(stuid)
    combo = userdb.db_get_user_daka_combo(stuid)
    records = userdb.db_get_user_daka_records(stuid)
    if lasttime + 60 > int(datetime.now().timestamp()):
        return jsonify({'msg': '刷新成功', 'code': 200, 'records': records,
            'combo': combo})
    student = userdb.db_get_user_by_stuid(stuid)
    userdb.db_put_user_reflush_daka_record_time(stuid, datetime.now())
    thread_pool.submit(dkrecords, student)
    return jsonify({'msg': '刷新成功', 'code': 200, 'records': records, 'combo':
        combo})


@app.route('/stu/<stuid>/dknotify', methods=['POST'])
@jwt_required()
def set_push_setting(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    required = ['push_type', 'key']
    rf = request.json
    if not rf or not all(k in rf for k in required):
        return jsonify({'msg': '必要信息缺失', 'code': 403})
    ptype = rf.get('push_type', None)
    key = rf.get('key', None)
    userdb.db_put_push_type(stuid, ptype)
    if ptype == 'serverchan':
        userdb.db_put_server_chan_key(stuid, key)
    elif ptype == 'qmsg':
        userdb.db_put_qmsg_key(stuid, key)
    elif ptype == 'email':
        userdb.db_put_user_email(stuid, key)
    return jsonify({'code': 200, 'msg': '设置成功', 'push_type': ptype, 'key': key}
        )


@app.route('/stu/<stuid>/dknotify/info', methods=['POST'])
@jwt_required()
def push_setting_info(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    ptype = userdb.db_get_push_type(stuid)
    key = userdb.db_get_push_key_by_type(stuid, ptype)
    return jsonify({'msg': '查询成功', 'key': key, 'ptype': ptype, 'code': 200})


@app.route('/admin/daka/all', methods=['GET'])
def admin_daka_for_all():
    supercode = request.args.get('supercode', None)
    if not supercode:
        return jsonify({'msg': '缺少参数', 'code': 403})
    if not APP_ADMIN_KEY:
        return jsonify({'msg': '拒绝访问', 'code': 403})
    if not safe_str_cmp(supercode, APP_ADMIN_KEY):
        return jsonify({'msg': '拒绝访问', 'code': 401})
    app.logger.info(f"admin daka exec {datetime.now().strftime('%Y/%m/%d')}")
    thread_pool.submit(daka_worker)
    return jsonify({'msg': '执行成功', 'code': 200})


@app.route('/stu/<stuid>/dkphoto/reflush', methods=['POST'])
@jwt_required()
def reflush_dk_photo(stuid):
    if not safe_str_cmp(stuid, current_user['stuid']):
        return jsonify({'msg': '不允许访问', 'code': 401})
    app.logger.info(f"{stuid}执行了刷新截图 {datetime.now().strftime('%Y/%m/%d')}")
    student = userdb.db_get_user_by_stuid(stuid)
    thread_pool.submit(get_photo, student)
    return jsonify({'msg': '执行成功', 'code': 200})


def daka_task(student, config):
    dakala(student, config)
    dkrecords(student)
    userdb.db_put_user_reflush_daka_record_time(student['stuid'], datetime.
        now())


def daka_worker():
    all_stu = userdb.find_all_user()
    for stu in all_stu:
        trigger = userdb.db_get_user_daka_trigger(stu['stuid'])
        if trigger is None or trigger is True:
            config = userdb.db_get_user_config(stu['stuid'])
            thread_pool.submit(daka_task, stu, config)
        else:
            sid = stu['stuid']
            userdb.db_put_dk_callback_info(sid,
                f"{datetime.now().strftime('%Y/%m/%d')} 已暂停打卡")
            app.logger.info(f'{sid} 关闭了每日打卡')


@scheduler.task('cron', id='interval_daka', timezone='Asia/Shanghai',
    day_of_week='0-6', hour=8, minute=10, misfire_grace_time=36000)
def interval_daka():
    app.logger.info(f"每日打卡任务执行 {datetime.now().strftime('%Y/%m/%d')}")
    thread_pool.submit(daka_worker)

from app.service.photo import get_photo
from datetime import timedelta, datetime
from flask_apscheduler import APScheduler
from apscheduler.schedulers.gevent import GeventScheduler

import os
import logging
from flask_jwt_extended.utils import create_access_token
from werkzeug.security import safe_str_cmp
import fcntl
from concurrent.futures import ThreadPoolExecutor
from flask import Flask, request, send_file, jsonify
from flask_jwt_extended import JWTManager, jwt_required, current_user
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore
import atexit
from flask_cors import CORS
from app.service.daka import dakala
from app.service.record import dkrecords
from app.config import APP_ADMIN_KEY, APP_SECRET_KEY
import app.userdb as userdb
from flask import render_template

app = Flask(__name__)

jwt = JWTManager(app)


@jwt.user_lookup_loader
def user_lookup(header, data):
    stuid = data["sub"]
    return userdb.db_get_user_by_stuid(stuid)


# gunicorn logger intergation
if __name__ != "__main__":
    gunicorn_logger = logging.getLogger("gunicorn.error")
    app.logger.handlers = gunicorn_logger.handlers
    app.logger.setLevel(gunicorn_logger.level)

CORS(app, resources={r"/*": {"origins": "*"}})

# jwt auth init
app.config["JWT_SECRET_KEY"] = APP_SECRET_KEY  # jwt secret key
app.config["JWT_ACCESS_TOKEN_EXPIRES"] = timedelta(days=1)  # jwt expire time
# jsonify config
app.config["JSON_AS_ASCII"] = False

scheduler = APScheduler(scheduler=GeventScheduler())

# thread_pool
thread_pool = ThreadPoolExecutor(os.cpu_count() + 2)

# scheduler init
JOBS_STORE_LOCATION = os.path.abspath("./data/db/job.db")
app.logger.info(f"定时任务文件位置: {JOBS_STORE_LOCATION}")


class SchedulerConfig:
    SCHEDULER_JOBSTORES = {
        "default": SQLAlchemyJobStore(url=f"sqlite:///{JOBS_STORE_LOCATION}")
    }
    SCHEDULER_TIMEZONE = "Asia/Shanghai"


app.config.from_object(SchedulerConfig())


SCHEDULE_LOCK_FILE = os.path.abspath("./data/db/schedule.lock")
if os.path.exists(SCHEDULE_LOCK_FILE):
    os.remove(SCHEDULE_LOCK_FILE)
app.logger.info(f"锁文件位置: {SCHEDULE_LOCK_FILE}")


def register():
    f = open(SCHEDULE_LOCK_FILE, "wb")
    try:
        fcntl.flock(f, fcntl.LOCK_EX | fcntl.LOCK_NB)
        scheduler.init_app(app)
        scheduler.start()
        app.logger.info("定时任务启动")
    except:
        pass

    def unlock():
        fcntl.flock(f, fcntl.LOCK_UN)
        f.close()

    atexit.register(unlock)


register()


@app.route("/stu/login", methods=["POST"])
def user_login():
    rf = request.json
    stuid = rf.get("stuid", None)
    password = rf.get("password", None)
    if not stuid or not password:
        return jsonify({"msg": "用户名或密码错误", "code": 403})
    stu = userdb.db_get_user_by_stuid(stuid)
    if not stu:
        return jsonify({"msg": "用户名或密码错误", "code": 403})
    if not safe_str_cmp(stu["password"], password):
        return jsonify({"msg": "用户名或密码错误", "code": 403})
    token = create_access_token(identity=stuid)
    config = userdb.db_get_user_config(stuid)
    trigger = userdb.db_get_user_daka_trigger(stuid)
    if trigger is None:
        trigger = True
    ck_info = userdb.db_get_dk_callback_info(stuid)
    return jsonify(
        {
            "msg": "登录成功",
            "code": 200,
            "trigger": trigger,
            "ck": ck_info,
            "token": token,
            "stuid": stuid,
            "config": config,
        }
    )


@app.route("/stu/register", methods=["POST"])
def user_register():
    rf = request.json
    required = [
        "stuid",
        "password",
        "cityStatus",
        "workingPlace",
        "healthStatus",
        "livingStatus",
        "homeStatus",
    ]
    if not rf or not all(k in rf for k in required):
        return jsonify({"msg": "必要信息未填写", "code": 403})
    password = rf.get("password", None)
    stuid = rf.get("stuid", None)
    if not stuid.strip() or not password.strip():
        return jsonify({"msg": "学号或密码不正确", "code": 403})
    if len(stuid) < 10:
        return jsonify({"msg": "学号长度不正确", "code": 403})
    if userdb.db_get_user_by_stuid(stuid):
        return jsonify({"msg": "已经被注册", "code": 403})

    config = {
        "homeStatus": rf.get("homeStatus", None),
        "livingStatus": rf.get("livingStatus", None),
        "healthStatus": rf.get("healthStatus", None),
        "workingPlace": rf.get("workingPlace", None),
        "cityStatus": rf.get("cityStatus", None),
        "application_location": rf.get("application_location", None),
        "application_reason": rf.get("application_reason", None),
        "application_start_time": rf.get("application_start_time", None),
        "application_start_day": rf.get("application_start_day", None),
        "application_end_day": rf.get("application_end_day", None),
        "application_end_time": rf.get("application_end_time", None),
        "scheduler_time_segment": rf.get("scheduler_time_segment", None),
        "scheduler_start_time": rf.get("scheduler_start_time", None),
    }
    userdb.db_put_user_info(stuid, password)
    userdb.db_put_user_config(stuid, config)

    app.logger.info(f"学号 {stuid} , 移动端注册成功")
    return jsonify({"msg": "注册成功", "code": 200})


@app.route("/stu/<stuid>/info", methods=["GET"])
@jwt_required()
def user_info(stuid):
    if not safe_str_cmp(stuid, current_user["stuid"]):
        return jsonify({"msg": "不允许访问", "code": 401})
    stu_config = userdb.db_get_user_config(stuid)
    trigger = userdb.db_get_user_daka_trigger(stuid)
    ck_info = userdb.db_get_dk_callback_info(stuid)
    if trigger is None:
        trigger = True
    return jsonify(
        {
            "stuid": stuid,
            "trigger": trigger,
            "ck": ck_info,
            "config": stu_config,
            "code": 200,
        }
    )


@app.route("/stu/<stuid>/daka", methods=["POST"])
@jwt_required()
def user_daka(stuid):
    app.logger.debug(stuid)
    app.logger.debug(current_user["stuid"])
    if not safe_str_cmp(stuid, current_user["stuid"]):
        return jsonify({"msg": "不允许访问", "code": 401})
    app.logger.info(
        f"学号 {stuid},执行了手动打卡,时间为{datetime.now().strftime('%m/%d/%Y, %H:%M:%S')}"
    )
    student = userdb.db_get_user_by_stuid(stuid)
    config = userdb.db_get_user_config(stuid)
    thread_pool.submit(daka_task, student, config)
    return jsonify({"msg": "打卡命令执行成功", "code": 200})


@app.route("/stu/<stuid>/info/update", methods=["POST"])
@jwt_required()
def update_config(stuid):
    if not safe_str_cmp(stuid, current_user["stuid"]):
        return jsonify({"msg": "不允许访问", "code": 401})
    required = ["stuid"]
    rf = request.json
    if not rf or not all(k in rf for k in required):
        return jsonify({"msg": "必要信息缺失", "code": 403})
    origin_conf = userdb.db_get_user_config(rf.get("stuid"))

    config = {
        "homeStatus": rf.get("homeStatus", origin_conf["homeStatus"]),
        "livingStatus": rf.get("livingStatus", origin_conf["livingStatus"]),
        "healthStatus": rf.get("healthStatus", origin_conf["healthStatus"]),
        "workingPlace": rf.get("workingPlace", origin_conf["workingPlace"]),
        "cityStatus": rf.get("cityStatus", origin_conf["cityStatus"]),
        "application_location": rf.get(
            "application_location", origin_conf["application_location"]
        ),
        "application_reason": rf.get(
            "application_reason", origin_conf["application_reason"]
        ),
        "application_start_time": rf.get(
            "application_start_time", origin_conf["application_start_time"]
        ),
        "application_start_day": rf.get(
            "application_start_day", origin_conf["application_start_day"]
        ),
        "application_end_day": rf.get(
            "application_end_day", origin_conf["application_end_day"]
        ),
        "application_end_time": rf.get(
            "application_end_time", origin_conf["application_end_time"]
        ),
        "scheduler_time_segment": rf.get(
            "scheduler_time_segment", origin_conf["scheduler_time_segment"]
        ),
        "scheduler_start_time": rf.get(
            "scheduler_start_time", origin_conf["scheduler_start_time"]
        ),
    }
    userdb.db_put_user_config(stuid, config)

    if rf.get("password", None):
        userdb.db_put_user_info(stuid, rf.get("password"))

    return jsonify({"msg": "更新成功", "config ": config, "code": 200})


@app.route("/stu/<stuid>/photo", methods=["GET"])
@jwt_required()
def user_photo(stuid):
    if not safe_str_cmp(stuid, current_user["stuid"]):
        return jsonify({"msg": "不允许访问", "code": 401})
    pic_path = os.path.abspath(f"./data/pic/{stuid}_img.png")
    if os.path.exists(pic_path):
        return send_file(pic_path)
    return bytes(0)


@app.route("/stu/<stuid>/callback", methods=["GET"])
@jwt_required()
def user_callback(stuid):
    if not safe_str_cmp(stuid, current_user["stuid"]):
        return jsonify({"msg": "不允许访问", "code": 401})
    ck_info = userdb.db_get_dk_callback_info(stuid)
    return jsonify({"ck": ck_info, "code": 200})


@app.route("/stu/<stuid>/del", methods=["POST"])
@jwt_required()
def user_del(stuid):
    if not safe_str_cmp(stuid, current_user["stuid"]):
        return jsonify({"msg": "不允许访问", "code": 401})
    userdb.db_delete_user_info(stuid)
    pic_path = os.path.abspath(f"./data/pic/{stuid}_img.png")
    if os.path.exists(pic_path):
        os.remove(pic_path)
    return jsonify({"msg": "删除成功", "code": 401})


@app.route("/stu/<stuid>/dakatrigger/update", methods=["POST"])
@jwt_required()
def trigger_daka_update(stuid):
    if not safe_str_cmp(stuid, current_user["stuid"]):
        return jsonify({"msg": "不允许访问", "code": 401})
    rf = request.json
    required = ["daka_trigger"]
    if not rf or not all(k in rf for k in required):
        return jsonify({"msg": "必要信息缺失", "code": 403})
    trigger = rf.get("daka_trigger", True)
    userdb.db_put_user_daka_trigger(stuid, trigger)
    return jsonify({"msg": "更新成功", "code": 200, "trigger": trigger})


@app.route("/stu/<stuid>/dakatrigger/info", methods=["GET"])
@jwt_required()
def daka_trigger_info(stuid):
    if not safe_str_cmp(stuid, current_user["stuid"]):
        return jsonify({"msg": "不允许访问", "code": 401})
    flag = userdb.db_get_user_daka_trigger(stuid)
    if flag is None:
        # 默认trigger开启
        flag = True
    return jsonify({"msg": "查询成功", "code": 200, "trigger": flag})


@app.route("/stu/<stuid>/dkrecords/info", methods=["GET"])
@jwt_required()
def get_dk_record(stuid):
    if not safe_str_cmp(stuid, current_user["stuid"]):
        return jsonify({"msg": "不允许访问", "code": 401})
    combo = userdb.db_get_user_daka_combo(stuid)
    records = userdb.db_get_user_daka_records(stuid)
    return jsonify({"msg": "查询成功", "code": 200, "combo": combo, "records": records})


@app.route("/stu/<stuid>/dkrecords/reflush", methods=["POST"])
@jwt_required()
def reflush_dk_record(stuid):
    if not safe_str_cmp(stuid, current_user["stuid"]):
        return jsonify({"msg": "不允许访问", "code": 401})
    lasttime = userdb.db_get_user_relfush_daka_record_time(stuid)
    combo = userdb.db_get_user_daka_combo(stuid)
    records = userdb.db_get_user_daka_records(stuid)
    if lasttime + 60 > int(datetime.now().timestamp()):
        return jsonify({"msg": "刷新成功", "code": 200, "records": records, "combo": combo})
    student = userdb.db_get_user_by_stuid(stuid)
    userdb.db_put_user_reflush_daka_record_time(stuid, datetime.now())
    thread_pool.submit(dkrecords, student)
    return jsonify({"msg": "刷新成功", "code": 200, "records": records, "combo": combo})


@app.route("/stu/<stuid>/dknotify", methods=["POST"])
@jwt_required()
def set_push_setting(stuid):
    if not safe_str_cmp(stuid, current_user["stuid"]):
        return jsonify({"msg": "不允许访问", "code": 401})
    required = ["push_type", "key"]
    rf = request.json
    if not rf or not all(k in rf for k in required):
        return jsonify({"msg": "必要信息缺失", "code": 403})
    ptype = rf.get("push_type", None)
    key = rf.get("key", None)
    userdb.db_put_push_type(stuid, ptype)
    if ptype == "serverchan":
        userdb.db_put_server_chan_key(stuid, key)
    elif ptype == "qmsg":
        userdb.db_put_qmsg_key(stuid, key)
    elif ptype == "email":
        userdb.db_put_user_email(stuid, key)
    return jsonify({"code": 200, "msg": "设置成功", "push_type": ptype, "key": key})


@app.route("/stu/<stuid>/dknotify/info", methods=["POST"])
@jwt_required()
def push_setting_info(stuid):
    if not safe_str_cmp(stuid, current_user["stuid"]):
        return jsonify({"msg": "不允许访问", "code": 401})
    ptype = userdb.db_get_push_type(stuid)
    key = userdb.db_get_push_key_by_type(stuid, ptype)
    return jsonify({"msg": "查询成功", "key": key, "ptype": ptype, "code": 200})


@app.route("/admin/daka/all", methods=["GET"])
def admin_daka_for_all():
    supercode = request.args.get("supercode", None)
    if not supercode:
        return jsonify({"msg": "缺少参数", "code": 403})
    if not APP_ADMIN_KEY:
        return jsonify({"msg": "拒绝访问", "code": 403})
    if not safe_str_cmp(supercode, APP_ADMIN_KEY):
        return jsonify({"msg": "拒绝访问", "code": 401})
    app.logger.info(f'admin daka exec {datetime.now().strftime("%Y/%m/%d")}')
    thread_pool.submit(daka_worker)
    return jsonify({"msg": "执行成功", "code": 200})


@app.route("/stu/<stuid>/dkphoto/reflush",methods=["POST"])
@jwt_required()
def reflush_dk_photo(stuid):
    if not safe_str_cmp(stuid, current_user["stuid"]):
        return jsonify({"msg": "不允许访问", "code": 401})
    app.logger.info(f'{stuid}执行了刷新截图 {datetime.now().strftime("%Y/%m/%d")}')
    student = userdb.db_get_user_by_stuid(stuid)
    thread_pool.submit(get_photo, student)
    return jsonify({"msg": "执行成功", "code": 200})


def daka_task(student, config):
    dakala(student, config)
    dkrecords(student)
    userdb.db_put_user_reflush_daka_record_time(student["stuid"], datetime.now())


def daka_worker():
    all_stu = userdb.find_all_user()
    for stu in all_stu:
        trigger = userdb.db_get_user_daka_trigger(stu["stuid"])
        if trigger is None or trigger is True:
            config = userdb.db_get_user_config(stu["stuid"])
            thread_pool.submit(daka_task, stu, config)
        else:
            sid = stu["stuid"]
            userdb.db_put_dk_callback_info(
                sid, f'{datetime.now().strftime("%Y/%m/%d")} 已暂停打卡'
            )
            app.logger.info(f"{sid} 关闭了每日打卡")


@scheduler.task(
    "cron",
    id="interval_daka",
    timezone="Asia/Shanghai",
    day_of_week="0-6",
    hour=8,
    minute=10,
    misfire_grace_time=36000,
)
# @scheduler.task('cron', id="d", minute='*')  # for test
def interval_daka():
    app.logger.info(f'每日打卡任务执行 {datetime.now().strftime("%Y/%m/%d")}')
    thread_pool.submit(daka_worker)
