很早就有人建议让 SwitchHosts 支持 Alfred ,我也曾多次想过开发这个功能,但拖延症很严重,一直没有动手😅。最近注意到 SwitchHosts 在 GitHub 上已经有超过 2000 个 star,开心的同时也觉得压力开始大了起来,于是抽空认真梳理了一遍代码,做了若干改造,修复了若干遗留问题(当然,还有一些仍待处理),最后,添加了对 Alfred workflow 的支持。

SwitchHosts-with-alfred

当然,由于 Alfred 仅支持 macOS,这个功能也只支持 macOS。

使用方法:

  1. 下载 SwitchHosts 最新版本(v3.3.0 或更新版本)
  2. 下载对应的 workflow 文件并安装
  3. 在 Alfred 界面输入关键词 swh,即可列出当前 hosts 方案列表,并可直接在下拉菜单中进行切换

效果如下:

下面记录一下工作原理以及开发说明。

工作原理

工作原理非常简单:SwitchHosts 启动时,开启一个 HTTP 服务,提供了 list 以及 toggle 两个接口,顾名思义,list 就是列出所有可用的 hosts 方案,toggle 则是切换指定方案。接下来,Alfred 则根据用户指令,请求对应的接口。如下图所示:

HTTP 服务使用了 express.js 来实现(源码),随 app 启动,监听 50761 端口。启动 app 后,你可以在浏览器中访问 http://127.0.0.1:50761 ,如果能正常打开页面,并看到“Hello SwitchHosts”的字样,表示 HTTP 服务已正常启动。

list 接口(源码)的访问地址为 http://127.0.0.1:50761/api/list ,返回一个 JSON ,格式形如:

{
  "success": true,
  "data": [
    {
      "id": "1490959856569-442720",
      "title": "default",
      "content": "# ...",
      "on": true,
      "where": "local",
      "url": "",
      "last_refresh": null,
      "refresh_interval": 0,
      "include": []
    },
    {
      "id": "1490959856569-329205",
      "title": "my Tests",
      "content": "# ..",
      "on": true,
      "where": "local",
      "url": "",
      "last_refresh": null,
      "refresh_interval": 0,
      "include": []
    },
    ...

注意其中每个 hosts 方案都有一个 id,这个 id 将会在下面的 toggle 接口中使用。

toggle 接口(源码)的访问地址为 http://127.0.0.1:50761/api/toggle?id={id} ,它接受一个 id 参数,查询对应的 hosts,并进行切换。

目前 SwitchHosts 只提供了 Alfred 支持,不过有了 listtoggle 这两个接口,理论上用户可以为任何第三方工具开发 SwitchHosts 的支持。

代码实现

接下来就是 Alfred 的部分了,源码在这里,使用的是 Python 作为开发语言,参考了这个教程

其中生成列表的代码如下:

# -*- coding: utf-8 -*-

import sys
# the workflow package below is download from:
# https://github.com/deanishe/alfred-workflow/releases
from workflow import Workflow, ICON_WEB, web

def get_subtitle(item):
    content = item.get('content', '')
    return content.partition('\n')[0].strip()

def main(wf):
    url = 'http://127.0.0.1:50761/api/list'
    r = web.get(url)

    # throw an error if request failed
    # Workflow will catch this and show it to the user
    r.raise_for_status()

    # Parse the JSON returned by pinboard and extract the posts
    result = r.json()
    items = result['data']

    # Loop through the returned posts and add an item for each to
    # the list of results for Alfred
    for item in items:
        on = item.get('on', False)
        wf.add_item(title=item['title'],
                    subtitle=get_subtitle(item),
                    arg=item['id'],
                    valid=True,
                    icon='on.png' if on else 'off.png')

    # Send the results to Alfred as XML
    wf.send_feedback()

if __name__ == '__main__':
    my_wf = Workflow()
    sys.exit(my_wf.run(main))

用到的 python workflow 模块可以从这儿下载。

用户选中切换的脚本则非常简单,传入 id 后,访问对应的 toggle 接口即可,Alfred 中的 workflow 脚本只有一行:

curl 'http://127.0.0.1:50761/api/toggle?id={query}'

下载

最后,再贴一下 Alfred workflow 的下载地址,使用过程中有任何问题,欢迎给我提 issue

后续

【2022-06-08 更新】

macOS 12.3 中移除了 Python 2,导致原来的 Alfred workflow 失效(参见 #692),于是用 JavaScript 重写了这个 workflow,版本号升级为 1.3,下载地址不变。