WoX is a launcher for Windows that simply works. It’s an alternative to Alfred and Launchy. You can call it Windows omni-eXecutor if you want a long name.
这是 WoX 官网的简介,作为一个 Windows 下开源的快速启动工具,虽然没有 Launchy 成熟稳定,但是可以方便地写插件这点还是挺值得一试的。到这里获取 WoX :getWox Github
WoX 的插件开发支持 C# 和 Python 3 ,本文以 Python 为基础开发几款小插件。
IPIP
WoX 有一个提供 ip138 的 ip 地址查询的插件,然而在我这似乎并不奏效,索性就借助 trustauth.cn 的免费 API 来重新开发一个用来快速查询IP信息的小插件。(P.S. ipip 的免费API仅支持每天1000次查询,不过对个人用户来说绝对够用了,此外这个免费 API 貌似不支持 IPV6 和域名查询,所以本插件对这两者也就不做考虑)
首先要定义一个 plugin.json 用来储存插件的相关信息和配置:
1 2 3 4 5 6 7 8 9 10 11 12 | { “ID”: “92p6d94f-4e10-4dw4-b650-8c8aba1d7ace”, “ActionKeyword”: “ipi”, “Name”: “IPIP”, “Description”: “Get IP info from trustauth.cn”, “Author”: “Eason Yang”, “Version”: “0.0.1”, “Language”: “python”, “Website”: “https://trustauth.cn”, “ExecuteFileName”: “ipip.py”, “IcoPath”: “Images\\icon.png” } |
这些参数中需要特别说明的是:
随后 WoX 的插件需要编写一个继承 WoX 父类的子类,并且该子类必须重载参数为用户输入内容的 query 方法,插件的逻辑都在 query 方法中完成。
1 2 3 4 5 6 | class IpInfo(Wox): def query(self,query): “”” sub class need to override this method “”” return [] |
query 方法的返回值是一个由固定格式字典组成的列表,这个列表的元素也会作为 UI 中 item 显示,字典格式如下:
1 2 3 4 5 6 7 8 9 10 | result = [{ “Title”: “IPIP”, “SubTitle”: data, “IcoPath”: “app.ico”, “JsonRPCAction”: { “method”: “detail”, “parameters”: [ “http://www.trustauth.cn/ip.html”], “dontHideAfterAction”: False } }] |
数据放在副标题中显示即可,JsonRPCAction返回的这个字典用于 WoX 的主程序处理用户点击item后的动作,method 指要调用的方法,parameters 是要向方法中传递的值,dontHideAfterAction 指点击后是否隐藏 UI,根据官方文档,method 除了可以填咱们所创建的这个子类的方法,还可以调用系统自身的一些方法,详情参见文档。这里我们只简单地调用自定义的 detail 方法来在浏览器中打开网页,参数是固定的 ipip 网址。
trustauth.cn 的查询结果是一个 JSON 字符串,这样我们调用 requests 模块就可以很轻松地处理结果了。
1 2 3 4 5 | data = “Result:” url = “http://freeapi.trustauth.cn/” + query result = requests.get(url) for item in result.json(): data += ” “ + item |
但是这个插件在实际使用中会在日志中报错,原因就在于 WoX 在匹配到用户输入的关键字后,就会调用对应的插件处理此后用户的每个输入,所以这些不完整 IP 格式的就造成了插件报错,所以加入正则改进一下(P.S. 鉴于目的并不真的要判断 IP 地址格式是否正确,所以就不写那个一长串的匹配 IP 的正则表达式了):
1 2 3 | pattern = re.compile( ‘(\d{1,3}\.){3}\d’, re.S) if not re.search(pattern, query): return “” |
至此一个简单的插件就完成了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | class IpInfo(Wox): def query(self, query): if not query: return “” pattern = re.compile( ‘(\d{1,3}\.){3}\d’, re.S) if not re.search(pattern, query): return “” data = “Result:” url = “http://freeapi.trustauth.cn/” + query result = requests.get(url) for item in result.json(): data += ” “ + item result = [{ “Title”: “IPIP”, “SubTitle”: data, “IcoPath”: “app.ico”, “JsonRPCAction”: { “method”: “detail”, “parameters”: [ “http://www.trustauth.cn/ip.html”], “dontHideAfterAction”: False } }] return result def detail(self, url): webbrowser.open(url) if __name__ == “__main__”: IpInfo() |
注:在正式使用前,我们还可以通过运行脚本时传参的方式来模拟运行,也就是说我们可以借助这个方法来做 Debug。参数的格式是 "{\"method\":\"\",\"parameters\":[\"\"]}"
PING
接下来写一个利用 Python 调用系统 ping 命令并输出结果的小插件,同样只考虑 IPV4。
定义一个 ping 方法,在该方法中使用 subprocess 模块来执行命令后,将结果转为字符串并存入列表中返回,为了美观,把结果中的无用行忽略掉:
1 2 3 4 5 6 7 8 | def ping(self, ip): ping = subprocess.Popen([ “ping”, ip], shell= True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) result = [] line_to_print = [ 1, 2, 3, 4, 5, 8, 10] for line_number, line in enumerate(ping.stdout.readlines()): if line_number in line_to_print: result.append(line.strip().decode( ‘utf-8’)) return result |
随后仿照 IPIP 插件编写 query 方法,当 ip 符合格式时,调用 ping 方法,最后把结果处理之后返回:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | def query(self, query): ip = self.regex(query, ‘\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}’) if not ip: return “” ping = self.ping(ip) result = [] if len(ping) != 0: for item in ping: result.append({ “Title”: “Ping Result”, “SubTitle”: item, “IcoPath”: “Images/app.png”, “JsonRPCAction”: { “method”: “detail”, “parameters”:[ “http://ping.chinaz.com”], “dontHideAfterAction”: False } }) return result |
这里为了方便把上文的正则处理抽象为一个独立的方法:
1 2 3 4 5 6 7 | def regex(self, query, condition): pattern = re.compile(condition) result = re.search(pattern, query) if result: return result.group() else: return “” |
又一个小插件完成了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | class Ping(Wox): def query(self, query): ip = self.regex(query, ‘\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}’) if not ip: return “” ping = self.ping(ip) result = [] if len(ping) != 0: for item in ping: result.append({ “Title”: “Ping Result”, “SubTitle”: item, “IcoPath”: “Images/app.png”, “JsonRPCAction”: { “method”: “detail”, “parameters”:[ “http://ping.chinaz.com”], “dontHideAfterAction”: False } }) return result def regex(self, query, condition): pattern = re.compile(condition, re.S) result = re.findall(pattern, query) if len(result) == 1: return result[ 0] else: return [] def detail(self, url): webbrowser.open(url) def ping(self, ip): ping = subprocess.Popen([ “ping”, ip], shell= True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) result = [] line_to_print = [ 1, 2, 3, 4, 5, 8, 10] for line_number, line in enumerate(ping.stdout.readlines()): if line_number in line_to_print: result.append(line.strip().decode( ‘utf-8’)) return result if __name__ == “__main__”: Ping() |
IPIP&PING
最后为了便捷把这两个插件整合在一起:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 | # -*- coding: utf-8 -*- # Author: Eason Yang # Date: 6/18/2016 import webbrowser import requests import re import subprocess from wox import Wox, WoxAPI class IpInfo(Wox): def query(self, query): if not query: return “” ip = self.regex(query, ‘(\d{1,3}\.){3}\d’) if not ip: return “” data = “Result:” url = “http://freeapi.trustauth.cn/” + ip result = requests.get(url) for item in result.json(): data += ” “ + item result = [{ “Title”: “IPIP”, “SubTitle”: data, “IcoPath”: “Image/app.png”, “JsonRPCAction”: { “method”: “detail”, “parameters”: [ “http://www.trustauth.cn/ip.html”], “dontHideAfterAction”: False} }] if self.regex(query, “ping [\d\.]+”): ping = self.ping(ip) if len(ping) != 0: for item in ping: result.append({ “Title”: “Ping Result”, “SubTitle”: item, “IcoPath”: “Images/app.png”, “JsonRPCAction”: { “method”: “detail”, “parameters”: [ “”], “dontHideAfterAction”: False} }) return result def regex(self, query, condition): pattern = re.compile(condition) result = re.search(pattern, query) if result: return result.group() else: return “” def detail(self, url): webbrowser.open(url) def ping(self, ip): ping = subprocess.Popen([ “ping”, ip], shell= True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) result = [] line_to_print = [ 1, 2, 3, 4, 5, 8, 10] for line_number, line in enumerate(ping.stdout.readlines()): if line_number in line_to_print: result.append(line.strip().decode( ‘utf-8’)) return result if __name__ == “__main__”: IpInfo() |
这样就能输入 ipip+ip 来查询 IP 信息,也可以通过 ipip+ping+ip 来同时显示 IP 信息和 ping 的结果。
踩坑记录
1 2 3 4 | 2016-06-18 17:49:24.7948|ERROR|Wox.Core.Plugin.JsonRPCPlugin.Execute|Traceback (most recent call last): File “C:\Users\Eason Yang\AppData\Local\Wox\app-1.3.67\Plugins\Wox.Plugin.Ipip\ipip.py”, line 8, in <module> import requests ImportError: No module named ‘requests’ |
明显是缺少 requests 模块,但官方文档中写的是
Wox支持使用Python进行插件的开发。Wox自带了一个打包的Python及其标准库,所以使用Python 插件的用户不必自己再安装Python环境。同时,Wox还打包了requests和beautifulsoup4两个库, 方便用户进行网络访问与解析。
然后试了下官方的其他Python插件,都提示缺少request模块。后来几经周折,才发现原来还要在 WoX 的 UI 里设置 Python 路径才能正常使用。
1 2 3 4 | 2016-06-18 19:01:47.7101|ERROR|Wox.Core.Plugin.JsonRPCPlugin.Execute|Traceback (most recent call last): File “C:\Users\Eason Yang\AppData\Roaming\Wox\Plugins\Hacker News-36f26938-4a86-4a14-b105-68124c769c99\main.py”, line 6, in <module> from wox import Wox,WoxAPI ImportError: No module named ‘wox’ |
解决上一个问题的时候发现有些用 Python 写的插件运行时会报上面这个错,只要在能正常运行的插件(比如默认安装的 HelloWorldPython )目录中找到 wox.py 复制到报错插件的目录即可。
数安时代(GDCA)拥有国内自主签发信鉴易 TrustAUTH SSL证书以及是国际多家知名品牌:GlobalSign、Symantec、GeoTrust SSL证书指定的国内代理商。为了让国内更多的网站升级到安全的https加密传输协议,五一期间,GDCA推出多种国际知名SSL证书优惠活动,实现HTTPS加密并展示网站真实身份信息。详情请资讯GDCA产品官网在线客服https://www.trustauth.cn/。