Youtube 댓글 입맛대로 정렬 및 필터링 하는 크롬 확장 프로그램 만들기

전솔·2023년 7월 25일
1

결과물

기획 의도

최근 3년 동안 매주 목요일마다 "우니의끼니"라는 유튜브 채널을 재미있게 시청하고 있다. 이 채널에서는 연말이나 명절마다 해당 연도의 재밌었던 영상들을 하이라이트로 편집하여 공개해주는데 그 영상에는 재미있는 댓글들도 캡처되어 게시된다.

한 번 우니언니가 영상마다 댓글이 수백 개씩 달리는데, 그 중에서 재미있는 댓글을 하나하나 찾기 힘들다고 언급한 적이 있었다. 그래서 유튜브의 댓글 정렬 기능을 살펴봤는데, 최신순과 인기순 두 가지만 있었다. 인기순조차도 유튜브 알고리즘이 결정한 순서라서 추천을 많이 받은 댓글순으로 나오지 않아서 아쉬웠다.

추천이 많은 댓글순으로 정렬 가능하거나 타임라인이 찍힌 댓글만 필터링해서 보면 재밌는 댓글을 놓치지 않고 쉽게 볼 수 있지 않을까? 싶어서 youtube 댓글 확장 프로그램을 만들기로 했다.

(추가로..최근에 덱스 덕질을 시작했는데…댓글 중에 한국어 댓글만 보고 싶은데… 유튜브에는 국가별 언어로 필터링 하는 기능이 없어서 요 기능도 추가를 해보겠다…)

기능 명세

  1. 좋아요 많이 받은 순으로 댓글을 정렬하는 기능 구현
  2. 대댓글 많은 순으로 댓글을 정렬하는 기능 구현
  3. 타임라인이 찍힌 댓글을 필터링하는 기능 구현
  4. 국가별 언어로 필터링는 기능 구현

크롬 익스텐션

background.js vs content-script.js vs popup.js

익스텐션에서 사용하는 스크립트는 background scripts와 content scripts가 있다.

background.js

background scripts란 background page에서 동작하는 코드다. background page는 chrome:// 프로토콜로 시작하는 익스텐션 고유의 웹 페이지다. 일반적으로 크롬을 아예 종료하기 전까지 계속 살아있다. 실제로는 조금 다르지만 일단 이렇게 이해하면 편하다.

모든 웹 페이지처럼 background page는 고유의 HTML, CSS, Javascript를 가지며(실제로 볼 일은 없겠지만), 로컬 스토리지, 세션 스토리지도 있고, 브라우저 쿠키에도 접근할 수 있다. 그 중 가장 큰 특징은 Cross-origin XMLHttpReqeust가 가능하다는 점이다. 쉽게 말하면 권한(permission)을 추가했다면 외부 API를 마음껏 호출할 수 있다.

background.js는 manifest.json에서 주입된다.

{
  ...
  "background": {
    "service_worker": "background.js"
  },
}

content.js

content script는 웹 페이지에 직접적으로 주입(inject)되는 코드이다. 주입된 코드는 독립된 공간에서 실행되지만(따라서, 전역 변수를 선언하더라도 기존 사이트의 전역 변수와 겹칠 걱정은 하지 않아도 된다), 웹 페이지의 DOM에 접근하거나 아예 새로운 DOM을 그릴 수도 있고, 여러 가지 재미있는 일을 할 수 있게된다.

content.js는 보통 manifest.json에서 주입된다.

{
  ...
  "content_scripts": [
    {
      "js": ["content-script.js"],
      "matches": [
        "<all_urls>"
      ]
    }
  ]
}

popup.js에서 동적으로 주입할 수도 있다.

// 현재 활성화된 탭의 정보를 가져옵니다.
  chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
  // 가져온 탭 정보에서 원하는 탭을 선택합니다.
  var tab = tabs[0];

  chrome.scripting.executeScript({
    target: {tabId: tab.id, allFrames: true},
    files: ['content-script.js'],
  })
  .then(() => {
    // content script 내의 함수를 호출합니다.
    chrome.tabs.sendMessage(tab.id, { action: 'runFunction' });
  });

popup.js

popup script는 익스텐션 팝업(popup.html)에서 발생되는 (클릭과 같은)이벤트들을 핸들링 하거나 팝업의 DOM을 제어하고 조작하는데 사용한다. 여기서 작성되는 document는 웹사이트의 document가 아닌 popup.html의 document이니 주의해야한다.

popup.js는 popup.html에서 주입된다.

<!DOCTYPE html>
<html lang="en">
  <body>
    <script src="./popup.js"></script>
  </body>
</html>

tutorial sample

“크롬 익스텐션 만들기”를 구글링 했을 때, manifest v2의 예시가 많고, v3에서 바뀐 스펙이 몇 가지 있어서 기존에 작성된 예시를 따라하기가 꽤나 골치가 아팠는데, 크롬에서 제공하는 extension sample을 찾았다.

익스텐션을 처음 만들어보는 초심자라면 구글에서 제공하는 간단한 샘플들을 먼저 따라해보고 원하는 익스텐션을 만드는 것을 추천한다.

디자인

UI Design Daily는 UI 디자인 컴포넌트를 제공하는 사이트이다.
팝업 UI는 Filter Modal을 참고했고, 댓글 UI는 Comment를 참고했다.

마크업

최근에 figma plugin에서 디자인한 프로토타입을 html, css로 뽑아준다는 소식을 들었다.
물론 코드의 가독성, 웹접근성, 확장성이 떨어지겠지만 토이프로젝트에선 사용해도 괜찮을 거 같아서 처음 사용해봤다.
figma 파일을 html, css 파일로 변환하기 위해 Function12 를 사용하였다.

사용법

1. https://function12.io/ 로 접속해서 로그인 한다.

2. 아래 input에 fima link를 붙여넣기 한 후 Translate for Free 클릭

3. 변환이 완료되면 상단 탭 Code > Export > HTML 버튼을 클릭해서 다운로드 받는다.

HTML 위에 있는 버튼은 Flutter로 변환하는 버튼이다.

사용 후기

html, css, scss, assets 을 정성스럽게 추출해주지만, 구조가 너무 어글리해서 사용할 수가 없다..🫥
요행을 바라지 말고 스스로 마크업을 하자..

요구사항 분석

  • 닫기 버튼 클릭 시 팝업 닫기
  • videoId에 따른 원본 댓글 가져오기
  • SORT
    • Default: 원본 순서
    • Like Count: 좋아요 수
    • Reply Count: 답글 수
  • LANGUAGE: 체크된 언어가 60% 이상 포함된 댓글 필터
    • Default: 원본 순서
    • Ko: 한국어
    • En: 영어
    • Zh-Chs: 중국어
    • Ja: 일본어
    • Th: 태국어
  • FILTER
    • Timeline: Timeline 포함된 댓글 필터
  • clear 클릭 시 원본 댓글 주입
  • apply filters 클릭 시 조건에 맞게 댓글 주입

개발 과정

YouTube Data API

YouTube의 댓글을 불러오는 기능을 구현하기 위해서는 YouTube Data API를 사용해야 한다. 이 API는 인증 및 요청을 위한 키를 발급 받아야 한다.

YouTube Data API 키를 발급받기 위해서는 Google Cloud Console에서 프로젝트를 생성하고 API 키를 생성해야한다. 아래의 단계를 따라서 YouTube Data API 키를 발급받을 수 있다.

  1. Google Cloud Console에 접속 (https://console.cloud.google.com/)
  2. 새 프로젝트를 생성하거나 기존 프로젝트를 선택
  3. 왼쪽 상단의 "프로젝트 선택" 드롭다운 메뉴에서 프로젝트를 선택
  4. "API 및 서비스" → "대시보드"로 이동
  5. "API 및 서비스 사용 설정" 버튼을 클릭
  6. "YouTube Data API v3"를 검색하여 선택
  7. "사용" 버튼을 클릭
  8. 좌측의 "사용자 인증 정보" 메뉴로 이동
  9. "사용자 인증 정보 만들기" 버튼을 클릭
  10. "API 키"를 선택
  11. API 키가 생성되고 키 값이 표시

위의 단계를 따라서 YouTube Data API 키를 발급 받은 후, 해당 키를 아래 JavaScript 코드의 apiKey변수에 입력한다.

const apiKey = '__API_KEY__';
const videoId = '__VIDEO_ID__';

// 댓글을 가져오는 함수
function getComments() {
  // YouTube Data API 엔드포인트 URL
  const url = 'https://www.googleapis.com/youtube/v3/commentThreads';

  // 파라미터 설정
  const params = {
    key: apiKey,
    part: 'snippet',
    videoId: videoId,
    maxResults: 10  // 가져올 댓글의 최대 개수 설정
  };

  console.log(url + '?' + new URLSearchParams(params), 'dd')

  // API 요청 보내기
  fetch(url + '?' + new URLSearchParams(params))
    .then(function(response) {
      return response.json();
    })
    .then(function(data) {
      // 댓글 데이터를 처리하는 로직을 여기에 작성
      console.log(data);
    })
    .catch(function(error) {
      console.error('댓글을 불러오는 중 오류가 발생했습니다:', error);
    });
}

// 댓글 가져오기 함수 호출
getComments();

Youtube Data API에서 ‘maxResults’는 최대로 가져올 댓글의 개수를 나타내는 매개변수인데, ‘maxResults’에는 32비트 정수 값만 허용되므로 너무 큰 숫자는 사용할 수 없다.

‘maxResults’에 가장 큰 숫자를 지정하려면 API에서 허용하는 최대 값인 50으로 설정할 수 있다. 만약 50개 이상의 댓글을 가져와야 한다면, API를 여러 번 호출하여 다음 페이지의 댓글을 가져오는 방식을 사용해야 한다.

아래 코드는 ‘maxResults’를 50으로 설정하고 다음 페이지의 댓글을 가져오는 예시이다.

const apiKey = '__API_KEY__';
const videoId = '__VIDEO_ID__';

// 댓글을 가져오는 함수
function getComments(pageToken) {
  // YouTube Data API 엔드포인트 URL
  const url = 'https://www.googleapis.com/youtube/v3/commentThreads';

  // 파라미터 설정
  const params = {
    key: apiKey,
    part: 'snippet',
    videoId,
    maxResults: 50,  // 가져올 댓글의 최대 개수 설정
    pageToken // 다음 페이지의 토큰
  };

  // API 요청 보내기
  fetch(url + '?' + new URLSearchParams(params))
  .then(function(response) {
    return response.json();
  })
  .then(function(data) {
    // 댓글 데이터를 처리하는 로직을 여기에 작성
    console.log(data);

    // 다음 페이지의 댓글 가져오기 (만약 댓글이 더 이상 없으면 data.nextPageToken은 undefined입니다.)
    const nextPageToken = data.nextPageToken;
    if (nextPageToken) {
      getComments(nextPageToken);
    }
  })
  .catch(function(error) {
    console.error('댓글을 불러오는 중 오류가 발생했습니다:', error);
  });
}

// 댓글 가져오기 함수 호출
getComments();

결과물

🧷 Git: https://github.com/beforesol/youtube-comment-sort-and-filter/tree/master

🧷 Chrome Extension: https://chrome.google.com/webstore/detail/youtube-comment-sort-and/ngkecjlhpkenkbaceahnkeohmffaeaep/related?hl=ko&authuser=0

아쉬운 점

Fetch Comment Data

Youtube Data API에서 최대로 가져올 댓글의 개수가 50개여서 댓글의 양이 많을 경우 초반 로딩이 오래 걸리는 이슈가 있다. BTS의 Dynamite MV 같은 경우 댓글이 1600만개 정도 되는데 이 댓글을 모두 fetch 하기 위해선 32만번의 api 호출을 해야한다. Youtube Data API에서 제공하는 API 정책이 이러하니 어쩔 수 없지만 현실적으로 댓글이 많은 아이돌 MV에서는 사용하기 불편하다.

삽질 일지

이 글을 보는 다른 사람들은 같은 실수로 시간을 쏟지 않았으면 해서 내가 겪은 삽질을 간단하게 정리해보았다.

1. 크롬 익스텐션 업로드 후 새로고침

크롬 익스텐션을 업로드 후 크롬을 새로고침하지 않고 실행을 한다면 popup.js와 content-script.js가 통신하지 못해서 아래와 같은 이슈가 발생한다.

Uncaught (in promise) Error: Could not establish connection. Receiving end does not exist.

테스트를 위해 개발자 모드에서 확장 프로그램을 자주 로드 및 삭제를 반복하는데, 로드한 후에는 꼭 크롬을 새로고침 한 다음에 익스텐션을 실행해야 정상적으로 동작한다.

2. chrome.tabs

chrome.tabs API는 브라우저의 탭 시스템과 상호작용하는 역할을 한다. 이 API를 사용하여 브라우저에서 탭을 생성, 수정 및 재정렬할 수 있다.

웹페이지의 url을 통해 videoId를 가져오는 로직을 아래와 같이 작성했는데, tabs[0].url이 자꾸 undefined 되는 이슈가 있었다.

chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) {
    // 현재 활성 탭의 URL을 가져옴
    const currentUrl = tabs[0].url;
    alert(currentUrl); // undefined
  });

chat GPT와 구글링에서 검색한 방법으로 적용했는데도 동일한 이슈가 반복되어서 익스텐션 공식문서를 보니 manifest.json > permissions 에 tabs을 명시해줘야한다고 한다.

{
  "name": "My extension",
  ...
  "permissions": [
    "tabs"
  ],
  ...
}

permissions은 확장 프로그램이 특정 기능을 수행하거나 브라우저의 기능에 접근할 수 있도록 허용해주는 역할을 한다. 확장 프로그램의 권한은 사용자의 개인정보와 보안을 보호하기 위해 신중하게 사용되어야 한다. 필요한 권한만 등록하고, 권한이 부여된 경우 적절하게 사용하는 것이 중요하다.

References

profile
Frontend Engineer.

2개의 댓글

comment-user-thumbnail
2023년 7월 25일

좋은 글이네요. 공유해주셔서 감사합니다.

1개의 답글