22
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

NHK番組表API を使ってアニメの音楽番組をslackに通知する

Last updated at Posted at 2016-07-16

はじめに

  • ゆるふわです

概要

  • NHK番組表API を使ってアニメの音楽番組をslackに通知する

開発に至ったどうでもいい話

  • 2015年11月~12月に放送されたアニメロサマーライブ2015のテレビ放送に気づいたのが最終週だった
  • 前の週にまれいたそがgive me revolution歌ってたって知って絶望した
  • アニメロサマーライブ2014は2015年夏に再放送してた
  • API MeetupでNHKが番組表API作ったって聞いてたので最終週の放送が終わるまでに通知botを作ろうとおもった

動作環境(開発当時->現在)

  • raspberry pi(raspbian jessy) -> ec2.nano(ubuntu 15.10)
  • golang(1.4->1.6)
  • redis(???->2.8.4)
  • slack incoming webhook

開発

下調べ

仕様

  • リクエストパラメータ
    • service: tv(全チャンネル)
      • どのチャンネルで再放送するかわからなかったので全チャンネル取得して以下のチャンネルのみ抽出
        - G1(NHK総合1)
      • E1(NHKEテレ1)
      • S1(NHKBS1)
      • S3(NHKBSプレミアム)
    • area: 130(東京)
    • genre: 04[1~10](全ジャンル)
      • 04[0-9]? という指定ができなかったので10回叩く(ハードコーディング)
    • date: 翌日
  • responseのIDがKVS(redis)上に存在していれば無視, 存在していれば登録
    • lifetimeは24h
  • responseのTitle or Subtitleに "アニメ" がなければ無視
  • 番組情報は午前5時ごろデータが更新されるので05:30~6時間周期でにbatchを実行(cron)

実装

package main

import (
	"encoding/json"
	"fmt"
	"github.com/garyburd/redigo/redis"
	"net/http"
	"net/url"
	"strings"
	"sync"
	"time"
)

type Slack struct {
	Channel   string `json:"channel"`
	IconEmoji string `json:"icon_emoji"`
	IconUrl   string `json:"icon_url"`
	Text      string `json:"text"`
	Username  string `json:"username"`
}

type NihonHikikomoriKyokai struct {
	List List `json:"list"`
}

type List struct {
	G1 []Program `json:"g1"`
	G2 []Program `json:"g2"`
	E1 []Program `json:"e1"`
	E2 []Program `json:"e2"`
	E3 []Program `json:"e3"`
	E4 []Program `json:"e4"`
	S1 []Program `json:"s1"`
	S2 []Program `json:"s2"`
	S3 []Program `json:"s3"`
	S4 []Program `json:"s4"`
	R1 []Program `json:"r1"`
	R2 []Program `json:"r2"`
	R3 []Program `json:"r3"`
	N1 []Program `json:"n1"`
	N2 []Program `json:"n2"`
	N3 []Program `json:"n3"`
}

type Program struct {
	Area struct {
		ID   string `json:"id"`
		Name string `json:"name"`
	} `json:"area"`
	EndTime string   `json:"end_time"`
	EventID string   `json:"event_id"`
	Genres  []string `json:"genres"`
	ID      string   `json:"id"`
	Service struct {
		ID    string `json:"id"`
		LogoL struct {
			Height string `json:"height"`
			URL    string `json:"url"`
			Width  string `json:"width"`
		} `json:"logo_l"`
		LogoM struct {
			Height string `json:"height"`
			URL    string `json:"url"`
			Width  string `json:"width"`
		} `json:"logo_m"`
		LogoS struct {
			Height string `json:"height"`
			URL    string `json:"url"`
			Width  string `json:"width"`
		} `json:"logo_s"`
		Name string `json:"name"`
	} `json:"service"`
	StartTime string `json:"start_time"`
	Subtitle  string `json:"subtitle"`
	Title     string `json:"title"`
}

const (
	ApiKey = "{YOUR_NHKWEBAPI_TOKEN}"
)

func main() {
	genreCodes := []string{
		"0400",
		"0401",
		"0402",
		"0403",
		"0404",
		"0405",
		"0406",
		"0407",
		"0408",
		"0409",
		"0410",
	}
	for i := 0; i < len(genreCodes); i++ {
		endpoint := fmt.Sprintf("http://api.nhk.or.jp/v1/pg/genre/130/tv/%s/%s.json?key=%s", genreCodes[i], time.Now().AddDate(0, 0, 1).Format("2006-01-02"), ApiKey)
		resp, _ := http.Get(endpoint)
		defer resp.Body.Close()
		var nhk NihonHikikomoriKyokai
		decoder := json.NewDecoder(resp.Body)
		decoder.Decode(&nhk)
		c, _ := redis.Dial("tcp", ":6379")
		defer c.Close()
		giveMeRevolution(nhk.List.G1, c)
		giveMeRevolution(nhk.List.E1, c)
		giveMeRevolution(nhk.List.S1, c)
		giveMeRevolution(nhk.List.S3, c)
	}
}

func giveMeRevolution(programs []Program, c redis.Conn) {
	var wg sync.WaitGroup
	for _, program := range programs {
		if false == isChecked("nhk_"+program.ID, c) {
			keyword := "アニメ"
			if strings.Contains(program.Title, keyword) || strings.Contains(program.Subtitle, keyword) {
				var s Slack
				format := "2006-01-02T15:04:05+09:00"
				startTime, _ := time.Parse(format, program.StartTime)
				endTime, _ := time.Parse(format, program.EndTime)
				s.Text = fmt.Sprintf(
					"(\\( ⁰⊖⁰)/) タッタラタッタ、タッタ!\n```%s```\n```%s```\n```%s ~ %s```",
					program.Title,
					program.Subtitle,
					startTime.Format("2006-01-02(Mon) 15:04:05"),
					endTime.Format("2006-01-02(Mon) 15:04:05"),
				)
				j, _ := json.Marshal(s)
				wg.Add(1)
				go func(j []byte) {
					defer wg.Done()
					http.PostForm(
						"https://hooks.slack.com/services/{YOUR_WEBHOOK_URL}",
						url.Values{
							"payload": {
								string(j),
							},
						},
					)
					fmt.Printf("POSTED!! ID:%s Title:%s\n", program.ID, program.Title)
				}(j)
			} else {
				fmt.Printf("new program was found. but not contains keyword. ID:%s Title:%s\n", program.ID, program.Title)
			}
		} else {
			fmt.Printf("already checked. ID:%s Title:%s\n", program.ID, program.Title)
		}
	}
	wg.Wait()
}

func isChecked(key string, c redis.Conn) bool {
	//return false
	checked, err := redis.Bool(c.Do("GET", key))
	if err != nil || checked == false {
		c.Do("EXPIRE", key, 60*60*24)
		c.Do("SET", key, true)
		return false
	} else {
		return true
	}
}

結果

  • image
  • gotcha!!

所感

  • WebAPIの仕様書どこ
  • ジャンルまとめて取りたい
  • jsonのkey側が可変(G1, E1とか)なときのstructの定義がわからなくてハードコーディング放置
  • 翌日までしか取得できない(slack経由でpush通知してもらわないと録画が間に合わない)
  • v2に上がった際"1日"が0:00~23:59:59じゃなくなったっぽい
  • アニメロサマーライブは01:15~なのだけど突然通知がこなくなった
  • 当日じゃないと01:15のデータは取れないかもしれなくて当日+翌日にするか考え中(rate limit確認)
  • 動作環境をrasbianから ubuntuに途中で変えたけど一切ストレスなくてgolangマジ神
  • まれいたそ〜
    • give me revolutionの前振りでコールアンドレスポンスしてるまれいたそ〜
    • そばかす歌ってるまれいたそ〜
  • すみぺ〜

(\( ⁰⊖⁰)/) enjoy!!!

22
20
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
22
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?