dav

Henry Toys dav api

Dette scriptet burde ikke installeres direkte. Det er et bibliotek for andre script å inkludere med det nye metadirektivet // @require https://update.greasyfork.ip-ddns.com/scripts/539094/1606939/dav.js

// ==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()