创造自己的Alfred Workflow

1,871 阅读2分钟

Workflow

实现 WorkFlow 的几个基本对象:trigger、input、action、utility、output。Alfred 通过将这几个基本对象连接起来,便能实现各种高级功能。

配置界面

以有道翻译Alfred Workflow为例,分别介绍workflow的五大元素和三个功能。

五大元素

  1. trigger 和 input 主要用来触发后续操作(通过传递Arg)。其中trigger的触发相当于【打开alfred且输入关键词】这两个步骤。即同时按住control+option+z这一个步骤就相当于完成了【command+space 唤起alfred】+【输入yd】这两个步骤。(可以没有trigger,主要看具体需求)
  2. action负责处理进一步需求(对传进来的Arg做处理)。(可以没有action,主要看具体需求)
  3. output 负责把前面节点的结果以一定的形式传递给用户,可以是直接屏显,也可以是另存为文件,播放声音等。(可以没有output,主要看具体需求)
  4. utility是各种工具节点:如对中间文本进行过滤、替换等。(可以没有utility,主要看具体需求)
  5. 上述节点可以用「线」链接,来形成直接或间接的条件结果关系。线上的节点可以设置为按键触发。如图中按option键后回车可以播放在线语音

三个功能

环境变量

用来设置workflow运行中所需要的用户配置信息。如有道翻译中需要用户在平台申请key和secret信息供脚本在运行过程中请求对应的翻译API。如果没有配置则workflow运行中会报错。

Dont Export:表示在导出workflow分享给别人时,勾选上的变量的值不会进行导出以保护隐私

导出

调试

主要用于自己开发workflow时进行debug。比如根据报错信息或者自己打印出的关键步骤信息定位问题所在。

编写一个自己的workflow

Step1 配置workflow面板

  1. 在workflow面板下方点击+并选择Blank Workflow
  2. 填写相关信息

image.png

image.png

  1. 搞一个Script Filter,填写关键词(用来触发脚本),并明确在输入命令后需要执行的脚本指令(Script文本区域)。这里icon、cpu和expire是定义的环境变量,$1是需要输入的内容

  1. 根据需要加入元素,并用线连接起来

Step2 写代码

使用到的Go框架:github.com/deanishe/aw…

  1. go get -u github.com/deanishe/awgo
  2. 搭建框架代码
package main

// Package is called aw

import "github.com/deanishe/awgo"

// Workflow is the main API

var wf *aw.Workflow

func init() {

    // Create a new Workflow using default settings.

    // Critical settings are provided by Alfred via environment variables,

    // so this *will* die in flames if not run in an Alfred-like environment.

    wf = aw.New()

}



func main() {

    // Wrap your entry point with Run() to catch and log panics and

    // show an error in Alfred instead of silently dying

    wf.Run(run)

}



// Your workflow starts here

func run() {

    // Add a "Script Filter" result

    wf.NewItem("First result!")

    // Send results to Alfred

    wf.SendFeedback()

}
  1. 填充逻辑
package main



import (

   "encoding/json"

   "fmt"

   "github.com/deanishe/awgo"

   "github.com/shirou/gopsutil/process"

   "log"

   "os"

   "regexp"

   "strconv"

   "strings"

   "time"

)



var wf *aw.Workflow



const (

   DEFAULT_EXPIRE_SECONDS = 60

)



func init() {

   // Create a new Workflow using default settings.

   // Critical settings are provided by Alfred via environment variables,

   // so this *will* die in flames if not run in an Alfred-like environment.

   wf = aw.New()

}



func main() {

   // Wrap your entry point with Run() to catch and log panics and

   // show an error in Alfred instead of silently dying

   wf.Run(run)

}



// Your workflow starts here

func run() {

   log.Println("cmd = ", os.Args)



   processMap, err := GetProcessMapFromCacheOrRequest()

   if err != nil {

      wf.NewItem("出错了,请重试")

      wf.SendFeedback()

      return

   }



   query := os.Args[len(os.Args)-1]

   limit := 0

   for name, process := range processMap {

      if limit > 50 {

         break

      }

      pName := strings.Split(name, "::::")[0]

      if strings.Contains(strings.ToUpper(pName), strings.ToUpper(query)) {

         item := wf.NewItem(pName).Arg(fmt.Sprintf("%d", process.Pid)).Valid(true)

         if isShowCpu() {

            item.Subtitle(fmt.Sprintf("kill %s, pid=%d, cpu=%.2f%s", pName, process.Pid, process.CPU, "%"))

         } else {

            item.Subtitle(fmt.Sprintf("kill %s, pid=%d", pName, process.Pid))

         }

         if isShowIcon() {

            item.Icon(&aw.Icon{

               Value: process.IconPath,

               Type:  aw.IconTypeFileIcon,

            })

         }

         limit += 1

      }

   }

   if limit == 0 {

      wf.NewItem("no such process")

   }



   // Send results to Alfred

   wf.SendFeedback()

}



type Process struct {

   Pid      int32

   IconPath string

   CPU      float64

}



// GetProcessMapFromCacheOrRequest 从缓存中获取进程映射,如果过期则重新请求

func GetProcessMapFromCacheOrRequest() (map[string]*Process, error) {

   var processMap map[string]*Process

   if wf.Cache.Exists("processMap") && !wf.Cache.Expired("processMap", time.Second*time.Duration(GetExpireSeconds())) {

      log.Println("use cache")

      bytes, err := wf.Cache.Load("processMap")

      if err == nil {

         err = json.Unmarshal(bytes, &processMap)

         if err == nil {

            return processMap, nil

         } else {

            log.Printf("[GetProcessMapFromCacheOrRequest] load from cache failed, json.Unmarshal err=%s", err.Error())

         }

      }

   }



   log.Println("miss cache or occur error")

   processMap = ProcessMap()

   res, err := json.Marshal(processMap)

   if err == nil {

      wf.Cache.Store("processMap", res)

   } else {

      log.Printf("[GetProcessMapFromCacheOrRequest] Store cache failed, err=%s", err.Error())

   }

   return processMap, nil

}



func ProcessMap() map[string]*Process {

   pids, _ := process.Pids()

   processMap := make(map[string]*Process, len(pids))

   for _, pid := range pids {

      pn, err := process.NewProcess(pid)

      if err != nil {

         continue

      }

      // 进程名

      pName, err := pn.Name()

      if err != nil {

         continue

      }



      // 应用图标

      IconPath := ""

      if isShowIcon() {

         cmd, _ := pn.Cmdline()

         IconPath = GetIconPath(cmd)

      }



      // 当前CPU

      cpu := 0.0

      if isShowCpu() {

         cpu, _ = pn.CPUPercent()

      }



      processMap[fmt.Sprintf("%s::::%d", pName, pid)] = &Process{

         Pid:      pid,

         IconPath: IconPath,

         CPU:      cpu,

      }

   }

   return processMap

}



// 是否需要显示CPU信息

func isShowCpu() bool {

   isShowCpuStr, isExisted := wf.Alfred.Env.Lookup("show_cpu")

   return isExisted && isShowCpuStr == "1"

}



// 是否显示应用图标

func isShowIcon() bool {

   isShowIconStr, isExisted := wf.Alfred.Env.Lookup("show_icon")

   return isExisted && isShowIconStr == "1"

}



// GetExpireSeconds 获取缓存时长(单位:秒),默认60

func GetExpireSeconds() int64 {

   expireStr, isExisted := wf.Alfred.Env.Lookup("expire_seconds")

   if !isExisted {

      return DEFAULT_EXPIRE_SECONDS

 }



   expireSeconds, err := strconv.ParseInt(expireStr, 10, 64)

   if err != nil {

      return DEFAULT_EXPIRE_SECONDS

 }

   return expireSeconds

}



// GetIconPath 获取应用图片路径

func GetIconPath(cmd string) string {

   if cmd == "" {

      return cmd

   }

   re, err := regexp.Compile("\.app\/Contents\/.*$")

   if err != nil {

      return ""

   }

   return re.ReplaceAllString(cmd, ".app")

}

Step3 build

go build -o workflow

Step4 手动替换

  1. 找到刚刚build好的workflow

  1. 找到alfred里面该workflow所在的位置

image.png

  1. 将build好的workflow移动到第二步中所在文件夹即可

效果演示

输入 kill macd可以关闭macdown,动手试试吧~

kill_process.gif