如何写一个 things3 client
Things3 是一款苹果生态内的任务管理软件,是一家德国公司做的,非常好用。我前后尝试了众多任务管理软件,最终选定 things3,以后有机会会写文章介绍我是如何用 things3 来管理我的日常任务。
本文主要介绍欧神写的 tli 工具来学习如何写一个定制的通过邮件和 things3 沟通的工具。很多软件都有类似的邮件功能,例如给绑定的 kindle 邮件地址发送电子书文件,就可以在 kindle 设备上看到。学会写工具的套路后,今后就能自己写类似的工具了。
使用场景
正常情况下,我们可以在 mac/ipad/iphone 上可以通过软件界面添加 TODO 事项,而且 things3 本身也有全局快捷录入的功能,非常方便。但是 things3 只能在苹果生态内使用,当我们临时切换到 windows 或者 linux 上工作,就不好操作了。这时如果产生了新的 TODO,通过 tli,打开 terminal 工具就能将 TODO 加到 inbox 里。
命令行操作:
同步到 things3:
初始化配置
因为要通过邮件来和 things3 沟通,因此需要配置发送邮件的邮箱、SMTP 服务器、用户名、密码、things3 给我们的专属邮件地址。
由于通过 tli 发送 TODO 是一次性的任务,因此这些配置项需要保存在某个文件中,之后用到的时候直接读取就好了。
具体的配置项包括:
1type tliConf struct {
2 SMTPHost string `yaml:"smtp_host"`
3 SMTPPort string `yaml:"smtp_port"`
4 Avatar string `yaml:"avatar"`
5 EmailAddr string `yaml:"email_addr"`
6 Username string `yaml:"username"`
7 Password string `yaml:"password"`
8 ThingsAddr string `yaml:"things_addr"`
9}
我用的 gmail 作为发送邮件,配置的 SMTP 参数是:smtp.gmail.com:587。EmailAddr 就是 gmail 地址,Password 需要设置一个专用的。
使用 user.Current()
方法可以拿到当前用户的信息,包括 home directory,用户名等等。tli 将配置文件保存到 home 目录下。
使用 bufio 包,在 terminal 里读取用户的输入:
1s := bufio.NewScanner(os.Stdin)
2log.Printf("SMTP Host Address: ")
3if !s.Scan() {
4 log.Println("init was canceled.")
5 return
6}
7info := s.Text()
8tli.SMTPHost = info
将配置序列化成 yaml 后,写入 ~/.tli_config。
1func (c *tliConf) save() {
2 checkhome()
3 data, err := yaml.Marshal(c)
4 if err != nil {
5 log.Fatalf("cannot save your data, err: %v", err)
6 }
7
8 f, err := os.OpenFile(homedir+"/"+pathConf,
9 os.O_CREATE|os.O_RDWR, 0600)
10 if err != nil {
11 return
12 }
13 defer f.Close()
14
15 all := []byte("---\n")
16 all = append(all, data...)
17 if _, err := f.Write(all); err != nil {
18 return
19 }
20}
读取 title 和 body
用户执行 todo 命令时,可输入 title 和 body。且 body 支持多行输入,输入空行或者按 ctrl+C 时取消输入。
TODO title 通过命令行直接传入,body 则通过一个 for 循环等待用户输入:
1func (a *tliTODO) waitBody() bool {
2 s := bufio.NewScanner(os.Stdin)
3 fmt.Println("(Enter an empty line to complete; Ctrl+C/Ctrl+D to cancel)")
4
5 sigCh := make(chan os.Signal, 1)
6 signal.Notify(sigCh, os.Interrupt)
7
8 line := make(chan string, 1)
9 go func() {
10 for {
11 fmt.Print("> ")
12 if !s.Scan() {
13 sigCh <- os.Interrupt
14 return
15 }
16 l := s.Text()
17 if len(l) == 0 {
18 line <- ""
19 return
20 }
21 line <- l
22 }
23 }()
24
25 for {
26 select {
27 case <-sigCh:
28 return false
29 case l := <-line:
30 if len(l) == 0 {
31 return true
32 }
33 a.body = append(a.body, l)
34 }
35 }
36}
并且监听了取消息信号,异步启动一个协程去监听输入,再在 for select 中监听 sigCh,若用户手动取消了,则返回 false。若用户输入了空行,则返回 true,代表输入完成,之后就可以发送邮件。
发送邮件
因为 things3 有 2000 字的限制,所以需要做一个分割,防止被截断。
1func (a *tliTODO) Range(f func(string, string)) {
2 whole := strings.Join(a.body, "\n")
3
4 if len(whole) < maxlen {
5 f(a.title, whole)
6 return
7 }
8
9 count := 1
10 for i := 0; i < len(whole); i += maxlen {
11 f(a.title+fmt.Sprintf(" (%d)", count), whole[i:min(i+maxlen, len(whole))])
12 count++
13 }
14}
调了 smtp.SendMail 方法发送邮件。
注意,需要对中文字符做一个编码,否则 things3 里会出现乱码。
TODO 历史
每次执行 todo 命令时,都会保存到历史中,同样是用 yaml 序列化。之后,执行 log 命令时,可将其读出来,展示历史。
每条记录前加一个”—“用于分离,读的时候,就可以读出多条记录:
1rs := []record{}
2for {
3 var r record
4 err = d.Decode(&r)
5 if err != nil {
6 if err == io.EOF {
7 break
8 }
9 log.Fatalf("corrupted ~/.tli_history file, err: %v", err)
10 }
11 rs = append(rs, r)
12}
cobra 命令行
这是一个比较常用的库了,用于写命令行工具。
定义 init, log, todo 三个 command,再定义一个 root command,说明用法。
总结
总体来说这个项目比较简单,不到 500 行,但也能学到不少写工具软件的技巧,之后写类似的工具时可以参考。
- 使用 cobra 创建不同的命令。
- 将配置文件保存到用户 home 目录下。
- 如何从控制台接收用户的输入文本。
- 使用 smtp.SendMail 发送邮件。
- 原文作者:饶全成
- 原文链接:https://qcrao.com/post/how-to-write-a-things3-client/
- 版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 4.0 国际许可协议进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。