LoginSignup
0
0

More than 1 year has passed since last update.

Rust でYouTube API Client を作成してみた

Posted at

はじめに

全力回避フラグちゃん! のチャンネルの情報を取得するのに,Python を使用してAPI にアクセスするクライアントプログラムを作成していましたが,最近Rust に興味がありRust で書いて見たいと思ったので,とりあえず書いてみました.

プログラムの作成

HTTP Client にはreqwest を使用しました.
作成中に色々試行していたたため,Cargo.toml には不要なものまで書いてありますが,とりあえず書いておけばコンパイルは通ります.

API Key などセンシティブな情報をプログラム中にハードコーディングしておきたくないので,configparser を使用して別ファイルに保存されている情報を取得しています.

Cargo.toml
[package]
name = "proto"
version = "0.1.0"
edition = "2021"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
reqwest = { version = "0.11.9", features = ["json", "blocking"] }
text-colorizer = "1"
regex = "1"
proconio = "0.3.6"
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
configparser = "3.0.0"

API のレスポンス結果は,json 形式で返ってくるため,reqwest のjson() メソッドでjson 形式にして結果を受け取ります.この際,結果の型が推論できないため,型を指定しないといけません.serde_json::Value で受け取れますが,今回は練習のため結果の方をすべて構造体で定義して受け取っています.
このページに書いてある以下のような書き方で書いています.

#[derive(Deserialize)]
struct Ip {
    origin: String,
}

let json: Ip = reqwest::blocking::get("http://httpbin.org/ip")?.json()?;

実際にAPI にアクセスしたり,API のドキュメントを読んで構造体を定義しました.
API のアクセス結果は,以下のようなjson レスポンスになります.

APIレスポンス
{
  "kind": "youtube#playlistListResponse",
  "etag": "pi8uuHdFJzC2ByJy0bNodG6nMuA",
  "pageInfo": {
    "totalResults": 10,
    "resultsPerPage": 50
  },
  "items": [
    {
      "kind": "youtube#playlist",
      "etag": "u3mq8p7n4nwZwe_sMM70uYrrFSE",
      "id": "PLnN2g2346RAUZm1NOZ2IK4EGOv-MuRwtb",
      "snippet": {
        "publishedAt": "2022-01-06T10:01:11Z",
        "channelId": "UCo_nZN5yB0rmfoPBVjYRMmw",
        "title": "失恋フラグちゃん",
        "description": "",
        "thumbnails": {
          "default": {
            "url": "https://i.ytimg.com/vi/m3olUiuH4Qk/default.jpg",
            "width": 120,
            "height": 90
          },
          "medium": {
            "url": "https://i.ytimg.com/vi/m3olUiuH4Qk/mqdefault.jpg",
            "width": 320,
            "height": 180
          },
          "high": {
            "url": "https://i.ytimg.com/vi/m3olUiuH4Qk/hqdefault.jpg",
            "width": 480,
            "height": 360
          },
          "standard": {
            "url": "https://i.ytimg.com/vi/m3olUiuH4Qk/sddefault.jpg",
            "width": 640,
            "height": 480
          },
          "maxres": {
            "url": "https://i.ytimg.com/vi/m3olUiuH4Qk/maxresdefault.jpg",
            "width": 1280,
            "height": 720
          }
        },
        "channelTitle": "全力回避フラグちゃん!",
        "localized": {
          "title": "失恋フラグちゃん",
          "description": ""
        }
      },
      "status": {
        "privacyStatus": "public"
      }
    },
    {
      "kind": "youtube#playlist",
      "etag": "D9204-uXkSD3J1w8xtksPlG2pAc",
      "id": "PLnN2g2346RAVpAFQqIBOA-wR73BEMS6rF",
      "snippet": {
        "publishedAt": "2021-08-13T06:12:32Z",
        "channelId": "UCo_nZN5yB0rmfoPBVjYRMmw",
        "title": "コラボ動画",
        "description": "",
        "thumbnails": {
          "default": {
            "url": "https://i.ytimg.com/vi/q-TYbEx9hCo/default.jpg",
            "width": 120,
            "height": 90
          },
          "medium": {
            "url": "https://i.ytimg.com/vi/q-TYbEx9hCo/mqdefault.jpg",
            "width": 320,
            "height": 180
          },
          "high": {
            "url": "https://i.ytimg.com/vi/q-TYbEx9hCo/hqdefault.jpg",
            "width": 480,
            "height": 360
          }
        },
        "channelTitle": "全力回避フラグちゃん!",
        "localized": {
          "title": "コラボ動画",
          "description": ""
        }
      },
      "status": {
        "privacyStatus": "public"
      }
    },
    {
      "kind": "youtube#playlist",
      "etag": "BRdHsd8MxqkP4oAX9KYCzsb3sxg",
      "id": "PLnN2g2346RAXtwKCCfMDX4QQKV9dBg75f",
      "snippet": {
        "publishedAt": "2021-05-19T09:09:11Z",
        "channelId": "UCo_nZN5yB0rmfoPBVjYRMmw",
        "title": "恋愛フラグ師匠",
        "description": "",
        "thumbnails": {
          "default": {
            "url": "https://i.ytimg.com/vi/mcbWG8QQ2Eo/default.jpg",
            "width": 120,
            "height": 90
          },
          "medium": {
            "url": "https://i.ytimg.com/vi/mcbWG8QQ2Eo/mqdefault.jpg",
            "width": 320,
            "height": 180
          },
          "high": {
            "url": "https://i.ytimg.com/vi/mcbWG8QQ2Eo/hqdefault.jpg",
            "width": 480,
            "height": 360
          },
          "standard": {
            "url": "https://i.ytimg.com/vi/mcbWG8QQ2Eo/sddefault.jpg",
            "width": 640,
            "height": 480
          },
          "maxres": {
            "url": "https://i.ytimg.com/vi/mcbWG8QQ2Eo/maxresdefault.jpg",
            "width": 1280,
            "height": 720
          }
        },
        "channelTitle": "全力回避フラグちゃん!",
        "localized": {
          "title": "恋愛フラグ師匠",
          "description": ""
        }
      },
      "status": {
        "privacyStatus": "public"
      }
    },
    {
      "kind": "youtube#playlist",
      "etag": "e0CO3AMFPpLqWLTzgx4NV-zfqZg",
      "id": "PLnN2g2346RAWysxlPBAFnBTts3jIgcIJg",
      "snippet": {
        "publishedAt": "2021-03-29T03:30:24Z",
        "channelId": "UCo_nZN5yB0rmfoPBVjYRMmw",
        "title": "EDコーナー【フラグラ!】集",
        "description": "",
        "thumbnails": {
          "default": {
            "url": "https://i.ytimg.com/vi/jeNS8nK_LzQ/default.jpg",
            "width": 120,
            "height": 90
          },
          "medium": {
            "url": "https://i.ytimg.com/vi/jeNS8nK_LzQ/mqdefault.jpg",
            "width": 320,
            "height": 180
          },
          "high": {
            "url": "https://i.ytimg.com/vi/jeNS8nK_LzQ/hqdefault.jpg",
            "width": 480,
            "height": 360
          },
          "standard": {
            "url": "https://i.ytimg.com/vi/jeNS8nK_LzQ/sddefault.jpg",
            "width": 640,
            "height": 480
          },
          "maxres": {
            "url": "https://i.ytimg.com/vi/jeNS8nK_LzQ/maxresdefault.jpg",
            "width": 1280,
            "height": 720
          }
        },
        "channelTitle": "全力回避フラグちゃん!",
        "localized": {
          "title": "EDコーナー【フラグラ!】集",
          "description": ""
        }
      },
      "status": {
        "privacyStatus": "public"
      }
    },
    {
      "kind": "youtube#playlist",
      "etag": "Jx4e-5tbMs5N5RZYzkIuquCHKu4",
      "id": "PLnN2g2346RAWelVUAE06Cx1x4wssLR9Zb",
      "snippet": {
        "publishedAt": "2020-06-08T04:21:29Z",
        "channelId": "UCo_nZN5yB0rmfoPBVjYRMmw",
        "title": "短編集",
        "description": "",
        "thumbnails": {
          "default": {
            "url": "https://i.ytimg.com/vi/QYlqt-Xf7PY/default.jpg",
            "width": 120,
            "height": 90
          },
          "medium": {
            "url": "https://i.ytimg.com/vi/QYlqt-Xf7PY/mqdefault.jpg",
            "width": 320,
            "height": 180
          },
          "high": {
            "url": "https://i.ytimg.com/vi/QYlqt-Xf7PY/hqdefault.jpg",
            "width": 480,
            "height": 360
          },
          "standard": {
            "url": "https://i.ytimg.com/vi/QYlqt-Xf7PY/sddefault.jpg",
            "width": 640,
            "height": 480
          },
          "maxres": {
            "url": "https://i.ytimg.com/vi/QYlqt-Xf7PY/maxresdefault.jpg",
            "width": 1280,
            "height": 720
          }
        },
        "channelTitle": "全力回避フラグちゃん!",
        "localized": {
          "title": "短編集",
          "description": ""
        }
      },
      "status": {
        "privacyStatus": "public"
      }
    },
    {
      "kind": "youtube#playlist",
      "etag": "wqKXDjopNolNb1I5sAX-rpqzeIs",
      "id": "PLnN2g2346RAXibP0efNEswg18ZJ-_nhlt",
      "snippet": {
        "publishedAt": "2020-05-21T10:47:15Z",
        "channelId": "UCo_nZN5yB0rmfoPBVjYRMmw",
        "title": "フラグちゃん傑作選",
        "description": "",
        "thumbnails": {
          "default": {
            "url": "https://i.ytimg.com/vi/yOiVhabTJHI/default.jpg",
            "width": 120,
            "height": 90
          },
          "medium": {
            "url": "https://i.ytimg.com/vi/yOiVhabTJHI/mqdefault.jpg",
            "width": 320,
            "height": 180
          },
          "high": {
            "url": "https://i.ytimg.com/vi/yOiVhabTJHI/hqdefault.jpg",
            "width": 480,
            "height": 360
          },
          "standard": {
            "url": "https://i.ytimg.com/vi/yOiVhabTJHI/sddefault.jpg",
            "width": 640,
            "height": 480
          }
        },
        "channelTitle": "全力回避フラグちゃん!",
        "localized": {
          "title": "フラグちゃん傑作選",
          "description": ""
        }
      },
      "status": {
        "privacyStatus": "public"
      }
    },
    {
      "kind": "youtube#playlist",
      "etag": "D4Blp1Gog2xJ0-wEkikORBVDffA",
      "id": "PLnN2g2346RAXExojgZcYGqOtwWi6B0sYH",
      "snippet": {
        "publishedAt": "2020-05-21T10:46:00Z",
        "channelId": "UCo_nZN5yB0rmfoPBVjYRMmw",
        "title": "生存フラグさん",
        "description": "",
        "thumbnails": {
          "default": {
            "url": "https://i.ytimg.com/vi/gA27AM_osM0/default.jpg",
            "width": 120,
            "height": 90
          },
          "medium": {
            "url": "https://i.ytimg.com/vi/gA27AM_osM0/mqdefault.jpg",
            "width": 320,
            "height": 180
          },
          "high": {
            "url": "https://i.ytimg.com/vi/gA27AM_osM0/hqdefault.jpg",
            "width": 480,
            "height": 360
          },
          "standard": {
            "url": "https://i.ytimg.com/vi/gA27AM_osM0/sddefault.jpg",
            "width": 640,
            "height": 480
          },
          "maxres": {
            "url": "https://i.ytimg.com/vi/gA27AM_osM0/maxresdefault.jpg",
            "width": 1280,
            "height": 720
          }
        },
        "channelTitle": "全力回避フラグちゃん!",
        "localized": {
          "title": "生存フラグさん",
          "description": ""
        }
      },
      "status": {
        "privacyStatus": "public"
      }
    },
    {
      "kind": "youtube#playlist",
      "etag": "mKNV-knrEEIOgk5V6yGoOGZOeGk",
      "id": "PLnN2g2346RAWd0PryMMtRROzfOBOpHVJg",
      "snippet": {
        "publishedAt": "2020-05-21T10:45:26Z",
        "channelId": "UCo_nZN5yB0rmfoPBVjYRMmw",
        "title": "長編集",
        "description": "",
        "thumbnails": {
          "default": {
            "url": "https://i.ytimg.com/vi/cd-8yMc0FjI/default.jpg",
            "width": 120,
            "height": 90
          },
          "medium": {
            "url": "https://i.ytimg.com/vi/cd-8yMc0FjI/mqdefault.jpg",
            "width": 320,
            "height": 180
          },
          "high": {
            "url": "https://i.ytimg.com/vi/cd-8yMc0FjI/hqdefault.jpg",
            "width": 480,
            "height": 360
          },
          "standard": {
            "url": "https://i.ytimg.com/vi/cd-8yMc0FjI/sddefault.jpg",
            "width": 640,
            "height": 480
          },
          "maxres": {
            "url": "https://i.ytimg.com/vi/cd-8yMc0FjI/maxresdefault.jpg",
            "width": 1280,
            "height": 720
          }
        },
        "channelTitle": "全力回避フラグちゃん!",
        "localized": {
          "title": "長編集",
          "description": ""
        }
      },
      "status": {
        "privacyStatus": "public"
      }
    },
    {
      "kind": "youtube#playlist",
      "etag": "-mMxsKnfifN1lSCDe-kZ6izNgk4",
      "id": "PLnN2g2346RAXJzpPY4A2-LUUdCYn64X9P",
      "snippet": {
        "publishedAt": "2020-02-28T08:59:53Z",
        "channelId": "UCo_nZN5yB0rmfoPBVjYRMmw",
        "title": "フラグちゃんストーリー編",
        "description": "",
        "thumbnails": {
          "default": {
            "url": "https://i.ytimg.com/vi/blnM5yFOIK4/default.jpg",
            "width": 120,
            "height": 90
          },
          "medium": {
            "url": "https://i.ytimg.com/vi/blnM5yFOIK4/mqdefault.jpg",
            "width": 320,
            "height": 180
          },
          "high": {
            "url": "https://i.ytimg.com/vi/blnM5yFOIK4/hqdefault.jpg",
            "width": 480,
            "height": 360
          },
          "standard": {
            "url": "https://i.ytimg.com/vi/blnM5yFOIK4/sddefault.jpg",
            "width": 640,
            "height": 480
          },
          "maxres": {
            "url": "https://i.ytimg.com/vi/blnM5yFOIK4/maxresdefault.jpg",
            "width": 1280,
            "height": 720
          }
        },
        "channelTitle": "全力回避フラグちゃん!",
        "localized": {
          "title": "フラグちゃんストーリー編",
          "description": ""
        }
      },
      "status": {
        "privacyStatus": "public"
      }
    },
    {
      "kind": "youtube#playlist",
      "etag": "2tJoid-K4sIXoxdHIy_apt6EH6g",
      "id": "PLnN2g2346RAUtrLI_UhEcdrGYxb2F1p_d",
      "snippet": {
        "publishedAt": "2019-11-01T14:14:06Z",
        "channelId": "UCo_nZN5yB0rmfoPBVjYRMmw",
        "title": "フラグちゃん!全話",
        "description": "",
        "thumbnails": {
          "default": {
            "url": "https://i.ytimg.com/vi/5tPE8hZEEDM/default.jpg",
            "width": 120,
            "height": 90
          },
          "medium": {
            "url": "https://i.ytimg.com/vi/5tPE8hZEEDM/mqdefault.jpg",
            "width": 320,
            "height": 180
          },
          "high": {
            "url": "https://i.ytimg.com/vi/5tPE8hZEEDM/hqdefault.jpg",
            "width": 480,
            "height": 360
          },
          "standard": {
            "url": "https://i.ytimg.com/vi/5tPE8hZEEDM/sddefault.jpg",
            "width": 640,
            "height": 480
          },
          "maxres": {
            "url": "https://i.ytimg.com/vi/5tPE8hZEEDM/maxresdefault.jpg",
            "width": 1280,
            "height": 720
          }
        },
        "channelTitle": "全力回避フラグちゃん!",
        "localized": {
          "title": "フラグちゃん!全話",
          "description": ""
        }
      },
      "status": {
        "privacyStatus": "public"
      }
    }
  ]
}

プログラムは,「全力回避フラグちゃん!」というYouTube のチャンネルのプレイリストの一覧を取得するプログラムになっています.みなさんが使用する際は,URL やAPI Key 情報,チャンネルID 情報を変更して使用してください.

main.rs

use std::result::Result;
use std::error::Error;
use serde::{Deserialize, Serialize};
use configparser::ini::Ini;

// API Response のjson を構造体で定義
#[derive(Serialize, Deserialize, Debug)]
struct PlaylistResults {
    kind: String,
    etag: String,
    pageInfo: PageInfo,
    items: Vec<Item>
}

#[derive(Serialize, Deserialize, Debug)]
struct PageInfo {
    totalResults: u32,
    resultsPerPage: u32
}

#[derive(Serialize, Deserialize, Debug)]
struct Item {
    kind: String,
    etag: String,
    id: String,
    snippet: Snippet,
    channelTitle: Option<String>,
    localized: Option<Localized>,
    status: Option<Status>,
}

#[derive(Serialize, Deserialize, Debug)]
struct Snippet {
    publishedAt: String,
    channelId: String,
    title: String,
    description: String,
    thumbnails: Thumbnails
}

#[derive(Serialize, Deserialize, Debug)]
struct Thumbnails {
    default: Option<Thumbnail>,
    medium: Option<Thumbnail>,
    high: Option<Thumbnail>,
    standard: Option<Thumbnail>,
    maxres: Option<Thumbnail>
}

#[derive(Serialize, Deserialize, Debug)]
struct Thumbnail {
    url: String,
    width: u32,
    height: u32,
}

#[derive(Serialize, Deserialize, Debug)]
struct Localized {
    title: String,
    description: String
}

#[derive(Serialize, Deserialize, Debug)]
struct Status {
    privacyStatus: String
}


// API Client の関数
fn get_access_api(url: &str) -> Result<Vec<Item>, Box<dyn Error>> {
    // configparser でAPI Key やチャンネルID 情報を取得
    let mut config = Ini::new();
    let _conf_map = config.load("conf/config.ini");
    let api_key = config.get("YouTube", "api_key").unwrap();
    let channel_id = config.get("YouTube", "channel_id").unwrap();
    let max_results = "50";

    // 上記の情報をGet メソッドのパラメータに指定
    let params = [("key", api_key), ("channelId", channel_id), 
                  ("part", "id".to_string()), ("part", "snippet".to_string()), 
                  ("part", "status".to_string()), ("maxResults", max_results.to_string())];
    let client = reqwest::blocking::Client::new();

    let response = client.get(url).query(&params).send()?;
        
    if !response.status().is_success() {
        println!("{}", response.url());
        Err(format!("{}", response.status()))?;
    }

    // レスポンス結果をjson として受け取る
    let response_json: PlaylistResults = response.json()?;
    let items = response_json.items;

    Ok(items)
}


fn main() {

    let playlists_url = "https://www.googleapis.com/youtube/v3/playlists";
    let results = get_access_api(playlists_url);
    match results {
        Ok(response) => println!("{:?}", response),
        Err(err) => eprintln!("error: {}", err),
    }
    
}

#実行結果

上記のプログラムを実行します.

プログラム実行
cargo run

コンパイルの際,convert the identifier to snake case: `channel_id というキャメルケースではなくスネークケースにしろ,という警告が出ますが,ここは結果のjson のキー名と同じにしておかないと実行エラーが出るので,警告が出ることを承知して実行します.実行した結果が下記になります.

実行結果
[Item { kind: "youtube#playlist", etag: "u3mq8p7n4nwZwe_sMM70uYrrFSE", id: "PLnN2g2346RAUZm1NOZ2IK4EGOv-MuRwtb", snippet: Snippet { publishedAt: "2022-01-06T10:01:11Z", channelId: "UCo_nZN5yB0rmfoPBVjYRMmw", title: "失恋フラグちゃん", description: "", thumbnails: Thumbnails { default: Some(Thumbnail { url: "https://i.ytimg.com/vi/m3olUiuH4Qk/default.jpg", width: 120, height: 90 }), medium: Some(Thumbnail { url: "https://i.ytimg.com/vi/m3olUiuH4Qk/mqdefault.jpg", width: 320, height: 180 }), high: Some(Thumbnail { url: "https://i.ytimg.com/vi/m3olUiuH4Qk/hqdefault.jpg", width: 480, height: 360 }), standard: Some(Thumbnail { url: "https://i.ytimg.com/vi/m3olUiuH4Qk/sddefault.jpg", width: 640, height: 480 }), maxres: Some(Thumbnail { url: "https://i.ytimg.com/vi/m3olUiuH4Qk/maxresdefault.jpg", width: 1280, height: 720 }) } }, channelTitle: None, localized: None, status: Some(Status { privacyStatus: "public" }) }, Item { kind: "youtube#playlist", etag: "D9204-uXkSD3J1w8xtksPlG2pAc", id: "PLnN2g2346RAVpAFQqIBOA-wR73BEMS6rF", snippet: Snippet { publishedAt: "2021-08-13T06:12:32Z", channelId: "UCo_nZN5yB0rmfoPBVjYRMmw", title: "コラボ動画", description: "", thumbnails: Thumbnails { default: Some(Thumbnail { url: "https://i.ytimg.com/vi/q-TYbEx9hCo/default.jpg", width: 120, height: 90 }), medium: Some(Thumbnail { url: "https://i.ytimg.com/vi/q-TYbEx9hCo/mqdefault.jpg", width: 320, height: 180 }), high: Some(Thumbnail { url: "https://i.ytimg.com/vi/q-TYbEx9hCo/hqdefault.jpg", width: 480, height: 360 }), standard: None, maxres: None } }, channelTitle: None, localized: None, status: Some(Status { privacyStatus: "public" }) }, Item { kind: "youtube#playlist", etag: "BRdHsd8MxqkP4oAX9KYCzsb3sxg", id: "PLnN2g2346RAXtwKCCfMDX4QQKV9dBg75f", snippet: Snippet { publishedAt: "2021-05-19T09:09:11Z", channelId: "UCo_nZN5yB0rmfoPBVjYRMmw", title: "恋愛フラグ師匠", description: "", thumbnails: Thumbnails { default: Some(Thumbnail { url: "https://i.ytimg.com/vi/mcbWG8QQ2Eo/default.jpg", width: 120, height: 90 }), medium: Some(Thumbnail { url: "https://i.ytimg.com/vi/mcbWG8QQ2Eo/mqdefault.jpg", width: 320, height: 180 }), high: Some(Thumbnail { url: "https://i.ytimg.com/vi/mcbWG8QQ2Eo/hqdefault.jpg", width: 480, height: 360 }), standard: Some(Thumbnail { url: "https://i.ytimg.com/vi/mcbWG8QQ2Eo/sddefault.jpg", width: 640, height: 480 }), maxres: Some(Thumbnail { url: "https://i.ytimg.com/vi/mcbWG8QQ2Eo/maxresdefault.jpg", width: 1280, height: 720 }) } }, channelTitle: None, localized: None, status: Some(Status { privacyStatus: "public" }) }, Item { kind: "youtube#playlist", etag: "e0CO3AMFPpLqWLTzgx4NV-zfqZg", id: "PLnN2g2346RAWysxlPBAFnBTts3jIgcIJg", snippet: Snippet { publishedAt: "2021-03-29T03:30:24Z", channelId: "UCo_nZN5yB0rmfoPBVjYRMmw", title: "EDコーナー【フラグラ!】集", description: "", thumbnails: Thumbnails { default: Some(Thumbnail { url: "https://i.ytimg.com/vi/jeNS8nK_LzQ/default.jpg", width: 120, height: 90 }), medium: Some(Thumbnail { url: "https://i.ytimg.com/vi/jeNS8nK_LzQ/mqdefault.jpg", width: 320, height: 180 }), high: Some(Thumbnail { url: "https://i.ytimg.com/vi/jeNS8nK_LzQ/hqdefault.jpg", width: 480, height: 360 }), standard: Some(Thumbnail { url: "https://i.ytimg.com/vi/jeNS8nK_LzQ/sddefault.jpg", width: 640, height: 480 }), maxres: Some(Thumbnail { url: "https://i.ytimg.com/vi/jeNS8nK_LzQ/maxresdefault.jpg", width: 1280, height: 720 }) } }, channelTitle: None, localized: None, status: Some(Status { privacyStatus: "public" }) }, Item { kind: "youtube#playlist", etag: "Jx4e-5tbMs5N5RZYzkIuquCHKu4", id: "PLnN2g2346RAWelVUAE06Cx1x4wssLR9Zb", snippet: Snippet { publishedAt: "2020-06-08T04:21:29Z", channelId: "UCo_nZN5yB0rmfoPBVjYRMmw", title: "短編集", description: "", thumbnails: Thumbnails { default: Some(Thumbnail { url: "https://i.ytimg.com/vi/QYlqt-Xf7PY/default.jpg", width: 120, height: 90 }), medium: Some(Thumbnail { url: "https://i.ytimg.com/vi/QYlqt-Xf7PY/mqdefault.jpg", width: 320, height: 180 }), high: Some(Thumbnail { url: "https://i.ytimg.com/vi/QYlqt-Xf7PY/hqdefault.jpg", width: 480, height: 360 }), standard: Some(Thumbnail { url: "https://i.ytimg.com/vi/QYlqt-Xf7PY/sddefault.jpg", width: 640, height: 480 }), maxres: Some(Thumbnail { url: "https://i.ytimg.com/vi/QYlqt-Xf7PY/maxresdefault.jpg", width: 1280, height: 720 }) } }, channelTitle: None, localized: None, status: Some(Status { privacyStatus: "public" }) }, Item { kind: "youtube#playlist", etag: "wqKXDjopNolNb1I5sAX-rpqzeIs", id: "PLnN2g2346RAXibP0efNEswg18ZJ-_nhlt", snippet: Snippet { publishedAt: "2020-05-21T10:47:15Z", channelId: "UCo_nZN5yB0rmfoPBVjYRMmw", title: "フラグちゃん傑作選", description: "", thumbnails: Thumbnails { default: Some(Thumbnail { url: "https://i.ytimg.com/vi/yOiVhabTJHI/default.jpg", width: 120, height: 90 }), medium: Some(Thumbnail { url: "https://i.ytimg.com/vi/yOiVhabTJHI/mqdefault.jpg", width: 320, height: 180 }), high: Some(Thumbnail { url: "https://i.ytimg.com/vi/yOiVhabTJHI/hqdefault.jpg", width: 480, height: 360 }), standard: Some(Thumbnail { url: "https://i.ytimg.com/vi/yOiVhabTJHI/sddefault.jpg", width: 640, height: 480 }), maxres: None } }, channelTitle: None, localized: None, status: Some(Status { privacyStatus: "public" }) }, Item { kind: "youtube#playlist", etag: "D4Blp1Gog2xJ0-wEkikORBVDffA", id: "PLnN2g2346RAXExojgZcYGqOtwWi6B0sYH", snippet: Snippet { publishedAt: "2020-05-21T10:46:00Z", channelId: "UCo_nZN5yB0rmfoPBVjYRMmw", title: "生存フラグさん", description: "", thumbnails: Thumbnails { default: Some(Thumbnail { url: "https://i.ytimg.com/vi/gA27AM_osM0/default.jpg", width: 120, height: 90 }), medium: Some(Thumbnail { url: "https://i.ytimg.com/vi/gA27AM_osM0/mqdefault.jpg", width: 320, height: 180 }), high: Some(Thumbnail { url: "https://i.ytimg.com/vi/gA27AM_osM0/hqdefault.jpg", width: 480, height: 360 }), standard: Some(Thumbnail { url: "https://i.ytimg.com/vi/gA27AM_osM0/sddefault.jpg", width: 640, height: 480 }), maxres: Some(Thumbnail { url: "https://i.ytimg.com/vi/gA27AM_osM0/maxresdefault.jpg", width: 1280, height: 720 }) } }, channelTitle: None, localized: None, status: Some(Status { privacyStatus: "public" }) }, Item { kind: "youtube#playlist", etag: "mKNV-knrEEIOgk5V6yGoOGZOeGk", id: "PLnN2g2346RAWd0PryMMtRROzfOBOpHVJg", snippet: Snippet { publishedAt: "2020-05-21T10:45:26Z", channelId: "UCo_nZN5yB0rmfoPBVjYRMmw", title: "長編集", description: "", thumbnails: Thumbnails { default: Some(Thumbnail { url: "https://i.ytimg.com/vi/cd-8yMc0FjI/default.jpg", width: 120, height: 90 }), medium: Some(Thumbnail { url: "https://i.ytimg.com/vi/cd-8yMc0FjI/mqdefault.jpg", width: 320, height: 180 }), high: Some(Thumbnail { url: "https://i.ytimg.com/vi/cd-8yMc0FjI/hqdefault.jpg", width: 480, height: 360 }), standard: Some(Thumbnail { url: "https://i.ytimg.com/vi/cd-8yMc0FjI/sddefault.jpg", width: 640, height: 480 }), maxres: Some(Thumbnail { url: "https://i.ytimg.com/vi/cd-8yMc0FjI/maxresdefault.jpg", width: 1280, height: 720 }) } }, channelTitle: None, localized: None, status: Some(Status { privacyStatus: "public" }) }, Item { kind: "youtube#playlist", etag: "-mMxsKnfifN1lSCDe-kZ6izNgk4", id: "PLnN2g2346RAXJzpPY4A2-LUUdCYn64X9P", snippet: Snippet { publishedAt: "2020-02-28T08:59:53Z", channelId: "UCo_nZN5yB0rmfoPBVjYRMmw", title: "フラグちゃんストーリー編", description: "", thumbnails: Thumbnails { default: Some(Thumbnail { url: "https://i.ytimg.com/vi/blnM5yFOIK4/default.jpg", width: 120, height: 90 }), medium: Some(Thumbnail { url: "https://i.ytimg.com/vi/blnM5yFOIK4/mqdefault.jpg", width: 320, height: 180 }), high: Some(Thumbnail { url: "https://i.ytimg.com/vi/blnM5yFOIK4/hqdefault.jpg", width: 480, height: 360 }), standard: Some(Thumbnail { url: "https://i.ytimg.com/vi/blnM5yFOIK4/sddefault.jpg", width: 640, height: 480 }), maxres: Some(Thumbnail { url: "https://i.ytimg.com/vi/blnM5yFOIK4/maxresdefault.jpg", width: 1280, height: 720 }) } }, channelTitle: None, localized: None, status: Some(Status { privacyStatus: "public" }) }, Item { kind: "youtube#playlist", etag: "2tJoid-K4sIXoxdHIy_apt6EH6g", id: "PLnN2g2346RAUtrLI_UhEcdrGYxb2F1p_d", snippet: Snippet { publishedAt: "2019-11-01T14:14:06Z", channelId: "UCo_nZN5yB0rmfoPBVjYRMmw", title: "フラグちゃん!全話", description: "", thumbnails: Thumbnails { default: Some(Thumbnail { url: "https://i.ytimg.com/vi/5tPE8hZEEDM/default.jpg", width: 120, height: 90 }), medium: Some(Thumbnail { url: "https://i.ytimg.com/vi/5tPE8hZEEDM/mqdefault.jpg", width: 320, height: 180 }), high: Some(Thumbnail { url: "https://i.ytimg.com/vi/5tPE8hZEEDM/hqdefault.jpg", width: 480, height: 360 }), standard: Some(Thumbnail { url: "https://i.ytimg.com/vi/5tPE8hZEEDM/sddefault.jpg", width: 640, height: 480 }), maxres: Some(Thumbnail { url: "https://i.ytimg.com/vi/5tPE8hZEEDM/maxresdefault.jpg", width: 1280, height: 720 }) } }, channelTitle: None, localized: None, status: Some(Status { privacyStatus: "public" }) }]

定義した構造体が結果と一致したため,きちんと結果を構造体として受け取れている事がわかります.

おわりに

Rust でAPI Client を作成した.書き方を少しだけ覚えられたので,今後も少しずつ勉強していく.次は,多くのリクエストを並行処理するプログラムをかけるようになりたい.

ここまで読んでいる人はいないと思いますが,もしいたらまずは,以下のリンクから全力回避フラグちゃん! チャンネルとフラグちゃんのTwitter をフォローしてください.この記事を読むより大切なことです.
大事なことなのでもう一度,チャンネル登録Twitter のフォローをよろしくお願いいたします.

参考文献

関連リンク

0
0
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
0
0