Surviv.io input replay recorder

Records lightweight game recordings, which can be reviewed with any custom mods applied

目前为 2022-07-19 提交的版本。查看 最新版本

// ==UserScript==
// @name         Surviv.io input replay recorder
// @namespace    https://github.com/notKaiAnderson/
// @version      1.0.3
// @description   Records lightweight game recordings, which can be reviewed with any custom mods applied
// @author       garlic
// @match        *://snake.io/
// @match        *://surviv.io/*
// @match        *://surviv2.io/*
// @match        *://2dbattleroyale.com/*
// @match        *://2dbattleroyale.org/*
// @match        *://piearesquared.info/*
// @match        *://thecircleisclosing.com/*
// @match        *://archimedesofsyracuse.info/*
// @match        *://secantsecant.com/*
// @match        *://parmainitiative.com/*
// @match        *://nevelskoygroup.com/*
// @match        *://kugahi.com/*
// @match        *://chandlertallowmd.com/*
// @match        *://ot38.club/*
// @match        *://kugaheavyindustry.com/*
// @match        *://drchandlertallow.com/*
// @match        *://rarepotato.com/*
// @grant        none
// @icon         https://i.imgur.com/jgHdTYA.png
// @license      MIT
// ==/UserScript==

/*** contributors
 * garlic
 * samer kizi
 * preacher
***/

var survivharplayerconfig={ silly1:false};
var ifsurviv = false;
if (window.zzpseudoalert && window.log) {
    window.zzpseudoalert("already active");
    return;
}
try {
    document.getElementsByClassName("ftue-header")[0].style.backgroundColor = "violet";
    document.getElementsByClassName("ftue-body")[0].style.backgroundColor = "red";
    ifsurviv = true;
} catch (e) {};
window.zzpseudoalert = function(e) {
    var x = document.createElement('div');
    x.style = 'zIndex:255;position:fixed;left:50%;width:40%;top:20%;height:fit-content;background-color:rgba(0,0,0,.5);color:#e4d338;text-align:center;border-radius:10px;padding:10px 20px;-webkit-transform: translate(-50%, -50%);transform: translate(-50%, -50%);';
    x.innerHTML = e;
    document.body.append(x);
    x.style.fontSize = '20px';
    x.animate(
        [
            {opacity: '1'},
            {opacity: '0'}
        ],
        {duration: 3000}
    );
    setTimeout(() => document.body.removeChild(x), 3000);
};
window.zzpseudoalert.small = function(e) {
    var x = document.createElement('div');
    x.style = 'zIndex:255;position:fixed;left:50%;width:40%;top:20%;height:fit-content;background-color:rgba(0,0,0,.5);color:#e4d338;text-align:center;border-radius:10px;padding:10px 20px;-webkit-transform: translate(-50%, -50%);transform: translate(-50%, -50%);';
    x.innerHTML = e;
    document.body.append(x);
    x.style.fontSize = '20px';
    x.animate(
        [
            {opacity: '1'},
            {opacity: '0'}
        ],
        {duration: 3000}
    );
    setTimeout(() => document.body.removeChild(x), 3000);
};
zzpseudoalert('wsrapper init');

function arrayBufferToBase64(buffer) {
    let binary = '';
    let bytes = new Uint8Array(buffer);
    let len = bytes.byteLength;
    for (let i = 0; i < len; i++) {
        binary += String.fromCharCode(bytes[i]);
    }
    return window.btoa(binary);
}
window.logentries = [];
window.log = {
    log: {
        version: '1.2',
        'creator': {
            'name': 'wswrapperallium',
            'version': '0.0031'
        },
        entries: window.logentries
    }
};

var lastgameforunlink;

class RecordingWebSocket extends WebSocket {
    constructor(...args) {
        super(...args);
    }

    unlinkfromclient() {
        if(this.readyState != this.OPEN) return false;

        let seq=0;
        let wso = this.__lientry._webSocketMessages;

        for(let i=wso.length-1;i>=0;i--) {
            if(wso[i].type=="send") {
                let msgab = from_base64s_ab(wso[i].data);
                let msgbytes = new Uint8Array(msgab);
                if(msgbytes[0]==3) { seq = msgbytes[1];
                                    break;
                                   }
            }
        }

        var oldsend = this.send;
        Object.defineProperty(this,"send",{
            configurable:true
            ,get: function() { return ()=>{}; }
            ,set: function() {}
        });
        Object.defineProperty(this,"close",{
            configurable:true
            ,get: function() { return ()=>{}; }
            ,set: function() {}
        });

        var thisArg=this;
        function keepAlive() {
            seq=(seq+1)&255;
            let a;

            if(window.spectatepinginsteadofinput) {
                a = new Uint8Array(4);
                "C90bAA==";
                a[0]=11; a[1]=0; a[2]=0; a[3]=2;
            } else {
                a = new Uint8Array(9);

                a[0]=3; a[1]=seq; a[3]=255; a[4]=3; a[5]=8;
            }
            oldsend.apply(thisArg,[a]);
            console.info('tick',seq);
        }

        var keepAliveIt = setInterval(()=>keepAlive(),1017);
        console.info('unlink toggled',seq);
        this.onerror=()=>{};
        this.onclose();
        this.onclose=()=>{ clearInterval(keepAliveIt); console.info('unlinked conn closed'); };
        this.__spy_onmessage=()=>{};
        return true;
    }

    send(...args) {
        let thisArg = this;
        let e = args[0];
        if (thisArg.__lientry == undefined) {
            thisArg.__lientry = {
                'startedTime': new Date().toISOString(),
                'time': 0,
                'request': {
                    'method': 'GET',
                    'url': thisArg.url
                },
                '_webSocketMessages': []
            };
            if(ifsurviv) {
                thisArg.__lientry.__survivio = {};
                thisArg.onEach=function() {
                };
                if(thisArg.url.includes('/play?game'))
                    lastgameforunlink=thisArg;
            }

            window.logentries.push(thisArg.__lientry);
            let wsa = thisArg.__lientry._webSocketMessages;
            let tos = /*thisArg.__lientry.time.toString=  */ () => {
                let t1 = 0;
                if (wsa.length > 1) {
                    t1 = wsa.slice(-1)[0].time - wsa[0].time;
                }
                return (t1) + '';
            };
            thisArg.__lientry.time = {
                toString: tos
            };
            zzpseudoalert(thisArg.url.replace('wss://', '').replace('/', '<p>'));
            thisArg.__spy_onmessage = thisArg.onmessage;
            let recvHandler = function(...args) {
                let e = args[0]; {
                    let temp = {
                        'type': 'receive',
                        'time': new Date().getTime() / 1e3,
                        'opcode': 2
                    };
                    this.__lientry._webSocketMessages.push(temp);
                    if (typeof e.data !== 'string') {
                        temp.opcode = 2;
                        new Response(e.data).arrayBuffer().then(ifAblob => temp.data = arrayBufferToBase64(ifAblob));
                    } else {
                        temp.opcode = 1;
                        temp.message = e.data;
                    }
                    return this.__spy_onmessage.apply(this, args);
                }
            };
            thisArg.onmessage = recvHandler;
        }
        let temp = {
            'type': 'send',
            'time': new Date().getTime() / 1e3,
            'opcode': 2
        };
        thisArg.__lientry._webSocketMessages.push(temp);
        if (typeof e !== 'string') {
            temp.opcode = 2;
            temp.data = arrayBufferToBase64(e);
        } else {
            temp.opcode = 1;
            temp.message = e;
        }
        return super.send(...args);
    };
}
RecordingWebSocket.unlink = function () {
    let msg='';
    if(lastgameforunlink) {
        if(lastgameforunlink.unlinkfromclient())
            msg='unlink function activated';
        else
            msg='unlink function not activated';
    } else msg='nothing';
    window.zzpseudoalert(msg);
};

window.WebSocket = RecordingWebSocket;

void('surviv .har repeat script. new103. open up at surviv.io, load .har file. f1f2f3f6 -- change replay speed');
{
    let cconsole = console.log;
    Object.defineProperty(console, 'log', {
        configurable: true,
        get: function() {
            return cconsole;
        },
        set: function(e) {}
    });
}(function() {
    'use strict';
    let cycle = (x) => (x == false) ? undefined : x == undefined;
    let cycle2 = (x, y, z) => x == y ? z : y;
    const keys = ["F1", "F2", "F3", "F4", "F6", "F7", "F8", "F9", "F10"];
    const disableKey = key => keys.push(key);
    ["keypress", "keydown", "keyup"].forEach(type => {
        document.addEventListener(type, e => {
            if (keys.indexOf(e.key) !== -1) {
                if (e.type == "keydown") {
                    if (e.key == "F1") window.slowmo = 0.33;
                    if (e.key == "F2") window.slowmo = 1;
                    if (e.key == "F3") window.slowmo = 2;
                    if (e.key == "F4") window.slowmo = 2.5;
                    if (e.key == "F6") window.slowmo = 16;
                    window.slowmor = window.slowmo;
                }
                return e.preventDefault();
            }
        });
    });
})();

window.slowmo = 1;
window.harRead = {
    files: [],
    games: {},
    alert: window.alert.bind(window),
    ui:{ window: window }
};
window.foo = (function() {


    function unlinkfromUI() {
        RecordingWebSocket.unlink();
    }

    function tabuntab() {
        if(!xpassblock) return;
        if (document.getElementById("missions-name").style.display === "none") {
            document.getElementById("missions-name").style = "display:block";
            document.getElementById("pass-quest-wrapper").style = "display:block"
        } else {
            document.getElementById("missions-name").style = "display:none";
            document.getElementById("pass-quest-wrapper").style = "display:none"
        }
        if(x.parentElement.id =="pass-block")
        {
            window.zzpseudoalert("FUG");
            x.otherwindow = window.open('', '', 'height=250,width=300');
            x.otherwindow.document.body.append(x);
            window.harRead.ui.window=x.otherwindow;
        } else {
            xpassblock.insertBefore(x, xpassblock.firstElementChild);
            window.harRead.ui.window=window;
            x.otherwindow.close();
        }
    }

    function saveharlog() {

        0,function(filename, data) {
            try{
                var blob = new Blob([data], {type: 'text/plain'});
            }catch(e){
                alert('whoops, doesnt work');
            }

            if(window.navigator.msSaveOrOpenBlob) {
                window.navigator.msSaveBlob(blob, filename);    }
            else{        var elem = window.document.createElement('a');
                 elem.href = window.URL.createObjectURL(blob);
                 elem.download = filename;
                 document.body.appendChild(elem);        elem.click();
                 document.body.removeChild(elem);    }}
        (document.location.host+ (new Date().toISOString().slice(0,10))
         +Date.now()+'.har',JSON.stringify(log));

    }

    function selmodech(tt,event,ename) {
        if(ename=="change") {
            if(tt.value=="Tab/untab") {
                'prompt("wha")';
            }
            if(tt.selectedIndex==5) {
                saveharlog();
                tt.selectedIndex=tt.prevselectedIndex;
                return;
            }
            if(tt.selectedIndex==6) {
                if((survivharplayerconfig.silly1 =!survivharplayerconfig.silly1)===false) {
                    dosomething_with_send_disable();
                    window.zzpseudoalert('silly pseudo 3d is now: DISABLED');
                } else {
                    window.zzpseudoalert('silly pseudo 3d is now: ENABLED');
                }
                tt.selectedIndex=tt.prevselectedIndex;
                return;
            }
            tt.prevselectedIndex=tt.selectedIndex;
            if(tt.selectedIndex==0) {
                if(window.WebSocket.name=="ReplayWebSocket") window.WebSocket = RecordingWebSocket;
            } else {
                if(window.WebSocket.name=="RecordingWebSocket") window.WebSocket = ReplayWebSocket;
            }
            if(tt.selectedIndex==1) {
                applyParsedHarFile(log);
            }
            if(tt.value=="Replay from prompt") {
                let pp = window.harRead.ui.window.prompt("input har file, or http/file address");
                let done=false;
                if(pp.startsWith('file://') || pp.startsWith('http') || !pp.startsWith('{"log"')) {
                    window.zzpseudoalert('trying to fetch file');
                    fetch(pp).then(r=>r.json()).then(e=>applyParsedHarFile(e));
                } else doa(pp);
                function  doa(pp) {
                    if(pp.startsWith('{"log"') ) {
                        try {
                            let yi = JSON.parse(pp);
                            applyParsedHarFile(yi);
                        } catch(e) {
                            window.zzpseudoalert('could not read/parse... failed');
                        }
                    }
                }
            }
        }
    };

    var xpassblock = document.getElementById("pass-block");
    'var ifsurviv=true';
    var xwin,x;
    if(!xpassblock) {
        xwin = window.open('','');
        x = xwin.document.createElement("div");
        xwin.document.body.append(x);
        'ifsurviv=false';
    } else {
        x = document.createElement("div");
        xpassblock.insertBefore(x, xpassblock.firstElementChild);
    }

    var tt = document.createElement("select");
    tt.id = "modepl";
    tt.style = `
      background: rgb(122, 122, 122);
      box-shadow: rgb(62 62 62) 0px -3px inset;
      color: rgb(255, 255, 255);
      cursor: pointer;
      width: 30%;
      border: none;
      border-radius: 5px;
      font-size: 18px;
      padding: 10px 20px;
      text-align: center;
    `
    tt.innerHTML="<option>Record</option><option>Replay recent</option><option>Replay from file</option><option>Replay from prompt</option><option>Tab/untab</option><option>save har log</option><option>silly 3d toggle</option>";
    let captt=tt;
    tt.onchange=e=>selmodech(captt,e,"change");
    tt.onclick=e=>selmodech(captt,e,"click");
    x.appendChild(tt);

    var tt=document.createElement('button');
    tt.style=`
      background-image: url(https://surviv.io/img/gui/link.svg);
      background-size: 27px;
      background-repeat: no-repeat;
      background-position: center 42%;
      width: 20%;
      height: 45px;
      position: absolute;
      background-color: #cd3232;
      box-shadow: #781e0a 0px -4px inset;
      color: #fff;
      cursor: pointer;
      text-shadow: rgb(0 0 0 / 25%) 0px 1px 2px;
      border: none;
      border-radius: 5px;
      font-size: 18px;
      margin: 0 0 0 3px;
    `
    tt.onclick=tabuntab;
    x.appendChild(tt);

    var tt=document.createElement('button');
    tt.innerHTML="unlink & record";
    tt.style = `
      height: 45px;
      background-color: rgb(205, 50, 50);
      box-shadow: rgb(120 30 10) 0px -4px inset;
      color: rgb(255, 255, 255);
      cursor: pointer;
      text-shadow: rgb(0 0 0 / 25%) 0px 1px 2px;
      border: none;
      border-radius: 5px;
      font-size: 18px;
      margin: 0 20px 0 66px;
      position: absolute;
    `
    tt.onclick=unlinkfromUI;
    x.appendChild(tt);

    var tt = document.createElement("select");
    tt.id = "xyz";
    window.harRead.ui.matchSelectorComboBox = tt;
    tt.style = "background: rgb(122, 122, 122); box-shadow: rgb(62, 62, 62) 0px -2px inset; color: rgb(255, 255, 255); cursor: pointer; width: 100%; border: none; border-radius: 5px; font-size: 18px; padding: 5px 20px; margin: 5px 0px;";
    tt.style = `
      width: 100%;
      height: 45px;
      background-color: #7a7a7a;
      box-shadow: #3e3e3e 0px -4px inset;
      color: #fff;
      cursor: pointer;
      text-shadow: rgb(0 0 0 / 25%) 0px 1px 2px;
      border: none;
      border-radius: 5px;
      font-size: 18px;
      margin: 3px 0 3px 0;
    `
    x.appendChild(tt);
    var t = document.createElement("input");
    t.type = "file";
    t.style = `
      position: absolute;
      margin: 62px 0 0 -126px;
      opacity: 0;
      cursor: pointer;
      transform: scale(2);
    `;
    t.multiple = true;
    t.accept = ".har";
    t.id = "ssk";
    x.appendChild(t);
    if(ifsurviv) {
        document.getElementById("missions-name").style = "display:none";
        document.getElementById("pass-quest-wrapper").style = "display:none";
    }
    var skk = document.createElement("button");
    skk.id = "skk";
    skk.onclick = function() {document.getElementById('ssk').click()};
    skk.innerHTML = "Choose file";
    skk.style = "background: rgb(30, 144, 255);box-shadow: rgb(24, 113, 200) 0px -5px inset;color: rgb(255, 255, 255);cursor: pointer;text-shadow: rgba(0, 0, 0, 0.25) 0px 1px 2px;font-weight: 700;width: 100%;border: none;border-radius: 5px;padding: 12px 20px;font-size: 18px;";
    x.appendChild(skk);
    tt.onchange = function() {
        harRead.selected = tt.value;
    };


    let applySVGtext = function(nam, jo) {
        let ii = nam.indexOf("---");
        if (ii >= 0) nam = nam.slice(0, ii);
        let blob1 = new Blob([jo], {
            type: "image/svg+xml"
        });
        let url1 = URL.createObjectURL(blob1);
        nam = nam.replace(".svg", ".img");
        PIXI.utils.TextureCache[nam] = PIXI.Texture.fromImage(url1, undefined, undefined, 2);
    };
    let applyParsedHarFile = function(jo) {
        window.harRead.games = {};

        window.harRead.files.push(jo);
        tt.innerHTML = "";
        if (1) {
            var ectr = 0;
            jo.log.entries.forEach(x => {
                if (x.request.url.slice(0, 2) == "ws") {
                    if (x._webSocketMessages.length < 2) return;
                    var time = x._webSocketMessages.slice(-1)[0].time - x._webSocketMessages[0].time;
                    time = (time / 60 | 0) + ":" + String((time | 0) % 60).padStart(2, "0") + " ";
                    console.log(time, x._webSocketMessages.length, x.request.url);
                    var ww = x.request.url;
                    var w0 = ww.lastIndexOf("=");
                    var wi = ww.lastIndexOf("=");

                    if (w0 >= 0 || wi >= 0 || ww.includes(".snake.io:9092") ) {
                        var output = time;
                        if(w0>=0 || wi>=0) output+= " " + ww.slice(6, 6 + 9) + ww.slice(wi);
                        var qi = document.createElement("option");
                        qi.value = ectr;
                        qi.innerHTML = output + "";
                        tt.appendChild(qi);
                        harRead.games[ectr] = x;
                    }
                }
                ectr += 1;
            });
        }
    };
    t.onchange = function handleFileSelect1(evt) {
        var filesreset = false;
        tt.innerHTML = "<option>loading...</option>";
        var files = evt.target.files;
        var output = [];
        for (var i = 0, f; f = files[i]; i++) {
            if (f.name.slice(-4) == ".har" && !filesreset) {
                window.harRead.files = [];
                window.harRead.games = {};
                filesreset = true;
            }
            var fr = new FileReader();
            harRead.files.push(f);
            if (f.name.slice(-4) == ".har") fr.onload = function(evt) {
                if (evt.loaded == evt.total) {
                    tt.innerHTML = "<option>parsing...</option>";
                    try {
                        var jo = JSON.parse(evt.currentTarget.result);
                    } catch (e) {
                        harRead.alert("This file is not a valid .har file, or gabled/truncated");
                    }
                    applyParsedHarFile(jo);
                }
            };
            else if (f.name.slice(-4) == ".svg") {
                let fname_temp = f.name;
                fr.onload = function(evt) {
                    if (evt.loaded == evt.total) {
                        applySVGtext(fname_temp, evt.currentTarget.result)
                    }
                };
            } else harRead.alert("I do not know what to do with " + f.name + "\nI expect .har or .svg files");
            fr.readAsText(f);
        }
        console.log(output);
    };
});
if (document.readyState === "complete" || document.readyState === "interactive") foo();
else window.addEventListener("DOMContentLoaded", foo);

function dosomething_with_send_disable(){
    cvs.style.transform='';
}

function dosomething_with_send(e){
    if(survivharplayerconfig.silly1!=true) return;


    let survivOneEps = 1.0001;
    function delerpOne10(e) {
        return (-1+2*(e/1023))*survivOneEps;
    }

    let x=from_base64s(e);
    console.info(x);
    try {
        let i = 3 + 3*!! (x[2]&0x80);
        let xdiv,ydiv;
        xdiv = x[i] + (x[i+1] &3)*256;
        ydiv = (x[i+1]>>2) + ((x[i+2]&15)*64);
        xdiv=delerpOne10(xdiv);
        ydiv=delerpOne10(ydiv);

        let az=Math.atan2(xdiv,ydiv);
        let cs = Math.cos(az);
        let sn = Math.sin(az);
        let pz = 0.9;
        let cz = Math.cos(pz);
        let zn = Math.sin(pz);
        cvs.style.transform=`perspective(800px) matrix3d(${cs}, ${-sn*cz}, ${-sn*zn}, 0,  ${sn}, ${cs*cz}, ${cs*zn}, 0, 0, ${-zn}, ${cz}, 0, 0, 0, 0  , 1)`;

    }catch(e) { console.error(e); }


};


function from_base64s(base64) {
    var raw = atob(base64);
    var rawLength = raw.length;
    var array = new Uint8Array(new ArrayBuffer(rawLength));
    for (let i = 0; i < rawLength; i++) {
        array[i] = raw.charCodeAt(i);
    }
    return array;
}

function from_base64s_ab(base64) {
    var raw = atob(base64);
    var rawLength = raw.length;
    var arrayb = new ArrayBuffer(rawLength);
    var array = new Uint8Array(arrayb);
    for (let i = 0; i < rawLength; i++) {
        array[i] = raw.charCodeAt(i);
    }
    return arrayb;
}
if (1 && "weboverload") {
    'var RecordingWebSocket';
    var oldWebSocket = window.WebSocket;
    var ReplayWebSocket = function(a) {
        console.info("ws creating:", a);
        if (a) a = a.replace("wss://", "ws://");
        this.sndwo = 0;
        if (a.indexOf("-p1.surviv.io") >= 0) {
            this.binaryType = "blob";
            this.stupidsum = this.static_counter + 4;
            this.static_counter = (this.static_counter + 3) % 19;
            this.url = a;
            this.__type = "echo";
            setTimeout(() => this.__onopen(), 100);
        } else if (a.includes("?gameId=") || a.includes('.snake.io:9092') ) {
            this.__type = "game";
            this.wsh = harRead.games[window.harRead.ui.matchSelectorComboBox.value];
            this.wso = this.wsh["_webSocketMessages"];
            this.wslen = this.wso.length;
            this.wsi = 0;
            this.binaryType = "blob";
            this.url = a;
            setTimeout(() => this.__onopen(), 100);
        }
    };
    ReplayWebSocket.name="ReplayWebSocket";
    ReplayWebSocket.prototype = {
        OPEN: 1,
        CLOSED: 3,
        static_counter: 10,
        __onopen: function() {
            this.readyState = this.OPEN;
            this.onopen && this.onopen();
        },
        __onclose: function() {
            this.readyState = this.CLOSED;
            this.onclose && this.onclose();
        },
        __onmessage: function() {
            if (this.readyState != this.OPEN) return;
            this.onmessage && this.onmessage({
                data: new Uint8Array(1)
            });
        },
        __onharmessage: function(e) {
            if (this.readyState != this.OPEN || !this.onmessage) return;
            try {
                this.onmessage(this.wsmsg);
            } catch (e) {
                console.error("onharmessage error");
                console.error(this.wsi);
                console.error(this.wsmsg);
                console.error(e);
            }
            if (this.nextmsg()) {} else "todo:close";
        },
        nextmsg: function() {
            while (++this.wsi < this.wslen) {
                if (this.wso[this.wsi].type == "receive") break;
                else if (this.wso[this.wsi].type == "send") {
                    dosomething_with_send(this.wso[this.wsi].data);
                }
            }
            if (this.wsi >= this.wslen) return false;
            var a1 = this.wso[this.wsi];
            var datat = a1.data;
            this.wsmsg = {
                data: from_base64s_ab(datat)
            };
            time = (this.wso[this.wsi].time * 1000 + this.__timediff - new Date().getTime() * slowmo);
            if (time < -1000 || time > 4000) {
                this.__timediff = 30 - this.wso[this.wsi].time * 1000 + new Date().getTime() * slowmo;
                time = 30;
            }
            if (time > 1) setTimeout(() => this.__onharmessage(), time);
            else this.__onharmessage();
            return true;
        },
        send: function(a) {
            this.sndwo = 0;
            if (this.__type == "echo") setTimeout(() => this.__onmessage(), this.stupidsum);
            if (this.__type == "game")
                if (this.__starttime == undefined) {
                    this.__starttime = new Date().getTime() + 100;
                    this.__timediff = this.__starttime * slowmo - this.wso[0].time * 1000;
                    this.nextmsg();
                }
        },
        close: function() {
            this.readyState = this.CLOSED;
        }
    };
    'window.WebSocket = ReplayWebSocket';
}

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址