aardio 文档

aardio 范例: WebView2 模拟手机设备 + 动态设置 SOCKS5 代理服务器

//WebView2 模拟手机设备 + 动态设置 SOCKS5 代理服务器
import fonts.fontAwesome;
import web.view;
import win.ui;
/*DSG{{*/
var winform = win.form(text="SOCKS5 浏览器";right=959;bottom=640;bgcolor=0xFFFFFF)
winform.add(
btnGo={cls="plus";left=895;top=7;right=950;bottom=33;color=0x3C3C3C;disabled=1;dr=1;dt=1;font=LOGFONT(h=-13);iconStyle={font=LOGFONT(h=-15;name='FontAwesome')};iconText='\uF1D8';notify=1;z=4};
btnRefresh={cls="plus";left=241;top=7;right=271;bottom=33;color=0x3C3C3C;dl=1;dt=1;font=LOGFONT(h=-13);iconStyle={font=LOGFONT(h=-13;name='FontAwesome')};iconText='\uF021';notify=1;z=6};
comboProxy={cls="combobox";left=50;top=9;right=240;bottom=33;dl=1;dt=1;edge=1;items={};mode="dropdown";z=2};
custom={cls="custom";left=0;top=40;right=942;bottom=640;db=1;dl=1;dr=1;dt=1;z=1};
editUrl={cls="plus";left=489;top=7;right=893;bottom=33;align="right";border={bottom=1;color=0x969696};disabled=1;dr=1;dt=1;editable="edit";font=LOGFONT(h=-13);notify=1;textPadding={left=5;top=3;bottom=3};z=3};
labelProxy={cls="static";text="代理:";left=8;top=12;right=48;bottom=28;dl=1;dt=1;transparent=1;z=5};
labelStatus={cls="static";text="正在搜索代理...";left=277;top=12;right=441;bottom=28;dl=1;dt=1;transparent=1;z=7}
)
/*}}*/

// 创建 SOCKS5 中转代理服务器
import wsock.tcp.socks5Server;
var socks5Server = wsock.tcp.socks5Server.async("127.0.0.1",0);
socks5Server.start();

// 创建 WebView2 控件,模拟手机浏览器
var wb = web.view(winform.custom,{
    startArguments = {  
        acceptLang = "zh-CN";
        userAgent = "Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/115.0.5790.166";
        proxyServer = socks5Server.getUrl();//使用 SOCKS5 代理
    }; 
    userDataDir = _STUDIO_INVOKED ? io.appData("/aardio/webview2/user-data/ide/socks5-browser")
}) 

// 启用 CDP 的 Emulation 域,模拟手机设备
wb.cdp("Emulation.enable");
wb.cdp("Emulation.setDeviceMetricsOverride", {
    mobile = true;
});

// 设置移动设备 HTTP 请求头
wb.cdp("Network.enable");
wb.cdp("Network.setExtraHTTPHeaders", {
    headers = {
        ["Sec-CH-UA-Mobile"] = "?1";
        ["Sec-CH-UA-Platform"] = '"Android"';
    }
});

wb.onDocumentComplete = function(url){ 

    if(!string.find(url,"^<chrome\-error>|<about>\:")){ 
        winform.editUrl.text = url;
    }
}

wb.html = $"/socks5.html";

// 应用代理设置
var applyProxy = function(proxy){ 
    if(#proxy){
        socks5Server.setUpstreamProxy(proxy);
        winform.text = "SOCKS5 浏览器 - 已切换到代理: " + proxy;
    }
}

// 代理下拉框选择事件
winform.comboProxy.onSelChange = function(){
    applyProxy(winform.comboProxy.selText);
}

import fsys.table;
var cfgTable = fsys.table(io.appData("/aardio/std/tools/socks5.webview.table"));
if(cfgTable.url){
    winform.editUrl.text = cfgTable.url;
}
else{
    winform.editUrl.text = "github.com"
}


// 打开网页
winform.btnGo.oncommand = function(){
    var proxy = winform.comboProxy.text;
    if(#proxy){
        applyProxy(winform.comboProxy.text);
        winform.text = "SOCKS5 浏览器 - 已切换到代理: " + proxy;
    }

    var url = winform.editUrl.text;
    if(#url){
        cfgTable.url = url;
        cfgTable.save();

        // 如果没有协议前缀,自动添加 https://
        if(!string.find(url,"^<<@@https@>|<@@http@>>?\://")){
            url = "https://" + url;
        }

        wb.html = "<body style='text-align: right;'>正在打开:"+url+" <progress></progress> </body>"
        wb.go(url);
    }
    else{
        winform.editUrl.editBox.showErrorTip("请输入网址");
    }

}

// 地址栏回车事件
winform.editUrl.editBox.onOk = function(ctrl,alt,shift){ 
    winform.btnGo.oncommand();
    return false; 
}

// 线程通信
import thread.command;
var threadCommand = thread.command();

// 记录已找到的代理数量
var proxyFoundCount = 0;

// 启用网址输入框和打开按钮
var enableUrlInput = function(){
    winform.editUrl.disabled = false;
    winform.btnGo.disabled = false;
    winform.editUrl.setFocus();
}

// 接收代理服务器
threadCommand.$onProxyFound = function(proxyStr){
    winform.comboProxy.add(proxyStr);
    proxyFoundCount++;

    // 第一个代理自动选中并应用,同时启用网址输入
    if(proxyFoundCount == 1){
        winform.comboProxy.selIndex = 1;
        applyProxy(winform.comboProxy.selText);
        enableUrlInput();
    }

    winform.labelStatus.text = "已找到 " + proxyFoundCount + " 个代理";
}

// 搜索完成
threadCommand.$onSearchComplete = function(total){
    if(total == 0){
        winform.labelStatus.text = "未找到可用代理";
    }
    else {
        winform.labelStatus.text = "共 " + total + " 个可用代理";
    }

    winform.btnRefresh.disabledText = null;
    winform.comboProxy.disabled = false;
}

// 启动代理搜索线程
var startProxySearch = function(){
    proxyFoundCount = 0;
    winform.comboProxy.clear();
    winform.labelStatus.text = "正在搜索代理...";
    winform.btnRefresh.disabledText = ['\uF254','\uF251','\uF252','\uF253','\uF250']
    winform.comboProxy.disabled = true;

    // 搜索时禁用网址输入
    winform.editUrl.disabled = true;
    winform.btnGo.disabled = true;

    thread.invoke(
        function(threadCommand){
            // 获取前 10 个可用代理
            var foundCount = 0;
            var maxCount = 10;

            var knownSocks5Ports = function (p) { 
                if( ({51837=1,40000=1,9050=1,9150=1,10085=1,2334=1,6153=1,20170=1,2801=1})[p]) return 1;
                if( p < 1080 || p >= 49152 ) return false;

                return (
                    (p >= 1080 && p < 1090) 
                    || (p >= 7890 && p < 7900) 
                    || (p >= 10800 && p < 10810) 
                    || (p >= 2080 && p <= 2081) 
                    || (p >= 8888 && p <= 8890) 
                );
            }

            import inet.stat;
            var stat = inet.stat();
            var listeningPorts = table.filter(
                stat.tcp, 
                lambda(conn) 
                    conn.state == "LISTENING" 
                    && knownSocks5Ports(conn.localPort)
                    && (conn.local == "0.0.0.0" || conn.local = "127.0.0.1" ) 
            );

            listeningPorts = table.unique(table.map(listeningPorts,lambda(v) v.localPort)); 
            table.sort(listeningPorts);

            var localSock5Proxy;
            import wsock.tcp.socks5Client; 
            for i, port in listeningPorts { 
                if( wsock.tcp.socks5Client.test("127.0.0.1",port)){
                    localSock5Proxy = "SOCKS5://127.0.0.1:"+port;
                    break
                }
            }

            if(localSock5Proxy){
                foundCount++;
                threadCommand.$onProxyFound(localSock5Proxy);
            }

            import web.rest.github;
            import thread.works;

            // 创建 250 个检测线程
            var works = thread.works(250,
                function(proxy){
                    import wsock.tcp.socks5Client;
                    // 检测 SOCKS5 可用性,参数 3 必须指定域名
                    if(wsock.tcp.socks5Client.testHttp(proxy.ip, proxy.port, "duck.ai")){
                        return proxy;
                    }
                }
            );

            // 从网络获取 SOCKS5 服务器列表
            var proxies = web.rest.github.getJson("proxifly","free-proxy-list","proxies/protocols/socks5/data.json");

            if(proxies){
                works.pushAll(proxies);

                var timeout = 30000; // 30 秒超时
                var startTime = time.tick();

                while(foundCount < maxCount){
                    var result = works.pop(1, true);
                    if(result){
                        var proxyStr = result.proxy;
                        threadCommand.$onProxyFound(proxyStr);
                        foundCount++;
                    }

                    // 超时检查
                    if(time.tick() - startTime > timeout){
                        break;
                    }
                }

                threadCommand.$onSearchComplete(foundCount);
                works.quit(10);
            }
            else {
                threadCommand.$onSearchComplete(foundCount);
            }
        }, threadCommand
    )
}

// 刷新按钮点击事件
winform.btnRefresh.oncommand = function(){
    startProxySearch();
}

// 启动时自动搜索代理
startProxySearch();
winform.editUrl.editBox.onFocusGot = function(){ 
    owner.selectAll()
}

winform.btnGo.skin({
    color={
        active=0xFF00FF00;
        default=0xFF3C3C3C;
        disabled=0xFF6D6D6D;
        hover=0xFF0078D4
    }
})

winform.btnRefresh.skin({
    color={
        active=0xFF00FF00;
        default=0xFF3C3C3C;
        disabled=0xFF6D6D6D;
        hover=0xFF0078D4
    }
})

winform.show(3/*_SW_MAXIMIZE*/);
win.loopMessage();
Markdown 格式