이 부분이 이번 서비스의 핵심이다.
http 통신으로 어떻게 보내야 하는지 감이 오지 않던 상황이었기 때문에 한번 테스트 해보았다.
https://developers.google.com/youtube/v3/docs/subscriptions/list?hl=ko
해당 유튜브 API 공식 문서 사이트에서 확인 가능한데, 우측에서 http로 보내는 것을 테스트 해볼 수 있다.
part에 snippet, mine 항목은 true로 하고 진행했다.
응답코드 200, 정상적인 응답을 돌려받았음을 알 수 있었다. 따라서 모든 매개변수를 전부 입력할 필요도 없고, 필요한 것들만 입력하면 되겠다 싶었다.
const { google } = require('googleapis');
const OAuth2 = google.auth.OAuth2
상단에 선언해야하는 라이브러리 및 OAuth2 정보다.
var oauth2Client = new OAuth2(
process.env.GOOGLE_ID,
process.env.GOOGLE_SECRET,
process.env.CALLBACK
);
oauth2Client.credentials = {
access_token: req.user.accessToken,
refresh_token: req.user.refreshToken
};
googleapis 라이브러리 사용시 parameter를 오브젝트 형식으로 받는데 오브젝트 형식은 자바스크립트에서 기본으로 제공하는 형태다. 따라서 변수로 처리후 적절히 데이터를 처리해 넣는 것이 가능하다.
let result = []
let params = {
part: 'snippet',
mine: true,
order:'unread',
maxResults:50,
headers: {}
}
result는 구독하고 있는 채널 목록을 저장할 배열이며, params는 api 호출이 필요한 parameter들을 저장하고 있다.
let youtubeRes = await google.youtube({
version: 'v3',
auth: oauth2Client
}).subscriptions.list(params );
result = [...result,...youtubeRes.data.items]
oauth2Client는 위에서 얻은 계정 정보이다. youtube api 호출이 비동기적으로 이루어지다보니 해당 호출이 끝난 후 진행하고 싶다면 await 키워드로 붙여주는게 좋다.
해당 문서 내용인데 maxResults 최대 값은 50이라고 했다. 그런데 한 계정에서 최대 구독 가능한 채널은 2000이니 한번에 모든 채널의 정보를 얻는건 불가능하다. 따라서 50개씩 반복해서 호출하여 모든 채널 목록을 얻어내야만 한다. 그걸 해주는 것이 바로 pageToken이다. 다음 페이지를 가는 방법은 나머지 paramater는 그대로 주되 pageToken 부분만 바꿔주면 된다. pageToken이 없으면 첫번째 페이지지만 여기에 값을 넣어주면 nextPageToken, prevPageToken 값이 주어진다. 만약 이전 페이지나 다음 페이지가 없다면 당연히 안주어지며 이 경우 undefined로 뜬다.
let result = []
let params = {
part: 'snippet',
mine: true,
order:'unread',
maxResults:50,
headers: {}
}
while (true){
let youtubeRes = await google.youtube({
version: 'v3',
auth: oauth2Client
}).subscriptions.list(params );
result = [...result,...youtubeRes.data.items]
if (youtubeRes.data.nextPageToken == undefined){
break
}
else{
params.pageToken = youtubeRes.data.nextPageToken
}
}
사실 이것도 youtube API 활용 단순 반복이다. 하지만 구독 채널 목록 얻을 땐 googleapis 라이브러리를 활용했지만 이번엔 구글 콘솔에서 등록한 youtube api를 활용해본다. 이를 위해 http 통신을 사용했으며 이를 위해 axios 라이브러리를 활용했다.
구독 채널 목록이 최대 2000개가 되는 만큼 각 채널마다 처리를 일일이 기다리는건 아무래도 시간적 손실이 너무 컸으며 비동기적으로 돌아간다는 점을 통해 promise.all를 이용해 최대한 시간을 절약하는 방식으로 진행했다.
promise.all은 비동기적 처리를 담당하는 promise 배열을 이용하는데, 그런 만큼 각 채널의 처리를 담당하는 promise들을 만드는 파트라 볼 수 있다.
async function updateChannelInfo ( channelId,channel){
return new Promise(async (resolve,reject) =>{
playlistId = channelId[0] + "ULF"+channelId.substr(2)
const params = {
key: process.env.API_KEY,
part: 'snippet',
playlistId: playlistId
}
await axios.get("https://www.googleapis.com/youtube/v3/playlistItems",{
params
})
.then((response) =>{
temp = {}
temp.title = channel.snippet.title
temp.channelId = channel.snippet.resourceId.channelId
temp.description = channel.snippet.description.substring(0,40)
temp.publishedAt = response.data.items[0].snippet.publishedAt
temp.videoId = response.data.items[0].snippet.resourceId.videoId
temp.videoTitle = response.data.items[0].snippet.title
console.log("Channel title:", temp.title)
resolve(temp)
})
.catch((err)=>{
console.error(err)
reject(err)
})
} )
}
채널 아이디와 전체 동영상 플레이리스트가 저 규칙으로 차이가 발생함을 발견했고 이를 통해 최신 동영상에 쉽게 접근했다.
이를 통해 얻은 params로 axios 호출.
저 주소는 공식 문서에 나와있으니 참고하면 좋다.
호출 성공했다면 최신 동영상 플레이 리스트를 얻었으니 temp라는 오브젝트를 생성 여기에 필요한 정보들을 삽입후 resolve로 내보낸다.
아까 언급했다시피 유튜브 api 호출은 비동기적으로 처리 된다. 이를 await 등으로 동기적으로 처리 하도록 만들수 있는데, 유튜브 api 서버에 호출을 보내고 받는 과정이 생각보다 빠르지 않아 채널 목록이 50개만 되어도 꽤 많은 딜레이가 진행된다. 이에 비동기적으로 처리되는 최신 동영상 확인 작업을 비동기적으로 처리시키고 전체적으로 완료되면 그제서야 진행되는 Promise.all을 사용했다.
const processItems = async (items) =>{
let promiseList = []
for (let index in items) {
let channel = items[index]
promiseList.push(updateChannelInfo(channel.snippet.resourceId.channelId,channel))
}
let result = Promise.all(promiseList)
return result
}
한 채널에서 정보를 얻는 파트에서 결과를 temp에 담아 resolve로 보냈는데, 값들이 result에 하나의 배열 속에 아이템으로 저장된다.
let tempResultList = await processItems(items)
let resultList = []
const curDate = Date.now()
for (let infoIndex in tempResultList){
const info = tempResultList[infoIndex]
const diff = (curDate - new Date(info.publishedAt)) / (1000*60*60*24)
if (diff >= 5){
resultList.push(info)
}
}
processItems() 함수가 바로 위에 언급했던 Promise.all을 사용했던 함수다. 또한 curDate는 현재 시간을 얻는다.
이제 각 채널을 순회하면서 시간 계산 하는데 curDate에서 업로드 일정을 뺀 것을 (10006060*24) 으로 나눠 대략적 일수를 얻어낸다.
그게 5일 이상 업로드 하지 않은 채널이라면 채널에 담아 반환하는 방식이다.
저 이후 내용은 프론트 엔드 영역과 걸쳐 있어 생략한다. 이번 파트의 포인트는 유튜브 API 활용법이기도 하지만 비동기 프로그래밍을 어떻게 동기적으로 운용할 수 있는지에 대한 고찰이기도 하다.
이에 callback, promise, async/await 와 같은 비동기를 처리할 수 있는 방식들에 대해 이해할 계기가 되었음.