GolangでFacebook feedを取得するSDKを書いた

bot作りに精を出している今日このごろ、Facebookのfeedが更新されたら通知するbotを作ろうと思い、自分用のfeed取得SDKを作ったので使い方をまとめます。 今のところGetFeedという関数しかないです。

Install

どちらも自作になってしまいますが、以下2つをgo getします。

go get github.com/cipepser/bots
go get github.com/cipepser/httpclient/sdk

アクセストークンの設定

Feedを取得するためには、アクセストークンが必要です。そのためにfacebook for developersでappを作成してください。
curlでFacebook API叩くまでがわりとめんどかったのでメモ - DRYな備忘録を参考にさせて頂きました。

appを作成するとアクセストークンがここから確認できます。 以下の黒く塗り潰している箇所のApp Tokenです。

f:id:cipepser:20180103204427p:plain

App Tokenをコピーし、./token/facebook_api.jsonに以下のように設定してください。

{
  "access_token":"<YOUR APP TOKEN>"
}

これで準備は完了です。

How to Use

URLに取得したいfeedのURLを入力し、GetFeed(URL)を実行するだけです。


package main

import (
    "github.com/cipepser/bots/facebook"
)

func main() {
  URL := "https://graph.facebook.com/v2.11/<user name>/feed"

  f, err := facebook.GetFeed(URL)
  if err != nil {
    panic(err)
  }
  
  fmt.Println(f)

}

結果(上記f)は、facebookパッケージの中で定義している以下のFeed型で返ってくるので、必要なものを利用できます。

type Feed struct {
    Data []struct {
        CreatedTime string `json:"created_time"`
        Message     string `json:"message"`
        ID          string `json:"id"`
        Story       string `json:"story,omitempty"`
    } `json:"data"`
    Paging struct {
        Cursors struct {
            Before string `json:"before"`
            After  string `json:"after"`
        } `json:"cursors"`
        Next string `json:"next"`
    } `json:"paging"`
}

実装について

実装の中身はGithubにあげてあります。 access_tokenを埋め込んで、HTTPでGETするだけなので、応用しやすいです。

References

GolangでLINE Notify

LINEのBOTをDeveloper Trialのときに遊んで以来、ご無沙汰だったのですが、改めてLINE Notifyを試したらものすごく手軽でした。 ぐぐってみるとherokuにデブロイする系の記事が多いのですが、HTTPでPOSTするだけで通知できるので、備忘がてら手順をまとめたいと思います。

アクセストークンの取得

LINE Notifyの画面からログインします。 ログイン後、マイページへ移動します。

f:id:cipepser:20171210180034p:plain

下の方に「アクセストークンの発行(開発者向け)」という項目があるので、 「トークンを発行する」を押下します。

f:id:cipepser:20171210180045p:plain

すると以下の画面が出てくるので、

を入力します。

f:id:cipepser:20171210180055p:plain

トークン名は、通知されるときにメッセージの先頭に付与されるので短めがおすすめです。

また、グループなどに通知したい場合は、先にグループを作っておく必要があります。 今回は「テスト」を事前に作っておきました。

f:id:cipepser:20171210180102p:plain

「発行する」を押下すると以下のようにアクセストークンが得られます。

f:id:cipepser:20171210180111p:plain

これをコピーしておきましょう。なくしてしまった場合は、もう一度同じ手順で発行します。

LINE Notifyアカウントをトークンルームに追加

アクセストークンを発行すると以下のように通知が来ます。 f:id:cipepser:20171210180123p:plain

指示通りトークルーム(今回は「テスト」グループ)にLINE Notifyのアカウント(トークン名「bot」ではなく、LINE Notifyなので注意)を招待してあげます。

f:id:cipepser:20171210180128p:plain

これで準備完了です。

メッセージを送る

実装は以下です。msgの内容を送信します。

package main

import (
    "fmt"
    "io/ioutil"
    "log"
    "net/http"
    "net/url"
    "strings"
)

func main() {
    accessToken := <YOUR ACCESS TOKEN>
    msg := "テストメッセージ"

    URL := "https://notify-api.line.me/api/notify"

    u, err := url.ParseRequestURI(URL)
    if err != nil {
        log.Fatal(err)
    }

    c := &http.Client{}

    form := url.Values{}
    form.Add("message", msg)

    body := strings.NewReader(form.Encode())

    req, err := http.NewRequest("POST", u.String(), body)
    if err != nil {
        log.Fatal(err)
    }

    req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
    req.Header.Set("Authorization", "Bearer "+accessToken)

    _, err := c.Do(req)
    if err != nil {
        log.Fatal(err)
    }
}

実行すると以下のようにメッセージを受信できます。

f:id:cipepser:20171210180444p:plain

References

Karabiner11.4.0で「ctrl+かな」を「ESC」にバインドする

Karabinerを11.4.0にアップデートしたら、今までprivate.xmlに設定してきたkey bindingsが使えなくなってしまいました。公式のマニュアルKarabiner-Elementsの設定項目をまとめましたを参考にしながら、「ctrl+かな」を「ESC」にバインドする設定をしたので残します。

前回の記事は、complex_modificationsをインポートしましたが、自分で設定するのが今回です。

環境

macOS High Sierra ver 10.13.2
JIS keyboard
Karabiner-Elements-11.4.0

設定ファイル

設定ファイルがxmlからjsonに変わり、配置も~/.config/karabinerに変わっています。

❯ tree
.
├── assets
│   └── complex_modifications
│       └── hoge.json
└── karabiner.json

karabiner.jsonは、メインのconfigファイルで、変更があれば自動でリロードされるそうです。

assets/complex_modificationsは、その名の通りcomplex_modificationsを設定するディレクトリで、この中にjsonファイルを格納します。

設定

assets/complex_modifications配下にctrl-kana-to-esc.jsonを作成し、以下の内容を記載します。

{
    "title": "Change ctrl+KANA to ESC",
    "rules": [{
        "description": "Change ctrl+KANA key to Escape key",
        "manipulators": [{
            "type": "basic",
            "from": {
                "key_code": "japanese_kana",
                "modifiers": {
                    "mandatory": ["control"]
                }
            },
            "to": [{
                "key_code": "escape"
            }]
        }]
    }]
}

あとはこの設定を適用してあげれば完了です。

f:id:cipepser:20171210161909p:plain

最後に

private.xmlよりも簡単に設定できるようになったように感じました。PreferencesLogタブでエラーが見れるのでデバッグも捗ります。

References

KarabinerのSimple Vi ModeをHigh Sierraでも使う

Karabinerが使えなくなると聞いていたので、ずっとHigh Sierraにアップデートするのを渋っていたのですが、ついにアップデートしました。渋っている間に対応が済んでおり、Simple Vi Modeも使えたので、設定方法をまとめます。

環境

macOS High Sierra ver 10.13.2
Karabiner-Elements-11.4.0

手順

Karabiner-Elementsを起動し、Complex Modificationsタブを選びます。 出てきた画面でAdd ruleを押下します。

f:id:cipepser:20171210153325p:plain

上のほうにあるImport more rules from the Internet (open a web browser)を押下します。

f:id:cipepser:20171210153342p:plain

ブラウザが立ち上がるので、Emulation Modesの項目のVi Mode (rev 4.2)Importします。

f:id:cipepser:20171210153347p:plain

Karabiner-Elementsに戻ってくるのでImportを押下します。

f:id:cipepser:20171210153356p:plain

最後にVi Mode [S as Trigger Key]Enableで有効化して完了です。

f:id:cipepser:20171210153404p:plain

簡単ですね。

References

Golangでx-www-form-urlencodedのリクエストを投げる

やりたいことはタイトルそのままです。 parameter=hogeをhttpでPOSTする方法は以下です。

form := url.Values{}
form.Add("paramter", "hoge")

body := strings.NewReader(form.Encode())

req, err := http.NewRequest("POST", "https://example.com", body)
if err != nil {
    log.Fatal(err)
}

req.Header.Set("Content-Type", "application/x-www-form-urlencoded")

References

Golangのnew()とmake()の違い

この記事は Go (その3) Advent Calendar 2017 の5日目の記事です。

本記事では、変数の宣言に用いられる組み込み関数new()make()の違いについてまとめます。

まとめ

さっそくですが、違いを表にまとめます。

new(T) make(T)
対象 任意の型 slice, map, channelのみ
初期化 初期化しない(ゼロ値になる) 初期化する
返り値 *T T

対象と初期化について

new()make()で、初期化しない/するの違いは、 slice, map, channelが、内部にデータ構造を持つことからきています。

以下にruntimeパッケージで、それぞれ型が定義されている箇所を引用します。 一番理解しやすいのが、sliceです。array(実データ), len, capを初期化してあげる必要があるため、make()が用意されています。

slice

type slice struct {
      array unsafe.Pointer
      len   int
      cap   int
  }

https://golang.org/src/runtime/slice.go#L11-15

map

type hmap struct {
      // Note: the format of the Hmap is encoded in ../../cmd/internal/gc/reflect.go and
      // ../reflect/type.go. Don't change this structure without also changing that code!
      count     int // # live cells == size of map.  Must be first (used by len() builtin)
      flags     uint8
      B         uint8  // log_2 of # of buckets (can hold up to loadFactor * 2^B items)
      noverflow uint16 // approximate number of overflow buckets; see incrnoverflow for details
      hash0     uint32 // hash seed
  
      buckets    unsafe.Pointer // array of 2^B Buckets. may be nil if count==0.
      oldbuckets unsafe.Pointer // previous bucket array of half the size, non-nil only when growing
      nevacuate  uintptr        // progress counter for evacuation (buckets less than this have been evacuated)
  
      extra *mapextra // optional fields
  }

https://golang.org/src/runtime/hashmap.go#L106-120

channel

type hchan struct {
      qcount   uint           // total data in the queue
      dataqsiz uint           // size of the circular queue
      buf      unsafe.Pointer // points to an array of dataqsiz elements
      elemsize uint16
      closed   uint32
      elemtype *_type // element type
      sendx    uint   // send index
      recvx    uint   // receive index
      recvq    waitq  // list of recv waiters
      sendq    waitq  // list of send waiters
  
      // lock protects all fields in hchan, as well as several
      // fields in sudogs blocked on this channel.
      //
      // Do not change another G's status while holding this lock
      // (in particular, do not ready a G), as this can deadlock
      // with stack shrinking.
      lock mutex
  }

https://golang.org/src/runtime/chan.go#L31-50

初期化について補足

Effective Goでは、new()では初期化が行われない、つまりゼロ値となることについて、helpfulであると述べられています。 これはゼロ値自体が意味を持つ場合には、初期化しているのと同じだけの意味があるということです。

Effective Goで述べられている例ですが、sync.Mutexでは以下のようにゼロ値自身がunlockなstateを表現します。

// A Mutex is a mutual exclusion lock.
// The zero value for a Mutex is an unlocked mutex.
//
// A Mutex must not be copied after first use.
type Mutex struct {
  state int32
    sema  uint32
}

自身で型を定義する際には、NewMyType()のようなコンストラクタを用意する場合が多いでしょう。このときにゼロ値自身に意味を持たせ、new()と組み合わせることでよい設計となると思います。

最後に

自分自身もあまりnew()make()の違いを認識できていなかったので、調べてまとめることができてよかったです。あといつか読もう読もうと思って着手できていなかったruntimeパッケージを読むきっかけにもなったので、思いの外収穫が多かったです。Goはソースコードにドキュメントレベルでコメントが書いてあるので、読むだけでなるほどと思うことが多く、とても勉強になります。

Githubの履歴を確認したらちょうどGoを書き始めてから1年が経過しました。書けば書くほど好きになっていくので、まだまだ書きますよー!

あぁ。水色gopherくんぬいぐるみ欲しいなぁ。。。

References

go tool compileの使い方

少し試したものの、忘れそうなのでメモ書きとして残します。

サンプルコード

package main

import "fmt"

func main() {
    fmt.Println("hello world")
}

オブジェクトファイルの生成

-Sが1つだと標準出力のみです。2つだと.oファイルが生成されます。

$ go tool compile -S -S main.go
"".main STEXT size=120 args=0x0 locals=0x48
    0x0000 00000 (main.go:5)    TEXT    "".main(SB), $72-0
  (中略)
    0x0000 65 48 8b 0c 25 00 00 00 00 48 3b 61 10 76 62 48  eH..%....H;a.vbH
    0x0010 83 ec 48 48 89 6c 24 40 48 8d 6c 24 40 48 c7 44  ..HH.l$@H.l$@H.D
    0x0020 24 30 00 00 00 00 48 c7 44 24 38 00 00 00 00 48  $0....H.D$8....H
    0x0030 8d 05 00 00 00 00 48 89 44 24 30 48 8d 05 00 00  ......H.D$0H....
    0x0040 00 00 48 89 44 24 38 48 8d 44 24 30 48 89 04 24  ..H.D$8H.D$0H..$
    0x0050 48 c7 44 24 08 01 00 00 00 48 c7 44 24 10 01 00  H.D$.....H.D$...
    0x0060 00 00 e8 00 00 00 00 48 8b 6c 24 40 48 83 c4 48  .......H.l$@H..H
    0x0070 c3 e8 00 00 00 00 eb 88                          ........
    rel 5+4 t=16 TLS+0
    rel 50+4 t=15 type.string+0
    rel 62+4 t=15 "".statictmp_0+0
    rel 99+4 t=8 fmt.Println+0
    rel 114+4 t=8 runtime.morestack_noctxt+0
  (中略)
type..importpath.fmt. SRODATA dupok size=6
    0x0000 00 00 03 66 6d 74                                ...fmt
gclocals·69c1753bd5f81501d95132d08af04464 SRODATA dupok size=8
    0x0000 02 00 00 00 00 00 00 00                          ........
gclocals·e226d4ae4a7cad8835311c6a4683c14f SRODATA dupok size=10
    0x0000 02 00 00 00 02 00 00 00 00 03                    ..........
gclocals·33cdeccccebe80329f1fdbee7f5874cb SRODATA dupok size=8
    0x0000 01 00 00 00 00 00 00 00                          ........

オブジェクトファイルから実行コードを生成

$ go tool link main.o

$ ls
a.out   main.go main.o

$ ./a.out
hello world

オブジェクトファイルに定義されているシンボルの表示

$ go tool nm main.o
         U
     44c T %22%22.init
     517 B %22%22.initdone·
     3b5 T %22%22.main
     507 R %22%22.statictmp_0
         U fmt.Println
         U fmt.init
     6de R gclocals·33cdeccccebe80329f1fdbee7f5874cb
     6cc R gclocals·69c1753bd5f81501d95132d08af04464
     6d4 R gclocals·e226d4ae4a7cad8835311c6a4683c14f
     4ea ? go.info.%22%22.init
     4cd ? go.info.%22%22.main
     507 ? go.range.%22%22.init
     4ea ? go.range.%22%22.main
     4c2 R go.string."hello world"
         U runtime.algarray
     517 R runtime.gcbits.01
     560 R runtime.gcbits.03
         U runtime.morestack_noctxt
         U runtime.throwinit
     646 R type.*[1]interface {}
     5c3 R type.*[]interface {}
     528 R type.*interface {}
     6c6 R type..importpath.fmt.
     633 R type..namedata.*[1]interface {}-
     5b1 R type..namedata.*[]interface {}-
     518 R type..namedata.*interface {}-
     67e R type.[1]interface {}
     5fb R type.[]interface {}
     561 R type.interface {}
         U type.string

実行コードの逆アセンブリ

$ go tool objdump a.out

(略)

References