Greasy Fork

Greasy Fork is available in English.

我的白菜菊花

Cloud sync for bcjh.xyz

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         我的白菜菊花
// @namespace    http://tampermonkey.net/
// @version      0.2
// @description  Cloud sync for bcjh.xyz
// @author       You
// @match        https://bcjh.gitee.io/
// @icon         https://www.google.com/s2/favicons?domain=gitee.io
// @grant        GM_addStyle
// @grant        GM_xmlhttpRequest
// @run-at       document-start
// ==/UserScript==

GM_addStyle ( `
    .el-input input {
    border: 1px solid #dcdfe6;
    box-sizing: border-box;
    border-radius: 4px;
    height: 26px;
    line-height: 24px;
    padding-left: 10px;
    padding-right: 23px;
    color: #606266;
    }
    .sync-indicator {
    position: absolute;
    right:0;
    top:0;
    width: 10px;
    height: 10px;
    opacity: 0.8;
    border-radius: 50%;
    background: gray;
    }
    .sync-indicator.unsync {
    background: red;
    }
    .sync-indicator.sync {
    background: green;
    }
    .sync-indicator.syncing {
    background: yellow !important;
    }
` );

const server = "https://keystore.mactype.win:10512"

let localstore = {
    _setItem: window.localStorage.setItem,
    _getItem: window.localStorage.getItem,
    setItem(name, value) {
        return this._setItem.call(window.localStorage, name, value)
    },
    getItem(name) {
        return this._getItem.call(window.localStorage, name)
    }
}

let lastUser = localStorage.getItem("fishcloud") || ""
let vueRoot = null, importCloud = false

let cloudServer = {
    delayedupload() {
        let value = window.localStorage.getItem("data")
        syncIndicator.syncing()
        console.log("Changes detected, uploading...")
        GM_xmlhttpRequest({
            url: server + "/put",
            method: "post",
            data: JSON.stringify({
                user: lastUser,
                key: "data",
                value: value
            }),
            onload: ()=>{
                syncIndicator.synced()
            }
        })
    },
    t: null,
    upload() {
        if (!lastUser) return
        syncIndicator.syncing()
        if (this.t) {
            clearTimeout(this.t)
        }
        this.t = setTimeout(()=>{
            this.t = null
            this.delayedupload()
        }, 1000)
    },
    download(quiet = true) {
        if (!lastUser) return
        syncIndicator.syncing()
        GM_xmlhttpRequest({
            url: server + "/get",
            method: "post",
            data: JSON.stringify({
                user: lastUser,
                key: "data"
            }),
            onload: ({responseText})=>{
                if (responseText) {
                    importCloud = true
                    localstore.setItem("data", responseText)
                    vueRoot && vueRoot.getUserData()
                    setTimeout(()=>{
                        importCloud = false
                    }, 500)
                }
                syncIndicator.synced()
                if (!quiet) {
                    vueRoot && vueRoot.$message({
                        message: "数据已同步",
                        type: "success"
                    })
                }
            },
            onerror: err=>{
                syncIndicator.fail()
                if (!quiet) {
                    alert("同步失败")
                }
            }
        })
    }
}

var syncIndicator = {
    el: null,
    create() {
        $("body").append("<div class='sync-indicator'></div>")
        this.el = $(".sync-indicator")
    },
    syncing() {
        this.el.addClass("syncing")
    },
    fail() {
        this.el.removeClass("syncing sync").addClass("unsync")
    },
    synced() {
        this.el.removeClass("syncing unsync").addClass("sync")
    },
    nop() {
        this.el.removeClass("syncing unsync sync")
    }
}

function updateUser(newUser) {
    lastUser = newUser
    localstore.setItem("fishcloud", newUser)
}

function hookUltimateNavi(e) {
    if (document.getElementById("mycloud")) return
    let box = document.getElementsByClassName("ultimate-box")
    if (box.length) {
        $(box).prepend('<div class="title" id="mycloud">大鱼云同步</div><div class="hr"></div><div class="box-body"><div class="el-input"><span style="margin-right:5px">请输入雨云用户名:</span><input type="text" value="'+lastUser+'" name="fishcloud"></div></div>')
        $(box).find("input[name=fishcloud]").on("change", async function (){
            let newUser = this.value
            if (newUser !== lastUser && lastUser !=="") {
                try {
                    let action = await vueRoot.$confirm("确定要从用户 \""+lastUser+"\" 切换到用户 \""+newUser+"\" 吗?", '切换账户', {
                        type: 'warning'
                    })
                    } catch {
                        this.value = lastUser
                        return
                    }
            }
            updateUser(newUser)
            vueRoot.$confirm("您已登录为用户 "+newUser+",请选择同步操作。如误操作请关闭对话框。", '提示', {
                confirmButtonText: '下载',
                cancelButtonText: '上传',
                distinguishCancelAndClose: true,
                type: 'success'
            }).then(()=>{
                cloudServer.download(false)
            }).catch((action)=>{
                if (action === 'cancel') {
                    cloudServer.upload()
                } else {
                    this.value = ""// 登出用户
                    updateUser("")
                    syncIndicator.nop()
                }
            })

        })
    }
}

function hookOpIcon(e) {
    let navs = document.getElementsByClassName("nav")
    if (navs.length) {
        console.log("found navigators")
        $(e.currentTarget).off("click", hookOpIcon)
        $(navs[8]).on("click", hookUltimateNavi)
    }
}

var i = 0

function hookLocalStorage() {
    window.localStorage.__proto__.setItem = function(name, value) {
        let result = localstore.setItem(name, value)
        if (vueRoot && !vueRoot.loading && !importCloud) {  // 忽略所有中途读写
            cloudServer.upload()
        }
        return result
    }
}

function hookVue() {
    let pVue = null
    Object.defineProperty(unsafeWindow,'Vue',{
        get: function(){
            return pVue
        },

        set: function(val){
            pVue = new Proxy(val, {
                construct: function(target, args) {
                    var obj = new target(...args)
                    if (args.length && typeof args[0]==="object" && args[0].el==="#main") {
                        vueRoot = obj
                    }
                    return obj
                },
            })
        },
        configurable: true,
    });
}

function oneTimeSyncOnLoad() {
    if (!lastUser) return
    if (!vueRoot || vueRoot.loading) {
        setTimeout(oneTimeSyncOnLoad, 1000)
    } else {
        console.log("Syncing cloud profile...")
        cloudServer.download()
    }
}

(function() {
    'use strict';
    console.log("Welcome to MyBCJHCloud 1.0")

    hookLocalStorage()
    hookVue()
    window.addEventListener('load', ()=>{
        new Promise(r=>{
            let checker = ()=>{
                let opIcon = document.getElementsByClassName("el-icon-s-operation icon-btn")
                if (opIcon.length) {
                    r(opIcon)
                } else {
                    setTimeout(checker, 500)
                }
            }
            checker()
        }).then((icon)=>{
            $(icon).on("click", hookOpIcon)
            syncIndicator.create()
            oneTimeSyncOnLoad()
        })
    })
})();