dav

Henry Toys dav api

目前為 2025-06-13 提交的版本,檢視 最新版本

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://updategf.qytechs.cn/scripts/539094/1606939/dav.js

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         dav
// @namespace    dav
// @version      1.0.0
// @description  Henry Toys dav api
// @author       Henry

// @grant        unsafeWindow

// @grant        GM_download
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_xmlhttpRequest
// @grant        GM_registerMenuCommand

// @license      CC-BY-4.0
// ==/UserScript==

const dav = {
    login: function () {
        // 添加样式,使用 !important 确保优先级
        GM_addStyle(`
            .modal_bg {
                position: fixed !important;
                top: 0 !important;
                left: 0 !important;
                width: 100% !important;
                height: 100% !important;
                background-color: rgba(0, 0, 0, 0.5) !important;
                justify-content: center !important;
                align-items: center !important;
                z-index: 9999 !important;
            }
            
            .modal_content {
                background-color: white !important;
                padding: 20px !important;
                border-radius: 10px !important;
                width: 300px !important;
                max-width: 90% !important;
                max-height: 80vh !important;
                overflow-y: auto !important;
                box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2) !important;
                box-sizing: border-box !important;
            }
            
            .modal_input {
                width: 100% !important;
                padding: 8px 10px !important;
                border: 1px solid #ddd !important;
                border-radius: 5px !important;
                display: block !important;
                box-sizing: border-box !important;
                font-family: inherit !important;
                font-size: inherit !important;
                line-height: 1.4 !important;
                height: 36px !important;
                -webkit-appearance: none !important;
                -moz-appearance: none !important;
                appearance: none !important;
                background-color: white !important;
                vertical-align: middle !important;
                outline: none !important;
                margin: 0 0 12px 0 !important;
                box-shadow: none !important;
                letter-spacing: normal !important;
                word-spacing: normal !important;
                text-transform: none !important;
                text-indent: 0px !important;
                text-shadow: none !important;
                text-align: start !important;
                -webkit-rtl-ordering: logical !important;
                cursor: text !important;
            }
            
            .modal_input:focus {
                border-color: #4CAF50 !important;
                box-shadow: 0 0 0 2px rgba(76, 175, 80, 0.2) !important;
            }
            
            .button_container {
                display: flex !important;
                gap: 10px !important;
                margin-top: 20px !important;
            }
            
            .modal_button {
                color: white !important;
                border: none !important;
                padding: 10px 15px !important;
                border-radius: 3px !important;
                cursor: pointer !important;
                flex: 1 !important;
                font-family: inherit !important;
                font-size: inherit !important;
            }
            
            .modal_button.login {
                color: #111 !important;
                background-color: #FFD814 !important;
            }
          
            .error_msg {
                color: red !important;
                margin-bottom: 15px !important;
            }
        `)
        // 创建模态框HTML结构
        const create_login_modal = function () {
            // 检查登录模态框是否已存在
            if (document.querySelector('.modal_bg')) {
                document.querySelector('.modal_bg').remove()
            }
            // 创建模态框容器
            const modal_bg = document.createElement('div')
            modal_bg.className = 'modal_bg'
            modal_bg.style.display = 'none'

            // 创建模态框内容
            const modal_content = document.createElement('div')
            modal_content.className = 'modal_content'

            const modal_title = document.createElement('h1')
            modal_title.textContent = '欢迎登录'

            const error_msg = document.createElement('div')
            error_msg.className = 'error_msg'
            error_msg.style.display = 'none'

            const loginurl_input = document.createElement('input')
            loginurl_input.type = 'text'
            loginurl_input.className = 'modal_input'
            loginurl_input.placeholder = '输入网址'

            // 添加用户名输入
            const username_input = document.createElement('input')
            username_input.type = 'text'
            username_input.className = 'modal_input'
            username_input.placeholder = '输入用户名'

            // 添加密码输入
            const password_input = document.createElement('input')
            password_input.type = 'password'
            password_input.className = 'modal_input'
            password_input.placeholder = '输入密码'

            // 添加按钮容器
            const button_container = document.createElement('div')
            button_container.className = 'button_container'

            // 添加提交按钮
            const submit_button = document.createElement('button')
            submit_button.type = 'submit'
            submit_button.textContent = '登录'
            submit_button.className = 'modal_button login'

            button_container.appendChild(submit_button)

            modal_content.appendChild(modal_title)
            modal_content.appendChild(error_msg)
            modal_content.appendChild(loginurl_input)
            modal_content.appendChild(username_input)
            modal_content.appendChild(password_input)
            modal_content.appendChild(button_container)

            modal_bg.appendChild(modal_content)
            document.body.appendChild(modal_bg)
            return {
                modal_bg,
                error_msg,
                loginurl_input,
                username_input,
                password_input,
                submit_button,

            }
        }

        // 创建模态框并添加事件处理
        const {
            modal_bg,
            error_msg,
            loginurl_input,
            username_input,
            password_input,
            submit_button,

        } = create_login_modal()

        // 显示模态框
        const show_login_modal = function () {
            modal_bg.style.display = 'flex'
            loginurl_input.focus()
        }

        // 隐藏模态框
        const hide_login_modal = function () {
            modal_bg.style.display = 'none'
            loginurl_input.value = ''
            username_input.value = ''
            password_input.value = ''
            error_msg.style.display = 'none'
        }

        // 验证登录
        const validate_login = function () {
            const loginurl = loginurl_input.value.trim()
            const username = username_input.value.trim()
            const password = password_input.value.trim()

            if (!loginurl || !username || !password) {
                error_msg.textContent = '请填写所有字段'
                error_msg.style.display = 'block'
                return false
            }

            // 验证URL格式
            try { new URL(loginurl) }
            catch (e) {
                error_msg.textContent = '无效的URL格式'
                error_msg.style.display = 'block'
                return false
            }

            try {
                GM_xmlhttpRequest({
                    method: "PROPFIND",
                    url: loginurl,
                    headers: { 'Authorization': 'Basic ' + btoa(username + ':' + password), 'Depth': '0' },
                    // responseType: responseType,
                    timeout: 10000, // 设置默认超时时间为10秒
                    onload: async (response) => {
                        if (response.status == 207) {
                            console.log('Request successful:', response)
                            const dav_login_data = {
                                "loginurl": loginurl,
                                "username": username,
                                "password": password,
                                "expires": new Date().getTime() + 1000 * 60 * 60 * 24 * 7 // 7天后过期
                            }
                            error_msg.style.display = 'none'
                            GM_setValue("dav_login_data", JSON.stringify(dav_login_data))
                            hide_login_modal()
                            return true
                        } else {
                            GM_deleteValue('dav_login_data')
                            error_msg.textContent = '登录失败,请检查地址,用户名和密码'
                            error_msg.style.display = 'block'
                            return false

                        }
                    },
                    onerror: (error) => {
                        GM_deleteValue('dav_login_data')
                        error_msg.textContent = 'Request error:' + error
                        error_msg.style.display = 'block'
                        return false
                    },
                    ontimeout: () => {
                        // console.log('Request timed out')
                        GM_deleteValue('dav_login_data')
                        error_msg.textContent = 'Request timed out'
                        error_msg.style.display = 'block'
                        return false
                    }
                })
            } catch (requestError) {
                GM_deleteValue('dav_login_data')
                error_msg.textContent = 'GM_xmlhttpRequest error:' + requestError
                error_msg.style.display = 'block'
                return false
            }
        }

        // 添加事件监听
        submit_button.addEventListener('click', validate_login)
        modal_bg.addEventListener('click', (e) => {
            if (e.target === modal_bg) {
                hide_login_modal()
            }
        })
        // 添加键盘事件监听器,按ESC键关闭模态框
        document.addEventListener('keydown', function (e) {
            if (e.key === 'Escape' && modal_bg.style.display === 'block') {
                hide_login_modal()
            }
        })
        loginurl_input.addEventListener('keypress', (e) => {
            if (e.key === 'Enter') {
                validate_login()
            }
        })
        username_input.addEventListener('keypress', (e) => {
            if (e.key === 'Enter') {
                validate_login()
            }
        })
        password_input.addEventListener('keypress', (e) => {
            if (e.key === 'Enter') {
                validate_login()
            }
        })
        GM_registerMenuCommand("登录", show_login_modal)
    },

    logout: function () {
        const confirm = window.confirm('确定要退出登录吗?')
        if (!confirm) { return }
        // 如果用户确认退出登录,则删除登录数据
        GM_deleteValue('dav_login_data')
        alert('已退出登录')
    },

    get_login_data: function () {
        let dav_login_data = JSON.parse(GM_getValue("dav_login_data", null))
        const self = this
        // 检查数据是否存在或已过期
        if (!dav_login_data ||
            !dav_login_data.loginurl ||
            !dav_login_data.username ||
            !dav_login_data.password ||
            dav_login_data.expires < new Date().getTime()) {
            GM_deleteValue('dav_login_data')
            self.login()
        } else {
            GM_registerMenuCommand("退出登录", self.logout)
        }
        return JSON.parse(GM_getValue("dav_login_data", null))
    },

    request: async function (method, path, callback, responseType = 'text', data = null) {
        const login_data = JSON.parse(GM_getValue("dav_login_data", null))
        if (!login_data ||
            !login_data.loginurl ||
            !login_data.username ||
            !login_data.password ||
            login_data.expires < new Date().getTime()) {
            GM_deleteValue('dav_login_data')
            this.get_login_data()
            throw new Error('Invalid or missing login credentials')
        } else {
            const updated_dav_login_data = {
                "loginurl": login_data.loginurl,
                "username": login_data.username,
                "password": login_data.password,
                "expires": new Date().getTime() + 1000 * 60 * 60 * 24 * 7 // 7天后过期
            }
            GM_setValue("dav_login_data", JSON.stringify(updated_dav_login_data))
        }
        return new Promise((resolve) => {
            try {
                GM_xmlhttpRequest({
                    method: method,
                    data: data,
                    url: login_data.loginurl + path,
                    headers: { 'Authorization': 'Basic ' + btoa(login_data.username + ':' + login_data.password), },
                    responseType: responseType,
                    timeout: 30000, // 设置默认超时时间为30秒
                    onload: async (response) => { await callback(response, resolve) },
                    onerror: (error) => {
                        console.log('Request error:', error)
                        GM_deleteValue('dav_login_data')
                        this.get_login_data()
                    },
                    ontimeout: () => {
                        console.log('Request timed out')
                        GM_deleteValue('dav_login_data')
                        this.get_login_data()
                    }
                })
            } catch (requestError) {
                console.log('GM_xmlhttpRequest error:', requestError)
                GM_deleteValue('dav_login_data')
                this.get_login_data()
            }
        })
    },

    get_file_data: function (file_path) {
        let callback = function (response, resolve) { resolve(response.responseText) }
        return this.request('GET', file_path, callback, 'text')
    },

    get_file_last_modified_time: function (file_path) {
        let callback = function (response, resolve) { resolve(response.responseXML.getElementsByTagName('d:getlastmodified')[0].textContent) }
        return this.request('PROPFIND', file_path, callback, 'document', '')
    },

    upload_file_through_file_data: function (file_path, file_data) {
        let callback = function () { }
        this.request('PUT', file_path, callback, 'text', file_data)
    },

    upload_file_through_url: async function (file_path, url) {
        function get_url_file_data(url) {
            return new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: "GET",
                    url: url,
                    responseType: "arraybuffer",
                    onload: (response) => { resolve(new Blob([response.response])) },
                    onerror: () => { reject('error'); }
                })
            })
        }
        let file_data = await get_url_file_data(url)
        this.upload_file_through_file_data(file_path, file_data)
    },

    check_path_exists: function (path) {
        let callback = function (response, resolve) {
            if (response.status === 404) { resolve(false) }
            else { resolve(true) }
        }
        return this.request('PROPFIND', path, callback, 'text', '')
    },

    create_dir_path: async function (dir_path) {
        let callback = function () { }
        try {
            let dir_exits = await this.check_path_exists(dir_path)
            if (dir_exits) { return }
            let currentPath = dir_path
            while (currentPath) {
                dir_exits = await this.check_path_exists(currentPath);
                if (dir_exits) { break }
                // 尝试创建目录
                await this.request('MKCOL', currentPath, callback, 'text', '');
                // 截取到上一级目录
                const lastIndex = currentPath.lastIndexOf('/');
                if (lastIndex === -1) { break }
                currentPath = currentPath.slice(0, lastIndex)
            }
        } catch (error) {
            console.error(`Error creating directory ${dir_path}:`, error);
        }
    },

    get_latest_modified_file: async function (dir_path) {
        let callback = function (response, resolve) {
            if (response.status === 404) { resolve("error") }
            else { resolve(response.responseXML.firstChild.lastChild.firstElementChild.innerHTML) }
        }
        return this.request('PROPFIND', dir_path, callback, 'text', '')
    }
}
dav.get_login_data()