import win.clip; import win.clip.bitmap; import inet.url; import crypt.otp; import zbar; import fonts.fontAwesome; import win.ui; /*DSG{{*/ var winform = win.form(text="动态口令(TOTP)工具";right=468;bottom=323;border="dialog frame";max=false) winform.add( btnQr={cls="button";text="识别屏幕或剪贴板二维码";left=131;top=261;right=365;bottom=291;z=11}; btnRefresh={cls="plus";text='\uF021';left=344;top=75;right=374;bottom=105;border={radius=-1};font=LOGFONT(h=-16;name='FontAwesome');z=8}; comboAlgo={cls="combobox";left=131;top=132;right=253;bottom=158;edge=1;items={"sha1","sha256","sha512"};mode="dropdownlist";z=2}; editDigits={cls="edit";text="6";left=131;top=173;right=253;bottom=197;edge=1;z=4}; lbSlider={cls="static";text="刷新周期(30 秒):";left=-4;top=216;right=114;bottom=241;align="right";transparent=1;z=7}; plusCountdown={cls="plus";left=314;top=123;right=397;bottom=206;notify=1;z=6}; plusOtp={cls="plus";left=131;top=57;right=299;bottom=104;align="right";border={bottom=1;color=0xFF808080};db=1;dl=1;editable=1;font=LOGFONT(h=-24);hidesel=false;notify=1;textPadding={top=7;right=45};z=13}; plusSecret={cls="plus";left=131;top=20;right=413;bottom=46;align="right";border={bottom=1;color=0xFF808080};db=1;dl=1;editable=1;notify=1;paddingTop=5;password=1;textPadding={right=24;bottom=1};z=12}; plusSlider={cls="plus";left=131;top=220;right=411;bottom=235;bgcolor=0xD9AB23;border={radius=-1};foreRight=15;forecolor=0x1C77FF;paddingBottom=5;paddingTop=5;z=5}; static={cls="static";text="哈希算法:";left=42;top=136;right=114;bottom=156;align="right";transparent=1;z=1}; static2={cls="static";text="密码位数:";left=42;top=178;right=114;bottom=198;align="right";transparent=1;z=3}; static4={cls="static";text="密钥(Base32):";left=16;top=27;right=114;bottom=47;align="right";transparent=1;z=9}; static5={cls="static";text="动态密码:";left=16;top=88;right=114;bottom=108;align="right";transparent=1;z=10} ) /*}}*/ // 1.初始化界面控件 //---------------------------------- // 可显示/隐藏密码的密钥输入框 var revealIcon = winform.plusSecret.addCtrl( cls="plus"; marginRight=0;marginBottom=2;width=24; iconText = '\uF023'/*锁*/; iconStyle={align="right";font=LOGFONT(h=-15;name='FontAwesome');padding={top=3}} ) revealIcon.skin({ color = {default = 0xC0000000;hover = 0xFFFF0000;active = 0xFF00FF00;}; checked = {iconText = '\uF06E'/*眼睛*/;} }) revealIcon.onMouseClick = function(){ winform.plusSecret.passwordChar = !owner.checked ? "*" : null; } // 带复制按钮的 OTP 显示框 var copyIcon = winform.plusOtp.addCtrl( cls="plus"; marginRight=8;marginTop=8;width=32; height=32; iconText = '\uF0C5'/*复制*/; iconStyle={align="center";font=LOGFONT(h=-18;name='FontAwesome');padding={top=2}} ) copyIcon.skin({ color = {default = 0xD0999999;hover = 0xFF0078D7;active = 0xFF005A9E;}; checked = {iconText = '\uF00C'/*对勾*/; color={default=0xFF008000}} }) copyIcon.onMouseClick = function(){ win.clip.write(winform.plusOtp.text); winform.plusOtp.editBox.selectAll(); // 全选 owner.checked = true; owner.setTimeout(function(){ copyIcon.checked = false; winform.plusOtp.editBox.setSel(); // 取消全选 }, 1000) } // 刷新周期滑块 winform.plusSlider.setTrackbarRange(5, 60); winform.plusSlider.progressPos = 30; // 倒计时圆形进度条 winform.plusCountdown.setPieRange(0, 100); winform.plusCountdown.foreground = 0x99008000; winform.plusCountdown.background = 0x30808080; winform.plusCountdown.text = "30s"; // 算法下拉框 winform.comboAlgo.selIndex = 1; // 2.核心逻辑 //---------------------------------- var mainTimer, countdownTimer; // 更新 OTP 的核心函数 var updateOtp = function(){ var secret = winform.plusSecret.text; if(!#secret){ winform.plusOtp.text = string.repeat(tonumber(winform.editDigits.text) : 6,"0"); return; } var period = winform.plusSlider.progressPos; var digits = tonumber(winform.editDigits.text); var algo = winform.comboAlgo.text; var code, expiresIn, err = crypt.otp.totp(secret, period, digits, algo); if(!code){ winform.msgboxErr(err,"错误"); winform.plusOtp.text = string.repeat(digits : 6,"!"); return; } winform.plusOtp.text = code; return expiresIn, period; } // 重启主定时器 var restartTimer restartTimer = function(){ // 停止旧的定时器 if(mainTimer) winform.clearInterval(mainTimer); if(countdownTimer) winform.clearInterval(countdownTimer); // 立即更新一次 var expiresIn = updateOtp(); if(!expiresIn) return; // 启动倒计时动画 var startTick = time.tick(); countdownTimer = winform.setInterval(function(){ var elapsed = (time.tick() - startTick)/1000; var progress = ( (expiresIn - elapsed) / expiresIn ) * 100; if(progress < 0) progress = 0; winform.plusCountdown.progressPos = progress; winform.plusCountdown.text = math.ceil(expiresIn - elapsed) ++ "s"; }, 100); // 启动下一个周期的主定时器 mainTimer = winform.setTimeout(restartTimer, expiresIn * 1000); } // 3. 事件绑定 //---------------------------------- // 滑块、输入框、下拉框变动时重启定时器 winform.plusSecret.onTextChange = restartTimer; winform.editDigits.onTextChange = restartTimer; winform.comboAlgo.oncommand = restartTimer; winform.plusSlider.onPosChanged = function( pos,triggeredByUser ){ winform.lbSlider.text = "刷新周期("+pos+" 秒):" restartTimer(); } // 手动刷新按钮 winform.btnRefresh.skin({ background = {hover=0xFFE1E1E1; active=0xFFCCCCCC} }) winform.btnRefresh.oncommand = restartTimer; var scanner = zbar.scanner(); scanner.config('qrcode.enable'); winform.btnQr.oncommand = function(id,event){ var bmp = win.clip.bitmap.read() var text = bmp && scanner.scanBitmap(bmp) if(!(text && string.startsWith(text,"otpauth://totp/"))) text = scanner.scanBitmap(com.picture.snap()); if(text && string.startsWith(text,"otpauth://totp/")){ var params = inet.url.getParams(text); if(params){ if(params.secret){ // OTP 密钥是 Base32 编码的 winform.plusSecret.text = params.secret; } if(params.algorithm){ winform.comboAlgo.text = string.lower(params.algorithm); } if(params.digits){ winform.editDigits.text = params.digits; } if(params.period){ winform.plusSlider.progressPos = tonumber(params.period); } restartTimer(); // 立即刷新 return; // 找到一个就退出 } } } // 4. 保存配置文件 //---------------------------------- import fsys.config; var config = fsys.config(io.appData("aardio/std/tools/otp")); winform.bindConfig( config.winform,{ editDigits = "text"; plusSecret = "text"; plusSlider = "progressPos"; combobox = "selIndex"; lbSlider = "text"; } ); restartTimer(); winform.show(); win.loopMessage();