まったりするmatayu

ツイートにゴミがついた感じのブログ

YouTube Data APIを叩いてみる(導入)

備忘録

YouTube  |  Google Developers

このサイトをみると,YouTubeAPIは大まかに

  • Play YouTube Videos

  • Add YouTube Data

  • Analytics & Reporting

  • Subscribe Buttons

  • Live streaming

みたいな5つがあって,

今回はYouTubeから動画のタイトルやLive配信の開始時間(これはLive streamingのほうにあるかも)などのDataをとりたいだけだから,

多分「Add YouTube Data」という API を使えば良いだろう.

上記のサイトから辿れるが次の,

YouTube Data API Overview  |  Google Developers

を参考に進めていく.

Before your start

YouTube Data API を使うにあたっての事前準備事項があるようだ.

  1. Google アカウントの作成.

  2. Google developers consoleにて,プロジェクトの作成.認証情報の取得.

  3. プロジェクトでYouTube Data API v3 がonになっていることを確認.

  4. ユーザー認証を必要としていればOAuth2.0認証の実装.

  5. クライアントライブラリを選択.

  6. JSONをしっかり理解.

まずはこいつらを全部クリアしていく.

1. Googleアカウントの作成

割愛.

2. consoleでプロジェクトの作成と認証情報の取得.

コンソールはここからアクセスできる.

Google Cloud Platform

プロジェクト作成

Google APIs と書いてあるところの右隣にある下三角を押すとウィンドウがポップアップするから,そこで新しいプロジェクトを押す.

そうしたら,そこでプロジェクト名やら組織名やらを入力してプロジェクトを作成する.

(3. )Data API を有効化する

認証情報を作成する前に Data API を有効化しておく.

ダッシュボードタブを選択して, そこでAPI とサービスを有効化を押す.

そこでYouTube Data API v3を探して有効化すればよい.

認証情報の作成

今回はOAuth2.0認証を使用するから,まずOAuth同意画面の設定をする.

OAuth同意画面タブを開いて,質問に答える.

次の画面で必要最低限,アプリケーション名を設定して認証情報の作成に進む.

認証情報タブを選択して,そこで認証情報を作成を押す.

OAuth2.0認証を使用するからOAuthクライアントIDを選ぶ.

そこで必要情報を埋めて,作成する.

作成できたら,OAuth2.0 クライアントIDの一覧から作成したものを選択し,

そこでJSONをダウンロードをクリックしてクライアントの情報が書かれたjsonファイルをダウンロード.

ダウンロードしたものをサーバーの任意の場所に置いておく.

(次のサーバー側のOAuth2.0設定をする時に使う)

4. OAuth2.0認証の実装

ここをメインの参考にOAuth2.0認証のサーバー側の設定やらコードやらを書いた.

Using OAuth 2.0 for Web Server Applications  |  YouTube Data API

使用したものとしては,

のような感じで,サブで参考にしたサイトはこの辺り.

godoc.org

godoc.org

github.com

書いたコードは次のような感じ(一部).

package oauth

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

    "github.com/gin-gonic/gin"
    // mysql is used for gorm.
    _ "github.com/go-sql-driver/mysql"
    "github.com/jinzhu/gorm"
    "github.com/matac42/LiveShare/database"
    "golang.org/x/oauth2"
)

// Conf wraped oauth2.Config.
type Conf struct {
    oauth2.Config
}

// CredentialInfo store oauth2 access token etc...
type CredentialInfo struct {
    gorm.Model
    oauth2.Token
}

// APIInfo include api info from google.
type APIInfo struct {
    Web struct {
        ClientID                string   `json:"client_id"`
        ProjectID               string   `json:"project_id"`
        AuthURI                 string   `json:"auth_uri"`
        TokenURI                string   `json:"token_uri"`
        AuthProviderX509CertURL string   `json:"auth_provider_x509_cert_url"`
        ClientSecret            string   `json:"client_secret"`
        RedirectURIs            []string `json:"redirect_uris"`
        JSOrigins               []string `json:"javascript_origins"`
    }
}

// CreateAPIInfo create APIInfo based on apiinfo.json.
func CreateAPIInfo() APIInfo {
    apiInfo := APIInfo{}
    raw, err := ioutil.ReadFile("/home/ubuntu/Downloads/apiinfo.json")
    if err != nil {
        fmt.Println(err.Error())
    }

    json.Unmarshal(raw, &apiInfo)

    return apiInfo
}

// CreateConf create oauth2 config structure.
func CreateConf() Conf {
    apiInfo := CreateAPIInfo()
    conf := Conf{
        oauth2.Config{
            ClientID:     apiInfo.Web.ClientID,
            ClientSecret: apiInfo.Web.ClientSecret,
            Scopes:       []string{"https://www.googleapis.com/auth/youtube.readonly"},
            Endpoint: oauth2.Endpoint{
                AuthURL:  apiInfo.Web.AuthURI,
                TokenURL: apiInfo.Web.TokenURI,
            },
            RedirectURL: apiInfo.Web.RedirectURIs[0],
        },
    }
    return conf
}

// Google redirect google oathorized page.
func (conf Conf) Google(c *gin.Context) {

    url := conf.AuthCodeURL("state", oauth2.AccessTypeOffline)
    c.Redirect(http.StatusMovedPermanently, url)

}

// CallBack fires fn when there was a connection to /callback.
func (conf Conf) CallBack(c *gin.Context) {
    code := GetAuthCode(c)
    tok := conf.GetAccessToken(c, code)
        cre := CredentialInfo{}
    cre.Token = *tok
    SaveCredentialInfo(cre)
}

// GetAuthCode get credential code from google.
func GetAuthCode(c *gin.Context) string {
    code := c.Request.URL.Query().Get("code")
    err := c.Request.URL.Query().Get("error")
    if err != "" {
        fmt.Println(err)
    }
    // if _, err := fmt.Scan(&code); err != nil {
    //     log.Fatal(err)
    // }

    return code
}

// GetAccessToken get accesstoken from google.
func (conf Conf) GetAccessToken(c *gin.Context, code string) *oauth2.Token {
    tok, err := conf.Exchange(c, code)
    if err != nil {
        log.Fatal(err)
    }

    return tok
}

// SaveCredentialInfo is routines for storing credential info to the database.
func SaveCredentialInfo(cre CredentialInfo) {
    db, err := database.SQLConnect()
    if err != nil {
        panic(err.Error())
    }
    defer db.Close()
    db.AutoMigrate(cre)
    error := db.Create(&cre).Error
    if error != nil {
        fmt.Println(error)
    } else {
        fmt.Println("success addition access token to db!!!")
    }
}

上から見ていくと,

Google()は認証ページへのリダイレクトを行い,CallBack()でgoogleからのcallbackを処理する.

GetAuthCode()はcallbackで返ってきた認証コードを処理し,

そのコードをGetAccessToken()に渡してAccess Tokenを取りにいく.

最後に取って来たAccess TokenをSaveCredentialInfo()でデータベースに保存する.

そんな感じの流れになっている.

一応,今後何らかのweb アプリケーションにすることを考えて,データベースに保存する形にしておいた.

5. クライアントライブラリの選択

クライアントライブラリはAPIの実装を簡単にする,パッケージみたいなものだと思う.

Client Libraries  |  YouTube Data API  |  Google Developers

ここをみるとGoogle API ClientLibrary for go なるものがあったから,これを使うのだろう.

github.com

使ってみて理解が進んだらまた記事にしようかな.

6. Jsonをしっかり理解

完全に理解した.

APIを叩いてみる.

とりあえず今回は,curl で叩く.

Access Token を使ってYouTubeのチャンネル情報を取得してみる.

$curl -H "Authorization: Bearer Access_token" https://www.googleapis.com/youtube/v3/channels?part=snippet\&mine=true

Access_tokenのところにAccess tokenを入れればよい.

得られた結果は次の通り(自分のチャンネルの情報).

{
  "kind": "youtube#channelListResponse",
  "etag": "d1bn9VpVzc_sJex25DypsDa7vYI",
  "pageInfo": {
    "totalResults": 1,
    "resultsPerPage": 1
  },
  "items": [
    {
      "kind": "youtube#channel",
      "etag": "qcyx0gNQud0DuE68vPz_b5ljXZ0",
      "id": "UCOiNaZYE2x6LVisRObnbwMQ",
      "snippet": {
        "title": "絵又-enomata",
        "description": "お絵かき練習の記録を残すために,お絵描き練習ライブや練習動画の投稿をしています.",
        "publishedAt": "2020-05-07T11:37:27Z",
        "thumbnails": {
          "default": {
            "url": "https://yt3.ggpht.com/a/AATXAJyZ3e7eyylr8gkXlKk4tihkVYwxY8G7FTlByg=s88-c-k-c0xffffffff-no-rj-mo",
            "width": 88,
            "height": 88
          },
          "medium": {
            "url": "https://yt3.ggpht.com/a/AATXAJyZ3e7eyylr8gkXlKk4tihkVYwxY8G7FTlByg=s240-c-k-c0xffffffff-no-rj-mo",
            "width": 240,
            "height": 240
          },
          "high": {
            "url": "https://yt3.ggpht.com/a/AATXAJyZ3e7eyylr8gkXlKk4tihkVYwxY8G7FTlByg=s800-c-k-c0xffffffff-no-rj-mo",
            "width": 800,
            "height": 800
          }
        },
        "localized": {
          "title": "絵又-enomata",
          "description": "お絵かき練習の記録を残すために,お絵描き練習ライブや練習動画の投稿をしています."
        },
        "country": "JP"
      }
    }
  ]
}

チャンネル名やらチャンネル説明やらがJsonで取得できた.

こんな感じのJsonを処理してフロントに持っていけばなんかwebアプリできそう.

夢が広がる.

後述

以前,githubでOAuth2.0認証実装したときはoauth2パッケージを使わなかったが今回は使ってみた.

めっちゃ便利だった.

なんかAPIたたく話よりOAuth2.0実装の話の方が多くなってしまった.

まあ導入だし良いか.

次は "YouTube APIつかってWebアプリ作ってみた" みたいな感じにする.

YouTubeAPIはとても要素が多く,使えるようになるのも大変そうだと思ったが,

使いどころもたくさんあるだろうから,楽しく覚えていこうと思う.

GET と POST の違い

以前 github api を利用して,ユーザー情報を取得することをした.

mattari-matayu.hatenablog.com

その時にGET と POST の違いがよく分からなくなったから,理解しようと思う.

実体をつかむ

マスタリングTCP/IP入門編によると...

www.amazon.co.jp

マスタリングTCP/IP入門編によると,HTTP通信の主なコマンドは次のようなものがある.

・OPTION・・・オプションの設定

・GET  ・・・指定したURLのデータを取得

・HEAD ・・・メッセージヘッダだけを取得

・POST ・・・指定したURIにデータを登録

・PUT ・・・指定したURIにデータを保存

・DELETE ・・・指定したURIのデータを削除

・TRACE ・・・リクエストメッセージをクライアントに戻す

GETとPOSTくらいしか見たことなかった.

GETとPOSTはHTTPコマンドの一つであることがわかった.

HTTPリクエストの中身をちょっと見てみる.

これはとあるHTTPリクエストをdelveで覗いたものである(golangのnet/http.Requestのフォーマット).

(dlv) p req
*net/http.Request {
    Method: "GET",
    URL: *net/url.URL {
        Scheme: "https",
        Opaque: "",
        User: *net/url.Userinfo nil,
        Host: "api.github.com",
        Path: "/user",
        RawPath: "",
        ForceQuery: false,
        RawQuery: "",
        Fragment: "",},
    Proto: "HTTP/1.1",
    ProtoMajor: 1,
    ProtoMinor: 1,
    Header: net/http.Header [
        "Accept": [
            "application/json",
        ], 
        "Authorization": [
            "bearer *",
        ], 
    ],
    Body: io.ReadCloser nil,
    GetBody: nil,
    ContentLength: 0,
    TransferEncoding: []string len: 0, cap: 0, nil,
    Close: false,
    Host: "api.github.com",
    Form: net/url.Values nil,
    PostForm: net/url.Values nil,
    MultipartForm: *mime/multipart.Form nil,
    Trailer: net/http.Header nil,
    RemoteAddr: "",
    RequestURI: "",
    TLS: *crypto/tls.ConnectionState nil,
    Cancel: <-chan struct {} {},
    Response: *net/http.Response nil,
    ctx: context.Context(*context.emptyCtx) *0,}

MethodのところがGETになっているからGETコマンドを使い,HTTPリクエスト飛ばそうとしていることがわかる.

POSTだったらMethodのところをPOSTにするはず.

GET と POST の違い

https://wa3.i-3-i.info/diff7method.html

大方このサイトの通りの認識で自分も大体は納得していたのだが,

以前 github API を使用してユーザー情報を取得しようとした時に疑問が出た.

上記のサイトの通りだとaccess token などの機密情報はgetではなくpostで送るべきだと思われるが, developer.github.com

このサイトによると,access tokenをgetで送ることになっているのだ.

これではaccess tokenが漏洩してしまうのではと考えた.

しかし調べてみるとそれでも大丈夫な理由がわかった.

qiita.com

このサイトからするに,GETとPOSTで違うのはリクエストボディの部分であって,

GETのセキュリティを心配するのもボディの部分についてであることがわかった.

githubaccess tokenはボディではなくヘッダに載せているから,問題ないということだろう.

だったら何でもかんでもヘッダーで送れば良いのでは

そう思ったが,ヘッダーに乗せて送ることができる情報は決まっていて,

Authorizationヘッダーもしっかり用意されたものであった.

ここにヘッダー一覧があった.

developer.mozilla.org

後述

もやがはれた.

お絵かき振り返り14~20

完成作品と動画

f:id:mattari_matayu:20200605003550p:plain
大空スバル

合計所要時間: 264分

14 https://youtu.be/8TgLZrmN4_c

15 https://youtu.be/u6u9FbKT0N4

16 https://youtu.be/qVuXVwL10DQ

17 https://youtu.be/pqemagWxtSc

18 https://youtu.be/aMWofyBkVbI

19 https://youtu.be/uz7afhdyh6E

20 https://youtu.be/U2tUJGJwxOU

完成作品を見て

胸のリボンとか帽子の紐とかを描き忘れているのは,

記憶だけで描いたというのと,

急ぎ目で描いたから(前回321min+動画外作業, 今回264min)というのがある.

まだまだ頭の中に大空スバルの像が構築されてないのがわかった(なんか悔しいな).

髪の毛の表面質は今までと比べて,だんだんいい感じになってきてる?

髪全体で見ると少し重たい印象がある.

顔の輪郭はもうちょい丸みを持たせた方がいいかも?

ここで本家のスバルをちょっと見てみる.

f:id:mattari_matayu:20200605004934j:plain
ガチ恋距離スバル

スバルって思ったよりまんじゅうみたいな顔してるな.

やっぱり本家は神.めっちゃ可愛い.

次はまんじゅうっぽく描いてみよう.

後述

以前の記事で動画の長さをなるべく短くしていこう,みたいなことを言ったが,難しい.

やっぱり30分以上は描いていたい.

以前の記事.

mattari-matayu.hatenablog.com

お絵かきは人生を豊かにする.

コーヒー

普段は紅茶やほうじ茶を飲むのだが,最近コーヒーをよく飲むようになった.

実は,コーヒーを飲むとお腹が痛くなることが多いのだが

コーヒの味自体は好きなので飲んでしまう.

しかし,お腹が比較的痛くならない飲み方を見つけた.

それはお菓子を一緒に食べることだ.

どら焼きとかクリームの入ったパンとかを一緒に食べる.

そうすると負担がかからないのか,あまりお腹を痛くしない.

それに苦味と甘味がマッチしてより美味しく感じるのだ.

コーヒーを飲む時のお菓子は必須である.

寝床から見えるところに時計を置かないという試み

大学が原則リモート講義になり1ヶ月と少しがたった.

それによって車での通学をする必要がなくなり,朝にやらなければならないことが無くなった.

自分の使える時間が増えたということだから得した気分になった.

しかしながら,人間は余裕が出るとその余裕を埋めたくなるような欲求があるのか知らないが,

私の余裕は二度寝で埋められてしまった.

せっかく通学の分の時間が自由に使えるようになったというのに,結局講義が始まる少し前まで寝ているのだ.

寝ることは大事なことだから,別にそれでも良いのではと言われればそうなのだが,

私は少し勿体なさを感じたからどうにか早く起きようといろいろ対策を考えた.

まず思いついた対策は「次の日の朝にやることを,寝る前に決めてから寝ること」だ.

そうすれば朝起きる目的ができて起きれるようになると思ったからだ.

しかしこの方法は失敗に終わってしまった.

なぜ失敗したかというと,朝やろうと決めたことは大抵朝でなくてもできることだったからだ.

自分がやろうと決めたのはプログラムを書くとか絵を描くとかそういうもので,自分の好きなことであった.

好きなことだから,寝る前はよしやるぞという気合いや楽しみの含まれた気持ちを持っているのだが,

不思議なことに朝になるとそんな気持ちは睡眠欲でかき消され,もう少し寝てからやろうといった感じで二度寝をしてしまう.

私の好きなことに対する熱意が足りないと言われるとそれまでだが,

睡眠欲というのは思ったより強烈なのだ.

同じように,「次の日にタスクを少し残しておく」という手法も失敗した.

だったら絶対朝にやらないといけないことを決めれば良いと思って考えてみるが,意外と思いつかない.

大抵のことは朝でなくてもできる.

それに絶対にやらないといけないことがあったらもうすでにやってるはずだから,

思いつくとかそういう話では無いなと思い至った.

しかし,ひょんなことから自分が朝に絶対にやっている行動を発見したのである.

それは,「時計を見ること」である.

自分はいつもapple watchをつけて寝るのだが,

その日はソフトウェアアップデートをするために充電器にセットして,身に付けずに寝た.

そうすると起きた時に時計を確認する手段がなくなるから,時計を確認するために起き上がらなくてはならない.

このことをそのまま利用すればきっと二度寝をせずにそのまま起きれるはず.

これは失敗する気がしない.きっと成功する.実践あるのみだ.

...

まだ実践していないから今日からやることにする.

後述

この記事を書くに至った経緯

この記事はあるエッセイを読んだ影響で, 試しに書いてみたくなった私によって書かれたものである.(黒歴史)

そのエッセイは2ページ(1000~1500文字くらい)で1つのテーマについて語るものであったから,

真似して文字数も大体それくらいにしてある.常体文であるのも真似した.

エッセイというものがいまいちピンとこなかったから日常っぽいものを書いてみた.

結論があやふやだ.

後から自分で読み返すととても読みづらい印象があったのだが,どうだろう.

エッセイとは

www.weblio.jp

根っこの意味に

試論「試みの論文」

というのがあるから,割と自分の思うエッセイと一致してるかも.

github api 叩いてユーザー情報を取得する

最終的に,こんな感じのコードを書いて取得できた.

type CredentialInfo struct {
    gorm.Model
    Login       string `json:"login"`
    AccessToken string `json:"access_token"`
    Scope       string `json:"scope"`
    TokenType   string `json:"token_type"`
}

func (c *CredentialInfo) GetGithubUserData() {

    req, err := http.NewRequest(
        "GET",
        "https://api.github.com/user",
        nil,
    )
    if err != nil {
        panic(err)
    }

    req.Header.Set("Accept", "application/json")
    req.Header.Set("Authorization", "bearer "+c.AccessToken)

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    byteArray, _ := ioutil.ReadAll(resp.Body)

    json.Unmarshal(byteArray, &c)
}

直さないといけない点

ORMにgormを使用しているが,

このままだとgorm.ModelのIDとgithub APIの方のIDが被って,IDの上書きが起こってしまう.

別の構造体を用意すれば解決する.共用する意味ないし.

つまづいた点

この記事によると

developer.github.com

user情報をrequestする時は http リクエストをGET で行わなければならない感じだが,

ずっと,POST で頑張ってた...

今回はAPIがGETリクエストを要求していたからGETにしたが,

GETとPOSTの違いがいまいちわかっていない.

golangデバッガ delve

golangデバッガのdelveを使ってみた.

参考にした記事はこちら.

qiita.com

基本的な動作として,dlv debugでdlvのプロンプトを起動し,

helpでコマンド一覧を表示できる.

(dlv) help
The following commands are available:

Running the program:
    call ------------------------ Resumes process, injecting a function call (EXPERIMENTAL!!!)
    continue (alias: c) --------- Run until breakpoint or program termination.
    next (alias: n) ------------- Step over to next source line.
    restart (alias: r) ---------- Restart process.
    step (alias: s) ------------- Single step through program.
    step-instruction (alias: si)  Single step a single cpu instruction.
    stepout (alias: so) --------- Step out of the current function.

Manipulating breakpoints:
    break (alias: b) ------- Sets a breakpoint.
    breakpoints (alias: bp)  Print out info for active breakpoints.
    clear ------------------ Deletes breakpoint.
    clearall --------------- Deletes multiple breakpoints.
    condition (alias: cond)  Set breakpoint condition.
    on --------------------- Executes a command when a breakpoint is hit.
    trace (alias: t) ------- Set tracepoint.

Viewing program variables and memory:
    args ----------------- Print function arguments.
    display -------------- Print value of an expression every time the program stops.
    examinemem (alias: x)  Examine memory:
    locals --------------- Print local variables.
    print (alias: p) ----- Evaluate an expression.
    regs ----------------- Print contents of CPU registers.
    set ------------------ Changes the value of a variable.
    vars ----------------- Print package variables.
    whatis --------------- Prints type of an expression.

Listing and switching between threads and goroutines:
    goroutine (alias: gr) -- Shows or changes current goroutine
    goroutines (alias: grs)  List program goroutines.
    thread (alias: tr) ----- Switch to the specified thread.
    threads ---------------- Print out info for every traced thread.

Viewing the call stack and selecting frames:
    deferred --------- Executes command in the context of a deferred call.
    down ------------- Move the current frame down.
    frame ------------ Set the current frame, or execute command on a different frame.
    stack (alias: bt)  Print stack trace.
    up --------------- Move the current frame up.

Other commands:
    config --------------------- Changes configuration parameters.
    disassemble (alias: disass)  Disassembler.
    edit (alias: ed) ----------- Open where you are in $DELVE_EDITOR or $EDITOR
    exit (alias: quit | q) ----- Exit the debugger.
    funcs ---------------------- Print list of functions.
    help (alias: h) ------------ Prints the help message.
    libraries ------------------ List loaded dynamic libraries
    list (alias: ls | l) ------- Show source code.
    source --------------------- Executes a file containing a list of delve commands
    sources -------------------- Print list of source files.
    types ---------------------- Print list of types

Type help followed by a command for full documentation.

break main.mainでmain packageのmain関数にブレークポイントを設定する.

nで1行進めて,p 変数名で変数の内容を確認できる.

終了する時はqという具合.

使いながら追記していく.

追記(2020/06/07)

step in はs

step outはstepout