├── 1. Simple Example1.ahk ├── 2. Simple Example2.ahk ├── 3. Translator.ahk ├── 4. Async.ahk ├── 5. Aggregate Translator.ahk ├── 6. Dictionary.ahk ├── Lib ├── BTT.ahk ├── BaiduTranslator.ahk ├── Chrome.ahk ├── DeepLTranslator.ahk ├── Gdip_All.ahk ├── NonNull.ahk ├── Paragraph.ahk ├── SogouTranslator.ahk ├── UriEncode.ahk └── YoudaoTranslator.ahk ├── README.md ├── icon.png ├── img ├── Aggregate Translator.ahk.gif ├── Aggregate Translator.ahk.png ├── Dictionary.ahk.png └── Translator.ahk.png └── 有道词典 ├── Lib ├── CrackUrl.ahk ├── GetTag.ahk ├── NonNull.ahk ├── RegEx.ahk └── WinHttp.ahk ├── autocomplete_json.js ├── jquery-1.8.2.min.js ├── result-min.css ├── result-min.js └── 有道词典.ahk /1. Simple Example1.ahk: -------------------------------------------------------------------------------- 1 | ; Translate text from Japanese to English. 2 | MsgBox,% BaiduTranslator.translate("今日の天気はとても良いです", "ja", "en") 3 | 4 | ExitApp 5 | 6 | #Include 7 | 8 | 9 | 10 | /* 11 | ------------------------------------------------------------------------- 12 | Language abbreviations may be different in different libraries. 13 | 14 | However, the following common languages always use the same abbreviations in different libraries. 15 | 16 | 阿拉伯语 ar Arabic 17 | 爱沙尼亚语 et Estonian 18 | 保加利亚语 bg Bulgarian 19 | 波兰语 pl Polish 20 | 丹麦语 da Danish 21 | 德语 de German 22 | 俄语 ru Russian 23 | 法语 fr French 24 | 芬兰语 fi Finnish 25 | 韩语 ko Korean 26 | 荷兰语 nl Dutch 27 | 捷克语 cs Czech 28 | 拉脱维亚语 lv Latvian 29 | 立陶宛语 lt Lithuanian 30 | 罗马尼亚语 ro Romanian 31 | 葡萄牙语 pt Portuguese 32 | 日语 ja Japanese 33 | 瑞典语 sv Swedish 34 | 书面挪威语 nb Norwegian 35 | 斯洛伐克语 sk Slovak 36 | 斯洛文尼亚语 sl Slovenian 37 | 泰语 th Thai 38 | 土耳其语 tr Turkish 39 | 乌克兰语 uk Ukrainian 40 | 西班牙语 es Spanish 41 | 希腊语 el Greek 42 | 匈牙利语 hu Hungarian 43 | 意大利语 it Italian 44 | 印尼语 id Indonesian 45 | 英语 en English 46 | 越南语 vi Vietnamese 47 | 中文 zh Chinese 48 | 49 | */ -------------------------------------------------------------------------------- /2. Simple Example2.ahk: -------------------------------------------------------------------------------- 1 | ; Initialization allows you to specify the chrome.exe path and also speeds up the first translation. 2 | ; BaiduTranslator.init() --- Automatically find the path of the installed chrome.exe 3 | ; BaiduTranslator.init("x:\xxx\chrome.exe") --- Use specified path of the chrome.exe 4 | BaiduTranslator.init() 5 | 6 | ; Translate text from Japanese to English. 7 | MsgBox,% BaiduTranslator.translate("今日の天気はとても良いです", "ja", "en") 8 | ; Supports automatic detection language by using "auto". 9 | MsgBox,% BaiduTranslator.translate("今日の天気はとても良いです", "auto", "en") 10 | ; Omitting the parameters 2 and 3 means translate text from English to Chinese. 11 | MsgBox,% BaiduTranslator.translate("Hello my love") 12 | 13 | ; It will automatically release resources on exit. 14 | ; But you can also release resources manually. 15 | ; Please note that after releasing resources, you need to re-initialize it before using again. 16 | BaiduTranslator.free() 17 | 18 | ExitApp 19 | 20 | #Include 21 | 22 | 23 | 24 | /* 25 | ------------------------------------------------------------------------- 26 | The other libraries are used in a very similar way to BaiduTranslator. 27 | 28 | For example: 29 | 30 | SogouTranslator.init() 31 | SogouTranslator.translate("text") 32 | SogouTranslator.free() 33 | #Include 34 | 35 | DeepLTranslator.init() 36 | DeepLTranslator.translate("text") 37 | DeepLTranslator.free() 38 | #Include 39 | 40 | */ -------------------------------------------------------------------------------- /3. Translator.ahk: -------------------------------------------------------------------------------- 1 | gosub, CreateGUI 2 | gosub, InitializeTranslator 3 | return 4 | 5 | CreateGUI: 6 | Gui Add, Edit, x16 y10 w450 h150 vOriginal +Disabled 7 | Gui Add, Edit, x16 y220 w450 h150 vTranslation +Disabled 8 | Gui Add, Button, x16 y170 w450 h40 vTranslate +Disabled, Translate 9 | Gui Show, w482 h380, Translator 10 | return 11 | 12 | InitializeTranslator: 13 | GuiControl, , Original, Initializing...`n正在初始化... 14 | ; SogouTranslator.multiLanguage.5 = Timeout 15 | if (SogouTranslator.init().Error=SogouTranslator.multiLanguage.5) 16 | GuiControl, , Original, Initialization failed, please exit and try again.`n初始化失败,请退出重试。 17 | else 18 | { 19 | GuiControl, Enable, Original 20 | GuiControl, Enable, Translation 21 | GuiControl, Enable, Translate 22 | GuiControl, , Original, Initialization succeeded, please enter the text to be translated.`n初始化完成,请输入待翻译文本。 23 | } 24 | return 25 | 26 | ButtonTranslate: 27 | Gui, Submit, NoHide 28 | GuiControl, , Translation, Translating...`n翻译中... 29 | 30 | ; To determine whether Chinese to English or English to Chinese translation is based on the percentage of Chinese characters in the original text 31 | RegExReplace(Original, "[一-龟]", , Chinese_Characters_Len) 32 | if (Chinese_Characters_Len/StrLen(Original) > 0.6) 33 | ret := SogouTranslator.translate(Original, "zh", "en") 34 | else 35 | ret := SogouTranslator.translate(Original) 36 | 37 | GuiControl, , Translation, % ret.Error ? ret.Error : ret 38 | return 39 | 40 | GuiEscape: 41 | GuiClose: 42 | ExitApp 43 | return 44 | 45 | #Include -------------------------------------------------------------------------------- /4. Async.ahk: -------------------------------------------------------------------------------- 1 | ; In my country, the connection to deepl is very unstable, it usually takes 5-30 seconds to get result. 2 | ; This means that your code will be stuck for 5-30 seconds at the same time (because it is waiting for the result). 3 | ; Using asynchronous mode, you can avoid this situation. 4 | 5 | ; If you don't use asynchronous mode, the program will get stuck on the next line for 5-30 seconds before it can run the code that follows. 6 | DeepLTranslator.init(,,,"async") 7 | ; Get initialization result. 8 | loop 9 | { 10 | ; Because asynchronous mode is used, you can do whatever you want first, and then check the result. 11 | ; Here we will display some information dynamically. 12 | ellipsis.="." 13 | ; By the way, there is a library called BTT, which is as easy to use as ToolTip but more powerful. 14 | ; https://github.com/telppa/BeautifulToolTip 15 | ToolTip, Initializing%ellipsis% 16 | 17 | if (DeepLTranslator.getInitResult()) 18 | { 19 | MsgBox, Initialization succeeded 20 | break 21 | } 22 | else 23 | Sleep, 1000 24 | } 25 | 26 | ret := DeepLTranslator.translate("Hello my love",,,"async") 27 | if (ret.Error) 28 | { 29 | MsgBox, % ret.Error 30 | ExitApp 31 | } 32 | 33 | ; Get translation result. 34 | loop 35 | { 36 | ellipsis2.="." 37 | ToolTip, Translating%ellipsis2% 38 | 39 | if (DeepLTranslator.getTransResult()) 40 | { 41 | MsgBox, % DeepLTranslator.getTransResult() 42 | break 43 | } 44 | else 45 | Sleep, 1000 46 | } 47 | 48 | ExitApp 49 | 50 | #Include -------------------------------------------------------------------------------- /5. Aggregate Translator.ahk: -------------------------------------------------------------------------------- 1 | /* 2 | Feature: 3 | 支持 /hide 参数启动 4 | 支持外部呼叫(本翻译器运行时,在自己程序中使用以下代码即可呼叫它翻译) 5 | 注意:当本翻译器使用管理员权限启动后,呼叫聚合翻译器() 必须同样使用管理员权限 6 | 呼叫聚合翻译器("我爱苹果") 7 | 呼叫聚合翻译器(Text) 8 | { 9 | Prev_DetectHiddenWindows := A_DetectHiddenWindows 10 | Prev_TitleMatchMode := A_TitleMatchMode 11 | DetectHiddenWindows, On 12 | SetTitleMatchMode, 1 13 | ControlSetText, Edit2, %Text%, 聚合翻译器 ahk_class AutoHotkeyGUI 14 | DetectHiddenWindows, %Prev_DetectHiddenWindows% 15 | SetTitleMatchMode, %Prev_TitleMatchMode% 16 | } 17 | 18 | Todo: 19 | 设置最佳显示位置 鼠标位置+窗口宽>屏幕宽 则用屏幕宽 避免超出屏幕 20 | --headless 下不能使用 --user-data-dir= 21 | --headless 下临时文件乱跑 22 | 历史记忆 23 | 24 | 已知问题: 25 | 当按住主窗口标题栏并拖动它超出屏幕最顶端,然后松手。 26 | 此时主窗口会自动回落到屏幕范围内,子窗口1号会跟随移动,但子窗口2号不会跟随移动。 27 | */ 28 | 29 | #SingleInstance Ignore 30 | #NoEnv 31 | SetBatchLines, -1 32 | SetWorkingDir, %A_ScriptDir% 33 | 34 | Init: 35 | ; 不要把 chromePath 造为超级全局变量,会污染到库中的同名变量 36 | chromePath := "Chrome\chrome.exe" 37 | 38 | global Translators, Original, OuterOriginal, btnTranslate 39 | 40 | Lang := MultiLanguage() 41 | 42 | Translators := {"DeepL" : {initState:"" ,initTime:"" ,resultState:"" ,resultTime:"", name:Lang.81} 43 | , "Sogou" : {initState:"" ,initTime:"" ,resultState:"" ,resultTime:"", name:Lang.82} 44 | , "Baidu" : {initState:"" ,initTime:"" ,resultState:"" ,resultTime:"", name:Lang.83} 45 | , "Youdao" : {initState:"" ,initTime:"" ,resultState:"" ,resultTime:"", name:Lang.84}} 46 | 47 | gosub, CreatMain 48 | gosub, CreatSub 49 | 50 | OnMessage(0x3, "WM_MOVE") 51 | POWERBROADCAST.Register("GUID_CONSOLE_DISPLAY_STATE") ; 屏幕重新亮起则重启 52 | 53 | ShowOrHideSub("", "", "", "") 54 | return 55 | 56 | CreatMain: 57 | if (A_Args.1="/hide") 58 | isHide := "Hide" 59 | 60 | Menu, Tray, NoStandard 61 | Menu, Tray, Icon, icon.png 62 | Menu, Tray, Add, % Lang.2, MenuHandler 63 | Menu, Tray, Add, % Lang.3, MenuHandler 64 | Menu, Tray, Add, % Lang.4, MenuHandler 65 | Menu, Tray, Add, % Lang.5, MenuHandler 66 | Menu, Tray, Default, % Lang.2 67 | 68 | Gui, +HwndhMain 69 | Gui, Font, s10, 微软雅黑 70 | 71 | Gui, Add, Edit, x16 y16 w450 h150 vOriginal +Disabled 72 | Gui, Add, Edit, x0 y0 w0 h0 vOuterOriginal gOuterCallHandler ; 隐藏控件,用于接收外部调用 73 | Gui, Add, Edit, x0 y0 w0 h0 vMergeOriginal ; 隐藏控件,用于记录合并段落前的原文 74 | 75 | Gui, Add, CheckBox, x16 y184 w60 h23 vDeepL gShowOrHideSub Checked, % Lang.81 76 | Gui, Add, CheckBox, x88 y184 w60 h23 vSogou gShowOrHideSub, % Lang.82 77 | Gui, Add, CheckBox, x160 y184 w60 h23 vBaidu gShowOrHideSub, % Lang.83 78 | Gui, Add, CheckBox, x232 y184 w60 h23 vYoudao gShowOrHideSub Checked, % Lang.84 79 | 80 | Gui, Add, Text, x16 y224 w60 h23 +0x200, % Lang.7 81 | Gui, Add, ComboBox, x80 y224 w80 vFrom, % Lang.31 82 | Gui, Add, Text, x180 y224 w60 h23 +0x200, % Lang.8 83 | Gui, Add, ComboBox, x244 y224 w80 vTo, % Lang.32 84 | 85 | Gui, Add, CheckBox, x365 y224 w140 h23 vMerge, % Lang.9 86 | 87 | Gui, Add, Button, x16 y264 w450 h40 vbtnTranslate gTranslate +Disabled, % Lang.6 88 | Gui, Show, %isHide% w482 h320, % Lang.1 " v1.4" 89 | return 90 | 91 | CreatSub: 92 | for k, v in Translators 93 | { 94 | Gui, %k%:+Owner%hMain% +Hwndh%k% +LabelSub ; 所有子窗口的事件标签都以 Sub 开头,例如 SubClose 95 | Gui, %k%:Font, s12, 微软雅黑 96 | Gui, %k%:Add, Edit, x0 y0 w482 h150 v%k%Edit +Disabled 97 | Gui, %k%:Show, Hide w482 h150, % v.name 98 | } 99 | return 100 | 101 | MultiLanguage() 102 | { 103 | ret := [] 104 | 105 | if (A_Language="0804") 106 | { 107 | ret.1 := "聚合翻译器" 108 | ret.2 := "显示" 109 | ret.3 := "隐藏" 110 | ret.4 := "重启" 111 | ret.5 := "退出" 112 | ret.6 := "翻译" 113 | ret.7 := "源语言:" 114 | ret.8 := "目标语言:" 115 | ret.9 := "自动合并段落" 116 | ret.21 := "正在初始化..." 117 | ret.22 := "初始化失败,请重试。" 118 | ret.23 := "初始化成功。" 119 | ret.24 := "翻译中..." 120 | ret.25 := "错误 : " 121 | ret.26 := "翻译失败,请重试。" 122 | ret.31 := "自动检测||中文|英语|日语|韩语|德语|法语|俄语|阿拉伯语|爱沙尼亚语|保加利亚语|波兰语|丹麦语|芬兰语|荷兰语|捷克语|拉脱维亚语|立陶宛语|罗马尼亚语|葡萄牙语|瑞典语|斯洛伐克语|斯洛文尼亚语|泰语|土耳其语|乌克兰语|西班牙语|希腊语|匈牙利语|意大利语|印尼语|越南语" 123 | ret.32 := "中文||英语|日语|韩语|德语|法语|俄语|阿拉伯语|爱沙尼亚语|保加利亚语|波兰语|丹麦语|芬兰语|荷兰语|捷克语|拉脱维亚语|立陶宛语|罗马尼亚语|葡萄牙语|瑞典语|斯洛伐克语|斯洛文尼亚语|泰语|土耳其语|乌克兰语|西班牙语|希腊语|匈牙利语|意大利语|印尼语|越南语" 124 | ret.81 := "DeepL" 125 | ret.82 := "搜狗" 126 | ret.83 := "百度" 127 | ret.84 := "有道" 128 | } 129 | else 130 | { 131 | ret.1 := "Aggregate Translator" 132 | ret.2 := "Show" 133 | ret.3 := "Hide" 134 | ret.4 := "Reload" 135 | ret.5 := "Exit" 136 | ret.6 := "Translate" 137 | ret.7 := "From:" 138 | ret.8 := "To:" 139 | ret.9 := "Merge Paragraph" 140 | ret.21 := "Initializing..." 141 | ret.22 := "Initialization failed, please try again." 142 | ret.23 := "Initialization succeeded." 143 | ret.24 := "Translating..." 144 | ret.25 := "ERROR : " 145 | ret.26 := "Translation failed, please try again." 146 | ret.31 := "auto||zh|en|ja|ko|de|fr|ru" 147 | ret.32 := "zh||en|ja|ko|de|fr|ru" 148 | ret.81 := "DeepL" 149 | ret.82 := "Sogou" 150 | ret.83 := "Baidu" 151 | ret.84 := "Youdao" 152 | } 153 | 154 | return, ret 155 | } 156 | 157 | CalculatePos(Margin) 158 | { 159 | global hMain 160 | static SubHeight 161 | 162 | Prev_DetectHiddenWindows := A_DetectHiddenWindows 163 | DetectHiddenWindows, On 164 | 165 | ; 获取主窗口坐标+宽高 166 | WinGetPos, X, Y, W, H, ahk_id %hMain% 167 | ; 获取子窗口高度 168 | if (!SubHeight) 169 | for k, v in Translators 170 | { 171 | WinGetPos, , , , SubHeight, % "ahk_id " h%k% 172 | if (SubHeight) 173 | break 174 | } 175 | 176 | DetectHiddenWindows, %Prev_DetectHiddenWindows% 177 | 178 | ; 计算主窗口+子窗口坐标 179 | H2 := SubHeight 180 | Y2 := (H2*3+Margin*2-(H+Margin+H2))//2 181 | return, Pos := [[X, Y+H+Margin] 182 | , [X+W+Margin, Y-Y2] 183 | , [X+W+Margin, Y-Y2+H2+Margin] 184 | , [X+W+Margin, Y-Y2+2*(H2+Margin)]] 185 | } 186 | 187 | ; 不要在此函数中使用 Critical ,会导致启动 chrome 变得很卡 188 | ShowOrHideSub(ControlHwnd, GuiEvent, EventInfo, ErrLevel:="") 189 | { 190 | global hMain, Lang, chromePath 191 | 192 | WinGetTitle, isMainShow, ahk_id %hMain% 193 | isMainHide := isMainShow ? "" : "Hide" 194 | 195 | Pos := CalculatePos(Margin:=10) 196 | 197 | for k, v in Translators 198 | { 199 | GuiControlGet, CheckBoxState, , %k% 200 | if (CheckBoxState) 201 | { 202 | ; 子窗口按预设位置显示出来 203 | n++ 204 | 205 | ; 避免子窗口移动到负坐标 206 | if (Pos[n, 1]>0 and Pos[n, 2]>0) 207 | { 208 | WinGetPos, x, , w, h, % "ahk_id " h%k% 209 | if (x!="") 210 | ; 子窗口存在则移动位置 211 | DllCall("MoveWindow", "Ptr", h%k%, "Int", Pos[n, 1], "Int", Pos[n, 2], "Int", w, "Int", h, "Int", 0) 212 | else 213 | ; 子窗口不存在则显示 214 | Gui, %k%:Show, % Format("{} x{} y{}", isMainHide, Pos[n, 1], Pos[n, 2]) 215 | } 216 | 217 | switch, v.initState 218 | { 219 | case 0,1 : continue 220 | case 2 : v.initState:=1 221 | case "" : 222 | v.initState:=0 223 | GuiControl, %k%:, %k%Edit, % Lang.21 224 | Translator := k "Translator" 225 | %Translator%.init(chromePath,,,"async") 226 | } 227 | } 228 | else 229 | { 230 | ; 隐藏起来 231 | Gui, %k%:Hide 232 | 233 | switch, v.initState 234 | { 235 | case "",0,2 : continue 236 | case 1 : v.initState:=2 237 | } 238 | } 239 | } 240 | 241 | ; 重复 SetTimer 只会更新原计时器的时间参数,不会中断正在运行的原计时器,也不会重复设置多个计时器 242 | SetTimer, CheckInit, 500 243 | } 244 | 245 | CheckInit() 246 | { 247 | global Lang 248 | 249 | initCount1:=0, initCount0:=0 250 | for k, v in Translators 251 | { 252 | ; 统计可用引擎数量 253 | if (v.initState=1) 254 | initCount1++ 255 | ; 统计正在初始化引擎数量 256 | if (v.initState=0) 257 | initCount0++ 258 | } 259 | 260 | ; 可用引擎数量大于等于1,则启用原文框与翻译按钮 261 | if (initCount1>=1) 262 | { 263 | GuiControl, Enable, Original 264 | GuiControl, Enable, btnTranslate 265 | } 266 | ; 没有可用引擎,则禁用原文框与翻译按钮 267 | else 268 | { 269 | GuiControl, Disable, Original 270 | GuiControl, Disable, btnTranslate 271 | } 272 | 273 | ; 没有正在初始化的引擎则返回 274 | if (initCount0=0) 275 | { 276 | SetTimer, CheckInit, Off 277 | return 278 | } 279 | 280 | for k, v in Translators 281 | { 282 | ; 正在初始化 283 | if (v.initState=0) 284 | { 285 | v.initTime := NonNull_Ret(v.initTime, A_TickCount) 286 | 287 | ; 初始化超时 288 | if (A_TickCount - v.initTime > 30*1000) 289 | { 290 | v.initState:="", v.initTime:="" 291 | GuiControl, %k%:, %k%Edit, % Lang.22 292 | continue 293 | } 294 | 295 | ; 初始化成功 296 | Translator := k "Translator" 297 | if (%Translator%.getInitResult()) 298 | { 299 | v.initState:=1, v.initTime:="" 300 | GuiControl, %k%:Enable, %k%Edit 301 | GuiControl, %k%:, %k%Edit, % Lang.23 302 | } 303 | } 304 | } 305 | } 306 | 307 | Translate(ControlHwnd, GuiEvent, EventInfo, ErrLevel:="") 308 | { 309 | global Lang, From, To 310 | 311 | Gui, Submit, NoHide 312 | 313 | GuiControlGet, CheckBoxState, , Merge 314 | if (CheckBoxState) 315 | { 316 | for k, v in Paragraph.merge(Original) 317 | OriginalMerged.=v "`r`n`r`n" 318 | 319 | Original := RTrim(OriginalMerged, "`r`n") 320 | GuiControl, , Original, %Original% 321 | } 322 | 323 | for k, v in Translators 324 | { 325 | if (v.initState=1) 326 | { 327 | GuiControl, %k%:, %k%Edit, % Lang.24 328 | 329 | dict := { 自动检测:"auto", 阿拉伯语:"ar", 爱沙尼亚语:"et" 330 | , 保加利亚语:"bg", 波兰语:"pl", 丹麦语:"da" 331 | , 德语:"de", 俄语:"ru", 法语:"fr" 332 | , 芬兰语:"fi", 韩语:"ko", 荷兰语:"nl" 333 | , 捷克语:"cs", 拉脱维亚语:"lv", 立陶宛语:"lt" 334 | , 罗马尼亚语:"ro", 葡萄牙语:"pt", 日语:"ja" 335 | , 瑞典语:"sv", 斯洛伐克语:"sk", 斯洛文尼亚语:"sl" 336 | , 泰语:"th", 土耳其语:"tr", 乌克兰语:"uk" 337 | , 西班牙语:"es", 希腊语:"el", 匈牙利语:"hu" 338 | , 意大利语:"it", 印尼语:"id", 英语:"en" 339 | , 越南语:"vi", 中文:"zh"} 340 | From := dict.HasKey(From) ? dict[From] : From 341 | To := dict.HasKey(To) ? dict[To] : To 342 | 343 | Translator := k "Translator" 344 | ret := %Translator%.translate(Original, From, To, "async") 345 | 346 | if (ret.Error) 347 | { 348 | GuiControl, %k%:, %k%Edit, % Lang.25 ret.Error 349 | continue 350 | } 351 | else 352 | { 353 | NeedToCheckTrans := 1 354 | v.resultState := 0 355 | } 356 | } 357 | } 358 | 359 | if (NeedToCheckTrans) 360 | SetTimer, CheckTrans, 500 361 | } 362 | 363 | CheckTrans() 364 | { 365 | global Lang 366 | 367 | resultCount0 := 0 368 | for k, v in Translators 369 | if (v.resultState=0) 370 | resultCount0++ 371 | 372 | if (resultCount0=0) 373 | { 374 | SetTimer, CheckTrans, Off 375 | return 376 | } 377 | 378 | for k, v in Translators 379 | { 380 | if (v.resultState=0) 381 | { 382 | v.resultTime := NonNull_Ret(v.resultTime, A_TickCount) 383 | 384 | ; 翻译超时 385 | if (A_TickCount - v.resultTime > 30*1000) 386 | { 387 | v.resultState:="", v.resultTime:="" 388 | GuiControl, %k%:, %k%Edit, % Lang.26 389 | continue 390 | } 391 | 392 | Translator := k "Translator" 393 | ret := %Translator%.getTransResult() 394 | if (ret) 395 | { 396 | v.resultState:="", v.resultTime:="" 397 | GuiControl, %k%:, %k%Edit, % ret 398 | } 399 | } 400 | } 401 | } 402 | 403 | ; 这是一个隐藏控件,用于接收外部对翻译器的调用 404 | OuterCallHandler(ControlHwnd, GuiEvent, EventInfo, ErrLevel:="") 405 | { 406 | ShowAll() 407 | 408 | Gui, Submit, NoHide 409 | if (Trim(OuterOriginal, " `t`r`n`v`f")!="") 410 | { 411 | GuiControl, , Original, %OuterOriginal% 412 | GuiControlGet, OutputVar, Enabled, btnTranslate 413 | if (OutputVar) 414 | Translate("", "", "", "") 415 | } 416 | } 417 | 418 | MenuHandler: 419 | if (A_ThisMenuItem=Lang.2) ; 托盘按钮 - 显示 420 | ShowAll() 421 | 422 | if (A_ThisMenuItem=Lang.3) ; 托盘按钮 - 隐藏 423 | HideAll() 424 | 425 | if (A_ThisMenuItem=Lang.4) ; 托盘按钮 - 重启 426 | Reload 427 | 428 | if (A_ThisMenuItem=Lang.5) ; 托盘按钮 - 退出 429 | ExitApp 430 | return 431 | 432 | ; 关闭主窗口时隐藏 433 | GuiEscape: 434 | GuiClose: 435 | HideAll() 436 | return 437 | 438 | ; 屏蔽子窗口关闭。这样就不用处理多选框的反向勾选问题了 439 | SubClose: 440 | return 441 | 442 | ShowAll() 443 | { 444 | for k, v in Translators 445 | { 446 | GuiControlGet, CheckBoxState, , %k% 447 | if (CheckBoxState) 448 | Gui, %k%:Show 449 | } 450 | Gui, Show ; 最后显示主窗口以使主窗口获得焦点 451 | } 452 | 453 | HideAll() 454 | { 455 | for k, v in Translators 456 | Gui, %k%:Hide 457 | Gui, Hide 458 | } 459 | 460 | WM_MOVE() 461 | { 462 | SetTimer, MoveSub, -500 463 | return 464 | 465 | MoveSub: 466 | ShowOrHideSub("", "", "", "") 467 | return 468 | } 469 | 470 | ; 屏幕关闭后再打开则重启 471 | GUID_CONSOLE_DISPLAY_STATE(wParam, lParam) 472 | { 473 | static flag 474 | 475 | switch NumGet(lParam+20, 0, "UChar") 476 | { 477 | case 0: flag := 1 ; 屏幕关闭 478 | case 1: ; 屏幕打开 479 | if (flag) 480 | Run "%A_AhkPath%" /restart "%A_ScriptFullPath%" /hide 481 | case 2: ; 屏幕即将关闭 约有5秒反应时间 482 | } 483 | 484 | return true 485 | } 486 | 487 | ; 大部分代码来自这里 https://www.autohotkey.com/boards/viewtopic.php?t=70645 488 | class POWERBROADCAST 489 | { 490 | Register(function) { 491 | ; https://learn.microsoft.com/zh-cn/windows/win32/power/power-setting-guids 492 | ; GUID_CONSOLE_DISPLAY_STATE 493 | this.RegisterPowerSettingNotification("6FE69556-704A-47A0-8F24-C28D936FDA47") 494 | 495 | BoundFunc := ObjBindMethod(this, "UnRegisterNotification") 496 | OnExit(BoundFunc) 497 | 498 | OnMessage(0x218, function) 499 | } 500 | 501 | RegisterPowerSettingNotification(GUID) { 502 | VarSetCapacity(UUID, 16, 0) 503 | DllCall("Rpcrt4\UuidFromString", "Str", GUID, "Ptr", &UUID) 504 | 505 | this.handle := DllCall("RegisterPowerSettingNotification" 506 | , "Ptr", A_ScriptHwnd 507 | , "Ptr", &UUID 508 | , "UInt", 0 ; DEVICE_NOTIFY_WINDOW_HANDLE 509 | , "Ptr") 510 | } 511 | 512 | UnRegisterNotification() { 513 | DllCall("UnregisterPowerSettingNotification", "Ptr", this.handle) 514 | } 515 | } 516 | 517 | #Include 518 | #Include 519 | #Include 520 | #Include 521 | #Include 522 | #Include 523 | -------------------------------------------------------------------------------- /6. Dictionary.ahk: -------------------------------------------------------------------------------- 1 | youdao.dict("apple") 2 | Sleep 5000 3 | youdao.dict("橘子") 4 | Sleep 5000 5 | ExitApp 6 | 7 | #Include 有道词典\有道词典.ahk -------------------------------------------------------------------------------- /Lib/BTT.ahk: -------------------------------------------------------------------------------- 1 | /* 2 | https://github.com/telppa/BeautifulToolTip 3 | 4 | If you want to add your own style to the built-in style, you can add it directly in btt(). 5 | 6 | version: 7 | 2022.11.29 8 | 9 | changelog: 10 | 2022.11.29 11 | 修复使用btt会改变“上次找到的窗口”的问题。 12 | 2021.10.03 13 | 改变 Include 方式,降低库冲突的可能性。 14 | 2021.09.29 15 | 支持设置 TabStops 。 16 | 除 GDIP 库外所有函数内置到 Class 中,降低库冲突的可能性。 17 | 2021.04.30 18 | 修复 Win7 下不能运行的 bug 。(2021.04.20 引起) 19 | 2021.04.20 20 | 支持根据显示器 DPI 缩放比例自动缩放,与 ToolTip 特性保持一致。 21 | 支持直接使用未安装的本地字体。 22 | Options 增加 JustCalculateSize 参数,可在不绘制内容的前提下直接返回尺寸。 23 | 2021.03.07 24 | 增加 4 种渐变模式。 25 | 2021.03.05 26 | 删除渐变方向参数。 27 | 增加渐变角度参数,支持 360° 渐变。 28 | 增加渐变模式参数,支持 4 种模式。 29 | 2021.03.03 30 | 文本色支持渐变。 31 | 增加 Style8 。 32 | 增加 3 个渐变方向。 33 | 2021.03.02 34 | 细边框色支持渐变。 35 | 增加 Style7 。 36 | 增加 2 个渐变方向。 37 | 2021.03.01 38 | BTT 的总在最上级别现在跟 ToolTip 一样高了。 39 | 解决 BTT 被不明原因置底导致误以为没显示的问题。 40 | 增加 Style6 。 41 | 文字显示更加居中。 42 | 2021.02.22 43 | 增加返回值。 44 | Options 增加 Transparent 参数,可直接设置整体透明度。 45 | 46 | todo: 47 | 指定高宽 48 | 文字可获取 49 | 降低内存消耗 50 | 阴影 51 | ANSI版本的支持 52 | 文字太多导致没有显示完全的情况下给予明显提示(例如闪烁) 53 | 54 | 优势: 55 | *高性能 是内置 ToolTip 2-1000 倍性能(文本越大性能对比越大,多数普通应用场景2-5倍性能) 56 | *高兼容 兼容内置 ToolTip 的一切,包括语法、WhichToolTip、A_CoordModeToolTip、自动换行等等 57 | *简单易用 一行代码即使用 无需手动创建释放资源 58 | *多套内置风格 可通过模板快速实现自定义风格 59 | *可自定义风格 细边框(颜色、大小) 圆角 边距 文字(颜色、字体、字号、渲染方式、样式) 背景色 所有颜色支持360°渐变与透明 60 | *可自定义参数 贴附目标句柄 坐标模式 整体透明度 文本框永不被遮挡 跟随鼠标距离 不绘制内容直接返回尺寸 61 | *不闪烁不乱跳 62 | *多显示器支持 63 | *缩放显示支持 64 | *跟随鼠标不出界 65 | *可贴附指定目标 66 | */ 67 | btt(Text:="", X:="", Y:="", WhichToolTip:="", BulitInStyleOrStyles:="", BulitInOptionOrOptions:="") 68 | { 69 | static BTT 70 | ; You can customize your own style. 71 | ; All supported parameters are listed below. All parameters can be omitted. 72 | ; Please share your custom style and include a screenshot. It will help a lot of people. 73 | ; Attention: 74 | ; Color => ARGB => Alpha Red Green Blue => 0x ff aa bb cc => 0xffaabbcc 75 | , Style99 := {Border:20 ; If omitted, 1 will be used. Range 0-20. 76 | , Rounded:30 ; If omitted, 3 will be used. Range 0-30. 77 | , Margin:30 ; If omitted, 5 will be used. Range 0-30. 78 | , TabStops:[50, 80, 100] ; If omitted, [50] will be used. This value must be an array. 79 | , BorderColor:0xffaabbcc ; ARGB 80 | , BorderColorLinearGradientStart:0xff16a085 ; ARGB 81 | , BorderColorLinearGradientEnd:0xfff4d03f ; ARGB 82 | , BorderColorLinearGradientAngle:45 ; Mode=8 Angle 0(L to R) 90(U to D) 180(R to L) 270(D to U) 83 | , BorderColorLinearGradientMode:1 ; Mode=4 Angle 0(L to R) 90(D to U), Range 1-8. 84 | , TextColor:0xff112233 ; ARGB 85 | , TextColorLinearGradientStart:0xff00416a ; ARGB 86 | , TextColorLinearGradientEnd:0xffe4e5e6 ; ARGB 87 | , TextColorLinearGradientAngle:90 ; Mode=8 Angle 0(L to R) 90(U to D) 180(R to L) 270(D to U) 88 | , TextColorLinearGradientMode:1 ; Mode=4 Angle 0(L to R) 90(D to U), Range 1-8. 89 | , BackgroundColor:0xff778899 ; ARGB 90 | , BackgroundColorLinearGradientStart:0xff8DA5D3 ; ARGB 91 | , BackgroundColorLinearGradientEnd:0xffF4CFC9 ; ARGB 92 | , BackgroundColorLinearGradientAngle:135 ; Mode=8 Angle 0(L to R) 90(U to D) 180(R to L) 270(D to U) 93 | , BackgroundColorLinearGradientMode:1 ; Mode=4 Angle 0(L to R) 90(D to U), Range 1-8. 94 | , Font:"Font Name" ; If omitted, ToolTip's Font will be used. Can specify the font file path. 95 | , FontSize:20 ; If omitted, 12 will be used. 96 | , FontRender:5 ; If omitted, 5 will be used. Range 0-5. 97 | , FontStyle:"Regular Bold Italic BoldItalic Underline Strikeout"} 98 | 99 | , Option99 := {TargetHWND:"" ; If omitted, active window will be used. 100 | , CoordMode:"Screen|Relative|Window|Client" ; If omitted, A_CoordModeToolTip will be used. 101 | , Transparent:"" ; If omitted, 255 will be used. 102 | , MouseNeverCoverToolTip:"" ; If omitted, 1 will be used. 103 | , DistanceBetweenMouseXAndToolTip:"" ; If omitted, 16 will be used. This value can be negative. 104 | , DistanceBetweenMouseYAndToolTip:"" ; If omitted, 16 will be used. This value can be negative. 105 | , JustCalculateSize:""} ; Set to 1, no content will be displayed, just calculate size and return. 106 | 107 | , Style1 := {TextColor:0xffeef8f6 108 | , BackgroundColor:0xff1b8dff 109 | , FontSize:14} 110 | 111 | , Style2 := {Border:1 112 | , Rounded:8 113 | , TextColor:0xfff4f4f4 114 | , BackgroundColor:0xaa3e3d45 115 | , FontSize:14} 116 | 117 | , Style3 := {Border:2 118 | , Rounded:0 119 | , TextColor:0xffF15839 120 | , BackgroundColor:0xffFCEDE6 121 | , FontSize:14} 122 | 123 | , Style4 := {Border:10 124 | , Rounded:20 125 | , BorderColor:0xff604a78 126 | , TextColor:0xffF3AE00 127 | , BackgroundColor:0xff6A537F 128 | , FontSize:20 129 | , FontStyle:"Bold Italic"} 130 | 131 | , Style5 := {Border:0 132 | , Rounded:5 133 | , TextColor:0xffeeeeee 134 | , BackgroundColorLinearGradientStart:0xff134E5E 135 | , BackgroundColorLinearGradientEnd:0xff326f69 136 | , BackgroundColorLinearGradientAngle:0 137 | , BackgroundColorLinearGradientMode:1} 138 | 139 | , Style6 := {Border:2 140 | , Rounded:5 141 | , TextColor:0xffCAE682 142 | , BackgroundColor:0xff434343 143 | , FontSize:14} 144 | 145 | , Style7 := {Border:20 146 | , Rounded:30 147 | , Margin:30 148 | , BorderColor:0xffaabbcc 149 | , TextColor:0xff112233 150 | , BackgroundColorLinearGradientStart:0xffF4CFC9 151 | , BackgroundColorLinearGradientEnd:0xff8DA5D3 152 | , BackgroundColorLinearGradientAngle:0 153 | , BackgroundColorLinearGradientMode:1 154 | , FontStyle:"BoldItalic"} 155 | 156 | , Style8 := {Border:3 157 | , Rounded:30 158 | , Margin:30 159 | , BorderColorLinearGradientStart:0xffb7407c 160 | , BorderColorLinearGradientEnd:0xff3881a7 161 | , BorderColorLinearGradientAngle:45 162 | , BorderColorLinearGradientMode:1 163 | , TextColor:0xffd9d9db 164 | , BackgroundColor:0xff26293a} 165 | 166 | ; 直接在 static 中初始化 BTT 会报错,所以只能这样写 167 | if (BTT="") 168 | BTT := new BeautifulToolTip() 169 | 170 | return, BTT.ToolTip(Text, X, Y, WhichToolTip 171 | ; 如果 Style 是一个内置预设的名称,则使用对应内置预设的值,否则使用 Styles 本身的值。 Options 同理。 172 | , %BulitInStyleOrStyles%="" ? BulitInStyleOrStyles : %BulitInStyleOrStyles% 173 | , %BulitInOptionOrOptions%="" ? BulitInOptionOrOptions : %BulitInOptionOrOptions%) 174 | } 175 | 176 | Class BeautifulToolTip 177 | { 178 | ; 以下这些是类中静态变量。末尾带数字1的,表明最多存在1-20这样的变体。例如 _BTT1 就有 _BTT1 ... _BTT20 共20个类似变量。 179 | ; pToken, Monitors, ToolTipFontName, DIBWidth, DIBHeight 180 | ; MouseNeverCoverToolTip, DistanceBetweenMouseXAndToolTip, DistanceBetweenMouseYAndToolTip 181 | ; hBTT1(GUI句柄), hbm1, hdc1, obm1, G1 182 | ; SavedText1, SavedOptions1, SavedX1, SavedY1, SavedW1, SavedH1, SavedCoordMode1, SavedTargetHWND1 183 | static DebugMode:=0 184 | 185 | __New() 186 | { 187 | if (!this.pToken) 188 | { 189 | ; 加速 190 | SavedBatchLines:=A_BatchLines 191 | SetBatchLines, -1 192 | 193 | ; 多实例启动 gdi+ 。避免有些人修改内部代码时与他自己的 gdi+ 冲突。 194 | this.pToken := Gdip_Startup(1) 195 | if (!this.pToken) 196 | { 197 | MsgBox, 48, gdiplus error!, Gdiplus failed to start. Please ensure you have gdiplus on your system 198 | ExitApp 199 | } 200 | 201 | ; 多显示器支持 202 | this.Monitors := MDMF_Enum() 203 | ; 获取多显示器各自 DPI 缩放比例 204 | for hMonitor, v in this.Monitors.Clone() 205 | { 206 | if (hMonitor="TotalCount" or hMonitor="Primary") 207 | continue 208 | ; https://github.com/Ixiko/AHK-libs-and-classes-collection/blob/e421acb801867edb659a54b7473e6e617f2b267b/libs/g-n/Monitor.ahk 209 | ; ahk 源码里 A_ScreenDPI 就是只获取了 dpiX ,所以这里保持一致 210 | osv := StrSplit(A_OSVersion, ".") ; https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/content/wdm/ns-wdm-_osversioninfoexw 211 | if (osv[1] < 6 || (osv[1] == 6 && osv[2] < 3)) ; WIN_8- 。Win7 必须用这种方式,否则会出错 212 | { 213 | hDC := DllCall("Gdi32.dll\CreateDC", "Str", hMonitor.name, "Ptr", 0, "Ptr", 0, "Ptr", 0, "Ptr") 214 | dpiX := DllCall("Gdi32.dll\GetDeviceCaps", "Ptr", hDC, "Int", 88) ; LOGPIXELSX = 88 215 | DllCall("Gdi32.dll\DeleteDC", "Ptr", hDC) 216 | } 217 | else 218 | DllCall("Shcore.dll\GetDpiForMonitor", "Ptr", hMonitor, "Int", Type, "UIntP", dpiX, "UIntP", dpiY, "UInt") 219 | this.Monitors[hMonitor].DPIScale := this.NonNull_Ret(dpiX, A_ScreenDPI)/96 220 | } 221 | 222 | ; 获取整个桌面的分辨率,即使跨显示器 223 | SysGet, VirtualWidth, 78 224 | SysGet, VirtualHeight, 79 225 | this.DIBWidth := VirtualWidth 226 | this.DIBHeight := VirtualHeight 227 | 228 | ; 获取 ToolTip 的默认字体 229 | this.ToolTipFontName := this.Fnt_GetTooltipFontName() 230 | 231 | ; create 20 guis for gdi+ 232 | ; 最多20个 ToolTip ,与原版对应。 233 | loop, 20 234 | { 235 | ; _BTT1(GUI 名称) 与 _hBTT1(GUI 句柄) 都是临时变量,后者被储存了。 236 | Gui, _BTT%A_Index%: +E0x80000 -Caption +ToolWindow +AlwaysOnTop +Hwnd_hBTT%A_Index% 237 | Gui, _BTT%A_Index%: Show, NA 238 | 239 | this["hBTT" A_Index] := _hBTT%A_Index% 240 | , this["hbm" A_Index] := CreateDIBSection(this.DIBWidth, this.DIBHeight) 241 | , this["hdc" A_Index] := CreateCompatibleDC() 242 | , this["obm" A_Index] := SelectObject(this["hdc" A_Index], this["hbm" A_Index]) 243 | , this["G" A_Index] := Gdip_GraphicsFromHDC(this["hdc" A_Index]) 244 | , Gdip_SetSmoothingMode(this["G" A_Index], 4) 245 | , Gdip_SetPixelOffsetMode(this["G" A_Index], 2) ; 此参数是画出完美圆角矩形的关键 246 | } 247 | SetBatchLines, %SavedBatchLines% 248 | } 249 | else 250 | return 251 | } 252 | 253 | ; new 后得到的变量在销毁后会自动跳这里来运行,因此很适合做自动资源回收。 254 | __Delete() 255 | { 256 | loop, 20 257 | { 258 | Gdip_DeleteGraphics(this["G" A_Index]) 259 | , SelectObject(this["hdc" A_Index], this["obm" A_Index]) 260 | , DeleteObject(this["hbm" A_Index]) 261 | , DeleteDC(this["hdc" A_Index]) 262 | } 263 | Gdip_Shutdown(this.pToken) 264 | } 265 | 266 | ; 参数默认全部为空只是为了让清空 ToolTip 的语法简洁而已。 267 | ToolTip(Text:="", X:="", Y:="", WhichToolTip:="", Styles:="", Options:="") 268 | { 269 | ; 给出 WhichToolTip 的默认值1,并限制 WhichToolTip 的范围为 1-20 270 | this.NonNull(WhichToolTip, 1, 1, 20) 271 | ; 检查并解析 Styles 与 Options 。无论不传参、部分传参、完全传参,此函数均能正确返回所需参数。 272 | O:=this._CheckStylesAndOptions(Styles, Options) 273 | 274 | ; 判断显示内容是否发生变化。由于前面给了 Options 一个默认值,所以首次进来时下面的 O.Options!=SavedOptions 是必然成立的。 275 | FirstCallOrNeedToUpdate:=(Text != this["SavedText" WhichToolTip] 276 | or O.Checksum != this["SavedOptions" WhichToolTip]) 277 | 278 | if (Text="") 279 | { 280 | ; 清空 ToolTip 281 | Gdip_GraphicsClear(this["G" WhichToolTip]) 282 | UpdateLayeredWindow(this["hBTT" WhichToolTip], this["hdc" WhichToolTip]) 283 | ; 清空变量 284 | this["SavedText" WhichToolTip] := "" 285 | , this["SavedOptions" WhichToolTip] := "" 286 | , this["SavedX" WhichToolTip] := "" 287 | , this["SavedY" WhichToolTip] := "" 288 | , this["SavedW" WhichToolTip] := "" 289 | , this["SavedH" WhichToolTip] := "" 290 | , this["SavedTargetHWND" WhichToolTip] := "" 291 | , this["SavedCoordMode" WhichToolTip] := "" 292 | , this["SavedTransparent" WhichToolTip] := "" 293 | 294 | return 295 | } 296 | else if (FirstCallOrNeedToUpdate) ; First Call or NeedToUpdate 297 | { 298 | ; 加速 299 | SavedBatchLines:=A_BatchLines 300 | SetBatchLines, -1 301 | 302 | ; 获取目标尺寸,用于计算文本大小时加上宽高限制,否则目标为屏幕时,可能计算出超宽超高的大小,导致无法显示。 303 | TargetSize:=this._CalculateDisplayPosition(X, Y, "", "", O, GetTargetSize:=1) 304 | ; 使得 文字区域+边距+细边框 不会超过目标宽度。 305 | , MaxTextWidth:=TargetSize.W - O.Margin*2 - O.Border*2 306 | ; 使得 文字区域+边距+细边框 不会超过目标高度的90%。 307 | ; 之所以高度限制90%是因为两个原因,1是留出一些上下的空白,避免占满全屏,鼠标点不了其它地方,难以退出。 308 | ; 2是在计算文字区域时,即使已经给出了宽高度限制,且因为自动换行的原因,宽度的返回值通常在范围内,但高度的返回值偶尔还是会超过1行,所以提前留个余量。 309 | , MaxTextHeight:=(TargetSize.H*90)//100 - O.Margin*2 - O.Border*2 310 | ; 为 _TextToGraphics 计算区域提供高宽限制。 311 | , O.Width:=MaxTextWidth, O.Height:=MaxTextHeight 312 | ; 计算文字显示区域 TextArea = x|y|width|height|chars|lines 313 | , TextArea:=StrSplit(this._TextToGraphics(this["G" WhichToolTip], Text, O, Measure:=1), "|") 314 | ; 这里务必向上取整。 315 | ; 向上的原因是,例如 1.2 如果四舍五入为 1,那么最右边的字符可能会显示不全。 316 | ; 取整的原因是,不取整无法画出完美的圆角矩形。 317 | ; 当使用 AutoTrim 选项,即自动将超出范围的文字显示为 “...” 时,此时返回的宽高度值是不包含 “...” 的。 318 | ; 所以加上 “...” 的宽高度后,仍然可能超限。故需要再次检查并限制。 319 | ; 一旦宽高度超过了限制(CreateDIBSection() 时创建的大小),会导致 UpdateLayeredWindow() 画不出图像来。 320 | , TextWidth:=Min(Ceil(TextArea[3]), MaxTextWidth) 321 | , TextHeight:=Min(Ceil(TextArea[4]), MaxTextHeight) 322 | 323 | , RectWidth:=TextWidth+O.Margin*2 ; 文本+边距。 324 | , RectHeight:=TextHeight+O.Margin*2 325 | , RectWithBorderWidth:=RectWidth+O.Border*2 ; 文本+边距+细边框。 326 | , RectWithBorderHeight:=RectHeight+O.Border*2 327 | ; 圆角超过矩形宽或高的一半时,会画出畸形的圆,所以这里验证并限制一下。 328 | , R:=(O.Rounded>Min(RectWidth, RectHeight)//2) ? Min(RectWidth, RectHeight)//2 : O.Rounded 329 | 330 | if (O.JustCalculateSize!=1) 331 | { 332 | ; 画之前务必清空画布,否则会出现异常。 333 | Gdip_GraphicsClear(this["G" WhichToolTip]) 334 | 335 | ; 准备细边框画刷 336 | if (O.BCLGA!="" and O.BCLGM and O.BCLGS and O.BCLGE) ; 渐变画刷 画细边框 337 | pBrushBorder := this._CreateLinearGrBrush(O.BCLGA, O.BCLGM, O.BCLGS, O.BCLGE 338 | , 0, 0, RectWithBorderWidth, RectWithBorderHeight) 339 | else 340 | pBrushBorder := Gdip_BrushCreateSolid(O.BorderColor) ; 纯色画刷 画细边框 341 | 342 | if (O.Border>0) 343 | switch, R 344 | { 345 | ; 圆角为0则使用矩形画。不单独处理,会画出显示异常的图案。 346 | case, "0": Gdip_FillRectangle(this["G" WhichToolTip] ; 矩形细边框 347 | , pBrushBorder, 0, 0, RectWithBorderWidth, RectWithBorderHeight) 348 | Default : Gdip_FillRoundedRectanglePath(this["G" WhichToolTip] ; 圆角细边框 349 | , pBrushBorder, 0, 0, RectWithBorderWidth, RectWithBorderHeight, R) 350 | } 351 | 352 | ; 准备文本框画刷 353 | if (O.BGCLGA!="" and O.BGCLGM and O.BGCLGS and O.BGCLGE) ; 渐变画刷 画文本框 354 | pBrushBackground := this._CreateLinearGrBrush(O.BGCLGA, O.BGCLGM, O.BGCLGS, O.BGCLGE 355 | , O.Border, O.Border, RectWidth, RectHeight) 356 | else 357 | pBrushBackground := Gdip_BrushCreateSolid(O.BackgroundColor) ; 纯色画刷 画文本框 358 | 359 | switch, R 360 | { 361 | case, "0": Gdip_FillRectangle(this["G" WhichToolTip] ; 矩形文本框 362 | , pBrushBackground, O.Border, O.Border, RectWidth, RectHeight) 363 | Default : Gdip_FillRoundedRectanglePath(this["G" WhichToolTip] ; 圆角文本框 364 | , pBrushBackground, O.Border, O.Border, RectWidth, RectHeight 365 | , (R>O.Border) ? R-O.Border : R) ; 确保内外圆弧看起来同心 366 | } 367 | 368 | ; 清理画刷 369 | Gdip_DeleteBrush(pBrushBorder) 370 | Gdip_DeleteBrush(pBrushBackground) 371 | 372 | ; 计算居中显示坐标。由于 TextArea 返回的文字范围右边有很多空白,所以这里的居中坐标并不精确。 373 | O.X:=O.Border+O.Margin, O.Y:=O.Border+O.Margin, O.Width:=TextWidth, O.Height:=TextHeight 374 | 375 | ; 如果显示区域过小,文字无法完全显示,则将待显示文本最后4个字符替换为4个英文省略号,表示显示不完全。 376 | ; 虽然有 GdipSetStringFormatTrimming 函数可以设置末尾显示省略号,但它偶尔需要增加额外的宽度才能显示出来。 377 | ; 由于难以判断是否需要增加额外宽度,以及需要增加多少等等问题,所以直接用这种方式自己实现省略号的显示。 378 | ; 之所以选择替换最后4个字符,是因为一般替换掉最后2个字符,才能确保至少一个省略号显示出来。 379 | ; 为了应对意外情况,以及让省略号更加明显一点,所以选择替换最后4个。 380 | ; 原始的 Text 需要用于显示前的比对,所以不能改原始值,必须用 TempText 。 381 | if (TextArea[5]4 ? SubStr(Text, 1 ,TextArea[5]-4) "…………" : SubStr(Text, 1 ,1) "…………" 383 | else 384 | TempText:=Text 385 | 386 | ; 写字到框上。这个函数使用 O 中的 X,Y 去调整文字的位置。 387 | this._TextToGraphics(this["G" WhichToolTip], TempText, O) 388 | 389 | ; 调试用,可显示计算得到的文字范围。 390 | if (this.DebugMode) 391 | { 392 | pBrush := Gdip_BrushCreateSolid(0x20ff0000) 393 | Gdip_FillRectangle(this["G" WhichToolTip], pBrush, O.Border+O.Margin, O.Border+O.Margin, TextWidth, TextHeight) 394 | Gdip_DeleteBrush(pBrush) 395 | } 396 | 397 | ; 返回文本框不超出目标范围(比如屏幕范围)的最佳坐标。 398 | this._CalculateDisplayPosition(X, Y, RectWithBorderWidth, RectWithBorderHeight, O) 399 | 400 | ; 显示 401 | UpdateLayeredWindow(this["hBTT" WhichToolTip], this["hdc" WhichToolTip] 402 | , X, Y, RectWithBorderWidth, RectWithBorderHeight, O.Transparent) 403 | 404 | ; 因为 BTT 总会在使用一段时间后,被不明原因的置底,导致显示内容被其它窗口遮挡,以为没有正常显示,所以这里提升Z序到最上面! 405 | ; 已测试过,此方法效率极高,远超 WinSet, Top 命令。 406 | ; hWndInsertAfter 407 | ; HWND_TOPMOST:=-1 408 | ; uFlags 409 | ; SWP_NOSIZE:=0x0001 410 | ; SWP_NOMOVE:=0x0002 411 | ; SWP_NOREDRAW:=0x0008 412 | ; SWP_NOACTIVATE:=0x0010 413 | ; SWP_NOOWNERZORDER:=0x0200 414 | ; SWP_NOSENDCHANGING:=0x0400 415 | ; SWP_DEFERERASE:=0x2000 416 | ; SWP_ASYNCWINDOWPOS:=0x4000 417 | DllCall("SetWindowPos", "ptr", this["hBTT" WhichToolTip], "ptr", -1, "int", 0, "int", 0, "int", 0, "int", 0, "uint", 26139) 418 | } 419 | 420 | ; 保存参数值,以便之后比对参数值是否改变 421 | this["SavedText" WhichToolTip] := Text 422 | , this["SavedOptions" WhichToolTip] := O.Checksum 423 | , this["SavedX" WhichToolTip] := X ; 这里的 X,Y 是经过 _CalculateDisplayPosition() 计算后的新 X,Y 424 | , this["SavedY" WhichToolTip] := Y 425 | , this["SavedW" WhichToolTip] := RectWithBorderWidth 426 | , this["SavedH" WhichToolTip] := RectWithBorderHeight 427 | , this["SavedTargetHWND" WhichToolTip] := O.TargetHWND 428 | , this["SavedCoordMode" WhichToolTip] := O.CoordMode 429 | , this["SavedTransparent" WhichToolTip] := O.Transparent 430 | 431 | SetBatchLines, %SavedBatchLines% 432 | } 433 | ; x,y 任意一个跟随鼠标位置 或 使用窗口或客户区模式(窗口大小可能发生改变或者窗口发生移动) 434 | ; 或 目标窗口发生变化 或 坐标模式发生变化 435 | ; 或 整体透明度发生变化 这5种情况可能需要移动位置,需要进行坐标计算。 436 | else if ((X="" or Y="") or O.CoordMode!="Screen" 437 | or O.TargetHWND!=this.SavedTargetHWND or O.CoordMode!=this.SavedCoordMode 438 | or O.Transparent!=this.SavedTransparent) 439 | { 440 | ; 返回文本框不超出目标范围(比如屏幕范围)的最佳坐标。 441 | this._CalculateDisplayPosition(X, Y, this["SavedW" WhichToolTip], this["SavedH" WhichToolTip], O) 442 | ; 判断文本框 显示位置 443 | ; 或 显示透明度 是否发生改变 444 | if (X!=this["SavedX" WhichToolTip] or Y!=this["SavedY" WhichToolTip] 445 | or O.Transparent!=this.SavedTransparent) 446 | { 447 | ; 显示 448 | UpdateLayeredWindow(this["hBTT" WhichToolTip], this["hdc" WhichToolTip] 449 | , X, Y, this["SavedW" WhichToolTip], this["SavedH" WhichToolTip], O.Transparent) 450 | 451 | ; 保存新的位置 452 | this["SavedX" WhichToolTip] := X 453 | , this["SavedY" WhichToolTip] := Y 454 | , this["SavedTargetHWND" WhichToolTip] := O.TargetHWND 455 | , this["SavedCoordMode" WhichToolTip] := O.CoordMode 456 | , this["SavedTransparent" WhichToolTip] := O.Transparent 457 | } 458 | } 459 | 460 | ret:={Hwnd : this["hBTT" WhichToolTip] 461 | , X : X 462 | , Y : Y 463 | , W : this["SavedW" WhichToolTip] 464 | , H : this["SavedH" WhichToolTip]} 465 | return, ret 466 | } 467 | 468 | ; 为了统一参数的传输,以及特殊模式的设置,修改了 gdip 库的 Gdip_TextToGraphics() 函数。 469 | _TextToGraphics(pGraphics, Text, Options, Measure:=0) 470 | { 471 | static Styles := "Regular|Bold|Italic|BoldItalic|Underline|Strikeout" 472 | 473 | ; 设置字体样式 474 | Style := 0 475 | for eachStyle, valStyle in StrSplit(Styles, "|") 476 | { 477 | if InStr(Options.FontStyle, valStyle) 478 | Style |= (valStyle != "StrikeOut") ? (A_Index-1) : 8 479 | } 480 | 481 | if (FileExist(Options.Font)) ; 加载未安装的本地字体 482 | { 483 | hFontCollection := Gdip_NewPrivateFontCollection() 484 | hFontFamily := Gdip_CreateFontFamilyFromFile(Options.Font, hFontCollection) 485 | } 486 | if !hFontFamily ; 加载已安装的字体 487 | hFontFamily := Gdip_FontFamilyCreate(Options.Font) 488 | if !hFontFamily ; 加载默认字体 489 | hFontFamily := Gdip_FontFamilyCreateGeneric(1) 490 | ; 根据 DPI 缩放比例自动调整字号 491 | hFont := Gdip_FontCreate(hFontFamily, Options.FontSize * Options.DPIScale, Style, Unit:=0) 492 | 493 | ; 设置文字格式化样式,LineLimit = 0x00002000 只显示完整的行。 494 | ; 比如最后一行,因为布局高度有限,只能显示出一半,此时就会让它完全不显示。 495 | ; 直接使用 Gdip_StringFormatGetGeneric(1) 包含 LineLimit 设置,同时可以实现左右空白区域最小化。 496 | ; 但这样有个副作用,那就是无法精确的设置文本框的宽度了,同时最右边文字的间距会被压缩。 497 | ; 例如指定宽度800,可能返回的宽度是793,因为右边没有用空白补上。 498 | ; 好处是右边几乎没有空白区域,左边也没有,所以接近完美的实现文字居中了。 499 | ; hStringFormat := Gdip_StringFormatCreate(0x00002000) 500 | ; if !hStringFormat 501 | hStringFormat := Gdip_StringFormatGetGeneric(1) 502 | 503 | ; 准备文本画刷 504 | if (Options.TCLGA!="" and Options.TCLGM and Options.TCLGS and Options.TCLGE 505 | and Options.Width and Options.Height) ; 渐变画刷 506 | { 507 | pBrush := this._CreateLinearGrBrush(Options.TCLGA, Options.TCLGM, Options.TCLGS, Options.TCLGE 508 | , this.NonNull_Ret(Options.X, 0), this.NonNull_Ret(Options.Y, 0) 509 | , Options.Width, Options.Height) 510 | } 511 | else 512 | pBrush := Gdip_BrushCreateSolid(Options.TextColor) ; 纯色画刷 513 | 514 | ; 检查参数是否齐全 515 | if !(hFontFamily && hFont && hStringFormat && pBrush && pGraphics) 516 | { 517 | E := !pGraphics ? -2 : !hFontFamily ? -3 : !hFont ? -4 : !hStringFormat ? -5 : !pBrush ? -6 : 0 518 | if pBrush 519 | Gdip_DeleteBrush(pBrush) 520 | if hStringFormat 521 | Gdip_DeleteStringFormat(hStringFormat) 522 | if hFont 523 | Gdip_DeleteFont(hFont) 524 | if hFontFamily 525 | Gdip_DeleteFontFamily(hFontFamily) 526 | if hFontCollection 527 | Gdip_DeletePrivateFontCollection(hFontCollection) 528 | return E 529 | } 530 | 531 | TabStops := [] 532 | for k, v in Options.TabStops 533 | TabStops.Push(v * Options.DPIScale) 534 | Gdip_SetStringFormatTabStops(hStringFormat, TabStops) ; 设置 TabStops 535 | Gdip_SetStringFormatAlign(hStringFormat, Align:=0) ; 设置左对齐 536 | Gdip_SetTextRenderingHint(pGraphics, Options.FontRender) ; 设置渲染模式 537 | CreateRectF(RC 538 | , this.NonNull_Ret(Options.X, 0) ; x,y 需要至少为0 539 | , this.NonNull_Ret(Options.Y, 0) 540 | , Options.Width, Options.Height) ; 宽高可以为空 541 | returnRC := Gdip_MeasureString(pGraphics, Text, hFont, hStringFormat, RC) ; 计算大小 542 | 543 | if !Measure 544 | _E := Gdip_DrawString(pGraphics, Text, hFont, hStringFormat, pBrush, RC) 545 | 546 | Gdip_DeleteBrush(pBrush) 547 | Gdip_DeleteFont(hFont) 548 | Gdip_DeleteStringFormat(hStringFormat) 549 | Gdip_DeleteFontFamily(hFontFamily) 550 | if hFontCollection 551 | Gdip_DeletePrivateFontCollection(hFontCollection) 552 | return _E ? _E : returnRC 553 | } 554 | 555 | _CreateLinearGrBrush(Angle, Mode, StartColor, EndColor, x, y, w, h) 556 | { 557 | ; Mode=8 Angle 0=左到右 90=上到下 180=右到左 270=下到上 558 | ; Mode=3 Angle 0=左到右 90=近似上到下 559 | ; Mode=4 Angle 0=左到右 90=下到上 560 | switch, Mode 561 | { 562 | case, 1,3,5,7:pBrush:=Gdip_CreateLinearGrBrush(x, y, x+w, y, StartColor, EndColor) 563 | case, 2,4,6,8:pBrush:=Gdip_CreateLinearGrBrush(x, y+h//2, x+w, y+h//2, StartColor, EndColor) 564 | } 565 | switch, Mode 566 | { 567 | case, 1,2: Gdip_RotateLinearGrBrushTransform(pBrush, Angle, 0) ; 性能比模式3、4高10倍左右 568 | case, 3,4: Gdip_RotateLinearGrBrushTransform(pBrush, Angle, 1) 569 | case, 5,6: Gdip_RotateLinearGrBrushAtCenter(pBrush, Angle, 0) 570 | case, 7,8: Gdip_RotateLinearGrBrushAtCenter(pBrush, Angle, 1) ; 可绕中心旋转 571 | } 572 | return, pBrush 573 | } 574 | 575 | ; 此函数确保传入空值或者错误值均可返回正确值。 576 | _CheckStylesAndOptions(Styles, Options) 577 | { 578 | O := {} 579 | , O.Border := this.NonNull_Ret(Styles.Border , 1 , 0 , 20) ; 细边框 默认1 0-20 580 | , O.Rounded := this.NonNull_Ret(Styles.Rounded , 3 , 0 , 30) ; 圆角 默认3 0-30 581 | , O.Margin := this.NonNull_Ret(Styles.Margin , 5 , 0 , 30) ; 边距 默认5 0-30 582 | , O.TabStops := this.NonNull_Ret(Styles.TabStops , [50] , "", "") ; 制表符宽 默认[50] 583 | , O.TextColor := this.NonNull_Ret(Styles.TextColor , 0xff575757 , "", "") ; 文本色 默认0xff575757 584 | , O.BackgroundColor := this.NonNull_Ret(Styles.BackgroundColor, 0xffffffff , "", "") ; 背景色 默认0xffffffff 585 | , O.Font := this.NonNull_Ret(Styles.Font , this.ToolTipFontName, "", "") ; 字体 默认与 ToolTip 一致 586 | , O.FontSize := this.NonNull_Ret(Styles.FontSize , 12 , "", "") ; 字号 默认12 587 | , O.FontRender := this.NonNull_Ret(Styles.FontRender , 5 , 0 , 5 ) ; 渲染模式 默认5 0-5 588 | , O.FontStyle := Styles.FontStyle ; 字体样式 默认无 589 | 590 | ; 名字太长,建个缩写副本。 591 | , O.BCLGS := Styles.BorderColorLinearGradientStart ; 细边框渐变色 默认无 592 | , O.BCLGE := Styles.BorderColorLinearGradientEnd ; 细边框渐变色 默认无 593 | , O.BCLGA := Styles.BorderColorLinearGradientAngle ; 细边框渐变角度 默认无 594 | , O.BCLGM := this.NonNull_Ret(Styles.BorderColorLinearGradientMode, "", 1, 8) ; 细边框渐变模式 默认无 1-8 595 | 596 | ; 名字太长,建个缩写副本。 597 | , O.TCLGS := Styles.TextColorLinearGradientStart ; 文本渐变色 默认无 598 | , O.TCLGE := Styles.TextColorLinearGradientEnd ; 文本渐变色 默认无 599 | , O.TCLGA := Styles.TextColorLinearGradientAngle ; 文本渐变角度 默认无 600 | , O.TCLGM := this.NonNull_Ret(Styles.TextColorLinearGradientMode, "", 1, 8) ; 文本渐变模式 默认无 1-8 601 | 602 | ; 名字太长,建个缩写副本。 603 | , O.BGCLGS := Styles.BackgroundColorLinearGradientStart ; 背景渐变色 默认无 604 | , O.BGCLGE := Styles.BackgroundColorLinearGradientEnd ; 背景渐变色 默认无 605 | , O.BGCLGA := Styles.BackgroundColorLinearGradientAngle ; 背景渐变角度 默认无 606 | , O.BGCLGM := this.NonNull_Ret(Styles.BackgroundColorLinearGradientMode, "", 1, 8) ; 背景渐变模式 默认无 1-8 607 | 608 | ; a:=0xaabbccdd 下面是运算规则 609 | ; a>>16 = 0xaabb 610 | ; a>>24 = 0xaa 611 | ; a&0xffff = 0xccdd 612 | ; a&0xff = 0xdd 613 | ; 0x88<<16 = 0x880000 614 | ; 0x880000+0xbbcc = 0x88bbcc 615 | , BlendedColor2 := (O.TCLGS and O.TCLGE and O.TCLGD) ? O.TCLGS : O.TextColor ; 使用文本渐变色替换文本色用于混合 616 | , BlendedColor := ((O.BackgroundColor>>24)<<24) + (BlendedColor2&0xffffff) ; 混合色 背景色的透明度与文本色混合 617 | , O.BorderColor := this.NonNull_Ret(Styles.BorderColor , BlendedColor , "", "") ; 细边框色 默认混合色 618 | 619 | , O.TargetHWND := this.NonNull_Ret(Options.TargetHWND , WinExist("A") , "", "") ; 目标句柄 默认活动窗口 620 | , O.CoordMode := this.NonNull_Ret(Options.CoordMode , A_CoordModeToolTip, "", "") ; 坐标模式 默认与 ToolTip 一致 621 | , O.Transparent := this.NonNull_Ret(Options.Transparent, 255 , 0 , 255) ; 整体透明度 默认255 622 | , O.MouseNeverCoverToolTip := this.NonNull_Ret(Options.MouseNeverCoverToolTip , 1 , 0 , 1 ) ; 鼠标永不遮挡文本框 623 | , O.DistanceBetweenMouseXAndToolTip := this.NonNull_Ret(Options.DistanceBetweenMouseXAndToolTip, 16, "", "") ; 鼠标与文本框的X距离 624 | , O.DistanceBetweenMouseYAndToolTip := this.NonNull_Ret(Options.DistanceBetweenMouseYAndToolTip, 16, "", "") ; 鼠标与文本框的Y距离 625 | , O.JustCalculateSize := Options.JustCalculateSize ; 仅计算显示尺寸并返回 626 | 627 | ; 难以比对两个对象是否一致,所以造一个变量比对。 628 | ; 这里的校验因素,必须是那些改变后会使画面内容也产生变化的因素。 629 | ; 所以没有 TargetHWND 和 CoordMode 和 Transparent ,因为这三个因素只影响位置。 630 | for k, v in O.TabStops 631 | TabStops .= v "," 632 | O.Checksum := O.Border "|" O.Rounded "|" O.Margin "|" TabStops "|" 633 | . O.BorderColor "|" O.BCLGS "|" O.BCLGE "|" O.BCLGA "|" O.BCLGM "|" 634 | . O.TextColor "|" O.TCLGS "|" O.TCLGE "|" O.TCLGA "|" O.TCLGM "|" 635 | . O.BackgroundColor "|" O.BGCLGS "|" O.BGCLGE "|" O.BGCLGA "|" O.BGCLGM "|" 636 | . O.Font "|" O.FontSize "|" O.FontRender "|" O.FontStyle 637 | return, O 638 | } 639 | 640 | ; 此函数确保文本框显示位置不会超出目标范围。 641 | ; 使用 ByRef X, ByRef Y 返回不超限的位置。 642 | _CalculateDisplayPosition(ByRef X, ByRef Y, W, H, Options, GetTargetSize:=0) 643 | { 644 | VarSetCapacity(Point, 8, 0) 645 | ; 获取鼠标位置 646 | , DllCall("GetCursorPos", "Ptr", &Point) 647 | , MouseX := NumGet(Point, 0, "Int"), MouseY := NumGet(Point, 4, "Int") 648 | 649 | ; x,y 即 ToolTip 显示的位置。 650 | ; x,y 同时为空表明完全跟随鼠标。 651 | ; x,y 单个为空表明只跟随鼠标横向或纵向移动。 652 | ; x,y 都有值,则说明被钉在屏幕或窗口或客户区的某个位置。 653 | ; MouseX,MouseY 是鼠标的屏幕坐标。 654 | ; DisplayX,DisplayY 是 x,y 经过转换后的屏幕坐标。 655 | ; 以下过程 x,y 不发生变化, DisplayX,DisplayY 储存转换好的屏幕坐标。 656 | ; 不要尝试合并分支 (X="" and Y="") 与 (A_CoordModeToolTip = "Screen")。 657 | ; 因为存在把坐标模式设为 Window 或 Client 但又同时不给出 x,y 的情况!!!!!! 658 | if (X="" and Y="") 659 | { ; 没有给出 x,y 则使用鼠标坐标 660 | DisplayX := MouseX 661 | , DisplayY := MouseY 662 | ; 根据坐标判断在第几个屏幕里,并获得对应屏幕边界。 663 | ; 使用 MONITOR_DEFAULTTONEAREST 设置,可以在给出的点不在任何显示器内时,返回距离最近的显示器。 664 | ; 这样可以修正使用 1920,1080 这种错误的坐标,导致返回空值,导致画图失败的问题。 665 | ; 为什么 1920,1080 是错误的呢?因为 1920 是宽度,而坐标起点是0,所以最右边坐标值是 1919,最下面是 1079。 666 | , hMonitor := MDMF_FromPoint(DisplayX, DisplayY, MONITOR_DEFAULTTONEAREST:=2) 667 | , TargetLeft := this.Monitors[hMonitor].Left 668 | , TargetTop := this.Monitors[hMonitor].Top 669 | , TargetRight := this.Monitors[hMonitor].Right 670 | , TargetBottom := this.Monitors[hMonitor].Bottom 671 | , TargetWidth := TargetRight-TargetLeft 672 | , TargetHeight := TargetBottom-TargetTop 673 | ; 将对应屏幕的 DPIScale 存入 Options 中。 674 | , Options.DPIScale := this.Monitors[hMonitor].DPIScale 675 | } 676 | ; 已给出 x和y 或x 或y,都会走到下面3个分支去。 677 | else if (Options.CoordMode = "Window" or Options.CoordMode = "Relative") 678 | { ; 已给出 x或y 且使用窗口坐标 679 | WinGetPos, WinX, WinY, WinW, WinH, % "ahk_id " Options.TargetHWND 680 | 681 | XInScreen := WinX+X 682 | , YInScreen := WinY+Y 683 | , TargetLeft := WinX 684 | , TargetTop := WinY 685 | , TargetWidth := WinW 686 | , TargetHeight := WinH 687 | , TargetRight := TargetLeft+TargetWidth 688 | , TargetBottom := TargetTop+TargetHeight 689 | , DisplayX := (X="") ? MouseX : XInScreen 690 | , DisplayY := (Y="") ? MouseY : YInScreen 691 | , hMonitor := MDMF_FromPoint(DisplayX, DisplayY, MONITOR_DEFAULTTONEAREST:=2) 692 | , Options.DPIScale := this.Monitors[hMonitor].DPIScale 693 | } 694 | else if (Options.CoordMode = "Client") 695 | { ; 已给出 x或y 且使用客户区坐标 696 | VarSetCapacity(ClientArea, 16, 0) 697 | , DllCall("GetClientRect", "Ptr", Options.TargetHWND, "Ptr", &ClientArea) 698 | , DllCall("ClientToScreen", "Ptr", Options.TargetHWND, "Ptr", &ClientArea) 699 | , ClientX := NumGet(ClientArea, 0, "Int") 700 | , ClientY := NumGet(ClientArea, 4, "Int") 701 | , ClientW := NumGet(ClientArea, 8, "Int") 702 | , ClientH := NumGet(ClientArea, 12, "Int") 703 | 704 | XInScreen := ClientX+X 705 | , YInScreen := ClientY+Y 706 | , TargetLeft := ClientX 707 | , TargetTop := ClientY 708 | , TargetWidth := ClientW 709 | , TargetHeight := ClientH 710 | , TargetRight := TargetLeft+TargetWidth 711 | , TargetBottom := TargetTop+TargetHeight 712 | , DisplayX := (X="") ? MouseX : XInScreen 713 | , DisplayY := (Y="") ? MouseY : YInScreen 714 | , hMonitor := MDMF_FromPoint(DisplayX, DisplayY, MONITOR_DEFAULTTONEAREST:=2) 715 | , Options.DPIScale := this.Monitors[hMonitor].DPIScale 716 | } 717 | else ; 这里必然 A_CoordModeToolTip = "Screen" 718 | { ; 已给出 x或y 且使用屏幕坐标 719 | DisplayX := (X="") ? MouseX : X 720 | , DisplayY := (Y="") ? MouseY : Y 721 | , hMonitor := MDMF_FromPoint(DisplayX, DisplayY, MONITOR_DEFAULTTONEAREST:=2) 722 | , TargetLeft := this.Monitors[hMonitor].Left 723 | , TargetTop := this.Monitors[hMonitor].Top 724 | , TargetRight := this.Monitors[hMonitor].Right 725 | , TargetBottom := this.Monitors[hMonitor].Bottom 726 | , TargetWidth := TargetRight-TargetLeft 727 | , TargetHeight := TargetBottom-TargetTop 728 | , Options.DPIScale := this.Monitors[hMonitor].DPIScale 729 | } 730 | 731 | if (GetTargetSize=1) 732 | { 733 | TargetSize := [] 734 | , TargetSize.X := TargetLeft 735 | , TargetSize.Y := TargetTop 736 | ; 一个窗口,有各种各样的方式可以让自己的高宽超过屏幕高宽。 737 | ; 例如最大化的时候,看起来刚好填满了屏幕,应该是1920*1080,但实际获取会发现是1936*1096。 738 | ; 还可以通过拖动窗口边缘调整大小的方式,让它变1924*1084。 739 | ; 还可以直接在创建窗口的时候,指定一个数值,例如3000*3000。 740 | ; 由于设计的时候, DIB 最大就是多个屏幕大小的总和。 741 | ; 当造出一个超过屏幕大小总和的窗口,又使用了 A_CoordModeToolTip = "Window" 之类的参数,同时待显示文本单行又超级长。 742 | ; 此时 (显示宽高 = 窗口宽高) > DIB宽高,会导致 UpdateLayeredWindow() 显示失败。 743 | ; 所以这里做一下限制。 744 | , TargetSize.W := Min(TargetWidth, this.DIBWidth) 745 | , TargetSize.H := Min(TargetHeight, this.DIBHeight) 746 | return, TargetSize 747 | } 748 | 749 | DPIScale := Options.DPIScale 750 | ; 为跟随鼠标显示的文本框增加一个距离,避免鼠标和文本框挤一起发生遮挡。 751 | ; 因为前面需要用到原始的 DisplayX 和 DisplayY 进行计算,所以在这里才增加距离。 752 | , DisplayX := (X="") ? DisplayX+Options.DistanceBetweenMouseXAndToolTip*DPIScale : DisplayX 753 | , DisplayY := (Y="") ? DisplayY+Options.DistanceBetweenMouseYAndToolTip*DPIScale : DisplayY 754 | 755 | ; 处理目标边缘(右和下)的情况,让文本框可以贴边显示,不会超出目标外。 756 | , DisplayX := (DisplayX+W>=TargetRight) ? TargetRight-W : DisplayX 757 | , DisplayY := (DisplayY+H>=TargetBottom) ? TargetBottom-H : DisplayY 758 | ; 处理目标边缘(左和上)的情况,让文本框可以贴边显示,不会超出目标外。 759 | ; 不建议合并代码,理解会变得困难。 760 | , DisplayX := (DisplayX=DisplayX and MouseY>=DisplayY and MouseX<=DisplayX+W and MouseY<=DisplayY+H) 771 | { 772 | ; MouseY-H-16 是往上弹,应对在左下角和右下角的情况。 773 | ; MouseY+H+16 是往下弹,应对在右上角和左上角的情况。 774 | ; 这里不要去用 Abs(Options.DistanceBetweenMouseYAndToolTip) 替代 16。因为当前者很大时,显示效果不好。 775 | ; 优先往上弹,如果不超限,则上弹。如果超限则往下弹,下弹超限,则不弹。 776 | DisplayY := MouseY-H-16>=TargetTop ? MouseY-H-16 : MouseY+H+16<=TargetBottom ? MouseY+16 : DisplayY 777 | } 778 | 779 | ; 使用 ByRef 变量特性返回计算得到的 X和Y 780 | X := DisplayX , Y := DisplayY 781 | } 782 | 783 | ; https://autohotkey.com/boards/viewtopic.php?f=6&t=4379 784 | ; jballi's Fnt Library 785 | Fnt_GetTooltipFontName() 786 | { 787 | static LF_FACESIZE:=32 ;-- In TCHARS 788 | return StrGet(this.Fnt_GetNonClientMetrics()+(A_IsUnicode ? 316:220)+28,LF_FACESIZE) 789 | } 790 | 791 | Fnt_GetNonClientMetrics() 792 | { 793 | static Dummy15105062 794 | ,SPI_GETNONCLIENTMETRICS:=0x29 795 | ,NONCLIENTMETRICS 796 | 797 | ;-- Set the size of NONCLIENTMETRICS structure 798 | cbSize:=A_IsUnicode ? 500:340 799 | if (((GV:=DllCall("GetVersion"))&0xFF . "." . GV>>8&0xFF)>=6.0) ;-- Vista+ 800 | cbSize+=4 801 | 802 | ;-- Create and initialize NONCLIENTMETRICS structure 803 | VarSetCapacity(NONCLIENTMETRICS,cbSize,0) 804 | NumPut(cbSize,NONCLIENTMETRICS,0,"UInt") 805 | 806 | ;-- Get nonclient metrics parameter 807 | if !DllCall("SystemParametersInfo" 808 | ,"UInt",SPI_GETNONCLIENTMETRICS 809 | ,"UInt",cbSize 810 | ,"Ptr",&NONCLIENTMETRICS 811 | ,"UInt",0) 812 | return false 813 | 814 | ;-- Return to sender 815 | return &NONCLIENTMETRICS 816 | } 817 | 818 | #IncludeAgain %A_LineFile%\..\NonNull.ahk 819 | } 820 | 821 | #Include %A_LineFile%\..\Gdip_All.ahk -------------------------------------------------------------------------------- /Lib/BaiduTranslator.ahk: -------------------------------------------------------------------------------- 1 | ; https://fanyi.baidu.com/ 2 | ; version: 2024.10.03 3 | 4 | class BaiduTranslator 5 | { 6 | init(chromePath:="Chrome\chrome.exe", profilePath:="baidu_translate_profile", debugPort:=9890, mode:="sync", timeout:=30) 7 | { 8 | ; 0开始初始化 1完成初始化 空值没有初始化 9 | this.ready := 0 10 | 11 | ; 加载多语言错误提示 12 | this._multiLanguage() 13 | 14 | ; 指定的 chrome.exe 不存在则尝试自动寻找 15 | if (!FileExist(chromePath)) 16 | chromePath := "" 17 | 18 | ; 默认将配置文件放到临时目录 19 | if (profilePath="baidu_translate_profile") 20 | profilePath := A_Temp "\baidu_translate_profile" 21 | 22 | ; 附着现存或打开新的 chrome 23 | if (Chrome.FindInstances().HasKey(debugPort)) 24 | ChromeInst := {"base": Chrome, "DebugPort": debugPort} 25 | else 26 | ChromeInst := new Chrome(profilePath,, "--headless", chromePath, debugPort) 27 | 28 | ; 退出时自动释放资源 29 | OnExit(ObjBindMethod(this, "_exit")) 30 | 31 | ; 初始化,也就是先加载一次页面 32 | this.page := ChromeInst.GetPage() 33 | this.page.Call("Page.navigate", {"url": "https://fanyi.baidu.com/mtpe-individual/multimodal?query=init&lang=en2zh"}, mode="async" ? false : true) 34 | 35 | ; 同步将产生阻塞直到返回结果,异步将快速返回以便用户自行处理结果 36 | this._receive(mode, timeout, "getInitResult") 37 | 38 | ; 完成初始化 39 | this.ready := 1 40 | } 41 | 42 | translate(str, from:="auto", to:="zh", mode:="sync", timeout:=30) 43 | { 44 | ; 没有初始化则初始化一遍 45 | if (this.ready="") 46 | this.init() 47 | 48 | ; 已经开始初始化则等待其完成 49 | while (this.ready=0) 50 | Sleep 500 51 | 52 | ; 待翻译的文字为空 53 | if (Trim(str, " `t`r`n`v`f")="") 54 | return {Error : this.multiLanguage.2} 55 | 56 | ; 将换行符统一为 `r`n 57 | ; 这样才能让换行数量在翻译前后保持一致 58 | str := StrReplace(str, "`r`n", "`n") 59 | str := StrReplace(str, "`r", "`n") 60 | str := StrReplace(str, "`n", "`r`n") 61 | 62 | ; 待翻译的文字超过 baidu 支持的单次最大长度 63 | if (StrLen(str)>1000) 64 | return {Error : this.multiLanguage.3} 65 | 66 | ; 清空上次翻译结果,避免获取到上次的结果 67 | this._clearTransResult() 68 | 69 | ; 构造 url 70 | l := this._convertLanguageAbbr(from, to) 71 | url := Format("https://fanyi.baidu.com/mtpe-individual/multimodal?query={}&lang={}2{}", this.UriEncode(str), l.from, l.to) 72 | 73 | ; url 超过最大长度 74 | if (StrLen(url)>8182) 75 | return {Error : this.multiLanguage.4} 76 | 77 | ; 翻译 78 | this.page.Call("Page.navigate", {"url": url}, mode="async" ? false : true) 79 | return this._receive(mode, timeout, "getTransResult") 80 | } 81 | 82 | getInitResult() 83 | { 84 | return this.getTransResult() 85 | } 86 | 87 | getTransResult() 88 | { 89 | ; 获取翻译结果 90 | try 91 | str := this.page.Evaluate("document.querySelector('#trans-selection').innerText;").value 92 | 93 | ; 去掉空白符后不为空则返回原文 94 | if (Trim(str, " `t`r`n`v`f")!="") 95 | { 96 | ; baidu 会返回多余的换行 97 | str := StrReplace(str, "`n`n", "`r") 98 | return StrReplace(str, "`r", "`n") 99 | } 100 | } 101 | 102 | free() 103 | { 104 | try ret := this.page.Call("Browser.getVersion",,, 1) ; 确保 ws 连接正常 105 | 106 | if (ret) 107 | this.page.Call("Browser.close") ; 关闭浏览器(所有页面和标签) 108 | } 109 | 110 | _multiLanguage() 111 | { 112 | this.multiLanguage := [] 113 | l := this.multiLanguage 114 | if (A_Language="0804") 115 | { 116 | l.1 := "自己先去源码里把 chrome.exe 路径设置好!" 117 | l.2 := "待翻译文字为空!" 118 | l.3 := "待翻译文字超过最大长度(1000)!" 119 | l.4 := "URL 超过最大长度(8182)!" 120 | l.5 := "超时!" 121 | l.6 := "不支持此两种语言间的翻译!" 122 | } 123 | else 124 | { 125 | l.1 := "Please set the chrome.exe path first!" 126 | l.2 := "The text to be translated is empty!" 127 | l.3 := "The text to be translated is over the maximum length(1000)!" 128 | l.4 := "The URL is over the maximum length(8182)!" 129 | l.5 := "Timeout!" 130 | l.6 := "Translation between these two languages is not supported!" 131 | } 132 | } 133 | 134 | _convertLanguageAbbr(from, to) 135 | { 136 | ; 由于 baidu 支持的语言实在太多,所以不进行语种是否支持的判断 137 | ; 除 ro 被罗姆语占用外,其它均是无冲突转换 138 | dict := { ar:"ara", et:"est", bg:"bul", pl:"pl", da:"dan" 139 | , de:"de", ru:"ru", fr:"fra", fi:"fin", ko:"kor" 140 | , nl:"nl", cs:"cs", lv:"lav", lt:"lit", ro:"rom" 141 | , pt:"pt", ja:"jp", sv:"swe", nb:"nob", sk:"sk" 142 | , sl:"slo", th:"th", tr:"tr", uk:"ukr", es:"spa" 143 | , el:"el", hu:"hu", it:"it", id:"id", en:"en" 144 | , vi:"vie", zh:"zh"} 145 | ret := {} 146 | ret.from := dict.HasKey(from) ? dict[from] : from 147 | ret.to := dict.HasKey(to) ? dict[to] : to 148 | return ret 149 | } 150 | 151 | _clearTransResult() 152 | { 153 | try this.page.Evaluate("document.querySelector('#editor-text > div > div > span').click();") 154 | } 155 | 156 | _receive(mode, timeout, result) 157 | { 158 | ; 异步模式直接返回 159 | if (mode="async") 160 | return 161 | 162 | ; 同步模式将在这里阻塞直到取得结果或超时 163 | startTime := A_TickCount 164 | loop 165 | { 166 | ret := result="getInitResult" ? this.getInitResult() : this.getTransResult() 167 | if (ret!="") 168 | return ret 169 | else 170 | Sleep 500 171 | 172 | if ((A_TickCount-startTime)/1000 >= timeout) 173 | return {Error : this.multiLanguage.5} 174 | } 175 | } 176 | 177 | _exit() 178 | { 179 | if (this.page.connected) 180 | this.free() 181 | } 182 | 183 | #IncludeAgain %A_LineFile%\..\UriEncode.ahk 184 | } 185 | 186 | #Include %A_LineFile%\..\Chrome.ahk -------------------------------------------------------------------------------- /Lib/DeepLTranslator.ahk: -------------------------------------------------------------------------------- 1 | ; https://www.deepl.com/translator 2 | ; version: 2024.10.03 3 | 4 | class DeepLTranslator 5 | { 6 | init(chromePath:="Chrome\chrome.exe", profilePath:="deepl_translate_profile", debugPort:=9888, mode:="sync", timeout:=30) 7 | { 8 | ; 0开始初始化 1完成初始化 空值没有初始化 9 | this.ready := 0 10 | 11 | ; 加载多语言错误提示 12 | this._multiLanguage() 13 | 14 | ; 指定的 chrome.exe 不存在则尝试自动寻找 15 | if (!FileExist(chromePath)) 16 | chromePath := "" 17 | 18 | ; 默认将配置文件放到临时目录 19 | if (profilePath="deepl_translate_profile") 20 | profilePath := A_Temp "\deepl_translate_profile" 21 | 22 | ; 附着现存或打开新的 chrome 23 | if (Chrome.FindInstances().HasKey(debugPort)) 24 | ChromeInst := {"base": Chrome, "DebugPort": debugPort} 25 | else 26 | ChromeInst := new Chrome(profilePath,, "--headless", chromePath, debugPort) 27 | 28 | ; 退出时自动释放资源 29 | OnExit(ObjBindMethod(this, "_exit")) 30 | 31 | ; 初始化,也就是先加载一次页面 32 | this.page := ChromeInst.GetPage() 33 | this.page.Call("Page.navigate", {"url": "https://www.deepl.com/translator#en/zh/init"}, mode="async" ? false : true) 34 | 35 | ; 同步将产生阻塞直到返回结果,异步将快速返回以便用户自行处理结果 36 | this._receive(mode, timeout, "getInitResult") 37 | 38 | ; 完成初始化 39 | this.ready := 1 40 | } 41 | 42 | translate(str, from:="auto", to:="zh", mode:="sync", timeout:=30) 43 | { 44 | ; 没有初始化则初始化一遍 45 | if (this.ready="") 46 | this.init() 47 | 48 | ; 已经开始初始化则等待其完成 49 | while (this.ready=0) 50 | Sleep 500 51 | 52 | ; 待翻译的文字为空 53 | if (Trim(str, " `t`r`n`v`f")="") 54 | return {Error : this.multiLanguage.2} 55 | 56 | ; 将换行符统一为 `r`n 57 | ; 这样才能让换行数量在翻译前后保持一致 58 | str := StrReplace(str, "`r`n", "`n") 59 | str := StrReplace(str, "`r", "`n") 60 | str := StrReplace(str, "`n", "`r`n") 61 | 62 | ; 待翻译的文字超过 deepl 支持的单次最大长度 63 | ; 这里需要注意,实际使用中会因未知原因触发 “xxxx个字符中仅3000个字符已翻译。免费注册以实现一次性翻译多达5000个字符。” 64 | ; 所以这里限制为3000 65 | if (StrLen(str)>3000) 66 | return {Error : this.multiLanguage.3} 67 | 68 | ; 清空原文 69 | this._clearTransResult() 70 | 71 | ; 选择语言 72 | if (this._convertLanguageAbbr(from, to).Error) 73 | return {Error : this.multiLanguage.6} 74 | 75 | ; 翻译 76 | this.page.Evaluate("document.querySelector('[data-testid=""translator-source-input""]').focus();") 77 | this.page.Call("Input.insertText", {"text": str}, mode="async" ? false : true) 78 | return this._receive(mode, timeout, "getTransResult") 79 | } 80 | 81 | getInitResult() 82 | { 83 | return this.getTransResult() 84 | } 85 | 86 | getTransResult() 87 | { 88 | ; 获取翻译结果 89 | try 90 | str := this.page.Evaluate("document.querySelectorAll('d-textarea')[1].innerText;").value 91 | 92 | ; 去掉空白符后不为空则返回原文 93 | if (Trim(str, " `t`r`n`v`f")!="") 94 | { 95 | ; deepl 会返回多余的换行 96 | str := StrReplace(str, "`n`n`n", "`r") 97 | str := StrReplace(str, "`n`n", "`r") 98 | return StrReplace(str, "`r", "`n") 99 | } 100 | } 101 | 102 | free() 103 | { 104 | try ret := this.page.Call("Browser.getVersion",,, 1) ; 确保 ws 连接正常 105 | 106 | if (ret) 107 | this.page.Call("Browser.close") ; 关闭浏览器(所有页面和标签) 108 | } 109 | 110 | _multiLanguage() 111 | { 112 | this.multiLanguage := [] 113 | l := this.multiLanguage 114 | if (A_Language="0804") 115 | { 116 | l.1 := "自己先去源码里把 chrome.exe 路径设置好!" 117 | l.2 := "待翻译文字为空!" 118 | l.3 := "待翻译文字超过最大长度(3000)!" 119 | l.4 := "URL 超过最大长度(8182)!" 120 | l.5 := "超时!" 121 | l.6 := "不支持此两种语言间的翻译!" 122 | } 123 | else 124 | { 125 | l.1 := "Please set the chrome.exe path first!" 126 | l.2 := "The text to be translated is empty!" 127 | l.3 := "The text to be translated is over the maximum length(3000)!" 128 | l.4 := "The URL is over the maximum length(8182)!" 129 | l.5 := "Timeout!" 130 | l.6 := "Translation between these two languages is not supported!" 131 | } 132 | } 133 | 134 | _convertLanguageAbbr(from, to) 135 | { 136 | languageSelected := from "-" to 137 | 138 | ; 语言发生变化,需要重新选择 139 | if (languageSelected!=this.languageSelected) 140 | { 141 | this.languageSelected := languageSelected 142 | 143 | if (from="auto" or to="auto" or from=to) 144 | { 145 | this.page.Evaluate("document.querySelector('[data-testid=""translator-source-lang-btn""]').click();") 146 | this.page.Evaluate("document.querySelector('[data-testid=""translator-lang-option-auto""]').click();") 147 | } 148 | else 149 | { 150 | /* 151 | 检测源语言 荷兰语 土耳其语 152 | 阿拉伯语 捷克语 乌克兰语 153 | 爱沙尼亚语 拉脱维亚语 西班牙语 154 | 保加利亚语 立陶宛语 希腊语 155 | 波兰语 罗马尼亚语 匈牙利语 156 | 丹麦语 葡萄牙语 意大利语 157 | 德语 日语 印尼语 158 | 俄语 瑞典语 英语 159 | 法语 书面挪威语 中文 160 | 芬兰语 斯洛伐克语 161 | 韩语 斯洛文尼亚语 162 | */ 163 | dict_from := { auto:"auto", nl:"nl", tr:"tr" 164 | , ar:"ar", cs:"cs", uk:"uk" 165 | , et:"et", lv:"lv", es:"es" 166 | , bg:"bg", lt:"lt", el:"el" 167 | , pl:"pl", ro:"ro", hu:"hu" 168 | , da:"da", pt:"pt", it:"it" 169 | , de:"de", ja:"ja", id:"id" 170 | , ru:"ru", sv:"sv", en:"en" 171 | , fr:"fr", nb:"nb", zh:"zh" 172 | , fi:"fi", sk:"sk" 173 | , ko:"ko", sl:"sl"} 174 | 175 | /* 176 | 阿拉伯语 捷克语 土耳其语 177 | 爱沙尼亚语 拉脱维亚语 乌克兰语 178 | 保加利亚语 立陶宛语 西班牙语 179 | 波兰语 罗马尼亚语 希腊语 180 | 丹麦语 葡萄牙语 匈牙利语 181 | 德语 葡萄牙语(巴西) 意大利语 182 | 俄语 日语 印尼语 183 | 法语 瑞典语 英语(美式) 184 | 芬兰语 书面挪威语 英语(英式) 185 | 韩语 斯洛伐克语 中文(简体) 186 | 荷兰语 斯洛文尼亚语 中文(繁体) 187 | */ 188 | dict_to := { ar:"ar", cs:"cs", tr:"tr" 189 | , et:"et", lv:"lv", uk:"uk" 190 | , bg:"bg", lt:"lt", es:"es" 191 | , pl:"pl", ro:"ro", el:"el" 192 | , da:"da", pt:"pt-PT", hu:"hu" 193 | , de:"de", pt2:"pt-BR", it:"it" 194 | , ru:"ru", ja:"ja", id:"id" 195 | , fr:"fr", sv:"sv", en:"en-US" 196 | , fi:"fi", nb:"nb", en2:"en-GB" 197 | , ko:"ko", sk:"sk", zh:"zh-Hans" 198 | , nl:"nl", sl:"sl", zh2:"zh-Hant"} 199 | 200 | if (!dict_from.HasKey(from) or !dict_to.HasKey(to)) 201 | return {Error : this.multiLanguage.6} 202 | else 203 | { 204 | this.page.Evaluate("document.querySelector('[data-testid=""translator-source-lang-btn""]').click();") 205 | this.page.Evaluate(Format("document.querySelector('[data-testid=""translator-lang-option-{}""]').click();"), dict_from[from]) 206 | 207 | this.page.Evaluate("document.querySelector('[data-testid=""translator-target-lang-btn""]').click();") 208 | this.page.Evaluate(Format("document.querySelector('[data-testid=""translator-lang-option-{}""]').click();"), dict_to[to]) 209 | } 210 | } 211 | } 212 | } 213 | 214 | _clearTransResult() 215 | { 216 | try this.page.Evaluate("document.querySelector('[data-testid=""translator-source-clear-button""]').click();") 217 | } 218 | 219 | _receive(mode, timeout, result) 220 | { 221 | ; 异步模式直接返回 222 | if (mode="async") 223 | return 224 | 225 | ; 同步模式将在这里阻塞直到取得结果或超时 226 | startTime := A_TickCount 227 | loop 228 | { 229 | ret := result="getInitResult" ? this.getInitResult() : this.getTransResult() 230 | if (ret!="") 231 | return ret 232 | else 233 | Sleep 500 234 | 235 | if ((A_TickCount-startTime)/1000 >= timeout) 236 | return {Error : this.multiLanguage.5} 237 | } 238 | } 239 | 240 | _exit() 241 | { 242 | if (this.page.connected) 243 | this.free() 244 | } 245 | 246 | #IncludeAgain %A_LineFile%\..\UriEncode.ahk 247 | } 248 | 249 | #Include %A_LineFile%\..\Chrome.ahk -------------------------------------------------------------------------------- /Lib/NonNull.ahk: -------------------------------------------------------------------------------- 1 | ; 变量为空,则使用默认值。变量不为空,则使用变量值。 2 | ; 同时可以检查变量是否超出最大最小范围。 3 | ; 注意,默认值不受最大最小范围的限制。 4 | ; 也就是说 5 | ; 当变量值为"",默认值为8,范围为2-5,此时变量值会是8。 6 | ; 当变量值为10,默认值为8,范围为2-5,此时变量值会是5。 7 | NonNull(ByRef var, DefaultValue, MinValue:="", MaxValue:="") ; 237ms 8 | { 9 | var:= var="" ? DefaultValue : MinValue="" ? (MaxValue="" ? var : Min(var, MaxValue)) : (MaxValue!="" ? Max(Min(var, MaxValue), MinValue) : Max(var, MinValue)) 10 | } 11 | 12 | ; 与 NonNull 一致,区别是通过 return 返回值,而不是 ByRef。 13 | NonNull_Ret(var, DefaultValue, MinValue:="", MaxValue:="") ; 237ms 14 | { 15 | return, var="" ? DefaultValue : MinValue="" ? (MaxValue="" ? var : Min(var, MaxValue)) : (MaxValue!="" ? Max(Min(var, MaxValue), MinValue) : Max(var, MinValue)) 16 | /* 17 | ; 下面的 if 版本与上面的三元版本是等价的 18 | ; 只是16w次循环的速度是 270ms,慢了13% 19 | if (var="") 20 | return, DefaultValue ; 变量为空,则返回默认值 21 | else 22 | { 23 | if (MinValue="") 24 | { 25 | if (MaxValue="") 26 | return, var ; 变量有值,且不检查最大最小范围,则直接返回变量值 27 | else 28 | return, Min(var, MaxValue) ; 变量有值,且只检查最大值,则返回不大于最大值的变量值 29 | } 30 | else 31 | { 32 | if (MaxValue!="") 33 | ; 三元的写法不会更快 34 | return, Max(Min(var, MaxValue), MinValue) ; 变量有值,且检查最大最小范围,则返回最大最小范围内的变量值 35 | else 36 | return, Max(var, MinValue) ; 变量有值,且只检查最小值,则返回不小于最小值的变量值 37 | } 38 | } 39 | */ 40 | } 41 | 42 | /* 单元测试 43 | 计时() 44 | loop,1000 45 | { 46 | gosub, UnitTest1 47 | gosub, UnitTest2 48 | } 49 | 计时() 50 | 51 | ; ByRef 版本的测试 52 | UnitTest1: 53 | v:="" 54 | NonNull(v, 8, 2, 10) 55 | if v!=8 56 | MsgBox, wrong 57 | v:="" 58 | NonNull(v, 8, "", "") 59 | if v!=8 60 | MsgBox, wrong 61 | v:="" 62 | NonNull(v, 8, 2, "") 63 | if v!=8 64 | MsgBox, wrong 65 | v:="" 66 | NonNull(v, 8, "", 10) 67 | if v!=8 68 | MsgBox, wrong 69 | 70 | v:=5 71 | NonNull(v, 8, 2, 10) 72 | if v!=5 73 | MsgBox, wrong 74 | v:=5 75 | NonNull(v, 8, "", "") 76 | if v!=5 77 | MsgBox, wrong 78 | v:=5 79 | NonNull(v, 8, 2, "") 80 | if v!=5 81 | MsgBox, wrong 82 | v:=5 83 | NonNull(v, 8, "", 10) 84 | if v!=5 85 | MsgBox, wrong 86 | 87 | v:=15 88 | NonNull(v, 8, 2, 10) 89 | if v!=10 90 | MsgBox, wrong 91 | v:=15 92 | NonNull(v, 8, "", "") 93 | if v!=15 94 | MsgBox, wrong 95 | v:=15 96 | NonNull(v, 8, 2, "") 97 | if v!=15 98 | MsgBox, wrong 99 | v:=15 100 | NonNull(v, 8, "", 10) 101 | if v!=10 102 | MsgBox, wrong 103 | 104 | v:=1 105 | NonNull(v, 8, 2, 10) 106 | if v!=2 107 | MsgBox, wrong 108 | v:=1 109 | NonNull(v, 8, "", "") 110 | if v!=1 111 | MsgBox, wrong 112 | v:=1 113 | NonNull(v, 8, 2, "") 114 | if v!=2 115 | MsgBox, wrong 116 | v:=1 117 | NonNull(v, 8, "", 10) 118 | if v!=1 119 | MsgBox, wrong 120 | return 121 | 122 | ; return 版本的测试 123 | UnitTest2: 124 | v:="" 125 | if NonNull_Ret(v, 8, 2, 10)!=8 126 | MsgBox, wrong 127 | v:="" 128 | if NonNull_Ret(v, 8, "", "")!=8 129 | MsgBox, wrong 130 | v:="" 131 | if NonNull_Ret(v, 8, 2, "")!=8 132 | MsgBox, wrong 133 | v:="" 134 | if NonNull_Ret(v, 8, "", 10)!=8 135 | MsgBox, wrong 136 | 137 | v:=5 138 | if NonNull_Ret(v, 8, 2, 10)!=5 139 | MsgBox, wrong 140 | v:=5 141 | if NonNull_Ret(v, 8, "", "")!=5 142 | MsgBox, wrong 143 | v:=5 144 | if NonNull_Ret(v, 8, 2, "")!=5 145 | MsgBox, wrong 146 | v:=5 147 | if NonNull_Ret(v, 8, "", 10)!=5 148 | MsgBox, wrong 149 | 150 | v:=15 151 | if NonNull_Ret(v, 8, 2, 10)!=10 152 | MsgBox, wrong 153 | v:=15 154 | if NonNull_Ret(v, 8, "", "")!=15 155 | MsgBox, wrong 156 | v:=15 157 | if NonNull_Ret(v, 8, 2, "")!=15 158 | MsgBox, wrong 159 | v:=15 160 | if NonNull_Ret(v, 8, "", 10)!=10 161 | MsgBox, wrong 162 | 163 | v:=1 164 | if NonNull_Ret(v, 8, 2, 10)!=2 165 | MsgBox, wrong 166 | v:=1 167 | if NonNull_Ret(v, 8, "", "")!=1 168 | MsgBox, wrong 169 | v:=1 170 | if NonNull_Ret(v, 8, 2, "")!=2 171 | MsgBox, wrong 172 | v:=1 173 | if NonNull_Ret(v, 8, "", 10)!=1 174 | MsgBox, wrong 175 | return 176 | */ -------------------------------------------------------------------------------- /Lib/Paragraph.ahk: -------------------------------------------------------------------------------- 1 | /* 2 | ; ver. 2021.10.31 3 | ; 以下是常见的一段文字被切割成多个单行的场景 4 | ; https://www.doc88.com/p-3062268332883.html 5 | ; pdf 中引用参考文献 6 | test1= 7 | ( 8 | esophageal varices? A randomized controlled study. Dig Dis 9 | Sci 1997;42:536–41. 10 | 33. Horst D, Grace NE, Conn HO, et al. Comparison of dietary 11 | protein with an oral, branched-chain enriched amino acid 12 | supplement in chronic portal-systemic encephalopathy. Hepa- 13 | tology 1984;2:279–87. 14 | 34. Van Thiel DH, Fagiuoli S, Wright HI, et al. Gastrointestinal 15 | transit in cirrhotic patients: Effect of hepatic encephalopathy 16 | and its treatment. Hepatology 1994;19:67–71. 17 | 35. Wolpert E, Phillips SF, Summerskill WH. Ammonia produc- 18 | tion in the human colon. Effects of cleansing, neomycin and 19 | acetohydroxamic acid. N Engl J Med 1970;283:159–64. 20 | 36. Rolachon A, Zarski JP, Lutz JM, et al. [Is the intestinal lavage 21 | with a solution of mannitol effective in the prevention of 22 | post-hemorrhagic hepatic encephalopathy in patients with 23 | liver cirrhosis? Results of a randomized prospective study]. 24 | Gastroenterol Clin Biol 1994;18:1057–62. 25 | 37. Cordoba J, Blei AT. Treatment of hepatic encephalopathy. 26 | Am J Gastroenterol 1997;92:1429–39. 27 | 38. Riordan SM, Williams R. Treatment of hepatic encephalopa- 28 | thy. N Engl J Med 1997;337:473–9. 29 | 39. Blanc P, Daures JP, Liautard J, et al. [Lactulose-neomycin 30 | combination versus placebo in the treatment of acute hepatic 31 | encephalopathy. Results of a randomized controlled trial]. 32 | Gastroenterol Clin Biol 1994;18:1063–8. 33 | 40. Clausen MR, Mortensen PB. Lactulose, disaccharides and 34 | colonic f l ora. Clinical consequences. Drugs 1997;53:930–42. 35 | 41. Warren SE, Mitas JA 2d, Swerdlin AH. Hypernatremia in 36 | hepatic failure. JAMA 1980;243:1257–60. 37 | 42. Uribe M, Campollo A, Vargas F, et al. Acidifying enemas 38 | (Lactitol and lactose) vs. non-acidifying enemas (tap water) to 39 | treat acute portal-systemic encephalopathy: A double-blind 40 | randomized clinical trial. Hepatology 1987;7:639–43. 41 | 43. Hawkins RA, Jessy J, Mans AM, et al. Neomycin reduces the 42 | intestinal production of ammonia from glutamine. Adv Exp 43 | Med Biol 1994;368:125–34. 44 | 44. Morgan MH, Read AE, Speller DC. Treatment of hepatic 45 | encephalopathy with metronidazole. Gut 1982;23:1–7. 46 | 45. Gubbins GP, Moritz TE, Marsano LS, et al. Helicobacter 47 | pylori is a risk factor for hepatic encephalopathy in acute 48 | alcoholic hepatitis: The ammonia hypothesis revisited. The 49 | Veterans Administration Cooperative Study Group No. 275. 50 | Am J Gastroenterol 1993;88:1906–10. 51 | 46. Vasconez C, Elizalde JI, Llach J, et al. Helicobacter pylori, 52 | hyperammonemia and subclinical portosystemic encephal- 53 | opathy: Effects of eradication. J Hepatol 1999;30:260–4. 54 | 47. Loft S, Sonne J, Dossing M, Andreasen PB. Metronidazole 55 | pharmacokinetics in patients with hepatic encephalopathy. 56 | Scand J Gastroenterol 1987;22:117–23. 57 | 48. Kircheis G, Nilius R, Held C, et al. Therapeutic eff i cacy of 58 | L-ornithine-L-aspartate infusions in patients with cirrhosis and 59 | hepatic encephalopathy: Results of a placebo-controlled, dou- 60 | ble-blind study. Hepatology 1997;25:1351–60. 61 | 49. Stauch S, Kircheis G, Adler G, et al. Oral L-ornithine-L- 62 | aspartate therapy ofchronic hepaticencephalopathy. Results of 63 | a placebo-controlled double-blind study. J Hepatol 1998;28: 64 | 856–64. 65 | 50. Schafer DF, Jones EA. Hepatic encephalopathy and the gam- 66 | ma-amino-butyric acid system. Lancet 1982;1:18–20. 67 | 51. Basile AS, Hughes RD, Harrison PM, et al. Elevated brain 68 | concentrations of 1,4-benzodiazepines in fulminant hepatic 69 | failure. N Engl J Med 1991;325:473–8. 70 | 52. Barbaro G, Di Lorenzo G, Soldini M, et al. Flumazenil for 71 | hepatic encephalopathy grade III and Iva in patients with 72 | cirrhosis: An Italian multicenter double-blind, placebo-con- 73 | trolled, cross-over study. Hepatology 1998;28:374–8. 74 | 53. James JH, Ziparo V, Jeppsson B, Fischer JE. Hyperammon- 75 | ) 76 | 77 | ; pdf 中正文 78 | test2= 79 | ( 80 | PREAMBLE 81 | Guidelines for clinical practice are intended to suggest pref- 82 | erable approaches to particular medical problems as estab- 83 | lished by interpretation and collation of scientif i cally valid 84 | research, derived from extensive review of the published 85 | literature. When data are not available that will withstand 86 | objective scrutiny, a recommendation may be made based 87 | on a consensus of experts. Guidelines are intended to apply 88 | to the clinical situation for all physicians without regard to 89 | specialty. Guidelines are intended to be f l exible, not neces- 90 | sarily indicating the only acceptable approach, and should 91 | be distinguished from standards of care that are inf l exible 92 | and rarely violated. Given the wide range of choices in any 93 | health care problem, the physician should select the course 94 | best suited to the individual patient and the clinical situation 95 | presented. These guidelines are developed under the aus- 96 | pices of the American College of Gastroenterology and its 97 | Practice Parameters Committee. These guidelines are also 98 | approved by the governing board of the American Gastro- 99 | enterological Association. Expert opinion is solicited from 100 | the outset for the document. Guidelines are reviewed in 101 | depth by the committee, with participation from experienced 102 | clinicians and others in related f i elds. The f i nal recommen- 103 | dations are based on the data available at the time of the 104 | production of the document and may be updated with per- 105 | tinent scientif i c developments at a later time. The following 106 | guidelines are intended for adults and not for pediatric 107 | patients. (Am J Gastroenterol 2001;96:1968–1976. © 2001 108 | by Am. Coll. of Gastroenterology) 109 | INTRODUCTION 110 | Hepatic encephalopathy (HE) may be def i ned as a distur- 111 | bance in central nervous system function because of hepatic 112 | insuff i ciency. This broad def i nition ref l ects the existence of 113 | a spectrum of neuropsychiatric manifestations related to a 114 | range of pathophysiological mechanisms. Present in both 115 | acute and chronic liver failure, these neuropsychiatric man- 116 | ifestations are potentially reversible. 117 | Multiple treatments have been used for HE. However, 118 | their eff i cacy has been infrequently assessed by well-de- 119 | signed randomized clinical trials. This handicap ref l ects the 120 | diff i culty in evaluating the wide range of neuropsychiatric 121 | symptomatology. Alteration ofconsciousness—its most rel- 122 | evant manifestation—undergoes spontaneous f l uctuations 123 | and will be inf l uenced by concurrent clinical factors, such as 124 | infection, hypoxemia, GI hemorrhage, or electrolyte distur- 125 | bances. Despite these limitations, a critical appraisal of 126 | available data renders it possible to delineate a rational 127 | approach to the treatment of HE. 128 | CLINICAL CONSIDERATIONS 129 | Pathophysiology 130 | The main tenet of all theories of the pathogenesis of HE is 131 | f i rmly accepted: nitrogenous substances derived from the 132 | gut adversely affect brain function. These compounds gain 133 | access to the systemic circulation as a result of decreased 134 | hepatic function or portal-systemic shunts. Once in brain 135 | tissue, they produce alterations of neurotransmission that 136 | affect consciousness and behavior. Abnormalities in gluta- 137 | matergic, serotoninergic,  -aminobutyric acid–ergic 138 | (GABA-ergic), and catecholamine pathways, among others, 139 | have been described in experimental HE (1). The research 140 | challenge lies in the dissection of each of these systems and 141 | their possible pharmacological manipulation to improve 142 | treatment. 143 | A large body of work points at ammonia as a key factor 144 | in the pathogenesis of HE (2, 3). Ammonia is released from 145 | several tissues (kidney, muscle), but its highest levels can be 146 | found in the portal vein. Portal ammonia is derived from 147 | both the urease activity of colonic bacteria and the deami- 148 | dation of glutamine in the small bowel, and is a key sub- 149 | strate for the synthesis of urea and glutamine in the liver. 150 | The hepatic process is eff i cient, with a f i rst pass extraction 151 | of ammonia of approximately 0.8 (4). In acute and chronic 152 | liver disease, increased arterial levels of ammonia are com- 153 | monly seen. In fulminant hepatic failure (FHF), elevated 154 | arterial levels (200  g/dl) have been associated with an 155 | increased risk of cerebral herniation (5). However, correla- 156 | tion of blood levels with mental state in cirrhosis is inac- 157 | curate. The blood-brain barrier permeability to ammonia is 158 | increased in patients with HE (6); as a result, blood levels 159 | will correlate weakly with brain values, though recent stud- 160 | ies indicate an improvement ofthis correlation by correcting 161 | the ammonia value to the blood pH (7). Furthermore, the 162 | alterations in neurotransmission induced by ammonia also 163 | occur after the metabolism of this toxin into astrocytes (3), 164 | ) 165 | 166 | ; 代码中注释 167 | test3= 168 | ( 169 | ; MIT License 170 | ; 171 | ; Permission is hereby granted, free of charge, to any person obtaining a copy 172 | ; of this software and associated documentation files (the "Software"), to deal 173 | ; in the Software without restriction, including without limitation the rights 174 | ; to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 175 | ; copies of the Software, and to permit persons to whom the Software is 176 | ; furnished to do so, subject to the following conditions: 177 | ; 178 | ; The above copyright notice and this permission notice shall be included in all 179 | ; copies or substantial portions of the Software. 180 | ; 181 | ; THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 182 | ; IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 183 | ; FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 184 | ; AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 185 | ; LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 186 | ; OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 187 | ; SOFTWARE. 188 | ) 189 | 190 | ; ocr 的结果 191 | test4= 192 | ( 193 | 很多人认为,没时间没精力去运动瘦身,那就吃 194 | 点健康食品吧,把碳酸饮料和烤肉都戒掉,总可 195 | 以瘦了吧? 196 | 不瞒你说,还真的不可以。 197 | 因为那些你认为的健康饮食,可能会让你更胖。 198 | 澳大利亚导演达蒙(Damon Gameau)做的一个 199 | 真人实验。 200 | 为了弄清楚糖对身体的影响,达蒙把自己当成白 201 | 老鼠,在医生、营养师和血液专家的监督下,每 202 | 天摄入40茶匙的糖(约160克),坚持60天,看 203 | 看身体会发生什么变化。 204 | 40茶匙的糖,听起来很多,其实相当于澳大利 205 | 亚人每天糖摄入量的平均水平。而且医生规定, 206 | 这些糖的来源,不能是高糖垃圾食品,比如雪糕 207 | 汽水等,必须选择公认为健康的食品和饮料。 208 | ) 209 | 210 | for k, v in Paragraph.merge(test1) 211 | MsgBox, % v 212 | 213 | ExitApp 214 | */ 215 | Class Paragraph 216 | { 217 | /* 218 | 1. 对 a 去注释得到 a2 219 | 2. 对 a2 按空行分段得到 a3 b3 c3 220 | 3. 对 a3 按序号进行分离得到 a4 221 | 4. 对 a4 按宽度进行合并得到 a5 222 | 5. 对 b3 c3 重复3-4 223 | */ 224 | merge(Text, Font:="", FontSize:="", ShortLineRatio:=0.9, LongLineRatio:=1.5) 225 | { 226 | ret:=[] 227 | 228 | ; 统一换行符 `r`n 为 `n 229 | Text1:=StrReplace(Text, "`r`n", "`n") 230 | 231 | ; 统一换行符 `r 为 `n 232 | Text1:=StrReplace(Text1, "`r", "`n") 233 | 234 | ; 删除行首注释符 235 | Text1:=this._deleteEscape(Text1) 236 | 237 | /* 像下面这种存在用空行分段的情况,则预先分段 238 | aaaaaaa 239 | bbbbb 240 | 241 | ccccccc 242 | */ 243 | oParagraph1:=StrSplit(Text1, "`n`n", " `t`r`n`v`f") 244 | 245 | ; 这里如果使用 for k, paragraph 则会在某些时候把 Class 自身给覆盖掉 246 | for k, paragraph1 in oParagraph1 247 | { 248 | oLines:=StrSplit(paragraph1, "`n") 249 | 250 | ; 以递增的序号为标记,再次分段 251 | oParagraph2:=this._splitBySerialNumber(oLines) 252 | 253 | ; 根据行的长度进行合并 254 | for k, paragraph2 in oParagraph2 255 | ret.Push(this._mergeByLen(paragraph2, Font, FontSize, ShortLineRatio, LongLineRatio)*) 256 | } 257 | 258 | return, ret 259 | } 260 | 261 | /* 像下面这种行首带注释符号的情况,则删除注释符号及其后面的空白符 262 | ; aaaaaaaaaa aaaaaaaaaa 263 | ; bbbbbbbbbb => bbbbbbbbbb 264 | ; cccccccccc cccccccccc 265 | */ 266 | _deleteEscape(Text) 267 | { 268 | ; 注释符号可以在这里进行自定义 269 | oEscape:=[";", "#", "@REM"] 270 | 271 | ; 根据空行分段。只有整个段落的行首都有相同注释符,注释符才会被删除 272 | oParagraph1:=StrSplit(Text, "`n`n", " `t`r`n`v`f") 273 | 274 | ; 这里如果使用 for k, paragraph 则会在某些时候把 Class 自身给覆盖掉 275 | for k, paragraph1 in oParagraph1 276 | { 277 | oLines:=StrSplit(paragraph1, "`n") 278 | 279 | for k, Escape in oEscape 280 | { 281 | NeedleRegEx:=Format("^\s*\Q{}\E\s*", Escape) 282 | 283 | /* 像下面左边这种每行都有注释符才进行删除处理,右边这种不删除 284 | ; aaaaa ; aaaaa 285 | ; bbbb bbbb 286 | ; ccc ; ccc 287 | */ 288 | for k, line in oLines 289 | { 290 | if (RegExMatch(line, NeedleRegEx)) 291 | needToDeleteEscape:=true 292 | else 293 | { 294 | needToDeleteEscape:=false 295 | break 296 | } 297 | } 298 | 299 | if (needToDeleteEscape) 300 | { 301 | for k, line in oLines 302 | ; 删掉注释符后,一并删掉句子左侧可能存在的不必要的空白符 303 | temp.=RegExReplace(line, NeedleRegEx, "", "", 1) "`n" 304 | 305 | break 306 | } 307 | } 308 | 309 | if (needToDeleteEscape) 310 | { 311 | ret.=temp "`n" 312 | temp:="" 313 | } 314 | else 315 | ret.=paragraph1 "`n`n" 316 | } 317 | 318 | return, RTrim(ret, "`n") 319 | } 320 | 321 | /* 像下面左边这种会被分成 1 2 987 988 共4段,右边会被分成 1-3 987 988 共3段 322 | 1. aaaa 1. aaaa 323 | 2. bbbb 3. bbbb 324 | 987. dddd 987. dddd 325 | 988. eeee 988. eeee 326 | */ 327 | _splitBySerialNumber(oLines) 328 | { 329 | FSM:=[] 330 | for i, line in oLines 331 | { 332 | /* 像下面这种带序号的情况,匹配1 2,不匹配3.1 333 | 1.aa文字 334 | 2. aa文字 335 | 3.1 aa文字 336 | */ 337 | haveSerialNumber:=RegExMatch(line, "^\s*(\d+)\.[^0-9]", serialNumber) 338 | 339 | ; 以下 if 与 else if 的顺序是会影响结果的,没深刻理解就不要乱改 340 | if (i=1 and i=oLines.MaxIndex()) 341 | FSM[i]:="start&end" 342 | else if (i=1) 343 | FSM[i]:="start" 344 | ; 序号递增1 或 第1次找到序号。这里存在隐藏条件 i>=2 345 | else if (serialNumber1-prevSerialNumber=1) or (haveSerialNumber and prevSerialNumber="") 346 | { 347 | ; 此 if 必须放在 FSM[i] 前面 348 | if (prevI and FSM[prevI]="normal") 349 | { 350 | FSM[prevI]:="start" 351 | FSM[prevI-1]:=(FSM[prevI-1]="start") ? "start&end" : "end" 352 | } 353 | 354 | FSM[i]:="start" 355 | FSM[i-1]:=(FSM[i-1]="start") ? "start&end" : "end" 356 | } 357 | else if (i=oLines.MaxIndex()) 358 | FSM[i]:="end" 359 | else 360 | FSM[i]:="normal" 361 | 362 | if (haveSerialNumber) 363 | { 364 | prevSerialNumber:=serialNumber1 365 | prevI:=(i-1>=1) ? i : "" 366 | } 367 | } 368 | 369 | ; 根据序号标记生成文本 370 | for i, v in FSM 371 | { 372 | if (v="start&end" or v="end") 373 | Text.=oLines[i] "`n`n" 374 | else if (v="start" or v="normal") 375 | Text.=oLines[i] "`n" 376 | } 377 | Text:=RTrim(Text, "`n`n") 378 | 379 | return, StrSplit(Text, "`n`n") 380 | } 381 | 382 | _mergeByLen(Paragraph, Font, FontSize, ShortLineRatio, LongLineRatio) 383 | { 384 | ret:=[] 385 | 386 | oLines:=StrSplit(Paragraph, "`n") 387 | 388 | FSM:=[], sum:=0, n:=0, avg:=0 389 | for i, line in oLines 390 | { 391 | prevAvg:=avg 392 | len:=this._getLineLen(line, Font, FontSize) 393 | sum+=len, n++ 394 | avg:=sum/n 395 | 396 | if (i=1 and i=oLines.MaxIndex()) 397 | FSM[i]:="start&end" 398 | else if (i=1) 399 | FSM[i]:="start" 400 | else if (i=oLines.MaxIndex()) 401 | FSM[i]:="end" 402 | else if (len/avg<=ShortLineRatio) 403 | { 404 | FSM[i]:="end" 405 | ; 不要在这里更新 avg 会导致 prevAvg 被错误更新 406 | sum:=0, n:=0 407 | } 408 | else if (len/prevAvg>=LongLineRatio) 409 | { 410 | FSM[i]:="start" 411 | FSM[i-1]:=(FSM[i-1]="start") ? "start&end" : "end" 412 | ; 一定要在这里更新 avg ,这样才会正确更新 prevAvg 413 | sum:=len, n:=1, avg:=sum/n 414 | } 415 | else 416 | FSM[i]:="normal" 417 | } 418 | 419 | for i, v in FSM 420 | { 421 | if (v="start&end" or v="end") 422 | Text.=oLines[i] "`n`n" 423 | else if (v="start" or v="normal") 424 | Text.=oLines[i] "`n" 425 | } 426 | Text:=RTrim(Text, "`n`n") 427 | 428 | for k, paragraph2 in StrSplit(Text, "`n`n") 429 | { 430 | str:="" 431 | 432 | oLines2:=StrSplit(paragraph2, "`n") 433 | 434 | ; 以整体文本判断语言 435 | lang:=this._getLang(paragraph2) 436 | 437 | ; 正式合并 438 | for k, v in oLines2 439 | { 440 | if (lang="en") 441 | /* 对行末的连接符进行删除处理 442 | Medical abor- => Medical abortion in family 443 | tion in family 444 | */ 445 | if (RegExMatch(oLines2[A_Index], "[a-zA-Z]+\-$")) 446 | str.=SubStr(oLines2[A_Index], 1, -1) 447 | /* 普通行则添加空格 448 | i love => i love apple 449 | apple 450 | */ 451 | else 452 | str.=oLines2[A_Index] A_Space 453 | 454 | if lang in zh,ja,ko,unknow 455 | str.=oLines2[A_Index] 456 | } 457 | 458 | ; 删除英文合并过程中在末尾产生的多余空格 459 | if (lang="en" and SubStr(str, 0, 1)=A_Space) 460 | str:=SubStr(str, 1, -1) 461 | 462 | /* 像下面这种行首带注释的情况,会在这里产生空行,所以强制有内容才能写入 463 | ; aaaaaaa 464 | ; aaaaaa 465 | ; 466 | ; bbbbbbb 467 | */ 468 | if (Trim(str, " `t`r`n`v`f")!="") 469 | ret.Push(str) 470 | } 471 | 472 | return, ret 473 | } 474 | 475 | _getLineLen(Text, Font, FontSize) 476 | { 477 | return, btt(Text,,, 478 | , {Font:Font, FontSize:FontSize} 479 | , {JustCalculateSize:true}).w 480 | } 481 | 482 | _getLang(Text) 483 | { 484 | RegExReplace(Text, "[a-zA-Z]",, English_Characters_Len) ; 英文 485 | RegExReplace(Text, "[\x{4e00}-\x{9fa5}]",, Chinese_Characters_Len) ; 中文 486 | RegExReplace(Text, "[\x{0800}-\x{4e00}]",, Japanese_Characters_Len) ; 日文 487 | RegExReplace(Text, "[\x{ac00}-\x{d7ff}]",, Korean_Characters_Len) ; 韩文 488 | 489 | if (English_Characters_Len/StrLen(Text) > 0.6) 490 | return, "en" 491 | else if (Chinese_Characters_Len/StrLen(Text) > 0.6) 492 | return, "zh" 493 | else if (Japanese_Characters_Len/StrLen(Text) > 0.5) 494 | return, "ja" 495 | else if (Korean_Characters_Len/StrLen(Text) > 0.5) 496 | return, "ko" 497 | else 498 | return, "unknow" 499 | } 500 | } -------------------------------------------------------------------------------- /Lib/SogouTranslator.ahk: -------------------------------------------------------------------------------- 1 | ; https://fanyi.sogou.com/ 2 | ; version: 2024.10.03 3 | 4 | class SogouTranslator 5 | { 6 | init(chromePath:="Chrome\chrome.exe", profilePath:="sogou_translate_profile", debugPort:=9889, mode:="sync", timeout:=30) 7 | { 8 | ; 0开始初始化 1完成初始化 空值没有初始化 9 | this.ready := 0 10 | 11 | ; 加载多语言错误提示 12 | this._multiLanguage() 13 | 14 | ; 指定的 chrome.exe 不存在则尝试自动寻找 15 | if (!FileExist(chromePath)) 16 | chromePath := "" 17 | 18 | ; 默认将配置文件放到临时目录 19 | if (profilePath="sogou_translate_profile") 20 | profilePath := A_Temp "\sogou_translate_profile" 21 | 22 | ; 附着现存或打开新的 chrome 23 | if (Chrome.FindInstances().HasKey(debugPort)) 24 | ChromeInst := {"base": Chrome, "DebugPort": debugPort} 25 | else 26 | ; 搜狗 headless 下必须加 user-agent 才能正常返回数据 27 | ChromeInst := new Chrome(profilePath,, "--headless --user-agent=""Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/93.0.4577.82 Safari/537.36""", chromePath, debugPort) 28 | 29 | ; 退出时自动释放资源 30 | OnExit(ObjBindMethod(this, "_exit")) 31 | 32 | ; 初始化,也就是先加载一次页面 33 | this.page := ChromeInst.GetPage() 34 | this.page.Call("Page.navigate", {"url": "https://fanyi.sogou.com/text?keyword=init&transfrom=en&transto=zh-CHS&model=general"}, mode="async" ? false : true) 35 | 36 | ; 同步将产生阻塞直到返回结果,异步将快速返回以便用户自行处理结果 37 | this._receive(mode, timeout, "getInitResult") 38 | 39 | ; 完成初始化 40 | this.ready := 1 41 | } 42 | 43 | translate(str, from:="auto", to:="zh", mode:="sync", timeout:=30) 44 | { 45 | ; 没有初始化则初始化一遍 46 | if (this.ready="") 47 | this.init() 48 | 49 | ; 已经开始初始化则等待其完成 50 | while (this.ready=0) 51 | Sleep 500 52 | 53 | ; 待翻译的文字为空 54 | if (Trim(str, " `t`r`n`v`f")="") 55 | return {Error : this.multiLanguage.2} 56 | 57 | ; 待翻译的文字超过 sogou 支持的单次最大长度 58 | if (StrLen(str)>5000) 59 | return {Error : this.multiLanguage.3} 60 | 61 | ; 检测语种是否在支持范围内 62 | l := this._convertLanguageAbbr(from, to) 63 | if (l.Error) 64 | return {Error : this.multiLanguage.6} 65 | 66 | ; 构造 url 67 | url := Format("https://fanyi.sogou.com/text?keyword={1}&transfrom={2}&transto={3}&model=general", this.UriEncode(str), l.from, l.to) 68 | 69 | ; url 超过最大长度 70 | if (StrLen(url)>8182) 71 | return {Error : this.multiLanguage.4} 72 | 73 | ; 翻译 74 | this.page.Call("Page.navigate", {"url": url}, mode="async" ? false : true) 75 | return this._receive(mode, timeout, "getTransResult") 76 | } 77 | 78 | getInitResult() 79 | { 80 | return this.getTransResult() 81 | } 82 | 83 | getTransResult() 84 | { 85 | ; 获取翻译结果 86 | try 87 | str := this.page.Evaluate("document.querySelector('#trans-result').textContent;").value 88 | 89 | ; 去掉空白符后不为空则返回原文 90 | if (Trim(str, " `t`r`n`v`f")!="") 91 | return str 92 | } 93 | 94 | free() 95 | { 96 | try ret := this.page.Call("Browser.getVersion",,, 1) ; 确保 ws 连接正常 97 | 98 | if (ret) 99 | this.page.Call("Browser.close") ; 关闭浏览器(所有页面和标签) 100 | } 101 | 102 | _multiLanguage() 103 | { 104 | this.multiLanguage := [] 105 | l := this.multiLanguage 106 | if (A_Language="0804") 107 | { 108 | l.1 := "自己先去源码里把 chrome.exe 路径设置好!" 109 | l.2 := "待翻译文字为空!" 110 | l.3 := "待翻译文字超过最大长度(5000)!" 111 | l.4 := "URL 超过最大长度(8182)!" 112 | l.5 := "超时!" 113 | l.6 := "不支持此两种语言间的翻译!" 114 | } 115 | else 116 | { 117 | l.1 := "Please set the chrome.exe path first!" 118 | l.2 := "The text to be translated is empty!" 119 | l.3 := "The text to be translated is over the maximum length(5000)!" 120 | l.4 := "The URL is over the maximum length(8182)!" 121 | l.5 := "Timeout!" 122 | l.6 := "Translation between these two languages is not supported!" 123 | } 124 | } 125 | 126 | _convertLanguageAbbr(from, to) 127 | { 128 | /* 129 | 自动检测 130 | 阿拉伯语 葡萄牙语 131 | 波兰语 日语 瑞典语 132 | 丹麦语 德语 泰语 土耳其语 133 | 俄语 西班牙语 匈牙利语 134 | 法语 芬兰语 英语 意大利语 越南语 135 | 韩语 荷兰语 中文 136 | 捷克语 137 | */ 138 | dict := { auto:"auto" 139 | , ar:"ar", pt:"pt" 140 | , pl:"pl", ja:"ja", sv:"sv" 141 | , da:"da", de:"de", th:"th", tr:"tr" 142 | , ru:"ru", es:"es", hu:"hu" 143 | , fr:"fr", fi:"fi", en:"en", it:"it", vi:"vi" 144 | , ko:"ko", nl:"nl", zh:"zh-CHS" 145 | , cs:"cs" } 146 | 147 | if (!dict.HasKey(from) or !dict.HasKey(to)) 148 | return {Error : this.multiLanguage.6} 149 | else 150 | return {from:dict[from], to:dict[to]} 151 | } 152 | 153 | _clearTransResult() 154 | { 155 | this.page.Evaluate("document.querySelector('#trans-result').textContent='';") 156 | } 157 | 158 | _receive(mode, timeout, result) 159 | { 160 | ; 异步模式直接返回 161 | if (mode="async") 162 | return 163 | 164 | ; 同步模式将在这里阻塞直到取得结果或超时 165 | startTime := A_TickCount 166 | loop 167 | { 168 | ret := result="getInitResult" ? this.getInitResult() : this.getTransResult() 169 | if (ret!="") 170 | return ret 171 | else 172 | Sleep 500 173 | 174 | if ((A_TickCount-startTime)/1000 >= timeout) 175 | return {Error : this.multiLanguage.5} 176 | } 177 | } 178 | 179 | _exit() 180 | { 181 | if (this.page.connected) 182 | this.free() 183 | } 184 | 185 | #IncludeAgain %A_LineFile%\..\UriEncode.ahk 186 | } 187 | 188 | #Include %A_LineFile%\..\Chrome.ahk -------------------------------------------------------------------------------- /Lib/UriEncode.ahk: -------------------------------------------------------------------------------- 1 | UriEncode(Uri) 2 | { 3 | VarSetCapacity(Var, StrPut(Uri, "UTF-8"), 0) 4 | StrPut(Uri, &Var, "UTF-8") 5 | f := A_FormatInteger 6 | SetFormat, IntegerFast, H 7 | while Code := NumGet(Var, A_Index - 1, "UChar") 8 | if (Code >= 0x30 && Code <= 0x39 ; 0-9 9 | || Code >= 0x41 && Code <= 0x5A ; A-Z 10 | || Code >= 0x61 && Code <= 0x7A) ; a-z 11 | Res .= Chr(Code) 12 | else 13 | Res .= "%" . SubStr(Code + 0x100, -1) 14 | SetFormat, IntegerFast, %f% 15 | return, Res 16 | } -------------------------------------------------------------------------------- /Lib/YoudaoTranslator.ahk: -------------------------------------------------------------------------------- 1 | ; https://fanyi.youdao.com/ 2 | ; version: 2024.10.03 3 | 4 | class YoudaoTranslator 5 | { 6 | init(chromePath:="Chrome\chrome.exe", profilePath:="youdao_translate_profile", debugPort:=9891, mode:="sync", timeout:=30) 7 | { 8 | ; 0开始初始化 1完成初始化 空值没有初始化 9 | this.ready := 0 10 | 11 | ; 加载多语言错误提示 12 | this._multiLanguage() 13 | 14 | ; 指定的 chrome.exe 不存在则尝试自动寻找 15 | if (!FileExist(chromePath)) 16 | chromePath := "" 17 | 18 | ; 默认将配置文件放到临时目录 19 | if (profilePath="youdao_translate_profile") 20 | profilePath := A_Temp "\youdao_translate_profile" 21 | 22 | ; 附着现存或打开新的 chrome 23 | if (Chrome.FindInstances().HasKey(debugPort)) 24 | ChromeInst := {"base": Chrome, "DebugPort": debugPort} 25 | else 26 | ChromeInst := new Chrome(profilePath,, "--headless", chromePath, debugPort) 27 | 28 | ; 退出时自动释放资源 29 | OnExit(ObjBindMethod(this, "_exit")) 30 | 31 | ; 初始化,也就是先加载一次页面 32 | this.page := ChromeInst.GetPage() 33 | this.page.Call("Page.navigate", {"url": "https://fanyi.youdao.com/index.html#/TextTranslate"}, mode="async" ? false : true) 34 | 35 | ; 同步将产生阻塞直到返回结果,异步将快速返回以便用户自行处理结果 36 | this._receive(mode, timeout, "getInitResult") 37 | 38 | ; 完成初始化 39 | this.ready := 1 40 | } 41 | 42 | translate(str, from:="auto", to:="zh", mode:="sync", timeout:=30) 43 | { 44 | ; 没有初始化则初始化一遍 45 | if (this.ready="") 46 | this.init() 47 | 48 | ; 已经开始初始化则等待其完成 49 | while (this.ready=0) 50 | Sleep 500 51 | 52 | ; 待翻译的文字为空 53 | if (Trim(str, " `t`r`n`v`f")="") 54 | return {Error : this.multiLanguage.2} 55 | 56 | ; 将换行符统一为 `r`n 57 | ; 这样才能让换行数量在翻译前后保持一致 58 | str := StrReplace(str, "`r`n", "`n") 59 | str := StrReplace(str, "`r", "`n") 60 | str := StrReplace(str, "`n", "`r`n") 61 | 62 | ; 待翻译的文字超过 youdao 支持的单次最大长度 63 | if (StrLen(str)>5000) 64 | return {Error : this.multiLanguage.3} 65 | 66 | ; 清空原文 67 | this._clearTransResult() 68 | 69 | ; 选择语言 70 | if (this._convertLanguageAbbr(from, to).Error) 71 | return {Error : this.multiLanguage.6} 72 | 73 | ; 翻译 74 | this.page.Call("Input.insertText", {"text": str}, mode="async" ? false : true) 75 | return this._receive(mode, timeout, "getTransResult") 76 | } 77 | 78 | getInitResult() 79 | { 80 | ; 页面是否加载完成 81 | if (this.page.Evaluate("document.readyState;").value = "complete") 82 | return "OK" 83 | } 84 | 85 | getTransResult() 86 | { 87 | ; 获取翻译结果 88 | try 89 | str := this.page.Evaluate("document.querySelector('#js_fanyi_output_resultOutput').innerText;").value 90 | 91 | ; 去掉空白符后不为空则返回原文 92 | if (Trim(str, " `t`r`n`v`f")!="") 93 | { 94 | ; youdao 会返回多余的换行 95 | str := StrReplace(str, "`n`n`n", "`r") 96 | str := StrReplace(str, "`n`n", "`r") 97 | return StrReplace(str, "`r", "`n") 98 | } 99 | } 100 | 101 | free() 102 | { 103 | try ret := this.page.Call("Browser.getVersion",,, 1) ; 确保 ws 连接正常 104 | 105 | if (ret) 106 | this.page.Call("Browser.close") ; 关闭浏览器(所有页面和标签) 107 | } 108 | 109 | _multiLanguage() 110 | { 111 | this.multiLanguage := [] 112 | l := this.multiLanguage 113 | if (A_Language="0804") 114 | { 115 | l.1 := "自己先去源码里把 chrome.exe 路径设置好!" 116 | l.2 := "待翻译文字为空!" 117 | l.3 := "待翻译文字超过最大长度(5000)!" 118 | l.4 := "URL 超过最大长度(8182)!" 119 | l.5 := "超时!" 120 | l.6 := "不支持此两种语言间的翻译!" 121 | } 122 | else 123 | { 124 | l.1 := "Please set the chrome.exe path first!" 125 | l.2 := "The text to be translated is empty!" 126 | l.3 := "The text to be translated is over the maximum length(5000)!" 127 | l.4 := "The URL is over the maximum length(8182)!" 128 | l.5 := "Timeout!" 129 | l.6 := "Translation between these two languages is not supported!" 130 | } 131 | } 132 | 133 | _convertLanguageAbbr(from, to) 134 | { 135 | languageSelected := from "-" to 136 | 137 | ; 语言发生变化,需要重新选择 138 | if (languageSelected!=this.languageSelected) 139 | { 140 | this.languageSelected := languageSelected 141 | 142 | if (from="auto" or to="auto" or from=to) 143 | { 144 | this.page.Evaluate("document.querySelector('div.lang-container.lanFrom-container').click();") 145 | this.page.Evaluate("document.querySelector('div.common-language-container > div > div:nth-child(1)').click();") 146 | } 147 | else 148 | { 149 | /* 150 | 阿拉伯语 德语 俄语 151 | 法语 韩语 荷兰语 152 | 葡萄牙语 日语 泰语 153 | 西班牙语 英语 意大利语 154 | 越南语 印度尼西亚语 中文 155 | */ 156 | dict := { ar:1, de:2, ru:3 157 | , fr:4, ko:5, nl:6 158 | , pt:7, ja:8, th:9 159 | , es:10, en:11, it:12 160 | , vi:13, id:14, zh:15 } 161 | 162 | if (!dict.HasKey(from) or !dict.HasKey(to)) 163 | return {Error : this.multiLanguage.6} 164 | else 165 | { 166 | this.page.Evaluate("document.querySelector('div.lang-container.lanFrom-container').click();") 167 | this.page.Evaluate(Format("document.querySelector('div.specify-language-container > div > div > div > div:nth-child({})').click();", dict[from])) 168 | 169 | this.page.Evaluate("document.querySelector('div.lang-container.lanTo-container').click();") 170 | this.page.Evaluate(Format("document.querySelector('div.specify-language-container > div > div > div > div:nth-child({})').click();", dict[to])) 171 | } 172 | } 173 | } 174 | } 175 | 176 | _clearTransResult() 177 | { 178 | try this.page.Evaluate("document.querySelector('#TextTranslate > div.source > div.text-translate-top-right > a').click();") 179 | } 180 | 181 | _receive(mode, timeout, result) 182 | { 183 | ; 异步模式直接返回 184 | if (mode="async") 185 | return 186 | 187 | ; 同步模式将在这里阻塞直到取得结果或超时 188 | startTime := A_TickCount 189 | loop 190 | { 191 | ret := result="getInitResult" ? this.getInitResult() : this.getTransResult() 192 | if (ret!="") 193 | return ret 194 | else 195 | Sleep 500 196 | 197 | if ((A_TickCount-startTime)/1000 >= timeout) 198 | return {Error : this.multiLanguage.5} 199 | } 200 | } 201 | 202 | _exit() 203 | { 204 | if (this.page.connected) 205 | this.free() 206 | } 207 | } 208 | 209 | #Include %A_LineFile%\..\Chrome.ahk -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Translation-Terminator 2 | 3 | An easy-to-use library containing multiple translation interfaces. 4 | Currently supports [DeepL](https://www.deepl.com/translator) [Sogou](https://fanyi.sogou.com/) [Baidu](https://fanyi.baidu.com/) [Youdao](https://fanyi.youdao.com/) 5 | 6 | 一个包含多种翻译接口,简单易用的库。 7 | 目前支持 [DeepL](https://www.deepl.com/translator) [Sogou](https://fanyi.sogou.com/) [Baidu](https://fanyi.baidu.com/) [Youdao](https://fanyi.youdao.com/) 8 | 9 | ### [English Demo](https://www.autohotkey.com/boards/viewtopic.php?f=6&t=94823) 或 [中文演示](https://www.autoahk.com/archives/37113) 10 | 11 | ![效果图](https://raw.githubusercontent.com/telppa/Translation-Terminator/main/img/Dictionary.ahk.png) 12 | 13 | ![效果图](https://raw.githubusercontent.com/telppa/Translation-Terminator/main/img/Aggregate%20Translator.ahk.png) 14 | 15 | ![效果图](https://raw.githubusercontent.com/telppa/Translation-Terminator/main/img/Aggregate%20Translator.ahk.gif) 16 | -------------------------------------------------------------------------------- /icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telppa/Translation-Terminator/e18fdc76856cb7a5ee48be251bc3e12fb5f4b684/icon.png -------------------------------------------------------------------------------- /img/Aggregate Translator.ahk.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telppa/Translation-Terminator/e18fdc76856cb7a5ee48be251bc3e12fb5f4b684/img/Aggregate Translator.ahk.gif -------------------------------------------------------------------------------- /img/Aggregate Translator.ahk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telppa/Translation-Terminator/e18fdc76856cb7a5ee48be251bc3e12fb5f4b684/img/Aggregate Translator.ahk.png -------------------------------------------------------------------------------- /img/Dictionary.ahk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telppa/Translation-Terminator/e18fdc76856cb7a5ee48be251bc3e12fb5f4b684/img/Dictionary.ahk.png -------------------------------------------------------------------------------- /img/Translator.ahk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/telppa/Translation-Terminator/e18fdc76856cb7a5ee48be251bc3e12fb5f4b684/img/Translator.ahk.png -------------------------------------------------------------------------------- /有道词典/Lib/CrackUrl.ahk: -------------------------------------------------------------------------------- 1 | ; 取自 “http://ahkscript.org/boards/viewtopic.php?f=6&t=2512&hilit=parse+url” 2 | ; 修复原版不能在64位运行的问题。 3 | ; 返回值为空说明字符串不是 url ,否则返回包含具体信息的对象。 4 | CrackUrl(url) 5 | { 6 | ; 生成结构体 7 | VarSetCapacity(URL_COMPONENTS, A_PtrSize == 8 ? 104 : 60, 0) 8 | 9 | ; 必须先设置以下项的值才能完成结构体的初始化 10 | ; https://docs.microsoft.com/zh-tw/previous-versions/aa919268(v=msdn.10) 11 | NumPut(A_PtrSize == 8 ? 104 : 60, URL_COMPONENTS, 0, "UInt") ; dwStructSize 12 | NumPut(1, URL_COMPONENTS, A_PtrSize == 8 ? 16 : 8, "UInt") ; dwSchemeLength 13 | NumPut(1, URL_COMPONENTS, A_PtrSize == 8 ? 32 : 20, "UInt") ; dwHostNameLength 14 | NumPut(1, URL_COMPONENTS, A_PtrSize == 8 ? 48 : 32, "UInt") ; dwUserNameLength 15 | NumPut(1, URL_COMPONENTS, A_PtrSize == 8 ? 64 : 40, "UInt") ; dwPasswordLength 16 | NumPut(1, URL_COMPONENTS, A_PtrSize == 8 ? 80 : 48, "UInt") ; dwUrlPathLength 17 | NumPut(1, URL_COMPONENTS, A_PtrSize == 8 ? 96 : 56, "UInt") ; dwExtraInfoLength 18 | 19 | ; 通过返回值是否为1可以判断字符串是否是 url 20 | if (DllCall("Winhttp.dll\WinHttpCrackUrl", "Ptr",&url, "UInt",StrLen(url), "UInt",0, "Ptr",&URL_COMPONENTS)=1) 21 | { 22 | lpszScheme := NumGet(URL_COMPONENTS, A_PtrSize == 8 ? 8 : 4, "Ptr") 23 | dwSchemeLength := NumGet(URL_COMPONENTS, A_PtrSize == 8 ? 16 : 8, "UInt") 24 | lpszHostName := NumGet(URL_COMPONENTS, A_PtrSize == 8 ? 24 : 16, "Ptr") 25 | dwHostNameLength := NumGet(URL_COMPONENTS, A_PtrSize == 8 ? 32 : 20, "UInt") 26 | nPort := NumGet(URL_COMPONENTS, A_PtrSize == 8 ? 36 : 24, "Int") 27 | lpszUserName := NumGet(URL_COMPONENTS, A_PtrSize == 8 ? 40 : 28, "Ptr") 28 | dwUserNameLength := NumGet(URL_COMPONENTS, A_PtrSize == 8 ? 48 : 32, "UInt") 29 | lpszPassword := NumGet(URL_COMPONENTS, A_PtrSize == 8 ? 56 : 36, "Ptr") 30 | dwPasswordLength := NumGet(URL_COMPONENTS, A_PtrSize == 8 ? 64 : 40, "UInt") 31 | lpszUrlPath := NumGet(URL_COMPONENTS, A_PtrSize == 8 ? 72 : 44, "Ptr") 32 | dwUrlPathLength := NumGet(URL_COMPONENTS, A_PtrSize == 8 ? 80 : 48, "UInt") 33 | lpszExtraInfo := NumGet(URL_COMPONENTS, A_PtrSize == 8 ? 88 : 52, "Ptr") 34 | dwExtraInfoLength := NumGet(URL_COMPONENTS, A_PtrSize == 8 ? 96 : 56, "UInt") 35 | 36 | ret := {} 37 | ret.Scheme := StrGet(lpszScheme, dwSchemeLength) 38 | ret.HostName := StrGet(lpszHostName, dwHostNameLength) 39 | ret.Port := nPort 40 | ret.UserName := StrGet(lpszUserName, dwUserNameLength) 41 | ret.Password := StrGet(lpszPassword, dwPasswordLength) 42 | ret.UrlPath := StrGet(lpszUrlPath, dwUrlPathLength) 43 | ret.ExtraInfo := StrGet(lpszExtraInfo, dwExtraInfoLength) 44 | return, ret 45 | } 46 | } -------------------------------------------------------------------------------- /有道词典/Lib/GetTag.ahk: -------------------------------------------------------------------------------- 1 | /* 2 | 匹配到的字符串,包含标签本身。 3 | 例如 data="123abc456" tag="" 匹配到的结果是 “abc” 。 4 | 5 | 最后一个参数的意思是返回第n个符合的字串。 6 | 例如 data="123" tag="" occurrence=2 匹配到的结果是 “2” 。 7 | 8 | 匹配到的标签总是平衡的。 9 | 例如 data="12" tag="" 匹配到的结果是 “12” 。 10 | 11 | 修复了 GetNestedTag 的多个bug,并且大幅优化嵌套查找的性能。 12 | */ 13 | GetTag(data, tag, occurrence := 1, maximumDepth := 100) 14 | { 15 | tag := Trim(tag) ; 移除前后的空格和tab,使得匹配 “ 这样带数字的标签。 24 | , openstyle1 := "<" basetag1 ">" ; 匹配类似 的情况。 25 | , openstyle2 := "<" basetag1 " " ; 匹配类似 的情况。 26 | , closestyle := "" ; 匹配类似 的情况。 27 | loop 28 | { 29 | ; 假设当前找到的内容中,已经存在10个开标签,则至少需要10个闭标签,因此直接使用开标签数量而不是 A_Index 来进行查找优化。 30 | endpos := InStr(data, closestyle, false, startpos, NonNull_Ret(opencount1+opencount2, 1)) + StrLen(closestyle) 31 | string := SubStr(data, startpos, endpos - startpos) 32 | 33 | StrReplace(string, openstyle1, openstyle1, opencount1) 34 | StrReplace(string, openstyle2, openstyle2, opencount2) 35 | StrReplace(string, closestyle, closestyle, closecount) 36 | if (opencount1+opencount2 = closecount) ; 确保匹配到的标签是平衡的。 37 | break 38 | 39 | if (A_Index>maximumDepth or endpos=0) ; 避免陷入死循环以及 data 中标签不平衡时快速返回。 40 | return 41 | } 42 | return string 43 | } 44 | 45 | #IncludeAgain %A_LineFile%\..\NonNull.ahk -------------------------------------------------------------------------------- /有道词典/Lib/NonNull.ahk: -------------------------------------------------------------------------------- 1 | ; 变量为空,则使用默认值。变量不为空,则使用变量值。 2 | ; 同时可以检查变量是否超出最大最小范围。 3 | ; 注意,默认值不受最大最小范围的限制。 4 | ; 也就是说 5 | ; 当变量值为"",默认值为8,范围为2-5,此时变量值会是8。 6 | ; 当变量值为10,默认值为8,范围为2-5,此时变量值会是5。 7 | NonNull(ByRef var, DefaultValue, MinValue:="", MaxValue:="") ; 237ms 8 | { 9 | var:= var="" ? DefaultValue : MinValue="" ? (MaxValue="" ? var : Min(var, MaxValue)) : (MaxValue!="" ? Max(Min(var, MaxValue), MinValue) : Max(var, MinValue)) 10 | } 11 | 12 | ; 与 NonNull 一致,区别是通过 return 返回值,而不是 ByRef。 13 | NonNull_Ret(var, DefaultValue, MinValue:="", MaxValue:="") ; 237ms 14 | { 15 | return, var="" ? DefaultValue : MinValue="" ? (MaxValue="" ? var : Min(var, MaxValue)) : (MaxValue!="" ? Max(Min(var, MaxValue), MinValue) : Max(var, MinValue)) 16 | /* 17 | ; 下面的 if 版本与上面的三元版本是等价的 18 | ; 只是16w次循环的速度是 270ms,慢了13% 19 | if (var="") 20 | return, DefaultValue ; 变量为空,则返回默认值 21 | else 22 | { 23 | if (MinValue="") 24 | { 25 | if (MaxValue="") 26 | return, var ; 变量有值,且不检查最大最小范围,则直接返回变量值 27 | else 28 | return, Min(var, MaxValue) ; 变量有值,且只检查最大值,则返回不大于最大值的变量值 29 | } 30 | else 31 | { 32 | if (MaxValue!="") 33 | ; 三元的写法不会更快 34 | return, Max(Min(var, MaxValue), MinValue) ; 变量有值,且检查最大最小范围,则返回最大最小范围内的变量值 35 | else 36 | return, Max(var, MinValue) ; 变量有值,且只检查最小值,则返回不小于最小值的变量值 37 | } 38 | } 39 | */ 40 | } 41 | 42 | /* 单元测试 43 | 计时() 44 | loop,1000 45 | { 46 | gosub, UnitTest1 47 | gosub, UnitTest2 48 | } 49 | 计时() 50 | 51 | ; ByRef 版本的测试 52 | UnitTest1: 53 | v:="" 54 | NonNull(v, 8, 2, 10) 55 | if v!=8 56 | MsgBox, wrong 57 | v:="" 58 | NonNull(v, 8, "", "") 59 | if v!=8 60 | MsgBox, wrong 61 | v:="" 62 | NonNull(v, 8, 2, "") 63 | if v!=8 64 | MsgBox, wrong 65 | v:="" 66 | NonNull(v, 8, "", 10) 67 | if v!=8 68 | MsgBox, wrong 69 | 70 | v:=5 71 | NonNull(v, 8, 2, 10) 72 | if v!=5 73 | MsgBox, wrong 74 | v:=5 75 | NonNull(v, 8, "", "") 76 | if v!=5 77 | MsgBox, wrong 78 | v:=5 79 | NonNull(v, 8, 2, "") 80 | if v!=5 81 | MsgBox, wrong 82 | v:=5 83 | NonNull(v, 8, "", 10) 84 | if v!=5 85 | MsgBox, wrong 86 | 87 | v:=15 88 | NonNull(v, 8, 2, 10) 89 | if v!=10 90 | MsgBox, wrong 91 | v:=15 92 | NonNull(v, 8, "", "") 93 | if v!=15 94 | MsgBox, wrong 95 | v:=15 96 | NonNull(v, 8, 2, "") 97 | if v!=15 98 | MsgBox, wrong 99 | v:=15 100 | NonNull(v, 8, "", 10) 101 | if v!=10 102 | MsgBox, wrong 103 | 104 | v:=1 105 | NonNull(v, 8, 2, 10) 106 | if v!=2 107 | MsgBox, wrong 108 | v:=1 109 | NonNull(v, 8, "", "") 110 | if v!=1 111 | MsgBox, wrong 112 | v:=1 113 | NonNull(v, 8, 2, "") 114 | if v!=2 115 | MsgBox, wrong 116 | v:=1 117 | NonNull(v, 8, "", 10) 118 | if v!=1 119 | MsgBox, wrong 120 | return 121 | 122 | ; return 版本的测试 123 | UnitTest2: 124 | v:="" 125 | if NonNull_Ret(v, 8, 2, 10)!=8 126 | MsgBox, wrong 127 | v:="" 128 | if NonNull_Ret(v, 8, "", "")!=8 129 | MsgBox, wrong 130 | v:="" 131 | if NonNull_Ret(v, 8, 2, "")!=8 132 | MsgBox, wrong 133 | v:="" 134 | if NonNull_Ret(v, 8, "", 10)!=8 135 | MsgBox, wrong 136 | 137 | v:=5 138 | if NonNull_Ret(v, 8, 2, 10)!=5 139 | MsgBox, wrong 140 | v:=5 141 | if NonNull_Ret(v, 8, "", "")!=5 142 | MsgBox, wrong 143 | v:=5 144 | if NonNull_Ret(v, 8, 2, "")!=5 145 | MsgBox, wrong 146 | v:=5 147 | if NonNull_Ret(v, 8, "", 10)!=5 148 | MsgBox, wrong 149 | 150 | v:=15 151 | if NonNull_Ret(v, 8, 2, 10)!=10 152 | MsgBox, wrong 153 | v:=15 154 | if NonNull_Ret(v, 8, "", "")!=15 155 | MsgBox, wrong 156 | v:=15 157 | if NonNull_Ret(v, 8, 2, "")!=15 158 | MsgBox, wrong 159 | v:=15 160 | if NonNull_Ret(v, 8, "", 10)!=10 161 | MsgBox, wrong 162 | 163 | v:=1 164 | if NonNull_Ret(v, 8, 2, 10)!=2 165 | MsgBox, wrong 166 | v:=1 167 | if NonNull_Ret(v, 8, "", "")!=1 168 | MsgBox, wrong 169 | v:=1 170 | if NonNull_Ret(v, 8, 2, "")!=2 171 | MsgBox, wrong 172 | v:=1 173 | if NonNull_Ret(v, 8, "", 10)!=1 174 | MsgBox, wrong 175 | return 176 | */ -------------------------------------------------------------------------------- /有道词典/Lib/RegEx.ahk: -------------------------------------------------------------------------------- 1 | Class RegEx 2 | { 3 | ; 返回值是数组对象,其每个值都是使用 O 选项返回的匹配对象。 4 | ; 可用 返回值.MaxIndex()="" 判断无匹配。 5 | ; 可用 返回值.1.Pos[0] 或 返回值[2].Len[1] 等方式获取每个匹配的各种信息(帮助搜索 “匹配对象” )。 6 | GlobalMatch(Haystack, NeedleRegEx, StartingPos:=1) 7 | { 8 | ; 为正则添加 O 选项。 9 | NeedleRegEx:=this.AddOptions(NeedleRegEx, "O") 10 | Out:=[], Len:=StrLen(Haystack) 11 | ; RegExMatch() 返回值为0代表没有匹配,为空代表错误(例如正则表达式语法错误)。 12 | ; 表达式 “m)” 字符串 “” 能形成零宽匹配,因此需要验证 StartingPos<=Len 避免死循环。 13 | ; 注意第三个参数,是 ByRef 形式,所以无需引号。 14 | while (StartingPos<=Len and RegExMatch(Haystack, NeedleRegEx, OutputVar, StartingPos)) 15 | { 16 | ; 匹配成功则设置下次匹配起点为上次成功匹配字符串的末尾。 17 | ; 这样可以使表达式 “ABCABC” ,匹配字符串 “ABCABCABCABC” 时返回2次结果。 18 | ; 表达式 “(?=10)” 字符串 “100.10” 能形成零宽匹配,返回的位置是1,宽度是0。 19 | ; 因此需要 Max(OutputVar.Len[0], 1) 将宽度最小值设为1,才能避免死循环。 20 | StartingPos:=OutputVar.Pos[0]+Max(OutputVar.Len[0], 1) 21 | Out.Push(OutputVar) 22 | } 23 | return, Out 24 | } 25 | 26 | ; 此函数作用等同 RegExMatch() ,主要意义是统一返回值格式便于处理。 27 | Match(Haystack, NeedleRegEx, StartingPos:=1) 28 | { 29 | ; 为正则添加 O 选项。 30 | NeedleRegEx:=this.AddOptions(NeedleRegEx, "O") 31 | Out:=[] 32 | if (RegExMatch(Haystack, NeedleRegEx, OutputVar, StartingPos)) 33 | Out.Push(OutputVar) 34 | return, Out 35 | } 36 | 37 | ; 此函数用于给正则表达式添加选项。 38 | ; 添加的选项严格区分大小写!!!例如支持 (*ANYCRLF) 不支持 (*AnyCRLF) 。 39 | ; 选项将被确保存在且仅存在一个,不会出现 OimO)abc.* 这种情况。 40 | AddOptions(NeedleRegEx, Options*) 41 | { 42 | ; 因为存在 \Qim)\E 这样的免转义规则(表示原义的匹配字符 “im)” )。 43 | ; 所以必须使用第一个右括号左边的参数去判断此右括号是否为选项分隔符。 44 | 选项分隔符位置:=InStr(NeedleRegEx, ")") ; 获取第一个右括号的位置。 45 | if (选项分隔符位置) 46 | { 47 | 正则选项:=SubStr(NeedleRegEx, 1, 选项分隔符位置) 48 | 正则本体:=SubStr(NeedleRegEx, 选项分隔符位置+1) 49 | 50 | Prev_StringCaseSense:=A_StringCaseSense 51 | StringCaseSense, On ; 大小写敏感。 52 | temp:=正则选项 53 | StringReplace, temp, temp, i, , All ; 以下是正则表达式选项中可能存在的字符。 54 | StringReplace, temp, temp, m, , All 55 | StringReplace, temp, temp, s, , All 56 | StringReplace, temp, temp, x, , All 57 | StringReplace, temp, temp, A, , All 58 | StringReplace, temp, temp, D, , All 59 | StringReplace, temp, temp, J, , All 60 | StringReplace, temp, temp, U, , All 61 | StringReplace, temp, temp, X, , All 62 | StringReplace, temp, temp, P, , All 63 | StringReplace, temp, temp, O, , All 64 | StringReplace, temp, temp, S, , All 65 | StringReplace, temp, temp, C, , All 66 | StringReplace, temp, temp, `n, , All 67 | StringReplace, temp, temp, `r, , All 68 | StringReplace, temp, temp, `a, , All 69 | StringReplace, temp, temp, %A_Space%, , All 70 | StringReplace, temp, temp, %A_Tab%, , All 71 | StringCaseSense, %Prev_StringCaseSense% 72 | } 73 | 74 | if (!选项分隔符位置 or temp!=")") ; temp 若包含除 ")" 之外的字符,则说明它不是选项分隔符。 75 | { 76 | 正则选项:=")" ; 没有选项分隔符,则说明没有正则选项,所以创建一个空选项。 77 | 正则本体:=NeedleRegEx 78 | } 79 | 80 | ; 将特殊选项 (*UCP)(*ANYCRLF)(*BSR_ANYCRLF) 去重。 81 | RegExMatch(正则本体, "^(\Q(*UCP)\E|\Q(*ANYCRLF)\E|\Q(*BSR_ANYCRLF)\E)+", 正则特殊选项) 82 | if (正则特殊选项) 83 | { 84 | if (InStr(正则特殊选项, "(*UCP)"), true) ; 标记存在哪个特殊选项。true 表示区分大小写。 85 | flag1:=1 86 | if (InStr(正则特殊选项, "(*ANYCRLF)"), true) 87 | flag2:=1 88 | if (InStr(正则特殊选项, "(*BSR_ANYCRLF)"), true) 89 | flag3:=1 90 | 91 | ; 删除特殊选项,便于之后单独添加。 92 | 正则本体:=RegExReplace(正则本体, "^(\Q(*UCP)\E|\Q(*ANYCRLF)\E|\Q(*BSR_ANYCRLF)\E)+", "", "", 1) 93 | } 94 | 95 | Prev_StringCaseSense:=A_StringCaseSense 96 | StringCaseSense, On 97 | for k, Option in Options 98 | switch, Option 99 | { 100 | case "(*UCP)": 101 | flag1:=1 102 | 103 | case "(*ANYCRLF)": 104 | flag2:=1 105 | 106 | case "(*BSR_ANYCRLF)": 107 | flag3:=1 108 | 109 | case "i","m","s","x","A","D","J","U","X","P","O","S","C","``n","``r","``a": 110 | if (!InStr(正则选项, Option, true)) ; 检查目前选项中是否存在待添加选项,确保其唯一。true 表示区分大小写。 111 | 正则选项:=Option 正则选项 112 | } 113 | StringCaseSense, %Prev_StringCaseSense% 114 | 115 | ; 根据标记单独进行特殊选项添加,确保特殊选项唯一。 116 | if (flag3) 117 | 正则本体:="(*BSR_ANYCRLF)" 正则本体 118 | if (flag2) 119 | 正则本体:="(*ANYCRLF)" 正则本体 120 | if (flag1) 121 | 正则本体:="(*UCP)" 正则本体 122 | 123 | return, 正则选项 正则本体 124 | } 125 | } -------------------------------------------------------------------------------- /有道词典/Lib/WinHttp.ahk: -------------------------------------------------------------------------------- 1 | /* 2 | 更新日志: 3 | 2021.11.15 4 | 不再抛出错误,避免打断下载流程。 5 | 错误信息全部记录在 WinHttp.Error 中,故删除 WinHttp.Extra 。 6 | NumberOfRetries 默认值为1。 7 | 版本号3.9 8 | 9 | 2021.11.12 10 | 修复 Timeout 参数无效的问题。 11 | 增加 ConnectTimeout 与 DownloadTimeout 参数。 12 | ConnectTimeout 默认值为30秒。 13 | DownloadTimeout 默认值为无限。 14 | 版本号3.8 15 | 16 | 2021.10.22 17 | 改变 Include 方式,降低库冲突的可能性。 18 | 版本号3.7 19 | 20 | 2021.08.18 21 | 修复响应头的 Set-Cookie 总是被改变为 Cookie 的问题。 22 | 版本号3.6 23 | 24 | 2021.08.16 25 | 修复请求头行首包含空白符会被错误解析的问题。 26 | 增加 Cookie 的快速获取。 27 | 版本号3.5 28 | 29 | 2021.04.11 30 | 集成 CreateFormData 函数。 31 | 集成 BinArr 系列函数。 32 | 版本号为3.2 33 | 34 | 2021.04.05 35 | 修复部分 URL ( http://snap.ie.sogou.com ),请求头重复添加 Connection: Keep-Alive 的问题。 36 | 添加默认 User-Agent 。 37 | gzip 解压目前只找到了32位的 dll ,所以还是用老方法解决 gzip 压缩数据的问题。 38 | 版本号为3.1 39 | 40 | 2021.04.04 41 | 代码重构。 42 | 版本号为3.0 43 | 44 | 2020.08.18 45 | 请求头可直接使用响应头中的“Set-Cookie”值,会自动处理为“Cookie”。 46 | 小幅修改说明。 47 | 版本号为2.0 48 | 49 | 2020.08.16 50 | 如果 “Accept-Encoding” 中含有关键词“gzip”,则选项将被自动删除,以避免服务器发送gzip压缩数据回来,无法解压而导致乱码的问题。 51 | “Msxml2.XMLHTTP” 可以自动解压gzip数据。https://www.autohotkey.com/boards/viewtopic.php?f=76&t=51629&hilit=gzip 52 | “Msxml2.XMLHTTP” “Msxml2.ServerXMLHTTP” “WinHttp.WinHttpRequest.5.1” 功能都差不多,2是对3的进一步封装,1和2封装的dll不同,因此只有1能自动解压gzip数据。 53 | 版本号为1.6 54 | 55 | 2016.01.23 56 | 修改超时设置,可以指定任意时长的超时。 57 | 版本号为1.5 58 | 59 | 2015.09.12 60 | 优化代码结构。 61 | 版本号为1.4 62 | 63 | 2015.09.11 64 | 修复超时会在错误时间被激活的问题。 65 | 以下是tmplinshi对这个问题的详细描述。(https://www.autohotkey.com/boards/viewtopic.php?t=9137) 66 | 版本号为1.3 67 | 68 | 2015.06.05 69 | 添加静态变量Status、StatusText,用法和ResponseHeaders一致。 70 | 添加新功能,若指定状态码与重试次数,将重试n次,直至状态码与预期一致。 71 | 版本号为1.2 72 | 73 | 已知问题: 74 | 超时后,进行重试时, WaitForResponse 明显返回速度过快。 75 | 访问连接很不稳定的网站(例如 github.com ),超时设置为大于21秒时,大概率会在21秒时超时返回,原因未知。 76 | cookie 没有实现像浏览器那样根据属性值自动管理。但是你可以在需要的时候随时取出,自行管理。 77 | 请求头 Content-Type: 末尾会自动追加一个 Charset=UTF-8 (需使用 Fiddler 抓包查看)。 78 | */ 79 | 80 | /* 81 | ; 使用工具 “AHK 爬虫终结者” 可以用 GUI 的形式使用本库,并自动生成代码。 82 | 83 | ; 简单示例 84 | MsgBox, % WinHttp.Download("https://www.example.com/") ; 网页内容 85 | MsgBox, % WinHttp.ResponseHeaders["Content-type"] ; 响应头 Content-type 段 86 | MsgBox, % WinHttp.StatusCode ; 状态码 87 | MsgBox, % WinHttp.Cookie ; Cookie 88 | 89 | ; CreateFormData 示例 90 | objParam := {"file": ["截图.png"]} 91 | WinHttp.CreateFormData(out_postData, out_ContentType, objParam,,,"image/jpg") 92 | RequestHeaders := "Content-Type: " out_ContentType 93 | MsgBox, % WinHttp.Download("http://snap.ie.sogou.com/recognition",, RequestHeaders, out_postData) 94 | */ 95 | class WinHttp 96 | { 97 | static ResponseHeaders:={}, StatusCode:="", StatusText:="", Cookie:="", Error:={} 98 | 99 | /* 100 | ; *****************参数***************** 101 | ; URL 网址,必须包含类似 http:// 这样的开头。 102 | ; www. 最好也带上,有些网站需要。 103 | 104 | ; Options 设置,是 Method:GET 105 | ; Timeout:30 106 | ; EnableRedirects:1 这样使用换行分隔的字符。 107 | ; 具体支持的值看下方 ***************Options*************** 。 108 | 109 | ; RequestHeaders 请求头,格式同 Options 。 110 | ; 具体支持的值看下方 ************RequestHeaders************ 。 111 | 112 | ; Data 提交数据,支持 Request Payload 与 Form Data 两种格式。 113 | ; 默认格式是文本,即 Request Payload 。 114 | ; Form Data 格式需要使用 WinHttp.CreateFormData() 进行构造。 115 | ; 此参数无值时,默认使用 GET 请求。有值时,默认使用 POST 请求。 116 | 117 | ; FilePath 此参数非空,则下载到此路径,否则下载到变量。 118 | 119 | ; ***************Options*************** 120 | ; 支持以下11种设置,输入其它值无任何效果,不区分大小写。 121 | 122 | ; Method 请求方法,支持 GET, HEAD, POST, PUT, PATCH, DELETE, CONNECT, OPTIONS, TRACE 共9种。 123 | ; 此参数可以小写,但在程序内部,依然会被转换为全大写。 124 | ; 留空表示自动选择 GET 或 POST 。 125 | 126 | ; EnableRedirects 重定向,1为获取跳转后的页面信息,0为不跳转。 127 | ; 留空表示1。 128 | 129 | ; Charset 网页字符集,也就是网页的编码。 130 | ; 是 UTF-8、gb2312 这样的字符。 131 | ; 留空表示自动选择。 132 | 133 | ; URLCodePage URL 代码页,也就是网址的编码。 134 | ; 是 65001、936 这样的数字。 135 | ; 留空表示65001。 136 | 137 | ; Timeout 超时,单位为秒,0为无限。 138 | ; 当设置此参数,会同时覆盖 “连接超时” 与 “下载超时” 两项参数。 139 | ; 留空表示不使用此参数。 140 | 141 | ; ConnectTimeout 连接超时,单位为秒,0为无限。 142 | ; 当设置此参数,会在设置时间内尝试连接。 143 | ; 连接失败,超时返回。连接成功,则继续尝试下载。 144 | ; 留空表示30。 145 | 146 | ; DownloadTimeout 下载超时,单位为秒,0为无限。 147 | ; 此参数与 “连接超时” 共享设置的时间。 148 | ; 例如此参数设为30,尝试连接时花费10秒,则 “下载超时” 将只剩20秒。 149 | ; 留空表示0。 150 | 151 | ; ExpectedStatusCode 期望的状态码,重复访问直到服务器返回的状态码与此参数相同时才停止。 152 | ; 通常服务器返回的状态码为200表示网页正常,404表示网页找不到了。 153 | ; 参数 “重试次数” 可设置重复访问的最大次数。 154 | ; 留空表示不使用此参数。 155 | 156 | ; NumberOfRetries 重试次数,重复访问的最大次数。 157 | ; 与 “期望的状态码” 配对使用。 158 | ; 留空表示1。 159 | 160 | ; Proxy 代理服务器,是 http://www.tuzi.com:80 这样的字符。 161 | ; 有些抓包程序,例如 Fiddler 需要在这里填入 127.0.0.1:8888 才能抓到数据。 162 | ; 留空表示不使用此参数。 163 | 164 | ; ProxyBypassList 代理服务器白名单,是 *.microsoft.com 这样的域名。 165 | ; 符合域名的网址,将不通过代理服务器访问。 166 | ; 留空表示不使用此参数。 167 | 168 | ; ************RequestHeaders************ 169 | ; 支持所有 RequestHeader ,可能区分大小写。常见的有以下这些。 170 | 171 | ; Cookie 常用于登录验证。 172 | 173 | ; Referer 引用网址,常用于防盗链。 174 | 175 | ; User-Agent 浏览器标识,常用于防盗链。 176 | 177 | */ 178 | Download(URL, Options:="", RequestHeaders:="", Data:="", FilePath:="") 179 | { 180 | oOptions := this.解析信息为对象(Options) 181 | oRequestHeaders := this.解析信息为对象(RequestHeaders) 182 | oRequestHeaders := this.解析SetCookie为Cookie(oRequestHeaders) 183 | 184 | this.Error := {} 185 | ComObjError(0) ; 禁用 COM 错误通告。禁用后,检查 A_LastError 的值,脚本可以实现自己的错误处理。 186 | 187 | wr := ComObjCreate("WinHttp.WinHttpRequest.5.1") 188 | 189 | /* Options 190 | https://docs.microsoft.com/en-us/windows/win32/winhttp/winhttprequestoption 191 | 192 | UserAgentString := 0 193 | URL := 1 194 | URLCodePage := 2 195 | EscapePercentInURL := 3 196 | SslErrorIgnoreFlags := 4 197 | SelectCertificate := 5 198 | EnableRedirects := 6 199 | UrlEscapeDisable := 7 200 | UrlEscapeDisableQuery := 8 201 | SecureProtocols := 9 202 | EnableTracing := 10 203 | RevertImpersonationOverSsl := 11 204 | EnableHttpsToHttpRedirects := 12 205 | EnablePassportAuthentication := 13 206 | MaxAutomaticRedirects := 14 207 | MaxResponseHeaderSize := 15 208 | MaxResponseDrainSize := 16 209 | EnableHttp1_1 := 17 210 | EnableCertificateRevocationCheck := 18 211 | */ 212 | 213 | if (oOptions.URLCodePage != "") ; 设置 URL 的编码。 214 | wr.Option(2) := oOptions.URLCodePage 215 | 216 | if (oOptions.EnableRedirects != "") ; 设置是否获取重定向跳转后的页面信息。 217 | wr.Option(6) := oOptions.EnableRedirects 218 | 219 | if (oOptions.Proxy != "") 220 | wr.SetProxy(2, oOptions.Proxy, oOptions.ProxyBypassList) ; 首个参数为0表示遵循 Proxycfg.exe 的设置。1表示忽略代理直连。2表示使用代理。 221 | 222 | Timeout := this.ValidateTimeout(oOptions.Timeout 223 | , oOptions.ConnectTimeout 224 | , oOptions.DownloadTimeout) 225 | ValidatedConnectTimeout := Timeout[1]*1000 226 | ; 第一个超时参数必须为0,否则会发生内存泄露。 227 | ; https://docs.microsoft.com/en-us/windows/win32/winhttp/what-s-new-in-winhttp-5-1 228 | wr.SetTimeouts(0 229 | , ValidatedConnectTimeout 230 | , ValidatedConnectTimeout 231 | , ValidatedConnectTimeout) 232 | 233 | ; HTTP/1.1 支持以下9种请求方法。 234 | Methods := {GET:1, HEAD:1, POST:1, PUT:1, PATCH:1, DELETE:1, CONNECT:1, OPTIONS:1, TRACE:1} 235 | if (!Methods.Haskey(oOptions.Method)) 236 | oOptions.Method := Data="" ? "GET" : "POST" ; 请求方法为空或错误,则根据 Data 是否有值自动判断方法。 237 | oOptions.Method := Format("{:U}", oOptions.Method) ; 转换为大写,小写在很多网站会出错。 238 | wr.Open(oOptions.Method, URL, true) ; true 为异步获取。默认是 false ,龟速的根源!!!卡顿的根源!!! 239 | 240 | ; 如果自己不设置 User-Agent 那么实际上会被自动设置为 Mozilla/4.0 (compatible; Win32; WinHttp.WinHttpRequest.5) 。影响数据抓取。 241 | if (oRequestHeaders["User-Agent"] = "") 242 | oRequestHeaders["User-Agent"] := "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.114 Safari/537.36" 243 | if (InStr(oRequestHeaders["Accept-Encoding"], "gzip")) ; 这里必须用 oRequestHeaders["Accept-Encoding"] 而不是 oRequestHeaders.Accept-Encoding 。 244 | oRequestHeaders.Delete("Accept-Encoding") ; 删掉含 “gzip” 的 “Accept-Encoding” ,避免服务器返回 gzip 压缩后的数据。 245 | if (InStr(oRequestHeaders["Connection"], "Keep-Alive")) 246 | oRequestHeaders.Delete("Connection") ; 删掉含 “Keep-Alive” 的 “Connection” ,因为默认就会发送这个值,删掉避免重复发送。 247 | for k, v in oRequestHeaders ; 原来的 MSDN 推荐在设置 Cookie 前手动添加一个值,新版找不到这个推荐了,并且 Fiddler 抓包发现这样会让 Cookie 变多,故取消手动添加。 248 | wr.SetRequestHeader(k, v) ; SetRequestHeader() 必须 Open() 之后才有效。 249 | 250 | ; 为 NumberOfRetries 设置初始值 251 | if (oOptions.NumberOfRetries = "") 252 | oOptions.NumberOfRetries := 1 253 | 254 | loop 255 | { 256 | wr.Send(Data) 257 | ValidatedDownloadTimeout := Timeout[2] 258 | ; 虽然在 msdn 中,写着 WaitForResponse() 有两个参数,但实际上对于脚本语言,第二个参数就是返回值本身。 259 | ; https://docs.microsoft.com/en-us/windows/win32/winhttp/iwinhttprequest-waitforresponse 260 | ; https://www.autohotkey.com/boards/viewtopic.php?f=76&t=96806 261 | ; https://bbs.csdn.net/topics/240032183 262 | if (wr.WaitForResponse(ValidatedDownloadTimeout) != -1) ; 根据测试,返回-1代表正确,返回空值或0一般是超时了。 263 | this.SaveError("超时。", URL, Options, RequestHeaders, Data, FilePath) 264 | 265 | this.StatusCode := wr.Status() ; 获取状态码,一般 StatusCode 为200说明请求成功。 266 | this.StatusText := wr.StatusText() 267 | 268 | if (oOptions.ExpectedStatusCode = "" or oOptions.ExpectedStatusCode = this.StatusCode) 269 | break 270 | ; 尝试指定次数后服务器返回的状态码依旧与预期状态码不一致,则记录错误信息。 271 | else if (A_Index >= oOptions.NumberOfRetries) 272 | { 273 | Msg := "经过 " oOptions.NumberOfRetries " 次尝试,服务器返回状态码始终与期望值不符。" 274 | this.SaveError(Msg, URL, Options, RequestHeaders, Data, FilePath) 275 | break 276 | } 277 | } 278 | 279 | this.ResponseHeaders := this.解析信息为对象(wr.GetAllResponseHeaders()) ; 存响应头 280 | temp_ResponseHeaders := this.解析信息为对象(wr.GetAllResponseHeaders()) ; 解析SetCookie为Cookie() 会改变传入的值,所以这里创建一个备份用于解析 281 | this.Cookie := this.解析SetCookie为Cookie(temp_ResponseHeaders).Cookie ; 存 Cookie 282 | 283 | if (FilePath != "") 284 | return, this.BinArr_ToFile(wr.ResponseBody(), FilePath) ; 存为文件 285 | else if (oOptions.Charset != "") 286 | return, this.BinArr_ToString(wr.ResponseBody(), oOptions.Charset) ; 存为变量,自定义字符集 287 | else 288 | return, wr.ResponseText() ; 存为变量 289 | } 290 | 291 | /* 292 | infos 的格式:每行一个参数,行首至第一个冒号为参数名,之后至行尾为参数值。多个参数换行。 293 | 注意第一行的 “GET /?tn=sitehao123 HTTP/1.1” 其实是没有任何作用的,因为没有 “:” 。但复制过来了也并不会影响正常解析。 294 | 换句话说, Chrome 开发者工具中的 “Request Headers” 那段内容直接复制过来就能用。 295 | 296 | infos= 297 | ( 298 | GET /?tn=sitehao123 HTTP/1.1 299 | Host: www.baidu.com 300 | Connection: keep-alive 301 | Cache-Control: max-age=0 302 | Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8 303 | User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.153 Safari/537.36 SE 2.X MetaSr 1.0 304 | DNT: 1 305 | Referer: http://www.hao123.com/ 306 | Accept-Encoding: gzip,deflate,sdch 307 | Accept-Language: zh-CN,zh;q=0.8 308 | ) 309 | */ 310 | 解析信息为对象(infos) 311 | { 312 | if (IsObject(infos)) 313 | return, infos 314 | 315 | ; 以下两步可将 “infos” 换行符统一为 “`r`n” ,避免正则表达式提取时出错。 316 | StringReplace, infos, infos, `r`n, `n, All 317 | StringReplace, infos, infos, `n, `r`n, All 318 | 319 | ; 使用正则而不是 StrSplit() 进行处理的原因是,后者会错误处理这样的情况 “程序会根据 “Proxy:” 的值自动设置” 。 320 | infos_temp := this.RegEx.GlobalMatch(infos, "m)^\s*([\w\-]*?):(.*$)", 1) 321 | ; 将正则匹配到的信息存入新的对象中,像这样 {"Connection":"keep-alive", "Cache-Control":"max-age=0"} 。 322 | obj := {} 323 | Loop, % infos_temp.MaxIndex() 324 | { 325 | name := Trim(infos_temp[A_Index].Value[1], " `t`r`n`v`f") ;Trim()的作用就是把“abc: haha”中haha的多余空白符消除 326 | value := Trim(infos_temp[A_Index].Value[2], " `t`r`n`v`f") 327 | 328 | ; “Set-Cookie” 是可以一次返回多条的,因此特殊处理将返回值存入数组。 329 | if (name="Set-Cookie") 330 | { 331 | if (!obj.HasKey(name)) 332 | obj[name] := [] 333 | obj[name].Push(value) 334 | } 335 | else 336 | obj[name] := value 337 | } 338 | 339 | return, obj 340 | } 341 | 342 | /* 343 | EnableRedirects: 344 | ExpectedStatusCode:200 345 | NumberOfRetries:5 346 | 347 | 如果 “ShowEmptyNameAndValue=0” ,那么输出的内容将不包含值为空的行(例如第一行)。 348 | */ 349 | 解析对象为信息(obj, ShowEmptyNameAndValue:=1) 350 | { 351 | if (!IsObject(obj)) 352 | return, obj 353 | 354 | for k, v in obj 355 | { 356 | if (ShowEmptyNameAndValue=0 and Trim(v, " `t`r`n`v`f")="") 357 | continue 358 | 359 | if (k="Set-Cookie") 360 | { 361 | loop, % v.MaxIndex() 362 | infos .= k ":" v[A_Index] "`r`n" 363 | } 364 | else 365 | infos .= k ":" v "`r`n" 366 | } 367 | return, infos 368 | } 369 | 370 | /* 371 | 在 “GetAllResponseHeaders” 中, “Set-Cookie” 可能一次存在多个,比如 “Set-Cookie:name=a; domain=xxx.com `r`n Set-Cookie:name=b; domain=www.xxx.com” 。 372 | 之后向服务器发送 cookie 的时候,会先验证 domain ,再验证 path ,两者都成功,再发送所有符合条件的 cookies 。 373 | domain 的匹配方式是从字符串的尾部开始比较。 374 | path 的匹配方式是从头开始逐字符串比较(例如 /blog 匹配 /blog 、 /blogrool 等等)。需要注意的是, path 只在 domain 完成匹配后才比较。 375 | 当下次访问 “www.xxx.com” 时,假如有2个符合条件的 cookie ,那么发送给服务器的 cookie 应该是 “name=b; name=a” 。 376 | 当下次访问 “xxx.com” 时,假如只有1个符合条件的 cookie,那么发送给服务器的 cookie 应该是 “name=a” 。 377 | 规则是, path 越详细,越靠前。 domain 越详细,越靠前( domain 和 path 加起来就是网址了)。 378 | 另外需要注意的是, “Set-Cookie” 中没有 domain 或者 path 的话,则以当前 url 为准。 379 | 如果要覆盖一个已有的 cookie 值,那么需要创建一个 name 、 domain 、 path ,完全相同的 “Set-Cookie” ( name 就是 “cookie:name=value; path=/” 中的 name )。 380 | 当一个 cookie 存在,并且可选条件允许的话,该 cookie 的值会在接下来的每个请求中被发送至服务器。 381 | 其值被存储在名为 Cookie 的 HTTP 消息头中,并且只包含了 cookie 的值,其它的属性全部被去除( expires 、 domain 、 path 、 secure 全部没有了)。 382 | 如果在指定的请求中有多个 cookies ,那么它们会被分号和空格分开,例如:( Cookie:value1 ; value2 ; name1=value1 ) 383 | 在没有 expires 属性时, cookie 的寿命仅限于单一的会话中。浏览器的关闭意味这一次会话的结束,所以会话 cookie 只存在于浏览器保持打开的状态之下。 384 | 如果 expires 属性设置了一个过去的时间点,那么这个 cookie 会被立即删除。 385 | 最后一个属性是 secure 。不像其它属性,该属性只是一个标记并且没有其它的值。 386 | 参考 “http://my.oschina.net/hmj/blog/69638” 。 387 | 388 | 此函数将所有 “Set-Cookie” 忽略全部属性后(例如 Domain 适用站点属性、 Expires 过期时间属性等),存为一个 “Cookie” 。 389 | 传入的值里只有 Cookie ,直接返回;只有 Set-Cookie ,处理成 Cookie 后返回;两者都有,处理并覆盖 Cookie 后返回;两者都无,直接返回。 390 | Cookie 的 name 和 value 不允许包含分号,逗号和空格符。如果包含可以使用 URL 编码。 391 | 参考 “https://blog.oonne.com/site/blog?id=31” “https://www.cnblogs.com/daysme/p/8052930.html” 392 | */ 393 | 解析SetCookie为Cookie(obj) 394 | { 395 | if (!obj.HasKey("Set-Cookie")) ; 没有待处理的 “Set-Cookie” 则直接返回。 396 | return, obj 397 | 398 | Cookies := {} 399 | loop, % obj["Set-Cookie"].MaxIndex() 400 | { 401 | ; 根据RFC 2965标准,cookie 的 name 可以和属性相同。 402 | ; 但因为 name 和 value 总在最前面,所以又不会和属性混淆。 403 | ; https://tools.ietf.org/html/rfc2965 404 | Set_Cookie := StrSplit(obj["Set-Cookie"][A_Index], ";", " `t`r`n`v`f") 405 | ; 可以正确处理 value 中含等号的情况 “Set-Cookie:BAIDUID=C04C13BA70E52C330434FAD20C86265C:FG=1;” 406 | , NameAndValue := StrSplit(Set_Cookie[1], "=", " `t`r`n`v`f", 2) 407 | , name := NameAndValue[1] 408 | , value := NameAndValue[2] 409 | , Cookies[name] := value 410 | } 411 | obj.Delete("Set-Cookie") ; “Set-Cookie” 转换完成后就删除。 412 | 413 | obj["Cookie"] := "" ; 同时存在 “Cookie” 和 “Set-Cookie” 时,后者处理完成的值将覆盖前者。 414 | for k, v in Cookies 415 | obj["Cookie"] .= k "=" v "; " 416 | obj["Cookie"] := RTrim(obj["Cookie"], " ") 417 | 418 | return, obj 419 | } 420 | 421 | ValidateTimeout(Timeout, ConnectTimeout, DownloadTimeout) 422 | { 423 | ; Timeout 为0或正数,则覆盖 ConnectTimeout 和 DownloadTimeout 424 | if (Timeout*1>=0) 425 | { 426 | ct := Round(Timeout*1) 427 | dt := Round(Timeout*1) 428 | } 429 | else 430 | { 431 | ; 将字符串等非法值转换为空值,保留数字。 432 | ct := ConnectTimeout*1 433 | dt := DownloadTimeout*1 434 | ; 将负数转换为空值。即此时可能值为空值、0、正数。 435 | ct := ct<0 ? "" : Round(ct) 436 | dt := dt<0 ? "" : Round(dt) 437 | } 438 | 439 | ; 空值 零 正 440 | ; 空值 零 正 交叉配对 441 | if (ct="" and dt="") 442 | ct := 30, dt := -1 443 | else if (ct="" and dt=0) 444 | ct := 30, dt := -1 445 | else if (ct="" and dt>0) 446 | ct := 30, dt := Max(ct, dt) 447 | 448 | else if (ct=0 and dt="") 449 | ct := 0, dt := -1 450 | else if (ct=0 and dt=0) 451 | ct := 0, dt := -1 452 | else if (ct=0 and dt>0) 453 | ct := 0, dt := dt 454 | 455 | else if (ct>0 and dt="") 456 | ct := ct, dt := -1 457 | else if (ct>0 and dt=0) 458 | ct := ct, dt := -1 459 | else if (ct>0 and dt>0) 460 | ct := Max(ct, dt), dt := ct 461 | 462 | return, [ct, dt] 463 | } 464 | 465 | SaveError(Message, URL, Options, RequestHeaders, Data, FilePath) 466 | { 467 | this.Error.Message := Message 468 | this.Error.URL := URL 469 | this.Error.Options := Options 470 | this.Error.RequestHeaders := RequestHeaders 471 | this.Error.Data := this.Data 472 | this.Error.FilePath := this.FilePath 473 | } 474 | 475 | /* 476 | CreateFormData - Creates "multipart/form-data" for http post by tmplinshi 477 | 478 | https://www.autohotkey.com/boards/viewtopic.php?t=7647 479 | 480 | Usage: CreateFormData(ByRef retData, ByRef retHeader, objParam, BoundaryString, RandomBoundaryLength, MimeType) 481 | retData - (out) Data used for HTTP POST. 482 | retHeader - (out) Content-Type header used for HTTP POST. 483 | objParam - (in) An object defines the form parameters. 484 | BoundaryString - (in) default "----WebKitFormBoundary". 485 | RandomBoundaryLength - (in) default 16. 486 | MimeType - (in) default auto get MimeType. 487 | 488 | To specify files, use array as the value. Example: 489 | objParam := { "key1": "value1" 490 | , "upload[]": ["1.png", "2.png"] } 491 | 492 | Version : 1.31 / 2021-04-05 - 支持自定义 BoundaryString RandomBoundaryLength MimeType 493 | 默认 BoundaryString 为 ----WebKitFormBoundary + 16位随机数 494 | 1.30 / 2019-01-13 - The file parameters are now placed at the end of the retData 495 | 1.20 / 2016-06-17 - Added CreateFormData_WinInet(), which can be used for VxE's HTTPRequest() 496 | 1.10 / 2015-06-23 - Fixed a bug 497 | 1.00 / 2015-05-14 498 | */ 499 | CreateFormData(ByRef retData, ByRef retHeader, objParam, BoundaryString:="", RandomBoundaryLength:="", MimeType:="") { 500 | 501 | this.NonNull(BoundaryString, "----WebKitFormBoundary") 502 | , this.NonNull(RandomBoundaryLength, 16, 1) 503 | 504 | CRLF := "`r`n" 505 | 506 | Boundary := this.RandomBoundary(RandomBoundaryLength) 507 | BoundaryLine := "--" . BoundaryString . Boundary 508 | 509 | ; Loop input paramters 510 | binArrs := [] 511 | fileArrs := [] 512 | For k, v in objParam 513 | { 514 | If IsObject(v) { 515 | For i, FileName in v 516 | { 517 | str := BoundaryLine . CRLF 518 | . "Content-Disposition: form-data; name=""" . k . """; filename=""" . FileName . """" . CRLF 519 | . "Content-Type: " . this.NonNull_ret(MimeType, this.GetMimeType(FileName)) . CRLF . CRLF 520 | fileArrs.Push( this.BinArr_FromString(str) ) 521 | fileArrs.Push( this.BinArr_FromFile(FileName) ) 522 | fileArrs.Push( this.BinArr_FromString(CRLF) ) 523 | } 524 | } Else { 525 | str := BoundaryLine . CRLF 526 | . "Content-Disposition: form-data; name=""" . k """" . CRLF . CRLF 527 | . v . CRLF 528 | binArrs.Push( this.BinArr_FromString(str) ) 529 | } 530 | } 531 | 532 | binArrs.push( fileArrs* ) 533 | 534 | str := BoundaryLine . "--" . CRLF 535 | binArrs.Push( this.BinArr_FromString(str) ) 536 | 537 | retData := this.BinArr_Join(binArrs*) 538 | retHeader := "multipart/form-data; boundary=" . BoundaryString . Boundary 539 | } 540 | 541 | RandomBoundary(length) { 542 | str := [0,1,2,3,4,5,6,7,8,9 543 | ,"a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z" 544 | ,"A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z"] 545 | loop, % length 546 | { 547 | Random, n, 1, % str.MaxIndex() 548 | ret .= str[n] 549 | } 550 | Return, ret 551 | } 552 | 553 | GetMimeType(FileName) { 554 | n := FileOpen(FileName, "r").ReadUInt() 555 | Return (n = 0x474E5089) ? "image/png" 556 | : (n = 0x38464947) ? "image/gif" 557 | : (n&0xFFFF = 0x4D42 ) ? "image/bmp" 558 | : (n&0xFFFF = 0xD8FF ) ? "image/jpeg" 559 | : (n&0xFFFF = 0x4949 ) ? "image/tiff" 560 | : (n&0xFFFF = 0x4D4D ) ? "image/tiff" 561 | : "application/octet-stream" 562 | } 563 | 564 | /* 565 | https://www.w3schools.com/asp/ado_ref_stream.asp 566 | https://gist.github.com/tmplinshi/a97d9a99b9aa5a65fd20 567 | Update: 2015-6-4 - Added BinArr_ToFile() 568 | */ 569 | BinArr_FromString(str) { 570 | oADO := ComObjCreate("ADODB.Stream") 571 | 572 | oADO.Type := 2 ; adTypeText 573 | oADO.Mode := 3 ; adModeReadWrite 574 | oADO.Open() 575 | oADO.Charset := "UTF-8" 576 | oADO.WriteText(str) 577 | 578 | oADO.Position := 0 ; 位置0, Type 可写。其它位置 Type 只读。 https://www.w3schools.com/asp/prop_stream_type.asp 579 | oADO.Type := 1 ; adTypeBinary 580 | oADO.Position := 3 ; Skip UTF-8 BOM 581 | return oADO.Read(), oADO.Close() 582 | } 583 | 584 | BinArr_FromFile(FileName) { 585 | oADO := ComObjCreate("ADODB.Stream") 586 | 587 | oADO.Type := 1 ; adTypeBinary 588 | oADO.Open() 589 | oADO.LoadFromFile(FileName) 590 | return oADO.Read(), oADO.Close() 591 | } 592 | 593 | BinArr_Join(Arrays*) { 594 | oADO := ComObjCreate("ADODB.Stream") 595 | 596 | oADO.Type := 1 ; adTypeBinary 597 | oADO.Mode := 3 ; adModeReadWrite 598 | oADO.Open() 599 | For i, arr in Arrays 600 | oADO.Write(arr) 601 | oADO.Position := 0 602 | return oADO.Read(), oADO.Close() 603 | } 604 | 605 | BinArr_ToString(BinArr, Encoding) { 606 | oADO := ComObjCreate("ADODB.Stream") 607 | 608 | oADO.Type := 1 ; 以二进制方式操作 609 | oADO.Mode := 3 ; 可同时进行读写。 Mode 必须在 Open 前才能设置。 https://www.w3schools.com/asp/prop_stream_mode.asp 610 | oADO.Open() ; 开启物件 611 | oADO.Write(BinArr) ; 写入物件。注意 wr.ResponseBody() 获取到的是无符号的 bytes,通过 adodb.stream 转换成字符串 string 612 | 613 | oADO.Position := 0 ; 位置0, Type 可写。其它位置 Type 只读。 https://www.w3schools.com/asp/prop_stream_type.asp 614 | oADO.Type := 2 ; 以文字模式操作 615 | oADO.Charset := Encoding ; 设定编码方式 616 | return oADO.ReadText(), oADO.Close() ; 将物件内的文字读出 617 | } 618 | 619 | BinArr_ToFile(BinArr, FileName) { 620 | oADO := ComObjCreate("ADODB.Stream") 621 | 622 | oADO.Type := 1 ; 以二进制方式操作 623 | oADO.Mode := 3 ; 可同时进行读写。 Mode 必须在 Open 前才能设置。 https://www.w3schools.com/asp/prop_stream_mode.asp 624 | oADO.Open() ; 开启物件 625 | oADO.Write(BinArr) ; 写入物件。注意没法将 wr.ResponseBody() 存入一个变量,所以必须用这种方式写文件 626 | oADO.SaveToFile(FileName, 2) ; 文件存在则覆盖 627 | oADO.Close() 628 | } 629 | 630 | #IncludeAgain %A_LineFile%\..\RegEx.ahk 631 | #IncludeAgain %A_LineFile%\..\NonNull.ahk 632 | } -------------------------------------------------------------------------------- /有道词典/autocomplete_json.js: -------------------------------------------------------------------------------- 1 | var youdaos = window.youdao || {}; 2 | youdaos.global = this, 3 | function(e) { 4 | function t() { 5 | for (var e, t = [], i = 0; i < arguments.length; i++) e = arguments[i], 6 | "string" == typeof e && (e = document.getElementById(e)), 7 | t.push(e); 8 | return t.length < 2 ? t[0] : t 9 | } 10 | e.mixin = function(e, t) { 11 | for (var i in t) e[i] = t[i] 12 | }, 13 | e.bind = function(t, i) { 14 | var s = i || e.global; 15 | if (arguments.length > 2) { 16 | var n = Array.prototype.slice.call(arguments, 2); 17 | return function() { 18 | var e = Array.prototype.slice.call(arguments); 19 | return Array.prototype.unshift.apply(e, n), 20 | t.apply(s, e) 21 | } 22 | } 23 | return function() { 24 | return t.apply(s, arguments) 25 | } 26 | }, 27 | e.events = { 28 | element: function(e) { 29 | return e.target || e.srcElement 30 | }, 31 | map: {}, 32 | listen: function(e, t, i, s) { 33 | e.addEventListener ? e.addEventListener(t, i, s) : e.attachEvent && (e["e" + t + i] = i, e[t + i] = function() { 34 | return e["e" + t + i](window.event) 35 | }, 36 | e.attachEvent("on" + t, e[t + i])); 37 | var n = this.map; 38 | n[e] ? n[e].push({ 39 | type: t, 40 | fn: i, 41 | mode: s 42 | }) : n[e] = [{ 43 | type: t, 44 | fn: i, 45 | mode: s 46 | }] 47 | }, 48 | unlisten: function(e, t, i, s) { 49 | e.addEventListener ? e.addEventListener(t, i, s) : e.attachEvent && (e["e" + t + i] = i, e[t + i] = function() { 50 | return e["e" + t + i](window.event) 51 | }, 52 | e.attachEvent("on" + t, e[t + i])); 53 | var n = this.map; 54 | if (n[e] && "[object Array]" === toString.call(n[e])) for (var h = 0, 55 | o = n[e].length; o > h; h++) { 56 | var r = n[e][h]; 57 | r.type == t && r.fn == i && n[e].splict(h, 1) 58 | } 59 | }, 60 | unlistenAll: function(e) { 61 | var t = this.map; 62 | if (t[e]) for (var i = 0; i < t[e].length; i++) { 63 | var s = t[e][i]; 64 | this.unlisten(e, s.type, s.fn, s.mode) 65 | } 66 | } 67 | }, 68 | e.dom = { 69 | visible: function(e) { 70 | return "none" != t(e).style.display 71 | }, 72 | hide: function() { 73 | for (var e = 0; e < arguments.length; e++) { 74 | var i = t(arguments[e]); 75 | i.style.display = "none" 76 | } 77 | }, 78 | show: function() { 79 | for (var e = 0; e < arguments.length; e++) { 80 | var i = t(arguments[e]); 81 | i.style.display = "" 82 | } 83 | }, 84 | getHeight: function(e) { 85 | return e = t(e), 86 | e.offsetHeight 87 | }, 88 | addClassName: function(e, i) { (e = t(e)) && (e.className = "" == e.className ? i: e.className + " " + i) 89 | }, 90 | removeClassName: function(e, i) { 91 | if (e = t(e)) { 92 | var s = new RegExp("(^| )" + i + "( |$)"); 93 | e.className = e.className.replace(s, "$1").replace(/ $/, "") 94 | } 95 | }, 96 | attr: function(e, t, i, s) { 97 | var n = t; 98 | if ("string" == typeof t) { 99 | if (void 0 === i) return e && e.getAttribute(t); 100 | n = {}, 101 | n[t] = i 102 | } 103 | for (var h in n) e.setAttribute(h, n[h]) 104 | } 105 | }, 106 | e.dimension = { 107 | cumOffset: function(e) { 108 | var t, i, s, n, h = 0, 109 | o = 0, 110 | r = !1; 111 | do h += e.offsetTop || 0, 112 | o += e.offsetLeft || 0, 113 | e = e.offsetParent, 114 | e && (t = window.getComputedStyle(e, null), i = t.getPropertyValue("position")); 115 | while (e && "fixed" != i); 116 | return "fixed" === i && (r = !0, s = Number(t.getPropertyValue("top").slice(0, -2)), n = Number(t.getPropertyValue("left").slice(0, -2))), 117 | [o, h, r, s, n] 118 | } 119 | }, 120 | e.SK = { 121 | BACKSPACE: 8, 122 | TAB: 9, 123 | RETURN: 13, 124 | ESC: 27, 125 | LEFT: 37, 126 | UP: 38, 127 | RIGHT: 39, 128 | DOWN: 40, 129 | DELETE: 46, 130 | PAGE_UP: 33, 131 | PAGE_DOWN: 34, 132 | END: 35, 133 | HOME: 36, 134 | INSERT: 45, 135 | SHIFT: 16, 136 | CTRL: 17, 137 | ALT: 18 138 | }, 139 | e.Autocomplete = function(t, i, s, n, h, o, r) { 140 | e.events.listen(document, "click", e.bind(this.hideOnDoc, this)), 141 | e.events.listen(document, "blur", e.bind(this.hideOnDoc, this)), 142 | this._dataCache = {}, 143 | this.blurOptions = o || {}, 144 | this.inputType = document.getElementById(r), 145 | this.objName = i || this.defSugName, 146 | this.isIE = navigator && -1 != navigator.userAgent.toLowerCase().indexOf("msie"), 147 | this.hideClose = !!n || !1, 148 | this.selectCallBack = !1, 149 | this.box = document.getElementById(t), 150 | e.events.listen(this.box, "keydown", e.bind(this.onkeydown, this)); 151 | var a = this; 152 | this.box.onblur = function(e) { 153 | a.hide(e), 154 | a.blurOptions.onblur && a.blurOptions.onblur() 155 | }, 156 | e.events.listen(this.box, "dblclick", e.bind(this.dbClick, this)), 157 | this.count = 0, 158 | this.sugServ = this.defSugServ, 159 | this.sugServUrlPost = this.S_QUERY_URL_POST, 160 | s && (this.sugServ = s), 161 | this.sugMoreParams = "", 162 | this.logServ = this.defSugServ, 163 | this.logServUrlPost = this.S_LOG_URL_POST, 164 | this.searchServ = this.defSearchServ, 165 | this.searchParamName = this.defSearchParamName, 166 | this.searchMoreParams = "", 167 | this.kf = this.defKeyfrom + this.KEYFROM_POST, 168 | this.openInNewWindow = !1, 169 | this.clickEnabled = !0, 170 | this.sptDiv = document.createElement("div"), 171 | document.body.appendChild(this.sptDiv), 172 | this.sdiv = document.createElement("div"), 173 | this.sdiv.style.position = "absolute", 174 | this.sdiv.style.zIndex = 1e4, 175 | e.dom.hide(this.sdiv), 176 | document.body.appendChild(this.sdiv), 177 | this.iframe = document.createElement("iframe"), 178 | this.iframe.style.position = "absolute", 179 | this.iframe.style.zIndex = 9999, 180 | e.dom.hide(this.iframe), 181 | document.body.appendChild(this.iframe), 182 | this.bdiv = document.createElement("div"), 183 | this.vis = !1, 184 | this.lastUserQuery = "", 185 | this.initVal = "", 186 | this.box && "" != this.box.value && (this.initVal = this.box.value), 187 | this.curUserQuery = this.initVal, 188 | this.upDownTag = !1, 189 | window.onresize = e.bind(this.winResize, this), 190 | this.clean(), 191 | !h && this.box && (this.timeoutId = setTimeout(e.bind(this.sugReq, this), this.REQUEST_TIMEOUT)) 192 | }, 193 | e.mixin(e.Autocomplete.prototype, { 194 | start: function() { 195 | 0 != this.timeoutId && clearTimeout(this.timeoutId), 196 | this.timeoutId = setTimeout(e.bind(this.sugReq, this), this.REQUEST_TIMEOUT), 197 | this.box && "" != this.box.value && (this.initVal = this.box.value, this.curUserQuery = this.initVal) 198 | }, 199 | setOffset: function(e, t) { 200 | this.offsetX = e, 201 | this.offsetY = t 202 | }, 203 | setObjectName: function(e) { 204 | this.objName = e 205 | }, 206 | setSugServer: function(e, t) { 207 | this.sugServ = e, 208 | t && (this.sugServUrlPost = t), 209 | this.clean() 210 | }, 211 | setSugMoreParams: function(e) { 212 | this.sugMoreParams = e 213 | }, 214 | setLogServer: function(e, t) { 215 | this.logServ = e, 216 | t && (this.logServUrlPost = t) 217 | }, 218 | setSearchServer: function(e) { 219 | this.searchServ = e 220 | }, 221 | setSearchParamName: function(e) { 222 | this.searchParamName = e 223 | }, 224 | setSearchMoreParams: function(e) { 225 | this.searchMoreParams = e 226 | }, 227 | setKeyFrom: function(e) { 228 | e.indexOf(this.KEYFROM_POST) > 0 ? this.kf = e: this.kf = e + this.KEYFROM_POST 229 | }, 230 | setSelectCallBack: function(e) { 231 | this.selectCallBack = e 232 | }, 233 | setOpenInNewWindow: function() { 234 | this.openInNewWindow = !0 235 | }, 236 | getSearchUrl: function(e) { 237 | var t = "eng"; 238 | return this.inputType && (t = this.inputType.value), 239 | encodeURI(this.searchServ + t + "/" + e + "/#keyfrom=" + this.kf) 240 | }, 241 | getSugQueryUrl: function(e, t, i) { 242 | return encodeURI(this.sugServ + this.sugServUrlPost + e + this.sugMoreParams + "&keyfrom=" + this.kf + "&o=" + this.objName + "&rn=10" + this.hour()) + "&le=" + i 243 | }, 244 | clicklog: function(e, t, i, s, n) { 245 | var h = ""; 246 | t && (h += t), 247 | i && (h += i), 248 | s && (h += s), 249 | n && (h += n); 250 | var o = new Image; 251 | return o.src = encodeURI(this.logServ + this.logServUrlPost + e + h + this.time()), 252 | !0 253 | }, 254 | dbClick: function() { 255 | if (this.box.createTextRange) { 256 | var e = this.box.createTextRange(); 257 | e.moveStart("character", 0), 258 | e.select() 259 | } else this.box.setSelectionRange && this.box.setSelectionRange(0, this.box.value.length); 260 | "" != this.box.value && (this.lastUserQuery == this.box.value ? this.sdiv.childNodes.length > 0 && (this.vis ? this.hide() : this.show()) : this.doReq()) 261 | }, 262 | winResize: function() { 263 | this.vis && this.show() 264 | }, 265 | onkeydown: function(t) { 266 | if (t.ctrlKey) return ! 0; 267 | var i = e.SK; 268 | switch (t.keyCode) { 269 | case i.PAGE_UP: 270 | case i.PAGE_DOWN: 271 | case i.END: 272 | case i.HOME: 273 | case i.INSERT: 274 | case i.CTRL: 275 | case i.ALT: 276 | case i.LEFT: 277 | case i.RIGHT: 278 | case i.SHIFT: 279 | case i.TAB: 280 | return ! 0; 281 | case i.ESC: 282 | return this.hide(), 283 | !1; 284 | case i.UP: 285 | if (this.vis) this.upDownTag = !0, 286 | this.up(); 287 | else { 288 | if (this.sdiv.childNodes.length > 1 && this.lastUserQuery == this.box.value) return this.show(), 289 | !1; 290 | "" != this.box.value && this.doReq() 291 | } 292 | return this.isIE ? t.returnValue = !1 : t.preventDefault(), 293 | !1; 294 | case i.DOWN: 295 | if (this.vis) this.upDownTag = !0, 296 | this.down(); 297 | else { 298 | if (this.sdiv.childNodes.length > 1 && this.lastUserQuery == this.box.value) return this.show(), 299 | !1; 300 | "" != this.box.value && this.doReq() 301 | } 302 | return this.isIE ? t.returnValue = !1 : t.preventDefault(), 303 | !1; 304 | case i.RETURN: 305 | return this.vis && this.curNodeIdx > -1 && !this.select() ? (this.isIE ? t.returnValue = !1 : t.preventDefault(), !1) : !0; 306 | case i.BACKSPACE: 307 | 1 == this.box.value.length && (this.curUserQuery = ""); 308 | default: 309 | return this.upDownTag = !1, 310 | !0 311 | } 312 | }, 313 | sugReq: function() { 314 | document.activeElement && document.activeElement != this.box || ("" != this.box.value && this.box.value != this.initVal ? this.lastUserQuery != this.box.value && (this.upDownTag || this.doReq()) : "" != this.lastUserQuery && (this.lastUserQuery = "", this.vis && (this.hide(), this.clean()))), 315 | 0 != this.timeoutId && clearTimeout(this.timeoutId), 316 | this.timeoutId = setTimeout(e.bind(this.sugReq, this), this.REQUEST_TIMEOUT) 317 | }, 318 | select: function(e) { 319 | if (e) var t = this.LOG_MOUSE_SELECT; 320 | else var t = this.LOG_KEY_SELECT; 321 | if (this.getCurNode()) { 322 | var i = this.getCurNode().getAttribute(this.ITEM_TYPE), 323 | s = this.getCurNode().getElementsByTagName("td")[0].innerHTML.replace(/<\/?[^>]*>/g, ""); 324 | if ("1" == i) { 325 | if (s = this.getElemAttr(this.getCurNode(), this.ITEM_LINK), this.clicklog(t, "&q=" + this.curUserQuery, "&index=0", "&select=" + s, "&direct=true"), this.hide(), this.selectCallBack) return void this.selectCallBack(this.box.value, this.kf); 326 | window.open(s, "_blank") 327 | } else try { 328 | if (e) this.clicklog(t, "&q=" + this.curUserQuery, "&index=" + this.curNodeIdx, "&select=" + s), 329 | this.curUserQuery = s, 330 | this.openInNewWindow || (this.box.value = s); 331 | else { 332 | if (this.box.value != s) return ! 0; 333 | this.clicklog(t, "&q=" + this.curUserQuery, "&index=" + this.curNodeIdx, "&select=" + s), 334 | this.curUserQuery = s, 335 | s = this.box.value 336 | } 337 | this.hide(); 338 | var n = this.getSearchUrl(s); 339 | if (this.openInNewWindow) { 340 | if (this.selectCallBack) return void this.selectCallBack(this.box.value, this.kf); 341 | window.open(n, "_blank") 342 | } else document.location = n 343 | } catch(e) {} 344 | } 345 | return ! 1 346 | }, 347 | submitForm: function(e) { 348 | var t = document.getElementById(e), 349 | i = this; 350 | t && ("_blank" === t.getAttribute("target") && this.setOpenInNewWindow(), this.setSelectCallBack(function(e, s) { 351 | var n = !1, 352 | h = !1, 353 | o = document.createElement("input"), 354 | r = !1, 355 | a = !1; 356 | o.style.display = "none", 357 | o.name = i.searchParamName || "query", 358 | o.value = e, 359 | t.keyfrom ? (r = t.keyfrom, n = r.value, r.value = s) : (a = document.createElement("input"), a.style.display = "none", a.name = "keyfrom", a.value = s), 360 | a && t.appendChild(a), 361 | t.appendChild(o), 362 | h = t.action, 363 | t.action = i.searchServ + i.curUserQuery, 364 | t.submit(), 365 | t.action = h, 366 | a && (t.removeChild(a), a = !1), 367 | r && (r.value = n, r = !1), 368 | t.removeChild(o) 369 | })) 370 | }, 371 | doReq: function() { 372 | this.initVal = "", 373 | this.curUserQuery = this.box.value; 374 | var e = this.box.value; 375 | this.lastUserQuery = this.box.value; 376 | var t = "eng"; 377 | if (this.inputType) var t = this.inputType.value; 378 | if (this._dataCache[e]) return void this.updateCall(this._dataCache[e]); 379 | this.count++; 380 | var i = this.getSugQueryUrl(e, this.count, t); 381 | this.excuteCall(i) 382 | }, 383 | clean: function() { 384 | this.size = 0, 385 | this.curNodeIdx = -1, 386 | this.sdiv.innerHTML = "", 387 | this.bdiv.innerHTML = "" 388 | }, 389 | onComplete: function() { 390 | setTimeout(e.bind(this.updateContent, this), 5) 391 | }, 392 | cleanScript: function() { 393 | for (; this.sptDiv.childNodes.length > 0;) this.sptDiv.removeChild(this.sptDiv.firstChild) 394 | }, 395 | isValidNode: function(e) { 396 | return 1 == e.nodeType 397 | }, 398 | getReqStr: function(e) { 399 | return e && e.getElementsByTagName("div").length > 0 ? this.getElemAttr(e.getElementsByTagName("div")[0], this.QUERY_ATTR) : null 400 | }, 401 | getElemAttr: function(e, t) { 402 | return this.unescape(e.getAttribute(t)) 403 | }, 404 | updateContent: function() { 405 | this.cleanScript(); 406 | this.box.value; 407 | if ("" == this.bdiv.innerHTML) return this.hide(), 408 | void this.clean(); 409 | var t; 410 | this.sdiv.innerHTML = this.bdiv.innerHTML; 411 | var i = this.sdiv.getElementsByTagName("table"); 412 | i[2].parentNode.removeChild(i[2]), 413 | children = i[1].getElementsByTagName("tr"), 414 | this.size = 0, 415 | this.childs = new Array; 416 | for (var s = 0; s < children.length; s++) t = children[s], 417 | this.isValidNode(t) && (t.setAttribute(this.ITEM_INDEX, this.size), e.events.listen(t, "mousemove", e.bind(this.mouseMoveItem, this)), e.events.listen(t, "mouseover", e.bind(this.mouseOverItem, this)), e.events.listen(t, "mouseout", e.bind(this.mouseOutItem, this)), e.events.listen(t, "click", e.bind(this.select, this)), this.childs.push(t), this.size++); 418 | Number(i.length) >= 3 && this.bindATagWithMouseEvent(i[2], !1), 419 | this.show(), 420 | this.canMouseOver = !1 421 | }, 422 | showContent: function() { 423 | var t = e.dimension.cumOffset(this.box); 424 | this.offsetX = this.offsetX || 0, 425 | this.offsetY = this.offsetY || 0, 426 | t[2] ? (this.sdiv.style.position = "fixed", this.sdiv.style.top = t[1] + (this.box.offsetHeight - 1) + t[3] + this.offsetY + "px", this.sdiv.style.left = t[0] + t[4] + this.offsetX + "px") : (this.sdiv.style.position = "absolute", this.sdiv.style.top = t[1] + (this.box.offsetHeight - 1) + this.offsetY + "px", this.sdiv.style.left = t[0] + this.offsetX + "px"), 427 | this.sdiv.style.cursor = "default", 428 | this.sdiv.style.width = this.box.offsetWidth - this.offsetX + "px", 429 | e.dom.show(this.sdiv), 430 | this.iframe.style.top = this.sdiv.style.top, 431 | this.iframe.style.left = this.sdiv.style.left, 432 | this.iframe.style.width = this.sdiv.style.width, 433 | this.iframe.style.height = this.sdiv.offsetHeight, 434 | this.iframe.style.border = 0, 435 | e.dom.show(this.iframe), 436 | this.vis = !0, 437 | this.curNodeIdx = -1 438 | }, 439 | show: function() { 440 | this.sdiv.childNodes.length < 1 || this.showContent() 441 | }, 442 | hide: function() { 443 | this.hlOff(), 444 | e.dom.hide(this.sdiv), 445 | e.dom.hide(this.iframe), 446 | this.curNodeIdx = -1, 447 | this.vis = !1 448 | }, 449 | hideOnDoc: function() { 450 | this.clickEnabled && (this.hide(), this.clickEnabled = !1, setTimeout(e.bind(this.enableClick, this), 60)) 451 | }, 452 | enableClick: function() { 453 | this.clickEnabled = !0 454 | }, 455 | mouseMoveItem: function(e) { 456 | this.canMouseOver = !0, 457 | this.mouseOverItem(e) 458 | }, 459 | mouseOverItem: function(t) { 460 | if (this.removeBoxBlur(), !this.canMouseOver) return void(this.canMouseOver = !0); 461 | for (var i = e.events.element(t); i.parentNode && (!i.tagName || null == i.getAttribute(this.ITEM_INDEX));) i = i.parentNode; 462 | var s = i.tagName ? i.getAttribute(this.ITEM_INDEX) : -1; - 1 != s && s != this.curNodeIdx && (this.hlOff(), this.curNodeIdx = Number(s), this.hlOn(!1)) 463 | }, 464 | mouseOutItem: function() { 465 | this.hlOff(), 466 | this.curNodeIdx = -1, 467 | this.revertBoxBlur() 468 | }, 469 | getNode: function(e) { 470 | return this.childs && e >= 0 && e < this.childs.length ? this.childs[e] : void 0 471 | }, 472 | getCurNode: function() { 473 | return this.getNode(this.curNodeIdx) 474 | }, 475 | hover: function(e, t) { 476 | e || (this.box.value = t) 477 | }, 478 | hlOn: function(t) { 479 | if (this.getCurNode()) { 480 | var i = this.getCurNode().getElementsByTagName("td"); 481 | this.procInstantResult(), 482 | t && (this.box.value = i[0].innerHTML.replace(/<\/?[^>]*>/g, "")); 483 | for (var s = 0; s < i.length; ++s) e.dom.addClassName(i[s], this.ITEM_HIGHLIGHT_STYLE) 484 | } 485 | }, 486 | hlOff: function() { 487 | if (this.getCurNode()) { 488 | for (var t = this.getCurNode().getElementsByTagName("td"), i = 0; i < t.length; ++i) e.dom.removeClassName(t[i], this.ITEM_HIGHLIGHT_STYLE); 489 | this.procInstantResultBack() 490 | } 491 | }, 492 | procInstantResult: function() { 493 | var e = this.getCurNode().innerHTML; 494 | if ( - 1 != e.indexOf("is_red")) { 495 | var t = document.getElementById("is_red"); 496 | t && (t.style.color = "#fff"), 497 | t = document.getElementById("is_green"), 498 | t && (t.style.color = "#fff") 499 | } 500 | }, 501 | procInstantResultBack: function() { 502 | var e = this.getCurNode().innerHTML; 503 | if ( - 1 != e.indexOf("is_red")) { 504 | var t = document.getElementById("is_red"); 505 | t && (t.style.color = "#c60a00"), 506 | t = document.getElementById("is_green"), 507 | t && (t.style.color = "#008000") 508 | } 509 | }, 510 | up: function() { 511 | var e = this.curNodeIdx; 512 | this.curNodeIdx > 0 ? (this.hlOff(), this.curNodeIdx = e - 1, this.hlOn(!0)) : 0 == this.curNodeIdx ? (this.hlOff(), this.curNodeIdx = e - 1, this.box.value = this.curUserQuery) : (this.curNodeIdx = this.size - 1, this.hlOn(!0)) 513 | }, 514 | down: function() { 515 | var e = this.curNodeIdx; 516 | this.curNodeIdx < 0 ? (this.curNodeIdx = e + 1, this.hlOn(!0)) : this.curNodeIdx < this.size - 1 ? (this.hlOff(), this.curNodeIdx = e + 1, this.hlOn(!0)) : (this.hlOff(), this.curNodeIdx = -1, this.box.value = this.curUserQuery) 517 | }, 518 | excuteCall: function(e) { 519 | var t = document.createElement("script"); 520 | t.src = e, 521 | t.charset = "utf-8", 522 | this.sptDiv.appendChild(t) 523 | }, 524 | unescape: function(e) { 525 | return e.replace(new RegExp(""", "gm"), '"').replace(new RegExp(">", "gm"), ">").replace(new RegExp("<", "gm"), "<").replace(new RegExp("&", "gm"), "&") 526 | }, 527 | escape: function(e) { 528 | return e.replace(new RegExp("&", "gm"), "&").replace(new RegExp("<", "gm"), "<").replace(new RegExp(">", "gm"), ">").replace(new RegExp('"', "gm"), """) 529 | }, 530 | subLink: function(e) { 531 | return e.length <= 43 ? e: e.substr(e, 40) + "..." 532 | }, 533 | updateCall: function(e) { 534 | var t = unescape(e); 535 | if (this.bdiv.innerHTML = t, this.bdiv.childNodes.length < 2) this.bdiv.innerHTML = ""; 536 | else { 537 | var i, s = this.bdiv.getElementsByClassName("remindtt75"), 538 | n = s.length, 539 | h = 5, 540 | o = s[0].parentNode.parentNode; 541 | for (i = n - 1; i > h - 1; i--) o.removeChild(s[i].parentNode); 542 | for (n = s.length, i = 0; n > i; i++) s[i].innerHTML = s[i].innerHTML.replace(this.curUserQuery, "" + this.curUserQuery + ""); 543 | var r = this.bdiv.getElementsByTagName("table")[0]; 544 | r.style.background = "#fcfcfe", 545 | r.style["box-shadow"] = "0 2px 1px 1px #e6e6e6", 546 | r.style["padding-bottom"] = "10px", 547 | r.style["border-radius"] = "0 0 5px 5px" 548 | } 549 | this.onComplete() 550 | }, 551 | focusBox: function() { 552 | if (this.box.focus(), this.box.createTextRange) { 553 | var e = this.box.createTextRange(); 554 | e.moveStart("character", this.box.value.length), 555 | e.select() 556 | } else this.box.setSelectionRange && this.box.setSelectionRange(this.box.value.length, this.box.value.length) 557 | }, 558 | pressPoint: function(t) { 559 | this.clickEnabled && (this.clickEnabled = !1, setTimeout(e.bind(this.enableClick, this), 20), this.clicklog(this.LOG_ICON_PRESS, "&q=" + this.box.value, "&visible=" + this.vis), this.focusBox(), this.vis ? this.hide() : this.lastUserQuery != this.box.value ? this.doReq() : "" == this.sdiv.innerHTML ? this.doReq() : this.show()) 560 | }, 561 | removeBoxBlur: function() { 562 | this.box.onblur = null 563 | }, 564 | revertBoxBlur: function() { 565 | this.box.onblur = e.bind(this.hide, this) 566 | }, 567 | bindATagWithMouseEvent: function(t, i) { 568 | try { 569 | if (this.hideClose && t.parentNode) return void t.parentNode.removeChild(t) 570 | } catch(s) {} 571 | var n = t.getElementsByTagName("A"); 572 | 0 == n.length && (n = t.getElementsByTagName("a")); 573 | var h = n[0]; 574 | i ? e.events.listen(h, "click", e.bind(this.turnOnSuggest, this)) : e.events.listen(h, "click", e.bind(this.turnOffSuggest, this)), 575 | e.events.listen(h, "mouseover", e.bind(this.removeBoxBlur, this)), 576 | e.events.listen(h, "mouseout", e.bind(this.revertBoxBlur, this)) 577 | }, 578 | onCompleteHint: function() { 579 | setTimeout(e.bind(this.showSugHint, this, arguments[0]), 5) 580 | }, 581 | showSugHint: function() { 582 | this.sdiv.childNodes.length < 1 || this.showContent() 583 | }, 584 | turnOnSuggest: function() { 585 | return this.clicklog(this.CHANGE_SUG_STATUS, "&s=open&q=" + this.box.value), 586 | this.lastUserQuery = "", 587 | this.initVal = this.box.value, 588 | this.curUserQuery = this.initVal, 589 | this.upDownTag = !1, 590 | this.vis && this.hide(), 591 | this.clean(), 592 | !1 593 | }, 594 | turnOffSuggest: function() { 595 | return this.clicklog(this.CHANGE_SUG_STATUS, "&s=close&q=" + this.box.value), 596 | this.vis && this.hide(), 597 | this.clean(), 598 | !1 599 | }, 600 | time: function() { 601 | return "&time=" + new Date 602 | }, 603 | hour: function() { 604 | return "&h=" + (new Date).getHours() 605 | }, 606 | LOG_MOUSE_SELECT: "mouseSelect", 607 | LOG_KEY_SELECT: "keySelect", 608 | LOG_ICON_PRESS: "iconPress", 609 | CHANGE_SUG_STATUS: "changeStatus", 610 | hintCode1: "
", 611 | hintCode2: "
", 612 | hintCode4: "
", 613 | REQUEST_TIMEOUT: 50, 614 | ITEM_INDEX: "s_index", 615 | ITEM_HIGHLIGHT_STYLE: "aa_highlight", 616 | ITEM_TYPE: "hitt", 617 | ITEM_QUERY: "hitq", 618 | ITEM_LINK: "hitl", 619 | QUERY_ATTR: "squery", 620 | KEYFROM_POST: ".suggest", 621 | S_QUERY_URL_POST: "/suggest.s?query=", 622 | S_LOG_URL_POST: "/clog.s?type=", 623 | defSugServ: "https://" + location.host + "/suggest/", 624 | defSearchServ: "http://" + document.domain + "/search?", 625 | defSearchParamName: "q", 626 | defKeyfrom: document.domain.replace(/.youdao.com/, ""), 627 | defSugName: "aa", 628 | sugCookieName: "SUG_STATUS" 629 | }) 630 | } (youdaos); -------------------------------------------------------------------------------- /有道词典/result-min.css: -------------------------------------------------------------------------------- 1 | body{padding:0;margin:0;font-size:14px;font-family:Arial,sans-serif;color:#434343;line-height:24px;background:#fcfcfe}h1,h2,h3,h4,h5,p,ul,form,li,ol,div,dl,dt,dd{margin:0;padding:0}ul,ol,li{list-style:none}img{border:0}a:link,a:visited{color:#35a1d4;outline:0}a.viaInner,.gray a{text-decoration:none}b{font-weight:normal;color:#638c0b}.additional{color:#959595}.sp,.result_navigator .go-top,.add_to_wordbook,.remove_from_wordbook,.desk-dict,.phone-dict,.wordbook,.yd-school,.sina,.netease,.tencent,.renren,.kaixin,.trans-wrapper h3 .toggleOpen,.trans-wrapper h3 .toggle,.more_sp,.more-collapse .more_sp,.video .close,.trans-container div .do-detail,.wt-collapse div .do-detail,.nav-collins .collins-icon,.sub-catalog .split,#editwordform #close-editwordform,.example_see_also .icon,.star{background:url("https://shared-https.ydstatic.com/dict/v2016/result/new-sprite.png") no-repeat;vertical-align:middle;overflow:hidden;display:inline-block}.app::after{content:".";clear:both;display:block;visibility:hidden;height:0}input::-ms-clear{display:none}#topImgAd{height:60px}#scontainer{margin-top:145px;padding-top:30px}#container{width:960px;margin:0 auto 0;zoom:1;padding-left:120px;padding-right:120px}#results{margin-top:25px;float:left;position:relative;width:640px}.results-content{width:640px;float:left;_display:inline}.ads{float:right;width:250px;margin-top:25px}.result_navigator{margin-right:20px;width:120px;margin-top:10px;position:absolute;text-align:center;left:-140px}html{_background-image:url(null)}.rel-search{clear:both}.rel-search a{margin-right:40px}.sub-catalog{margin-bottom:10px}.result_navigator h3{text-align:center;font:12px/27px normal;color:#aaa;margin-bottom:6px}.sub-catalog .split{display:block;background-position:-238px -8px;height:10px;width:120px;margin:0 auto;font-size:0}.example-group .split{margin:3px auto 0}#result_navigator .example-group h3{font-size:13px;margin-left:-18px}.sub-catalog li{font:12px/27px normal}.sub-catalog li a{color:#a0a0a0;display:inline-block;text-decoration:none;width:120px;height:27px}.sub-catalog li a:link,.sub-catalog li a:visited{color:#a0a0a0}.sub-catalog li a:hover,.example-group h3 a:hover{font-weight:bold;color:#434343}.result_navigator .go-top{background-position:-29px 0;display:inline-block;padding-top:24px;height:0;width:25px}.result_navigator .go-top:hover{background-position:-59px 0}.result_navigator .back-to-dict{display:block;margin:15px 0 0 30px;font-size:12px;color:#c9c9c9;text-decoration:none;width:56px;height:19px;line-height:19px;border:2px solid #ddd}.result_navigator .back-to-dict:hover{border:2px solid #a9c7d7;color:#a9c7d7}.nav-collins{position:relative}.nav-collins .collins-icon{position:absolute;top:-5px;left:78px;background-position:0 0;width:24px;height:9px}.example-group li a{text-align:left}.example-group h3 a{color:#000;text-decoration:none}.example-group .catalog-selected a{font-weight:bold;color:#434343}.example-group li{margin-left:16px;padding-left:19px}.example-group .sub-catalog-selected{border-left:2px solid #63bfeb;font-weight:bold}.example-group .sub-catalog-selected a{position:relative;left:-2px;color:#434343}.dict-votebar{text-align:center;padding:0 15px 20px}.dict-votebar:first-child{padding-top:20px}.dict-votebar .up,.dict-votebar .down{display:inline-block;vertical-align:middle;color:#698ebf;height:24px;line-height:24px;width:38px;border-radius:3px;border:0;cursor:pointer;background:#eee;font-weight:700;font-weight:500;text-align:center;position:relative}.dict-votebar .down{margin-left:6px}.dict-votebar .down .vote-arrow{border-bottom-width:0;border-top-color:#95bfdc}.dict-votebar .up .vote-arrow{position:absolute;top:9px;left:50%;margin-left:-6px;border-top-width:0;border-bottom-color:#95bfdc;border-top-style:solid}.dict-votebar .vote-arrow{width:0;height:0;border:6px solid transparent;font-size:0;_border-style:dashed;background-image:none}.dict-votebar .up:hover .vote-arrow,.dict-votebar .up.pressed .vote-arrow{border-bottom-color:#fff}.dict-votebar button{padding:0}.dict-votebar .vote-head{text-align:center;color:#a0a0a0;font-size:12px}.dict-votebar .down:hover .vote-arrow,.dict-votebar .down.pressed .vote-arrow{border-top-color:#fff}.dict-votebar .up:hover,.dict-votebar .down:hover,.dict-votebar .up.pressed,.dict-votebar .down.pressed{background:#95bfdc;color:#fff}.dict-inter,.follow{margin-bottom:15px;font-size:12px}#baidu-adv{margin-bottom:15px}.dict-inter a{text-decoration:none}.dict-inter a:hover{text-decoration:underline}.dict-inter li{float:left;width:114px;height:30px;line-height:30px;text-align:left;overflow:hidden;zoom:1}.pr-link{height:68px;padding:8px 4px 0 8px}.pr-link li{padding:0 4px 5px 0}.pr-link li a{display:inline-block;width:114px;height:30px;color:#a0a0a0;vertical-align:top;cursor:pointer}.pr-link li a:hover,.follow .bd a{text-decoration:none;zoom:1}.text-link{padding:10px 13px 10px 15px;line-height:26px}.desk-dict,.phone-dict,.wordbook,.yd-school{padding-left:46px}.desk-dict{background-position:-379px 4px}.pr-link li a:hover .desk-dict{background-position:-380px -31px} 2 | .phone-dict{background-position:-380px -137px}.pr-link li a:hover .phone-dict{background-position:-380px -171px}.wordbook{background-position:-379px -68px}.pr-link li a:hover .wordbook{background-position:-379px -102px}.yd-school{background-position:-380px -205px}.pr-link li a:hover .yd-school{background-position:-381px -244px}.follow .hd,#dict-inter .hd{margin-top:10px;height:44px;line-height:44px;border-bottom:1px solid #d9d9d9;font-size:14px;font-weight:bold}#dict-inter .hd{margin-top:-15px}.follow .bd iframe{width:250px;margin-top:20px;height:600px}.follow .bd .app{margin-top:20px}.follow .bd .app>a{float:left}.follow .bd .app h3{line-height:14px;font-size:14px;margin-bottom:9px}.follow .bd .app .app-detail{float:left;margin-left:15px;width:120px}.follow .bd .app .app-detail p{line-height:18px;color:#595959}.follow .bd .app .download{float:right;width:54px}.follow .bd .app .download .qr-code-icon{float:right;width:13px;height:13px;background:url("https://shared-https.ydstatic.com/dict/v2016/result/qr.png") no-repeat;margin-bottom:12px;position:relative}.follow .bd .app .download .qr-code-icon:hover{background:url("https://shared-https.ydstatic.com/dict/v2016/result/qr_hover.png") no-repeat}.follow .bd .app .download .qr-code-large{display:none;position:absolute;right:0;top:22px;box-shadow:0 0 3px 3px #e6e6e6;width:100px;height:100px;padding:7px;background:#fcfcfe;z-index:1}.follow .bd .app .download .qr-code-icon:hover .qr-code-large{display:block}.follow .bd .app .download .download-button{display:block;float:right;width:100%;width:50px;height:23px;border:1px solid #c2c2c1;text-align:center}.follow .bd .app .download .download-button:hover{border:1px solid #e00013;color:#e00013}.sina,.netease,.tencent,.renren,.kaixin{padding-top:40px}.sina{background-position:-319px -283px}.follow a:hover .sina{background-position:-377px -283px}.netease{background-position:-210px -285px}.follow a:hover .netease{background-position:-260px -285px}.tencent{background-position:10px -212px}.follow a:hover .tencent{background-position:-41px -213px}.renren{background-position:-92px -283px}.follow a:hover .renren{background-position:-152px -283px}.kaixin{background-position:10px -283px}.follow a:hover .kaixin{background-position:-35px -283px}#phrsListTab{margin-top:0;overflow:hidden}#phrsListTab .phonetic{font-size:14px}#phrsListTab .trans-container{margin:1em 0 2em 0;color:#000}#phrsListTab h2{line-height:30px;font-size:24px;margin-bottom:5px;overflow:hidden;zoom:1;word-break:break-all}.keyword{margin-right:1px}#phrsListTab .trans-container li{font-weight:bold;color:#434343}.img-list{float:right}.img-list img{height:80px;padding:2px;border:1px solid #e5e5e5}.add-fav{display:inline-block;width:23px;height:19px;background-position:-168px -132px;outline:0}.add-fav:hover{background-position:-168px -149px}.add-faved{outline:0;background-position:-209px -132px}.add-faved:hover{background-position:-209px -149px}#phrsListTab h2 .dictvoice,#jcTrans h2 .dictvoice{outline:0;display:inline-block;width:16px;height:25px;background-position:-121px 0;*background-position:-121px 3px;vertical-align:bottom;margin-left:.4em}#jcTrans h2 .dictvoice{background-position:-121px 5px;*background-position:-121px 3px}#phrsListTab h2 .dictvoice:hover,#jcTrans h2 .dictvoice:hover{background-position:-92px 0;*background-position:-92px 3px}#jcTrans h2 .dictvoice:hover{background-position:-92px 5px;*background-position:-92px 3px}#phrsListTab h2 .baav .dictvoice,#jcTrans h2 .baav .dictvoice{background-position:-121px 3px;*background-position:-121px 0}#phrsListTab h2 .baav .dictvoice:hover,#jcTrans h2 .baav .dictvoice:hover{background-position:-92px 3px;*background-position:-92px 0}#phrsListTab h2 .humanvoice{vertical-align:top;width:18px;height:20px;background-position:-179px 4px}#phrsListTab h2 .humanvoice:hover{background-position:-149px 4px}.dictvoice{vertical-align:middle;width:10px;height:21px;background-position:-47px -36px}.dictvoice:hover{background-position:-32px -36px}.humanvoice{vertical-align:top;width:18px;height:18px;background-position:-161px -110px}.humanvoice:hover{background-position:-188px -110px}.add_to_wordbook{vertical-align:top;background-position:-70px -30px;*background-position:-70px -32px;width:24px;padding-top:26px;height:0;margin-left:.5em;*margin-left:.75em}.add_to_wordbook:hover{background-position:-99px -30px;*background-position:-99px -32px}.remove_from_wordbook{vertical-align:top;background-position:0 -31px;*background-position:0 -33px;width:22px;padding-top:26px;height:0;margin-left:.5em;*margin-left:.75em}.remove_from_wordbook:hover{background-position:-205px 7px;*background-position:-205px 5px}.pos{margin-right:.1em}.def{margin-left:.1em}.trans-wrapper{position:relative;zoom:1}.trans-wrapper h3{font-size:14px;position:relative;margin:0 0 .7em 0;height:26px;line-height:26px;border-bottom:2px solid #ddd}.trans-wrapper h3 .toggle{position:absolute;top:12px;right:0;cursor:pointer;width:11px;height:20px;background-position:-181px -37px}.trans-wrapper h3 .toggle:hover{background-position:-202px -37px} 3 | .trans-wrapper h3 .toggleOpen{background-position:-138px -37px}.trans-wrapper h3 .toggleOpen:hover{background-position:-160px -37px}.tabs a,.tab-current{font-weight:normal;text-decoration:none;display:inline-block;border-bottom:2px solid #fff;height:26px;_position:relative;_top:2px}.tabs a span{cursor:pointer}.tabs a.tab-current{cursor:default}.tabs a.tab-current span{cursor:default;color:#434343;font-weight:bold}.tabs a span,.tab-current span{display:inline-block;padding:0 20px;height:26px;border-bottom:2px solid #bfbfbf;margin-right:2px;_position:relative;_top:2px}.results-content .tabs a:hover span{border-bottom:2px solid #5fc4f3}.results-content .tab-current span{border-bottom:2px solid #5fc4f3;color:#434343;font-weight:bold}.trans-container{margin:.9em 0 1.8em}.trans-container p,.trans-container li{line-height:24px}.trans-container .ol{margin:8px 15px 0 20px;margin-left:33px \9}.trans-container .ul{margin:8px 15px 18px 0}.trans-container .ol li{list-style:decimal;margin:0 0 .7em 0}.trans-container .ul li{margin:0 0 1em 0}.ar{color:#a0a0a0;font-size:12px}p.via,span.via{color:#959595}p.via{font-size:12px}p.via a{color:#35a1d4;font-size:12px}p.additional a{color:#35a1d4;text-decoration:none}.s1{margin:0 3px}.trans-container h4 sup{font-size:.77em;font-weight:normal}#examples_sentences .allExplanation{float:right;margin:0 15px 0 0;text-decoration:none;background:#63bfeb;color:#fff;padding:0 5px}#examples_sentences .allExplanation:hover{background:#a4d5ec}.search_result{margin:.83em 0 1.25em}.search_result .sresult_content{margin:.45em 0}.search_result .sresult_link{color:#54903f;text-decoration:none}.phonetic,.field,.origin,.complexfont{font-weight:normal;color:#666;margin:0 .1em}h4 .field,h4 .origin,h4 .def,h4 .collapsed-def{margin-left:10px;font-size:12px}.phonetic{color:#a0a0a0;font-family:"lucida sans unicode",arial,sans-serif}.dif{color:gray}.wordGroup .contentTitle{font-weight:bold;color:#35a1d4;margin-right:.5em}.wordGroup .contentTitle a{text-decoration:none;color:#35a1d4}.wordGroup .century21{cursor:pointer;width:16px;display:inline-block;height:16px;background:url("https://shared-https.ydstatic.com/dict/v2016/result/new-sprite.png") no-repeat -110px -150px;text-decoration:none}.wordGroup a.century21:hover{background:url("https://shared-https.ydstatic.com/dict/v2016/result/new-sprite.png") no-repeat -110px -132px}.ol .sense-ex{margin:0;padding:0;list-style:none;color:gray}.ol .sense-ex li{list-style:none;margin-bottom:0}.sense-ex .exam-sen{padding-left:2.5em}.sense-ex ul,.sense-ex ul li{margin:0;padding:0}#tWebTrans .wt-container p{margin:0 15px}#webPhrase{margin-top:10px}.wt-container .collapsed-def{display:none}.wt-collapse .collapsed-def{display:inline;font-weight:normal}.wt-collapse .collapse-content{display:none}.phrase-collapse .wt-more{display:none}.pr-container .title,.wt-container .title{font-weight:bold;position:relative}.wt-container .title span{zoom:1;vertical-align:middle}#tWebTrans .wt-container p{margin:.45em 15px}#results-content .wt-container div a{color:#2b2b2b;text-decoration:none}#results-content #collinsResult .wt-container div a{color:#35a1d4}.trans-container div .do-detail{background-position:-194px -74px;width:12px;height:12px;margin-right:5px}.trans-container div .do-detail:hover{background-position:-219px -74px}.wt-collapse div .do-detail{background-position:0 -74px}.wt-collapse div .do-detail:hover{background-position:-17px -74px}.wt-container a{outline:0}.more{height:18px;width:200px;line-height:18px;_line-height:23px;margin:15px 0;color:#35a1d4}.more_sp{background-position:-39px -74px;width:12px;height:12px;text-decoration:none}.more_sp:hover{background-position:-58px -74px}.more-collapse .more_sp{background-position:-76px -74px}.more-collapse .more_sp:hover{background-position:-96px -74px}.unfold{display:none}.more-collapse .collapse{display:none}.more-collapse .unfold{display:inline}.show_more{display:none}.more-collapse .show_more{display:inline}.show_less{display:inline}.more-collapse .show_less{display:none}.gram{display:block;font-style:normal;font-weight:normal;color:gray}.related-lan{padding:1em 22px}.related-lan a{margin-left:.5em}.hh-collapse .hh-more{display:none}.more-hh{width:103px;padding-top:20px;margin:.7em 0 0;background-position:-309px -90px}.hh-collapse .more-hh{background-position:-206px -90px}a.more-example{text-decoration:none;color:#35a1d4}.eBaike .trans-container{margin:.5em 0 1em}.eBaike .eBaike-index{overflow:hidden;zoom:1;padding-bottom:15px;border-bottom:1px dotted #ddd;line-height:24px}.eBaike-index .content{line-height:24px}.eBaike-detail .eBaike-index{border:0}.eBaike h2{font-size:12px;font-weight:normal;overflow:hidden;zoom:1}.eBaike h2 .subject{font-size:24px;float:left;margin:0 .5em 0 .3em;font-weight:bold}.eBaike .title{text-decoration:none;line-height:24px;font-weight:bold}.eBaike .img_r{float:right;margin-left:1em}.eBaike .img{background:#f7f7f7;border:1px solid #e1e1e1;color:#666;padding:3px;text-align:center}.eBaike .img strong{clear:both;display:block;font-size:12px;font-weight:normal;line-height:18px;overflow:hidden} 4 | .eBaike .see_more{text-decoration:none}.eBaike .ar{float:right}#bk .ar{float:none;text-align:right}#baike h2{font-size:1em;overflow:auto;zoom:1}#baike h2 .subject{font-size:1.13em;float:left}#baike h2 .via{float:right;color:#959595;font-weight:normal}#baike .via img{vertical-align:middle}#baike .description{line-height:1.5em;overflow:hidden;zoom:1}#baike .sub-title{float:left}#baike h3{background:0;font-size:1em;height:1.9em;line-height:1.9em;margin:.83em 0 .83em;position:relative;padding-left:5px;border-top:1px dotted #e0e0e0}#baike .trans-container .content{overflow:hidden;zoom:1;line-height:1.5em;margin:.25em 0}#baike .trans-container .content .term{font-weight:bold}#baike .trans-container a{text-decoration:none}#baike .img{background:#f7f7f7;border:1px solid #e1e1e1;color:#666;padding:3px;text-align:center}#baike .img_r{float:right;margin-left:1em}#baike .img strong{clear:both;display:block;font-size:1em;font-weight:normal;line-height:1.5em;overflow:hidden}#baike .img_l{float:right;margin-left:1em}#baike .table{border-spacing:0;border-collapse:collapse;empty-cells:show;background:#f7f7f7;border:1px solid #e1e1e1}#baike .table td{background:#fafafa;border:1px solid #DDD;padding:10px;vertical-align:top}#baike .suggests .suggest{border-top:1px dotted #e0e0e0;margin:10px 0;padding-top:10px;overflow:hidden;zoom:1}.example_content .content_title .boldWord{font-weight:bold}.example_content .content_title .boldWord a,.example_content .content_title .boldWord a:link,.example_content .content_title a:visited{text-decoration:none}.example_content .content_title .tabLink a:hover{color:#313131}.example_content .content_title .tabLink a.selected_link{cursor:default;color:#313131}.example_content .allExplanation{float:right;margin:2px 15px 0 0;width:85px;height:24px;text-decoration:none;font-weight:bold;background:url("https://shared-https.ydstatic.com/dict/v2016/result/new-sprite.png") no-repeat 0 -168px}.example_content a.allExplanation:hover{background-position:-85px -168px}.example_content .error-wrapper{margin:20px 10px 20px 0}.video{position:relative}.video .play{display:inline-block;position:relative}.video .close{width:14px;height:14px;background-position:-117px -72px;position:absolute;top:-1px;left:330px}.video .close:hover{background-position:-141px -72px}.video .playicon{cursor:pointer;position:absolute;width:30px;height:30px;top:50%;left:50%;margin-left:-15px;margin-top:-15px}.remind{margin:1.1em 0 2.85em}.remind a{text-decoration:none}.example_see_also{overflow:hidden;zoom:1}.example_see_also .info{float:left;width:185px;height:110px;margin:5px 10px 0 0;display:inline;background:#f5f5f5;cursor:pointer;text-decoration:none;padding:0 10px}a.info:hover{background:#78c8e3}.example_see_also div{line-height:24px;margin:10px 20px 10px 0}.example_see_also .info .description{color:#a0a0a0;font-size:12px;margin:0}.example_see_also .info:hover .description,.example_see_also .info:hover .title{color:#fff}.example_see_also .icon{display:inline-block;height:33px;width:46px;vertical-align:middle}.example_see_also .title{margin-left:10px;font-size:14px}.example_see_also .bilingual .icon{background-position:-46px -107px;width:50px}.example_see_also .originalSound .icon{background-position:6px -107px}.example_see_also .authority .icon{background-position:-96px -107px;width:50px}.bottom{position:absolute;bottom:0}.def a{text-decoration:none}.highLight{background:#e8e5cb}.dont_show_nav #example_navigator{display:none}.dont_show_nav #example_content{margin-left:15px}#tPowerTrans p.additional{margin:5px 0;text-indent:20px}#tEETrans p.additional{margin:5px 0;text-indent:14px}#tEETrans .ol p em,.gray{color:#a0a0a0}.wordTrans{display:inline-block;width:13px;height:13px;background-position:-183px -237px;cursor:pointer}.wordTrans a{color:#2779b6}.errorTip{height:18px;line-height:18px;font-weight:normal}#transDevotion form p{margin:8px 0;line-height:1.5em}#transDevotion form label{margin:0 .83em 0 0}#transDevotion form .center{margin:0 6px 0 0}.trans-container textarea.trans-content,.trans-container input.trans-message{vertical-align:middle;font-size:12px;font-weight:normal;width:220px}.trans-container textarea.trans-content{height:70px;resize:none}#transDevotion form .submitTip{margin-left:70px}#transDevotion .error-message{margin:0 0 -5px 72px;display:block}.trans-container .ensure,.trans-container a.ensure:hover{display:inline-block;width:61px;height:24px;cursor:pointer}.trans-container .ensure{background-position:-61px -237px}.trans-container .ensure:hover{background-position:0 -237px}.trans-container .ensure:active{background-position:-122px -237px}.trans-container .cancel,.trans-container .cancel:hover{display:inline-block;width:61px;height:24px;margin-left:30px;cursor:pointer}.trans-container .cancel{background-position:-61px -261px}.trans-container .cancel:hover{background-position:0 -261px}.trans-container .cancel:active{background-position:-122px -261px}.trans-container .alignCenter span{display:none}.collins .wordGroup{color:#35a1d4;margin-bottom:6px}.collins .wordGroup .contentTitle{color:#a0a0a0;font-weight:normal} 5 | .collins .more{float:right;margin:-23px 0;width:auto}.wt-container .trans-tip{font-weight:normal;color:#9d9d9d}.wt-container .trans-content{font-weight:normal}.wt-container .collins-intro{border:1px solid #cdcdcd;padding:6px 0 6px 5px;color:#959595}.trans-content ol{padding-left:23px}.trans-content .pattern,.trans-content .spell{font-weight:normal;margin-left:5px;font-size:12px}.collins-phonetic{font-family:"lucida sans unicode",arial,sans-serif}.trans-content .pattern{margin-left:15px}.trans-content .title,.trans-content .rank{font-size:14px;color:#313131;font-weight:bold}.trans-content .rank{margin-left:10px}.trans-content .trans{margin-left:10px;color:#313131}#collins .additional span{display:inline-block;margin-left:-1.83em}#collins .additional p{padding-left:1.83em}.usage{margin-left:15px}#collinsResult .ol{margin:8px 0 0 0}#collinsResult .ol li{list-style:none}#collinsResult .ol .collinsMajorTrans{background:#eff5f8;padding:5px 5px 5px 25px;width:525px}#collinsResult .collinsOrder{float:left;margin-left:-15px}#collinsResult .exampleLists{margin:15px 0 15px 40px;color:#626262}.collinsMajorTrans .additional{color:#626262}#collinsResult .examples{padding-left:15px}#collinsResult b{font-weight:bold}.collinsToggle h4 .title{color:#eb6100}.star{margin-left:10px;background-position:-225px -112px;_background-position:-225px -110px;height:14px;vertical-align:middle}.star5{width:64px}.star4{width:51px}.star3{width:37px}.star2{width:25px}.star1{width:11px}.p-type{text-decoration:none}.p-type:hover{color:#313131}.type-list{line-height:24px}.type-list .boldWord{margin-right:10px}.type-list ins{text-decoration:none;margin:0 10px;color:#959595;cursor:default}.type-list a:hover{color:#434343}.type-list .selected_link{color:#434343}#tPETrans .additional{font-size:12px}#tPETrans .additional a{text-decoration:underline}#tPETrans .type-list .selected_link{cursor:default;color:#313131}#tPETrans .all-trans{margin:16px 0}.all-trans .types .items{margin-bottom:16px;list-style:none}.all-trans .types .items a:link,.all-trans .types .items a:visited{font-size:12px;color:#959595;text-decoration:none}.all-trans .types .items a:hover{text-decoration:underline}.all-trans .items .title{font-weight:bold}.all-trans .types{display:none;margin:0}.all-trans .types .source{margin-top:6px}.all-trans .types .trans{margin-bottom:6px}.clearfix:after{content:".";display:block;height:0;visibility:hidden;clear:both}.clearfix{zoom:1}#results .middot{font-size:20px;_font-size:12px;font-weight:bold;vertical-align:middle;margin:0}#simple_dict_trans ul .small-lang{zoom:1}.ead_line div{zoom:1}.need-help{display:inline-block;margin-left:2px;margin-top:2px;background-position:-166px -72px;width:20px;height:16px}.wt-container div.title span,#webPhrase .more,#hhTrans .more-hh,#word_phrs .more,#collinsResult .pr-container .more{cursor:pointer}#examples_sentences,#examples_sentences .trans-container{margin-top:0}#examples_sentences .ol{margin-top:25px}.example-via a{color:#959595;text-decoration:none;font-size:12px}.error-typo{margin:0 0 15px 0;padding:9px 5px 14px;border:1px solid #e1deb6;background-color:#f9f8eb}.error-typo h4{margin:0 0 13px}.error-typo .typo-rel{margin:8px 0 0}.error-typo .typo-rel .title{font-weight:bold}.error-typo .typo-rel .title a{text-decoration:none}#relatedLan .trans-container a{text-decoration:none;margin-left:10px}.pm{display:none;position:absolute;z-index:1000;width:70px;background:white}.pm a{display:block;padding:4px 3px;text-decoration:none;zoom:1;color:#656565}#langSelection{width:120px;box-shadow:0 2px 1px 1px #e6e6e6;font-size:14px}#langSelection li{height:42px;line-height:42px;text-align:center}#langSelection a:hover{color:#000;background:#f5f5f5}#handWrite{width:346px;height:216px;border:1px solid #bfbfc8}#editwordform{position:absolute;top:222px;left:50%;padding:0 2px 2px;z-index:3;font-size:12px;background:#5db3dd;display:none;margin-left:-340px}#editwordform h3{line-height:26px;color:#fff;font-weight:bold;font-size:14px;padding-left:3px;margin:0}#editwordform form{background:#fff;border:#74abc6 solid 1px;padding:8px}#editwordform #wordbook-word{width:150px;display:inline;vertical-align:middle}#editwordform #wordbook-phonetic{font-family:"lucida sans unicode",arial,sans-serif}#editwordform #delword{margin-left:5px;vertical-align:middle;line-height:20px}#editwordform label{display:block;line-height:24px}#editwordform input{width:260px;display:block;height:18px}#editwordform select{width:150px}#editwordform #wordbook-desc{width:260px;height:50px;font-size:12px;display:block}#editwordform #addword,#editwordform #openwordbook{width:82px;height:30px;display:inline-block;color:#fff;line-height:30px;font-weight:bold;text-decoration:none}#editwordform #addword{background:#a7c36c;margin:10px 0 5px 0}#editwordform #openwordbook{background:#95cbe5;margin:10px 30px 5px 0}#editwordform #close-editwordform{position:absolute;background-position:-235px -32px;right:3px;top:4px;width:20px;height:20px;display:inline-block} 6 | #editwordform #close-editwordform:hover{background-position:-267px -32px}#editwordform #tag-select-list{width:262px;border:1px solid #ccc;display:none;position:absolute;background:#e3eff8;_left:10px;#left:10px;padding:0}#editwordform #tag-select-list li{display:block;width:100%}.error-note strong{word-wrap:break-word;word-break:break-all}#fanyiToggle .additional{font-size:12px}#research,#research21{position:absolute;top:6px;right:35px;text-decoration:none;padding-left:18px;background:url("https://shared-https.ydstatic.com/dict/v2016/result/research.png") 3px 3px no-repeat;height:18px;line-height:18px;font-size:12px}#researchZoon,#researchZoon21{width:250px;height:261px;border:2px solid #5db3dd}#researchZoon .title,#researchZoon21 .title{height:26px;line-height:26px;background-color:#5db3dd;color:#fff;font-weight:bold;padding-left:3px}#researchZoon .zoo-content,#researchZoon21 .zoo-content{font-size:12px;padding:12px 0 0 7px}#researchZoon a,#researchZoon21 a{padding:0;margin:0}#researchZoon .zoo-content p,#researchZoon21 .zoo-content p{margin:0 0 12px 12px;height:18px;line-height:18px}#researchZoon .zoo-content p input,#researchZoon21 .zoo-content p input{margin-right:6px}#researchZoon .submitResult,#researchZoon21 .submitResult{margin:14px auto 0 auto;width:82px;height:30px;line-height:30px;text-align:center;color:#fff;font-weight:bold;background-color:#a7c36c;display:block}#researchZoon label,#researchZoon21 label{cursor:pointer}#researchZoon a,#researchZoon21 a{display:inline}.baav{margin-top:3px}#pronounce{width:284px;position:absolute;left:10px;line-height:1.5em;z-index:3;display:none}.pr-source{text-align:right;color:#999;margin:6px 0}#pronounce .pr-content{padding:12px 12px 0 12px;margin:2px;border:1px solid #7bbeef;background-color:#fff;_border-width:2px}.keyword{vertical-align:bottom;margin-right:15px}.amend,.addExp{border:1px solid #35a1d4;border-radius:4px;text-decoration:none;padding:3px;font-size:.4em;vertical-align:bottom}.amend{margin-left:10px;color:#35a1d4!important}.addExp{margin-left:12px}#pronounce .alpha-bg{height:100%;width:100%;position:absolute;z-index:-1;*left:0;background-color:#7bbeef;opacity:.3;filter:Alpha(opacity=30);_width:0}.pronounce{margin-right:30px;font-size:14px;color:#666;display:inline-block;line-height:26px}.pronounce .phonetic{margin-left:.2em}.phonetic{font-family:"lucida sans unicode",arial,sans-serif}.t1 #chromeBox{height:45px;line-height:45px;background:url('https://shared-https.ydstatic.com/dict/v2016/result/t1-bg.png') repeat-x;border-bottom:1px solid #cccdce}.t1 .chromeWrap{width:960px;margin:0 auto;color:#6d6e6d;font-size:12px}.t1 #chromeBox .tips{display:inline-block;height:45px;line-height:45px}.t1 .chromeWrap .chromeExploreTip{font-weight:bold;margin-right:5px}.t1 .chromeWrap .toUse{margin-left:10px;background:url('https://shared-https.ydstatic.com/dict/v2016/result/for-chrome.png') no-repeat 0 8px;display:inline-block;width:141px;height:45px;text-indent:-9999px}.t1 .chromeWrap .no-remind{display:inline-block;float:right;width:13px;padding-top:30px;height:0;overflow:hidden;background:url('https://shared-https.ydstatic.com/dict/v2016/result/for-chrome.png') no-repeat -143px 18px;cursor:pointer}.dropHeight1 #custheme{top:46px}.dropHeight0 #custheme{top:33px}.t0 #chromeBox{height:32px;line-height:32px;background:#ffffe1;border-bottom:1px solid #aca899}.t0 .chromeWrap{width:960px;margin:0 auto;color:#000;font-size:12px}.t0 .chromeWrap a:link,.t0 .chromeWrap a:visited{color:#046f9c;outline:0;text-decoration:none}.t0 .chromeWrap .chromeExploreTip{color:#aaaa96;display:inline-block;padding-left:19px;margin-right:5px;background:url('https://shared-https.ydstatic.com/dict/v2016/result/for-chrome.png') no-repeat -143px -18px}.t0 .chromeWrap .no-remind{float:right;margin-top:6px;display:inline-block;width:16px;padding-top:16px;height:0;overflow:hidden;background:url('https://shared-https.ydstatic.com/dict/v2016/result/for-chrome.png') no-repeat -158px 0;cursor:pointer;zoom:1}.c-topbar-wrapper{width:100%;background-color:#fcfcfe;box-shadow:0 0 3px 3px #e6e6e6;height:145px;position:absolute;top:-145px;z-index:3;min-width:1200px}.c-topbar-wrapper.setTop{position:fixed;top:0;left:0;height:91px}.c-topbar{height:54px;line-height:54px;color:#626262;font-size:14px}.c-topbar.setTop{display:none}.c-subtopbar{width:960px;margin:0 auto;position:relative;zoom:1;z-index:3}.c-topbar a{text-decoration:none;color:#333}.c-snav{float:left;color:#ddd}.c-snav a,.c-snav b{text-align:center;display:inline-block;margin-right:45px}.c-snav a:hover,.c-snav b{text-decoration:none;color:#e00013}.c-snav b{color:#000;font-weight:bold}.c-snav .ne163:hover{border:0;text-decoration:underline}.c-sust{float:right;margin:0 10px 0 0;display:inline;position:relative}.c-sust>div{float:left}.c-sust a.login:hover{text-decoration:none;color:#e00013;border:1px solid #e00013}.c-sust .c-sp{margin:0 10px;color:#ddd}.c-sust .ac{position:relative;z-index:99}.c-sust .ac .ai{font-size:9px}.c-sust .dm{position:absolute;left:0;top:21px;width:138px;text-align:left;line-height:26px;background-color:#fff;border:1px solid #bfbfc8}.c-sust .dm a{display:block;padding:0 0 0 8px}#uname{font-weight:bold;color:#626262;cursor:pointer;position:relative;padding:0 5px 0 5px;display:block;line-height:54px;height:54px;float:left;overflow:hidden;max-width:270px;min-width:108px;white-space:nowrap;text-overflow:ellipsis;-o-text-overflow:ellipsis} 7 | #uname.nolist{min-width:unset}#uname u{height:21px;vertical-align:top}#unameop{border:1px solid #dcdddd;font-size:13px;background-color:#fff;position:absolute;top:40px;left:47px}#unameop a{color:#2a2a2a;display:block;line-height:24px;margin:0;padding:2px 0 0 9px;text-decoration:none;float:none;font-weight:normal}#unameop a:hover{background:#f5f5f5;color:#000;text-decoration:none}.login{width:62px;height:26px;border:1px solid #e6e6e6;display:inline-block;margin-top:13px;line-height:26px;text-align:center}.c-header{position:relative;clear:both;width:960px;margin:0 auto;margin-top:17px}.c-logo{display:inline-block;overflow:hidden;width:164px;height:0;padding-top:37px;vertical-align:top;background:url("https://shared-https.ydstatic.com/dict/v2016/result/logo.png") no-repeat;margin:6px 40px 0 0;float:left;_display:inline}.c-fm-w{position:relative;display:inline-block;z-index:2}.s-inpt-w{display:inline-block;width:620px;height:47px;line-height:47px;border:1px solid #e6e6e6;border-radius:5px}.s-inpt{border:0;background:transparent;width:484px;font-size:14px;overflow:hidden;margin-left:136px;outline:0}#bs .s-inpt{width:515px;margin:0;padding-left:5px}.s-btn{cursor:pointer;height:47px;width:113px;font-size:16px;text-align:center;border:0;background-color:#dd0a20;background-image:-webkit-linear-gradient(top,#ea1e4c,#dd0a20);background-image:-moz-linear-gradient(top,#ea1e4c,#dd0a20);background-image:-o-linear-gradient(top,#ea1e4c,#dd0a20);background-image:linear-gradient(top,#ea1e4c,#dd0a20);font-size:16px;color:#fff;border-radius:5px;margin-left:19px;outline:0}.s-btn:hover{background-color:#dd0a20;background-image:-webkit-linear-gradient(top,#fa2f6a,#f50e2d);background-image:-moz-linear-gradient(top,#fa2f6a,#f50e2d);background-image:-o-linear-gradient(top,#fa2f6a,#f50e2d);background-image:linear-gradient(top,#fa2f6a,#f50e2d)}.c-fm-ext{font-size:12px;padding:0 0 0 10px}.c-fm-ext a{font-size:12px;color:#999;text-decoration:none}.c-fm-ext a:hover{text-decoration:underline}.header-pic-tg{position:absolute;right:2px;top:0;z-index:0}.header-pic-tg img{border:1px solid #fff;font-size:0}.c-header-ext .header-pic-tg{right:0}.c-header-ext .header-pic-tg img{border:0}.c-bsearch{margin:35px 0 0;color:#999}.c-bsearch-box,.c-bsearch .fblinks{width:794px;margin:0 auto;position:relative;padding-left:166px}.c-bsearch .c-fm-w{margin:0}.c-bsearch a{color:#999;text-decoration:none;font-size:12px}.c-bsearch .fblinks{margin:11px auto 0 auto}.c-bsearch .fblinks a{color:#666}.c-bsearch .fblinks a:hover{text-decoration:underline}#c_footer{height:80px;line-height:80px;background-color:#f6f6f9;font-size:12px;text-align:center;color:#999}#c_footer a:hover{text-decoration:none;color:#000}#c_footer .wrap{width:960px;margin:auto}#c_footer .wrap>a{float:left;margin-right:30px}#c_footer a{color:#999;text-decoration:none}#c_footer .c_fnl{color:#ddd;margin:0 10px}#c_footer .c_fcopyright{float:right}.langSelector{position:absolute;top:7px;_top:12px;font-size:14px;cursor:pointer;color:#656565;line-height:auto}.langSelector .langText{width:85px;height:37px;line-height:37px;display:inline-block;text-align:center;background-color:#f3f3f3;border-radius:5px 0 0 5px;margin-left:5px;font-family:airal,'Microsoft YaHei',\5FAE\8F6F\96C5\9ED1}.langSelector .aca{background-color:#e5e5e5;width:30px;display:inline-block;height:37px;line-height:37px;border-radius:0 5px 5px 0;text-indent:-9999px;font-family:airal,'Microsoft YaHei',\5FAE\8F6F\96C5\9ED1}.langSelector .arrow{width:0;height:0;border-width:5px;border-style:solid;border-color:#000 #e5e5e5 #e5e5e5;position:absolute;left:100px;top:16px}.langSelector .arrow-up{border-color:#e5e5e5 #e5e5e5 #000;top:11px}.aca_btn_on{background-position:-341px 0}.hand-write{background:url("https://shared-https.ydstatic.com/dict/v2016/result/pic.gif") no-repeat -356px 0;display:inline-block;padding-top:16px;height:0;width:16px;overflow:hidden;position:absolute;top:15.5px;left:594px;cursor:pointer}.hnw_btn_hover{background-position:-375px 0}.hnw_btn_on{background-position:-394px 0}.remindtt752{padding:.2em;color:gray;font-size:14px}.remindtt75{padding-left:.2em;font-size:14px;height:30px;line-height:23px;font-weight:bold;text-indent:10px}.remindtt75 span{font-weight:normal;margin-right:2px}.aa_highlight{background:#f1f2f6}#custheme{background:url("https://shared-https.ydstatic.com/dict/v2016/result/skin.png") no-repeat;height:93px;left:0;overflow:hidden;position:absolute;top:0;width:100%;z-index:-1}@media only screen and (-webkit-min-device-pixel-ratio:1.5),only screen and (min--moz-device-pixel-ratio:1.5),only screen and (-o-min-device-pixel-ratio:3/2),only screen and (min-device-pixel-ratio:1.5),only screen and (min-resolution:192dpi){.c-logo{background-image:url("https://shared-https.ydstatic.com/dict/v2016/result/logo-result2x.png");background-size:164px 37px}}.dialog-guide-download{position:fixed;left:0;right:0;bottom:0;top:0;width:100%;height:100%;background-color:rgba(0,0,0,.5);filter:progid:DXImageTransform.Microsoft.gradient(startcolorstr=#7F000000,endcolorstr=#7F000000);text-align:center;z-index:50} 8 | .dialog-guide-download i,.dialog-guide-download s{display:inline-block;*display:inline;zoom:1;vertical-align:middle}.dialog-guide-download s{height:100%;width:0}.dialog-guide-download i{position:relative}.dialog-guide-download i a{position:absolute;display:block}.dialog-guide-download.mac-download i{width:1004px;height:494px;background:url("https://shared-https.ydstatic.com/dict/v2016/result/popup-mac-download.png") no-repeat}.dialog-guide-download.mac-download i a{width:246px;height:64px;top:385px}.dialog-guide-download.mac-download i .close{position:absolute;width:50px;height:50px;right:0;top:45px}.dialog-guide-download.mac-download .down{left:255px}.dialog-guide-download.mac-download .appstore{left:550px}.dialog-guide-download.win-download i{width:966px;height:456px;background:url(https://shared-https.ydstatic.com/dict/v2016/result/popup-win-download.png) 50% 50% no-repeat}.dialog-guide-download.win-download i .close{position:absolute;width:30px;height:30px;right:15px;top:15px}.dialog-guide-download.win-download .down{width:240px;height:60px;top:348px;left:362px} -------------------------------------------------------------------------------- /有道词典/有道词典.ahk: -------------------------------------------------------------------------------- 1 | ; 不要用 neutron 库来写 2 | ; 用 neutron.qs(".main").innerHTML := template 更新内容的话,切换不了网络释义等模块 3 | ; 用 neutron.doc.write(template) 的话,会嵌套出多个 和 ,导致定位失效 4 | class youdao 5 | { 6 | dict(word, url := "") 7 | { 8 | static wb 9 | 10 | ; 取得待显示的内容 11 | 设置= 12 | (`% 13 | ExpectedStatusCode:200 14 | NumberOfRetries:3 15 | ) 16 | 请求头= 17 | (`% 18 | User-Agent:Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3314.0 Safari/537.36 SE 2.X MetaSr 1.0 19 | ) 20 | ret := WinHttp.Download(url ? url : "https://dict.youdao.com/w/" word, 设置, 请求头) ; 下载并存到变量 21 | 22 | part1 := GetTag(ret, "

") ; 单词 23 | part2 := GetTag(ret, "
") ; 单词翻译 24 | part3 := GetTag(ret, "
35 | 提示:抱歉没有找到“%word%”相关的词 36 |
37 | 38 | 50 | ) 51 | else 52 | ; 找到了 53 | template= 54 | ( 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 |
64 |
65 | %part1% 66 | %part2% 67 |
68 | %part3% 69 | %part4% 70 | ) 71 | 72 | ; 首次运行则创建窗口 73 | if (!IsObject(wb)) 74 | { 75 | Gui youdao_dict:Add, ActiveX, x0 y0 w480 h360 vwb, Shell.Explorer 76 | 77 | this.wb := wb 78 | wb.silent := true ; 屏蔽 js 脚本错误提示 79 | wb.Navigate("about:blank") ; 打开空白页 80 | ComObjConnect(wb, this.wb_events) ; 修复内部跳转(通常是相关单词) 81 | 82 | ; 获取屏幕物理尺寸 83 | hdcScreen := DllCall("GetDC", "UPtr", 0) 84 | devicecaps_w := DllCall("GetDeviceCaps", "UPtr", hdcScreen, "Int", 4) ; 毫米 85 | devicecaps_h := DllCall("GetDeviceCaps", "UPtr", hdcScreen, "Int", 6) ; 毫米 86 | devicecaps_size := (Sqrt(devicecaps_w**2 + devicecaps_h**2)/25.4) ; 英寸 勾股求斜边 87 | 88 | ; 根据屏幕大小设置缩放比例 89 | if (devicecaps_size <= 15) 90 | scale := 1.4 91 | else if (devicecaps_size <= 17) 92 | scale := 1.25 93 | else 94 | scale := 1.1 95 | 96 | ; 设置缩放比例,此处比例是根据显示器尺寸与 dpiscale 综合得来 97 | zoom := (A_ScreenDPI/96*100)*scale & 0xFFFFFFFF ; ensure INT 98 | wb.ExecWB(OLECMDID_OPTICAL_ZOOM:=63, OLECMDEXECOPT_DONTPROMPTUSER:=2, zoom, 0) 99 | 100 | ; 让热键功能比如 Ctrl+C 等生效 101 | BoundFunc := ObjBindMethod(this, "WM_KeyPress") 102 | OnMessage( 0x0100, BoundFunc ) ; WM_KEYDOWN 103 | OnMessage( 0x0101, BoundFunc ) ; WM_KEYUP 104 | 105 | while wb.Busy 106 | sleep 10 107 | } 108 | 109 | ; 更新内容 110 | wb.document.write(template) 111 | wb.document.close() 112 | 113 | ; 显示 114 | Gui youdao_dict:Show, w480 h360,有道词典 115 | return 116 | 117 | 有道词典GuiClose: 118 | Gui youdao_dict:Hide 119 | return 120 | } 121 | 122 | ; 修复内部跳转(通常是相关单词) 123 | class wb_events 124 | { 125 | BeforeNavigate2(pDisp, Url, Flags, TargetFrameName, PostData, Headers, Cancel) 126 | { 127 | ; Url 形如 128 | ; about:/w/eng/apple_computer/#keyfrom=dict.phrase.wordgroup 129 | ; about:/w/price/#keyfrom=E2Ctranslation 130 | ; about:/example/价格/#keyfrom=dict.basic.ce27 131 | ; about:blank 132 | if (SubStr(Url, 1, 7) = "about:/") 133 | { 134 | Cancel[] := true ; -1 取消跳转,0 继续跳转 https://www.autohotkey.com/boards/viewtopic.php?t=7367 135 | obj := CrackUrl("https://dict.youdao.com/" SubStr(Url, 8)) 136 | Url := obj.Scheme "://" obj.HostName obj.UrlPath ; Url 必须去掉锚点的(带锚点会导致下载失败) 137 | youdao.dict("", Url) 138 | } 139 | else if (Url="about:blank") 140 | { 141 | Cancel[] := true 142 | this.ShowTip("此链接指向空白页", 5) 143 | } 144 | } 145 | 146 | ShowTip(text, WhichToolTip := 1) 147 | { 148 | ToolTip %text%, , , %WhichToolTip% 149 | ToolTipHide := ObjBindMethod(this, "HideTip", WhichToolTip) 150 | SetTimer % ToolTipHide, -1000 151 | } 152 | 153 | HideTip(WhichToolTip) 154 | { 155 | ToolTip, , , , %WhichToolTip% 156 | } 157 | } 158 | 159 | ; 让热键功能比如 Ctrl+C 等生效 160 | WM_KeyPress( wParam, lParam, nMsg, hWnd ) 161 | { 162 | static Vars := ["hWnd", "nMsg", "wParam", "lParam", "A_EventInfo", "A_GuiX", "A_GuiY"] 163 | 164 | if (A_Gui = "youdao_dict") 165 | { 166 | WinGetClass, ClassName, ahk_id %hWnd% 167 | if ( ClassName = "Internet Explorer_Server" ) 168 | { 169 | VarSetCapacity( MSG, 28, 0 ) ; MSG STructure http://goo.gl/4bHD9Z 170 | for k, v in Vars 171 | NumPut( %v%, MSG, ( A_Index-1 ) * A_PtrSize ) 172 | 173 | IOleInPlaceActiveObject_Interface := "{00000117-0000-0000-C000-000000000046}" 174 | pipa := ComObjQuery( this.wb, IOleInPlaceActiveObject_Interface ) 175 | TranslateAccelerator := NumGet( NumGet( pipa+0 ) + 5*A_PtrSize ) 176 | 177 | loop, 2 ; IOleInPlaceActiveObject::TranslateAccelerator method http://goo.gl/XkGZYt 178 | r := DllCall( TranslateAccelerator, "UInt", pipa, "UInt", &MSG ) 179 | until, (wParam != 9 or this.wb.document.activeElement != "") 180 | 181 | ObjRelease( pipa ) 182 | 183 | if (r = 0) 184 | return, 0 ; S_OK: the message was translated to an accelerator. 185 | } 186 | } 187 | } 188 | } 189 | 190 | #Include %A_LineFile%\..\Lib\WinHttp.ahk 191 | #Include %A_LineFile%\..\Lib\GetTag.ahk 192 | #Include %A_LineFile%\..\Lib\CrackUrl.ahk --------------------------------------------------------------------------------