はじめに
全力回避フラグちゃん! のチャンネルの情報を取得するのに,Python を使用してAPI にアクセスするクライアントプログラムを作成していましたが,最近Rust に興味がありRust で書いて見たいと思ったので,とりあえず書いてみました.
プログラムの作成
HTTP Client にはreqwest を使用しました.
作成中に色々試行していたたため,Cargo.toml には不要なものまで書いてありますが,とりあえず書いておけばコンパイルは通ります.
API Key などセンシティブな情報をプログラム中にハードコーディングしておきたくないので,configparser を使用して別ファイルに保存されている情報を取得しています.
[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 レスポンスになります.
{
"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 情報を変更して使用してください.
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(¶ms).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 のフォローをよろしくお願いいたします.
参考文献
関連リンク
- YouTube Data API Channels リソース https://developers.google.com/youtube/v3/docs/channels?hl=ja
- 全力回避フラグちゃん! https://www.youtube.com/channel/UCo_nZN5yB0rmfoPBVjYRMmw/videos
- 株式会社Plott / Plott Inc. https://plott.tokyo/#top
- フラグちゃんのTwitter https://twitter.com/flag__chan
- Github https://github.com/uky007/flag_analysis
- Reqwest https://docs.rs/reqwest/0.10.0-alpha.2/reqwest/blocking/struct.Response.html