/ alfred

Tips of Create Alfred Workflow

何かしらの API を使ってインタラクティブに結果を返すかっこいい Alfred ワークフローを作るために最低限必要なことを学んだのでまとめます。

API キーを使うとき

API を叩くたびに API キーを入力するのは面倒なのでどこかに永続化したいです。

Keyword オブジェクトで文字列を受取り、それを Write Text File オブジェクトでファイルに書き込むことで永続化出来ます。

参照する際は各オブジェクトで $(cat /path/to/apikey) するなり好きに参照すればよさげ。

read-file

重要なのは Write Text File の設定項目でどのパスにファイルを置くか決めることです。選べるパスは workflow's data folderworkflow folder です。

select-path

workflow's data folder は Workflow の設定で Bundle ID が設定されている場合、 ~/Library/Application\ Support/Alfred\ 3/Workflow\ Data/<Bundle ID> になります。

Bundle ID が設定されていない場合は ~/Library/Application\ Support/Alfred\ 3/Workflow\ Data/ 以下に直に置かれます。

workflow folder はそのワークフローのカレントディレクトリです。 私は Dropbox で Alfred のデータ同期をしているので、その場合のパスは

~/Dropbox/Alfred/Alfred.alfredpreferences/workflows/user.workflow.4D089449-6AB9-47D4-8308-3C4F0F8E09D7

のような感じになります。

workflow folder を指定して設置されたファイルは、 ワークフローをエクスポートする際に一緒に入ってしまいます。 うっかり API キーを入れたままエクスポートして配布なんてしてしまったら大変なので、基本的には workflow's data folder を使ったほうが良いと思います。

あと、環境によって workflow's data folder が何故かパーミッションエラーで読み込めない現象に遭遇しました。書き込みはできるのに読み込めない。謎。

その場合はファイルを書き込むパスを /tmp などにすればとりあえず回避できます…。

また、変数を使ったデータの保持の例が公式のドキュメントに書いてありました。

Have you found yourself wanting to create a workflow where you need to enter or select multiple arguments to use later on in your workflow? Or wanted to make it easy for users to add their own identifier or API key to a workflow?
This is where you can use the new variables in Alfred 3; They can be used in a multitude of places - as keywords, arguments, in JSON utilities - and allow you to easily store an argument for use later in your workflow.

Using Variables in Workflows - Alfred Help and Support

でも変数だと毎回実行するたびに入力しなくてはならない気がするけど、どうなんだろう。

入力に対して結果を複数・動的に返したい

よくワークフローで、ユーザが入力した値をもとに検索をかけて動的にブワーってプルダウン表示するやつがありますが、あれは Script Filter オブジェクトでやっています。

result-script-filter

何かしらのスクリプトで決まった形式の JSON か XML を Script Filter オブジェクトに渡すことで実現されています。

先日会社ブログでちょっと書きました。

上の例では、ただひとつの結果を返す JSON を返せば OK でした。

{
  "items": [
    {
      "name": "たわし",
      "price": 4000
    }
  ]
}

しかし、複数の結果を返したい場合はどうすれば良いでしょう…。

{
  "items": [
    {
      "name": "たわし",
      "price": 4000
    },
    {
      "name": "球根",
      "price": 1000
    },
    {
      "name": "土",
      "price": 4500
    },
    {
      "name": "フライパン",
      "price": 100
    }
  ]
}

Go でやろうとしてしばらく悩んでいたのですが、構造体のスライスがあることを知ったので、これを使って実現できました。

package main

import (
	"encoding/json"
	"fmt"
)

type Items struct {
	Items []Item `json:"items"`
}

type Item struct {
	Name  string `json:"name"`
	Price int    `json:"price"`
}

func main() {
	m := map[string]int{
		"フライパン": 100,
		"たわし":   4000,
		"球根":    1000,
		"土":     4500,
	}

    // items[] の中身を作る
	var items []Item
	for k, v := range m {
		items = append(items, Item{Name: k, Price: v})
	}

    // items[] の中に入れて Marshal する
	jsonBytes, _ := json.Marshal(Items{Items: items})
	fmt.Println(string(jsonBytes))
}

Script Filter の結果毎にアイコンを変えたい

Script Filter の JSON フォーマットにはアイコンを設定する項目があります。

{
  "items": [
    {
      "name": "たわし",
      "price": 4000,
      "icon" : {
        "path": "./たわし.png"
      }
    }
  ]
}

なのでこれを使って結果に応じてアイコンのパスを切り変える処理を入れれば、結果毎にアイコンを変えることができます。

package main

import (
	"encoding/json"
	"fmt"
)

type Items struct {
	Items []Item `json:"items"`
}

type Item struct {
	Name  string `json:"name"`
	Price int    `json:"price"`
	Icon  icon   `json:"icon"`
}

type icon struct {
	Path string `json:"path"`
}

func main() {
	m := map[string]int{
		"フライパン": 100,
		"たわし":   4000,
		"球根":    1000,
		"土":     4500,
	}

	// items[] の中身を作る
	var items []Item
	for k, v := range m {
		items = append(items, Item{Name: k, Price: v, Icon: icon{Path: "./" + k + ".png"}})
	}

	// items[] の中に入れて Marshal する
	jsonBytes, _ := json.Marshal(Items{Items: items})
	fmt.Println(string(jsonBytes))
}

Yay! 💫

icon-per-result

開発中はスクリプトのシンボリックリンクを貼っておくと楽

そのまんまですが、毎回コンパイルなりスクリプト編集するなりして Alfred ワークフローのディレクトリに入れ直して… は面倒なので、開発中はシンボリックリンクを貼るか、 Alfred から直接開発環境のパスを指定すると楽。

公開するとき

ワークフローをダブルクリックするとワークフローの情報を編集できます。 Name とか Description とかはそのまんまですね。

bundle-id

Bundle ID がなんぞこれって感じなので調べてみると、Bundle ID を入れるとワークフローのバージョンを更新したときに検知できるようになるみたいです(どうやってるんだろう…)。

Bundle ID の形式は特に決まっていないようですが、各個人の環境でユニークなものであるべきとのことです(同じ Bundle ID のワークフローが存在すると Alfred がアップデート対象を識別できない)。形式としては com.<user name>.<workflow name> がよく使われているみたいです。

ワークフローのバージョンはここから設定できます。

version-1-1

version-2

ワークフローのオブジェクトにはノートを追加することが出来るので、追加するとダウンロードした人が助かるかもしれないです。

edit-note

この辺の、公開するためには〜的な話は以下にまとまっています。

ドキュメント

Alfred のドキュメントにオブジェクトごとの記事があるのでそれを見ながらやると良い感じ。

以下のリンクにオブジェクト( Inputs とか Triggers とか)毎でまとまっているのでここから辿っていくと良い。