# Youtube Data API 使用手札
###### tags : `w3HexSchool` . `youtube` . `node.js`
> 最近因為搭捷運時 , 看 Hahow 影片卡爆 ,
> 所以起心動念想要將自己買的課程 , 放到私人的 Youtube 撥放清單中
> 手動一個影片 . 一個的影片下載 , 之後上傳實在太慢了 ,
> 因此我做了一個 electron 處理上傳 & 下載的事情
> 程序化上傳部分 , 我們可以用 Youtue Data API 處理
> 在此紀錄 , Youtube Data API In Node.js 的實做過程 , 以免下一次忘記如何做 !
> 題外話 , 不知道 Hahow 的使用者條款 , 有沒有禁止軟體工程師製作應用程式批次下載`已購買課程` 呢?
## 取得 Youtube API_KEY
首先進入 [Google Developers Console](https://console.developers.google.com/apis/dashboard) 建立新專案來產生一組 API_KEY
==建立一個新專案==
![新增專案](https://i.imgur.com/nNiZMJd.png)
==輸入專案名稱 `youtube-test`==
![輸入專案名稱](https://i.imgur.com/TJ3AvSW.png)
![切換面板到新的專案](https://i.imgur.com/6hqLdOF.png)
![到 API 資料庫](https://i.imgur.com/scPbZTs.png)
![查詢 YouTube Data API](https://i.imgur.com/TaXpTQX.png)
![啟用 YouTube Data API](https://i.imgur.com/thzVjn6.png)
![建立憑證](https://i.imgur.com/VdTiAqe.png)
==這次我們要建立的是 API_KEY==
![選擇建立 API_KEY](https://i.imgur.com/F3dkh8L.png)
![已建立的 API_KEY](https://i.imgur.com/nqIiVnl.png)
:::info
如果想要限制 API 金鑰 , 可自行設定 , 本文建議先測通 `Youtube Search API` 再做限制
:::
![](https://i.imgur.com/cxKz1XV.png)
## Youtube Search API
:::info
我們可以直接用 axios 做一個 get request 來訪問 Youtube Search API
:::
```javascript=
/* youtube-search.js */
// 參考資料 : https://medium.com/%E5%B0%8F%E9%83%AD-%E0%B9%80%E0%B8%88%E0%B8%99/%E8%8F%9C%E9%B3%A5%E5%B7%A5%E7%A8%8B%E5%B8%AB-youtube-data-api-%E8%BC%89%E5%85%A5%E6%92%AD%E6%94%BE%E6%B8%85%E5%96%AE%E4%B8%A6%E5%88%87%E6%8F%9B%E6%AD%8C%E6%9B%B2-356d8e454ca3
const axios = require('axios');
const API_KEY = 'AIzaSyCfTvOCJ5JA5eTeetkHSfDBUurdJeBDm94'; // you must replace API_KEY
async function runSample() {
const res = await axios.get('https://www.googleapis.com/youtube/v3/search',
{
params: {
part: 'id,snippet',// 必填,把需要的資訊列出來
q: '韓國瑜',// 查詢文字
maxResults: 50,// 預設為五筆資料,可以設定1~50
key: API_KEY, // 使用 API 只能取得公開的播放清單
}
});
console.log(res.data);
}
runSample().catch(err => console.error(err));
```
:::warning
別忘了 , 要將剛剛產生的 API_KEY 取代 `AIzaSyCfTvOCJ5JA5eTeetkHSfDBUurdJeBDm94` 歐!
:::
:::info
如果要確認 Search API 有甚麼其他的 `參數` 可以使用 , 請看 ==[官方文件](https://developers.google.com/youtube/v3/docs/search/list)==
:::
## Google OAuth2 身份認證
:::info
如果要上傳影片 , 就需要 OAuth2 登入 , 取得 `access_token` 才能上傳
:::
==先設定 OAuth 同意畫面==
![](https://i.imgur.com/6nZVZsd.png)
![](https://i.imgur.com/JKjQgCF.png)
==OAuth 同意畫面==
![](https://i.imgur.com/AsmbkmP.png)
==範圍==
![](https://i.imgur.com/039o6vf.png)
==選填資訊==
![](https://i.imgur.com/dljvh40.png)
==摘要==
![](https://i.imgur.com/A5wtFWG.png)
==建立憑證==
![建立憑證](https://i.imgur.com/ZfguEau.png)
==這次我們建立 OAuth 用戶端 ID==
![](https://i.imgur.com/2jLcBYp.png)
![建立 OAuth 用戶端 ID](https://i.imgur.com/3JUeBW0.png)
![建立 OAuth 用戶端 ID - 輸入文字](https://i.imgur.com/gcld6JY.png)
==下載 client_secret.json 檔==
![下載 client_secret.json檔](https://i.imgur.com/tAPDKQZ.png)
==client_secret.json 內容==
![](https://i.imgur.com/CUTcc5x.png)
:::warning
`redirect_uris` 與 `javascript_origins` 這兩個欄位必須存在
如果不存在 , 就需要重新設定 `OAuth 用戶端 ID`
:::
> 我們利用 `google-OAuth-test.js` 來產生 `access_token`
```javascript=
/* google-OAuth-test.js */
const fs = require('fs');
const path = require('path');
const http = require('http');
const url = require('url');
const opn = require('open');
const axios = require('axios');
const readline = require('readline');
const {google} = require('googleapis');
const youtube = google.youtube('v3'); // google.options 有設定 oauth2Client 之後會用那裏的驗證
const API_KEY = '[YOUR_API_KEY]';
/**
* To use OAuth2 authentication, we need access to a a CLIENT_ID, CLIENT_SECRET, AND REDIRECT_URI. To get these credentials for your application, visit https://console.cloud.google.com/apis/credentials.
*/
const keyPath = path.join(__dirname, './client_secret.json'); // 設定 client_secret.json 放的位置
let keys = {};
if (fs.existsSync(keyPath)) {
keys = require(keyPath).web;
}
/**
* Create a new OAuth2 client with the configured keys.
*/
const oauth2Client = new google.auth.OAuth2(
keys.client_id,
keys.client_secret,
keys.redirect_uris[0]
);
/**
* This is one of the many ways you can configure googleapis to use authentication credentials. In this method, we're setting a global reference for all APIs. Any other API you use here, like google.drive('v3'), will now use this auth client. You can also override the auth client at the service and method call levels.
*/
google.options({auth: oauth2Client});
/**
* Open an http server to accept the oauth callback. In this simple example, the only request to our webserver is to /callback?code=<code>
*/
async function authenticate(scopes) {
return new Promise((resolve, reject) => {
// grab the url that will be used for authorization
const authorizeUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: scopes.join(' '),
});
const server = http
.createServer((req, res) => {
if (req.url.indexOf('/oauth2callback') > -1) {
const qs = new url.URL(req.url, 'http://localhost:3000').searchParams;
oauth2Client.getToken(qs.get('code'))
.then(({tokens}) => {
// show access_token and api_key to html
res.end(`
<h1>Authentication successful!</h1>
<table style="border-collapse: collapse;">
<tr>
<td style="border: 1px solid black;padding: 10px;">ACCESS_TOKEN</td>
<td style="border: 1px solid black;padding: 10px;">${tokens.access_token}</td>
</tr>
<tr>
<td style="border: 1px solid black;padding: 10px;">API_KEY</td>
<td style="border: 1px solid black;padding: 10px;">${YOUTUBE_TOKEN.API_KEY}</td>
</tr>
</table>
`);
console.log('token=', tokens);
oauth2Client.credentials = tokens;
resolve({access_token: tokens.access_token, api_key: API_KEY});
// Close the server
server.close(() => console.log('Server closed!'));
})
.catch(e => {
res.end('Authentication failed.');
reject(e);
// Close the server
server.close(() => console.log('Server closed!'));
});
} else {
res.end('Wrong Url.You need to set http://localhost:3000/oauth2callback as redirect_uri');
}
})
.listen(3000, () => {
// open the browser to the authorize url to start the workflow
opn(authorizeUrl, {wait: false}).then(cp => cp.unref());
console.log('access api at http://localhost:3000');
});
});
}
const scopes = [
'https://www.googleapis.com/auth/youtube' // scope for add playlist . upload video ...
];
authenticate(scopes)
.then(({access_token,api_key}) => console.table({access_token,api_key}))
.catch(err => console.error(err));
```
==執行 `google-OAuth2-test.js` 後 , 會自動開啟瀏覽器==
![](https://i.imgur.com/pqzwHdG.png)
==因為使用 http , 所以會有 "應用程式未驗證"==
![](https://i.imgur.com/xBGnIp2.png)
![](https://i.imgur.com/wp2k0uM.png)
![](https://i.imgur.com/4TnxbLO.png)
==驗證成功後 , 會自動轉址到 `http://localhost:3000/oauth2callback`==
![](https://i.imgur.com/z6hJLEm.png)
:::info
之後 , 我們就可以使用 `access_token` 與 `api_key` 訪問 `Youtube Video Upload API` 來上傳影片檔
:::
## Video Upload API
:::info
延續之前的認證 , 取得 `access_token` 後 , 我們才能將影片上傳到自己的頻道
:::
```javascript=105
// 上傳影片 ( youtube-upload )
async function addVideo({access_token, api_key, channelId = 'UC8eCqbosTLZdvFCGid5YllA', fileName = '../data/video.mp4'}) {
// youtube.video.insert
const nowStr = new Date().getTime().toString(36).substring(0, 4);
const fileSize = fs.statSync(fileName).size;
const res = await youtube.videos.insert(
{
part: 'id,snippet,status,contentDetails',
notifySubscribers: false,
requestBody: {
snippet: {
channelId, // 上傳影片到哪個頻道
title: `your-video-${nowStr}`, // 影片標題
description: '測試上傳影片', // 影片描述
},
status: {
privacyStatus: "private"
},
},
media: {
body: fs.createReadStream(fileName),
},
},
{
// Use the `onUploadProgress` event from Axios to track the
// number of bytes uploaded to this point.
onUploadProgress: evt => {
const progress = (evt.bytesRead / fileSize) * 100;
console.log(`${Math.round(progress)}% complete`); // 顯示目前上傳進度
},
}
);
console.log('Video successful upload 😊');
return {access_token, api_key, data: res.data, videoId: res.data.id};
}
authenticate(scopes)
.then(addVideo)
.catch(err => console.error(err));
```
:::info
如果要確認 Video Upload API 有甚麼其他的 `參數` 可以使用 , 請看 ==[官方文件](https://developers.google.com/youtube/v3/docs/videos/insert)==
:::
## [Youbute Data API 配額](https://developers.google.com/youtube/v3/getting-started#calculating-quota-usage)
![](https://i.imgur.com/3M5QsFE.png)
:::info
==說明 [IAM 配額管理](https://console.cloud.google.com/iam-admin/quotas?service=youtube.googleapis.com&project=ezoom-test)==
- Youtube Data API 每天預設有 `10,000` units 可使用
- 每次 video upload 會用掉 `1600` units
- 每次 insert . update 會用掉 `50` units
:::
![](https://i.imgur.com/Z6MiGbp.png)
## 參考資料
- [YouTube Data API Overview - quota](https://developers.google.com/youtube/v3/getting-started#calculating-quota-usage)
- [youtube data api 載入播放清單並切換歌曲](https://medium.com/%E5%B0%8F%E9%83%AD-%E0%B9%80%E0%B8%88%E0%B8%99/%E8%8F%9C%E9%B3%A5%E5%B7%A5%E7%A8%8B%E5%B8%AB-youtube-data-api-%E8%BC%89%E5%85%A5%E6%92%AD%E6%94%BE%E6%B8%85%E5%96%AE%E4%B8%A6%E5%88%87%E6%8F%9B%E6%AD%8C%E6%9B%B2-356d8e454ca3)