golden-luckyの日記

ツイッターより長くなるやつ

GitHubで「コメントの一覧」を取得したい

近年、出版社でも原稿管理にGitの導入が進んでおり[要出典]GitHubのようなWebサービスへの需要が高まっている[要出典]。 これに伴い、WebブラウザGitHub上の原稿に対する特定のコミットを開き、そこに行コメントを残すといった利用も増えている[要出典]。 以下に例を挙げる。

f:id:golden-lucky:20191213225444p:plain

この「特定のコミットに対して行コメントを残す」機能は、ワープロソフトの編集履歴ツールと同じ感覚で原稿に局所的なツッコミを入れられるという点で大変に使い勝手が良い。 しかし難点が一つあって、GitHubのWebページではこのコメントを一覧で表示できない。 そのため、コメントに気付かずスルーしてしまうという、文書の編集において最悪の事態を招くことがある。

ただ、一覧表示する術がまったくないかというとそういうわけでもなく、GitHubが公開しているREST API v3経由で取得できる。

Go言語であれば、このREST APIを使って、以下の要領でコメントの本文をすべて取得できる。

package main

import (
    "encoding/json"
    "io/ioutil"
    "net/http"
    "fmt"
)

type Comment struct {
    Body string `json:"body"`
}

func main () {

    req, err := http.NewRequest("GET",
        "https://api.github.com/repos/〈アカウント〉/〈リポジトリ〉/comments", nil)

    if err != nil {
        panic(err)
    }
    req.Header.Set("Authorization", "token 〈好きなトークンを指定しよう〉")
    req.Header.Set("Content-Type", "application/json")

    resp, err := http.DefaultClient.Do(req)

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        panic(err)
    }

    var comments []Comment
    err = json.Unmarshal(body, &comments)
    if err != nil {
        fmt.Println(err.Error())
        return
    }
 
    fmt.Println(comments)
 
    defer resp.Body.Close()
}

おなじみ、http.NewRequestでリクエストを作ればいいのだが、そのヘッダには"Authorization"エントリでGitHubから取得したトークンを指定する必要がある。 "Content-Type""application/json"を指定しているのは、GitHub API v3のマニュアルに従ったものである。

そうやって作ったHTTPリクエストをhttp.DefaultClient.Doで発行し、ioutil.ReadAllでレスポンスを読み込んで、それをjson.Unmarshalする。 ここでは事前に定義したComment型の構造体に、コメントの各エントリを取り込むようにしている。 Comment型の構造体としては、とりあえずコメントの本文を表す"body"エントリだけを残すようにしてみた。

さっそく実行してみよう。〈アカウント〉に当社のGitHubアカウント、〈リポジトリ〉にとあるn月刊ラムダノートの記事のリポジトリを指定し、go buildして実行みると、現時点では2つのコメントが残されているっぽいことが判明した。

$ ./github-comments
[{Common Lispと見出しレベルが揃ってなかったので揃えました。目次に影響ありそう} {ありがとうございます。これどうしようかちょっと迷ってたところでした。}]

もちろん、これだけだと誰のコメントなのかわからないので、もうちょっとComment型を作りこむ必要があるだろう。 また、いつ書き込まれたコメントかわからないのは不便なので、日時くらいは取得するようにしたい。

さらに、せっかくならWebブラウザで関係者が閲覧できるようにして、該当のコメントが残されているコミットへのリンクを貼り、クリックすれば前後の本文の状況を確かめられるようにしたい。 そこで、JSONから取得したコメントを"html/template"モジュールを使ってHTMLのテーブルに流し込むようにし、HerokuかどこかでWebサーバとして動かすようにしよう。

ようするにこういうことである。

package main

import (
    "os"
    "encoding/json"
    "html/template"
    "io/ioutil"
    "net/http"
    "fmt"
)

type Comment struct {
    Body string `json:"body"`
    Date string `json:"created_at"`
    URL string `json:"html_url"`
    Author struct {
        Login string `json:"login"`
    } `json:"user"`   
}

func getComments (w http.ResponseWriter, r *http.Request) {

    req, err := http.NewRequest("GET",
        "https://api.github.com/repos/〈アカウント〉/〈リポジトリ〉/comments", nil)

    if err != nil {
        panic(err)
    }
    req.Header.Set("Authorization", "token 〈好きなトークンを指定しよう〉")
    req.Header.Set("Content-Type", "application/json")

    resp, err := http.DefaultClient.Do(req)

    body, err := ioutil.ReadAll(resp.Body)
    if err != nil {
        panic(err)
    }

    var comments []Comment
    err = json.Unmarshal(body, &comments)
    if err != nil {
        fmt.Println(err.Error())
        return
    }
 
    t := template.New("template.tpl")
    t, _ = t.ParseFiles("template.tpl")
    err = t.Execute(w, comments)
    if err != nil {
        panic(err)
    }

    defer resp.Body.Close()
}

func main () {
    port := os.Getenv("PORT")
    http.HandleFunc("/", getComments)
    http.ListenAndServe(":"+port, nil)
}

template.tplは適当に用意しよう。 それらをHerokuに挙げてWebブラウザから閲覧するとこんな感じになる。

f:id:golden-lucky:20191213230445p:plain

日時の欄をクリックすると、コメントがあるコミットのページに飛ぶようになっている。 net/httpにあるBasicAuth()を使った簡単な認証をかけて、このページを関係者と共有してもいいだろう。