Go言語を使ってローカルマシン上でbotを動かしSlackでtodo管理をする

前回の続編です。

Go言語を使ってローカルマシンに簡易botを作ってSlackに投稿

botを使ったtodo機能をslackに付与するべくgoのコードを書いてみました。

todo コマンド

という感じでつぶやくと、

チャンネル毎にtodoリストが管理できるようにしたいと思います。
dbがなくても動くようにファイルに保存します。

操作 コマンド
追加 add 追加したいテキスト
削除 del 削除したいテキスト
一覧 list
全削除 clear

こんな感じで動きます。

todobot

便利そうですね!

まずはslacbot.goにmainの処理を書いていきます。

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
package main

import (
  "./todo_list"
  "fmt"
  "net/http"
)

var p = fmt.Println

func todoListBot(w http.ResponseWriter, r *http.Request) {
  checkUser(w, r, func(text string, channel_name string) {
      return_text := todo.Accept(text, channel_name)
      fmt.Fprintf(w, "{\"text\": \"%s\"}", return_text)
  })
}

func checkUser(w http.ResponseWriter, r *http.Request, proc func(text string, channel_name string)) {
  if r.Method == "POST" {
      text := r.FormValue("text")
      user_name := r.FormValue("user_name")
      channel_name := r.FormValue("channel_name")

      if user_name != "slackbot" {
          p("user_name:", user_name)
          p("channel_name:", channel_name)
          proc(text, channel_name)
      }
  }
}

func main() {
  http.HandleFunc("/todo", todoListBot)
  http.ListenAndServe(":8888", nil)
}

続いて実装です。
一段階層を下げます。
todo_listというディレクトリを作ってそこにtodo.goを置きます。

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
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
package todo

import (
  "../util"
  "fmt"
  "io/ioutil"
  "log"
  "os"
  "strings"
)

const ADD = "add"
const DEL = "del"
const LIST = "list"
const CLEAR = "clear"
const STORE_DIR = "todo_list/stored_files"

var p = fmt.Println

func Accept(text string, channel_name string) string {
  var command string
  var message string
  var rtn_text string

  if validateParams(text) {
      command, message = parseText(text)
  } else {
      return "入力されたパラメータが不正です→ " + text
  }

  if command == ADD {
      rtn_text = add(channel_name, message)
  } else if command == DEL {
      rtn_text = del(channel_name, message)
  } else if command == LIST {
      rtn_text = list(channel_name)
  } else if command == CLEAR {
      rtn_text = clear(channel_name)
  }

  return rtn_text
}

func validateParams(text string) bool {
  var command string

  if len(strings.Split(text, " ")) < 2 {
      return false
  }

  command = getCommand(text)
  correct_commands := []string{ADD, LIST, DEL, CLEAR}
  if !util.Contains(correct_commands, command) {
      return false
  }

  return true
}

func parseText(text string) (command string, post_text string) {
  command = getCommand(text)
  post_text = getMessage(text)
  return
}

func getTriggerWord(text string) string {
  return strings.Split(text, " ")[0]
}

func getCommand(text string) string {
  return strings.Split(text, " ")[1]
}

func getMessage(text string) string {
  if len(strings.Split(text, " ")) > 2 {
      return strings.Split(text, " ")[2]
  } else {
      return ""
  }
}

func add(channel_name string, message string) string {
  // ディレクトリの作成
  err := os.MkdirAll(STORE_DIR, 0777)
  if err != nil {
      util.Perror(err)
  }

  file_path := getStoredPath(channel_name)
  // ファイルがなかったら作る
  if !util.FileExists(file_path) {
      util.CreateFile(file_path)
  }
  f, err := os.OpenFile(file_path, os.O_APPEND|os.O_WRONLY, 0600)

  if err != nil {
      return "ファイルのオープンに失敗しました"
  }
  defer f.Close()

  message = strings.Replace(message, "\n", " ", -1)
  if _, err = f.WriteString(message + "\n"); err != nil {
      return "書き込みに失敗しました"
  }

  lines, _ := getList(file_path)
  return "登録に成功しました :wink: \n 現在のタスク \n- " + strings.Join(lines, "\n- ")
}

func list(channel_name string) string {
  file_path := getStoredPath(channel_name)

  if !util.FileExists(file_path) {
      return "まだ何も書き込まれていません"
  }

  lines, _ := getList(file_path)

  return "現在のタスクです。\n気張っていきましょー :kissing_heart: \n\n- " + strings.Join(lines, "\n- ")
}

func del(channel_name string, message string) string {
  var del_flg bool
  var new_lines []string
  message = strings.Replace(message, "\n", " ", -1)
  file_path := getStoredPath(channel_name)

  if !util.FileExists(file_path) {
      return "まだ何も書き込まれていません"
  }

  lines, err := util.ReadLines(file_path)
  if err != nil {
      log.Fatalf("readLines: %s", err)
  }

  for i := 0; i < len(lines); i++ {
      if lines[i] == message {
          del_flg = true
      }

      if lines[i] != message {
          new_lines = append(new_lines, lines[i])
      }
  }

  if del_flg {
      if len(new_lines) == 0 {
          if err := os.Remove(file_path); err != nil {
              return "クリアに失敗しました"
          }
      } else {
          content := []byte(strings.Join(new_lines, "\n") + "\n")
          ioutil.WriteFile(file_path, content, 0600)
      }

      if len(new_lines) != 0 {
          lines, _ = getList(file_path)
      }
      return "削除に成功しました :neutral_face: \n 残りのタスク \n- " + strings.Join(lines, "\n- ")
  } else {
      return "一致するものが見つかりませんでした"
  }
}

func clear(channel_name string) string {
  file_path := getStoredPath(channel_name)
  if err := os.Remove(file_path); err != nil {
      return "クリアに失敗しました"
  }

  return "クリアしました"
}

func getStoredPath(channel_name string) string {
  return STORE_DIR + "/" + channel_name
}

func getList(file_path string) ([]string, error) {
  lines, err := util.ReadLines(file_path)
  if err != nil {
      log.Fatalf("readLines: %s", err)
  }

  return lines, err
}

Comments