slack から stackoverflow を検索出来る slack-overflow の lingr 版を作ってみた。
karan/slack-overflow - GitHub
https://github.com/karan/slack-overflow
akechi/stackoverflow-lingrbot - GitHub
https://github.com/akechi/stackoverflow-lingrbot
今回はなんとなく gin で書いた。
Gin Web Framework
Low Overhead Powerful API You can add global, per-group, and per-route middlewares, thousands of nes...
https://gin-gonic.github.io/gin/
コード短いのでのっけておく。
package main
import (
"encoding/json"
"flag"
"fmt"
"net/http"
"net/url"
"os"
"regexp"
"github.com/gin-gonic/gin"
"github.com/mattn/go-lingr"
)
type resp struct {
Items []struct {
Tags []string `json:"tags"`
Owner struct {
Reputation int `json:"reputation"`
UserID int `json:"user_id"`
UserType string `json:"user_type"`
ProfileImage string `json:"profile_image"`
DisplayName string `json:"display_name"`
Link string `json:"link"`
} `json:"owner"`
IsAnswered bool `json:"is_answered"`
ViewCount int `json:"view_count"`
AnswerCount int `json:"answer_count"`
Score int `json:"score"`
LastActivityDate int `json:"last_activity_date"`
CreationDate int `json:"creation_date"`
QuestionID int `json:"question_id"`
Link string `json:"link"`
Title string `json:"title"`
} `json:"items"`
HasMore bool `json:"has_more"`
QuotaMax int `json:"quota_max"`
QuotaRemaining int `json:"quota_remaining"`
}
var re = regexp.MustCompile(`^stackoverflow(?:\w+) (.+)$`)
func defaultAddr() string {
port := os.Getenv("PORT")
if port == "" {
return ":80"
}
return ":" + port
}
var addr = flag.String("addr", defaultAddr(), "server address")
func main() {
flag.Parse()
r := gin.Default()
r.GET("/", func(c *gin.Context) {
c.String(200, "")
})
f := func(c *gin.Context) {
site := c.Params.ByName("site")
if site == "" {
site = "stackoverflow"
}
var status lingr.Status
if !c.EnsureBody(&status) {
return
}
urls := ""
for _, event := range status.Events {
message := event.Message
if message == nil {
continue
}
if !re.MatchString(message.Text) {
continue
}
question := re.FindStringSubmatch(message.Text)[1]
params := url.Values{}
params.Add("intitle", question)
params.Add("site", site)
params.Add("sort", "activity")
params.Add("order", "desc")
res, err := http.Get("https://api.stackexchange.com/2.2/search?" + params.Encode())
println("https://api.stackexchange.com/2.2/search?" + params.Encode())
if err != nil {
println(err.Error())
continue
}
defer res.Body.Close()
var resp resp
if err := json.NewDecoder(res.Body).Decode(&resp); err != nil {
println(err.Error())
continue
}
for _, item := range resp.Items {
u := item.Link
if len(u) > 300 {
u = fmt.Sprintf("http://%s.com/q/%d", site, item.QuestionID)
}
s := fmt.Sprintf("%s\n%s\n", item.Title, u)
println(s)
if len(urls+s) > 1000 {
break
}
urls += s
}
}
c.String(200, urls)
return
}
r.POST("/", f)
r.POST("/:site", f)
r.Run(*addr)
}
僕がこういうのを書く時は、一度 JSON を何等かの方法で得て
JSON-to-Go: Convert JSON to Go instantly
JSON-to-Go Convert JSON to Go struct This tool instantly converts JSON into a Go type definition. Pa...
http://mholt.github.io/json-to-go/
このサイトで golang の struct に変換、クライアント処理として stackoverflow の検索処理を実装しながら最後に Web のガワを付けていく。実質20~30分程度で出来た。
困ったのが stackoverflow の日本語版にも対応したのだけど、日本語版は URL にタイトルの日本語が含まれていて平気で1000文字を超える。しかし lingr の API は1000文字までしか発言出来なくて(bot triggered アクションで分割送信なら可能)困った。どうやら URL の日本語部分は無くてもアクセス出来るのが分かったので300文字(適当)超えたらそっちを使う様に対応した。
lingr だと stackoverflow という名前で居ますので invite して「stackoverflow jquery」とか「stackoverflowja ウェブ」とか発言すると反応します。