오빠두엑셀 `2026 무료 챌린지` 오픈! 완주하고 수료증 받아가세요! 5년 연속 IT분야 베스트셀러! 「 진짜쓰는 실무엑셀 」로 2026년 공부 끝내기 엑셀이 막히셨나요? Q&A 게시판에서 바로 해결하세요.
메뉴
업무 생산성 강의

유튜브 자동 댓글 챗봇 만들기 (100% 무료! 구글시트+Gemini 활용)

오빠두엑셀 by 오빠두엑셀
  • 학습시간 46분
  • 난이도 초급
  • 작성일 2024.08.07

구글시트만 있으면 OK! Gemini 에서 무료로 제공하는 API를 활용해서 유튜브 자동 댓글 챗봇을 만들어보세요!🔥 (마스터 코드 제공)

이 강의에서는 구글시트의 Apps Script와 Google Gemini의 무료 API를 연동해, 유튜브에 달린 댓글을 자동으로 불러오고 AI가 채널 톤에 맞춰 답글 초안을 생성한 뒤 일괄 업로드하는 자동 댓글 챗봇을 만드는 방법을 다룹니다. 마스터 코드를 그대로 복사해 사용할 수 있어, 매일 새 댓글에 일일이 답변하기 어려운 채널 운영자도 손쉽게 적용할 수 있습니다.

유튜브 자동 댓글 챗봇 만들기 (100% 무료! 구글시트+Gemini 활용)
DOWNLOADS

관련 자료를 모았어요

더 깊이 살펴볼 수 있는 자료를 한곳에

실습 가이드
.

라이브 강의 전체영상도 함께 확인해보세요!

위캔두 멤버십에 가입하시면 매주 오빠두엑셀에서 진행하는 라이브 강의 풀영상을 함께 확인하실 수 있습니다.


실습에 필요한 관련 링크

  1. Gemini API 발급
  2. 구글 클라우드 플랫폼
  3. 구글시트 oAuth 스크립트 ID
    1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF
  4. Gemini 무료 API 요금 안내
  5. 유튜브 API 작업별 Cost 안내

Gemini 연동 마스터 코드

var scriptProperties = PropertiesService.getScriptProperties();
const geminiApiKey = scriptProperties.getProperty('GEMINI_API_KEY');
const openAi_ApiKey = scriptProperties.getProperty('OPENAI_API_KEY');
const geminiModel = 'gemini-1.5-flash'; // 다른 버전의 Gemini 모델 필요시, 여기서 수정하세요.
const geminiEndpoint = `https://generativelanguage.googleapis.com/v1beta/models/${geminiModel}-latest:generateContent?key=${geminiApiKey}`;
 
/**
ContetPrompt : 맥락, 상황을 작성합니다.(예: 채널 설명 등)
tontPrompt : 어조를 지정합니다. (예: 부드럽게, 유머스럽게 등)
instructionPrompt : 그 외 지시사항을 작성합니다. (예: 답글 마지막에는 '감사합니다!'로 끝낼 것 등)
**/
 
const contextPrompt = `* 나는 대한민국에서 직장인을 위해 엑셀 및 다양한 오피스 프로그램의 활용법을 알려주는 유튜브 채널을 운영하고 있어.
* 구독자가 작성한 댓글에 답변을 작성하는 작업에 도움이 필요해.`;
const tonePrompt = `* 답변은 친절하면서 간결하고, 명료하고, 전문성있게 작성해. 항상 전문성을 잃지 않도록 노력해.`;
const instructionPrompt = `* 마지막에는 "감사합니다! -오빠두봇드림✨"으로 마칠 것.
* 댓글 내용은 반복하지 말고, 답글만 작성할 것.
* 만약 댓글에 타임라인(: 12:34)이 있으면 커리큘럼에 있는 타임라인을 참고해서 응원의 문장을 작성할 것.\n`;
 
//--------------------------------------------
 
function 프롬프트미리보기 () {
generatePrompt('영상 제목','영상 설명','감사합니다!','별말씀을요!');
}
 
function YoutubeCommentAutoReply() {
GetCommentsInSheet();
generateReplies();
postRepliesToComments();
}
 
function generateReplies() {
var sheet = SpreadsheetApp.openById(sheetId).getActiveSheet();
var dataRange = sheet.getDataRange();
var data = dataRange.getValues();
 
var headers = data[0];
var idIndex = headers.indexOf("ID");
var titleIndex = headers.indexOf("영상 제목");
var descriptionIndex = headers.indexOf("영상 설명");
var textIndex = headers.indexOf("댓글");
var replyIndex = headers.indexOf("답글");
var keywordIndex = headers.indexOf("초안");
var nameIndex = headers.indexOf("닉네임");
var statusIndex = headers.indexOf("확인");
 
for (var i = data.length - 1; i >= 1; i--) {
if (data[i][statusIndex] === '') {
if (data[i][nameIndex] === yHandle) {
sheet.getRange(i + 1, replyIndex + 1).setValue('-');
sheet.getRange(i + 1, statusIndex + 1).setValue('X');
} else {
var commentId = data[i][idIndex];
var youtubeTitle = data[i][titleIndex];
var youtubeDescription = data[i][descriptionIndex];
var comment = data[i][textIndex];
var keyword = data[i][keywordIndex];
 
sheet.getRange(i + 1, replyIndex + 1).setValue('✨ 답변 생성중...');
SpreadsheetApp.flush();
if (commentId.includes('.')) {
var topLevelCommentId = commentId.split('.')[0];
var previousComments = '';
 
for (var j = 1; j < data.length - 1; j++) {
var prevCommentId = data[j][idIndex];
if (prevCommentId === topLevelCommentId || prevCommentId.startsWith(topLevelCommentId + '.')) {
var prevComment = data[j][textIndex];
var prevReply = data[j][replyIndex];
previousComments += 'Comment: ' + prevComment + '\nReply: ' + prevReply + '\n\n';
}
}
 
var prompt = generatePrompt(youtubeTitle, youtubeDescription, comment, keyword, previousComments);
var reply = askGemini(prompt); // <- ChatGPT 사용 시, 이 부분을 askChatGPT로 변경하세요!
sheet.getRange(i + 1, replyIndex + 1).setValue(reply);
} else {
var prompt = generatePrompt(youtubeTitle, youtubeDescription, comment, keyword);
var reply = askGemini(prompt); // <- ChatGPT 사용 시, 이 부분을 askChatGPT로 변경하세요!
sheet.getRange(i + 1, replyIndex + 1).setValue(reply);
}
}
} else if (data[i][statusIndex] === 'O') {
break;
} else {
sheet.getRange(i + 1, replyIndex + 1).setValue('-');
}
SpreadsheetApp.flush();
}
}
 
function generatePrompt(youtubeTitle='', youtubeDescription='', comment='', keyword='', previousComments='') {
var prompt = `###Context###\n${contextPrompt}\n\n`;
prompt += `###Objective###\n* Information에 제공한 영상 제목과 설명을 참고하여, 댓글에 대한 답변을 작성해.\n`;
 
if (keyword.length > 0) {
prompt += `* 답변은 초안을 참고하여 작성할 것.\n`;
}
 
prompt += '\n';
prompt += `###Information###\n`;
 
prompt += `* 영상 제목: ${youtubeTitle}\n* 영상 설명: ${youtubeDescription}\n`;
 
if (previousComments) {
prompt += `* 이전 댓글과 답변:\n${previousComments}\n`;
}
if (keyword.length > 0) {
prompt += `* 댓글: ${comment}\n* 답변 초안: ${keyword}\n\n`;
} else {
prompt += `* 댓글: ${comment}\n\n`;
}
 
prompt += `###Tone###\n${tonePrompt}`;
 
prompt += '\n\n';
 
prompt += `###Intruction###\n` +
`${instructionPrompt}`;
 
Logger.log(prompt);
return prompt;
}
 
/**
* Gemini API를 사용하여 응답을 생성합니다.
* @param {string} prompt - Gemini 모델에 전달할 프롬프트 텍스트
* @param {number} temperature - 생성 텍스트의 무작위성을 제어하는 온도 값 (기본값: 1)
* @return {string} Gemini 모델이 생성한 텍스트 응답
* @customfunction
*/
function askGemini(prompt, temperature=1) {
const payload = {
"contents": [
{
"parts": [
{
"text": prompt
}
]
}
],
"generationConfig": {
"temperature": temperature,
},
};
 
const options = {
'method' : 'post',
'contentType': 'application/json',
'payload': JSON.stringify(payload)
};
 
const response = UrlFetchApp.fetch(geminiEndpoint, options);
const data = JSON.parse(response);
const content = data["candidates"][0]["content"]["parts"][0]["text"];
return content;
}
 
function askChatGPT(prompt) {
var url = 'https://api.openai.com/v1/chat/completions';
var options = {
method: 'post',
contentType: 'application/json',
headers: {
'Authorization': 'Bearer ' + openAi_ApiKey
},
payload: JSON.stringify({
model: 'gpt-4o-mini', // Use the correct model name
messages: [
{role: 'system', content: '당신은 유튜브 채널에 남겨진 구독자 댓글에 대한 답변을 작성하는 대한민국 최고의 챗봇입니다.'},
{role: 'user', content: prompt}
]
}),
muteHttpExceptions: true
};
 
var response = UrlFetchApp.fetch(url, options);
if (response.getResponseCode() !== 200) {
Logger.log('Error: ' + response.getContentText());
throw new Error('Failed to fetch response from OpenAI API');
}
 
var json = response.getContentText();
var reply = JSON.parse(json).choices[0].message.content.trim();
 
return reply;
}

유튜브 데이터 API 연동 마스터 코드

const oAuthURL = 'https://accounts.google.com/o/oauth2/auth';
const tokenURL = 'https://accounts.google.com/o/oauth2/token';
const sslScope = 'https://www.googleapis.com/auth/youtube.force-ssl';
var scriptProperties = PropertiesService.getScriptProperties();
const clientId = scriptProperties.getProperty('CLIENT_ID');
const yHandle = scriptProperties.getProperty('YOUTUBE_HANDLE');
const channelId = scriptProperties.getProperty('YOUTUBE_CHANNEL_ID');
const sheetId = scriptProperties.getProperty('SHEET_ID');
 
function getYouTubeService() {
return OAuth2.createService('YouTube')
.setAuthorizationBaseUrl(oAuthURL)
.setTokenUrl(tokenURL)
.setClientId(clientId)
.setCallbackFunction('authCallback')
.setPropertyStore(PropertiesService.getUserProperties())
.setScope(sslScope)
.setParam('access_type', 'offline')
.setParam('approval_prompt', 'force')
.setParam('login_hint', Session.getActiveUser().getEmail());
}
 
function authCallback(request) {
var youtubeService = getYouTubeService();
var isAuthorized = youtubeService.handleCallback(request);
if (isAuthorized) {
return HtmlService.createHtmlOutput('인증에 성공하였습니다. 이 창을 종료 후, 앱스크립트 편집기로 돌아가도 괜찮습니다.');
} else {
return HtmlService.createHtmlOutput('인증 과정에 오류가 발생했습니다. 다시 시도하세요.');
}
}
 
function 답글생성하기() {
generateReplies();
}
 
function 댓글불러오기() {
var youtubeService = YouTube.CommentThreads;
 
var sheet = SpreadsheetApp.openById(sheetId).getActiveSheet();
var lastRow = sheet.getLastRow();
var lastTimestamp = lastRow > 1 ? new Date(sheet.getRange(lastRow, 6).getValue()) : null;
 
var videoResponse = youtubeService.list('snippet,replies', {
allThreadsRelatedToChannelId: channelId,
maxResults: 100
});
 
var commentThreads = [];
 
for (var i = 0; i <= videoResponse.items.length - 1 ; i++) {
var item = videoResponse.items[i];
var videoId = item.snippet.videoId;
 
if (!videoId) {
Logger.log('잘못된 Video ID 입니다. : ' + videoId);
continue;
}
 
var videoDetails = YouTube.Videos.list('snippet', {
id: videoId
}).items[0];
 
if (!videoDetails) {
Logger.log('영상 설명을 찾을 수 없습니다. : ' + videoId);
continue;
}
 
if (item.snippet.topLevelComment.snippet.authorDisplayName != yHandle) {
commentThreads.unshift([
item.snippet.topLevelComment.id,
videoDetails.snippet.title,
videoDetails.snippet.description,
item.snippet.topLevelComment.snippet.textDisplay.replace(/<[^>]*>/g, ''),
item.snippet.topLevelComment.snippet.authorDisplayName,
item.snippet.topLevelComment.snippet.publishedAt
]);
}
 
if (item.replies) {
for (var j = 0; j <= item.replies.comments.length - 1; j++) {
var reply = item.replies.comments[j];
if (reply.snippet.authorDisplayName != yHandle) {
commentThreads.unshift([
reply.id,
videoDetails.snippet.title,
videoDetails.snippet.description,
reply.snippet.textDisplay.replace(/<[^>]*>/g, ''),
reply.snippet.authorDisplayName,
reply.snippet.publishedAt
]);
}
}
}
 
}
 
var newCommentThreads = lastTimestamp ? commentThreads.filter(function(comment) {
var commentTimestamp = new Date(comment[5]);
return commentTimestamp > lastTimestamp;
}) : commentThreads;
 
newCommentThreads.sort(function(a, b) {
var timeA = new Date(a[5]);
var timeB = new Date(b[5]);
return timeA - timeB;
});
 
if (newCommentThreads.length > 0) {
sheet.getRange(lastRow + 1, 1, newCommentThreads.length, newCommentThreads[0].length).setValues(newCommentThreads);
}
}
 
function 답글업로드하기() {
var sheet = SpreadsheetApp.openById(sheetId).getActiveSheet();
var lastRow = sheet.getLastRow();
var idIndex = 1;
var replyIndex = 9;
var statusIndex = 7;
 
var youtubeService = YouTube.Comments;
 
for (var i = lastRow; i >= 2; i--) {
var commentId = sheet.getRange(i, idIndex).getValue();
var reply = sheet.getRange(i, replyIndex).getValue();
var status = sheet.getRange(i, statusIndex).getValue();
 
if (status == 'O') {
break;
}
 
Logger.log('Comment ID: ' + commentId);
Logger.log('Original Reply: ' + reply);
Logger.log('Status: ' + status);
var topLevelCommentId = commentId.split('.')[0];
 
if (reply.trim().length > 0 && status == '') {
var resource = {
snippet: {
parentId: topLevelCommentId,
textOriginal: reply
}
};
 
try {
youtubeService.insert(resource, 'snippet');
sheet.getRange(i, statusIndex).setValue('O');
Logger.log('ID의 답글을 성공적으로 게시했습니다. : ' + commentId);
} catch (error) {
Logger.log('해당 ID의 답글 게시 중 오류가 발생했습니다. : ' + commentId);
Logger.log('오류 메시지 : ' + error.message);
}
} else {
Logger.log('해당 ID의 답글이 비어있어 다음 답글로 넘어갑니다. : ' + commentId);
}
}
}

ChatGPT 기반 챗봇으로 전환하는 방법

Gemini 무료 API 대신 ChatGPT 기반의 챗봇을 사용하시려면, 다음 단계에 따라 OpenAI에서 API 키를 발급받은 뒤 마스터 코드를 일부 수정합니다.

  1. 아래 링크에서 OpenAI 개발자센터에 접속해 API 키를 발급받습니다.

    https://platform.openai.com/api-keys


    • Dashboard → API Keys → [Create New secret key] 버튼 클릭
    • 결제 수단이 등록되어 있지 않으면 API 키 발급이 제한될 수 있습니다. 이 경우 우측 상단의 [설정] → Billing 메뉴에서 결제 수단을 먼저 등록합니다.

    chatgpt-API키-발급

    오빠두Tip : OpenAI API 키 발급의 전체 과정과 엑셀+ChatGPT 추가기능 활용법은 아래 영상 강의에서 자세히 확인하실 수 있습니다.👇
  2. 앱스크립트 편집기로 돌아와 스크립트 속성에 새로운 키와 값을 추가합니다.
    Property : OPENAI_API_KEY
    Value : OpenAI API키

    구글시트-chatgpt-연동-api

  3. Gemini 연동 마스터 코드에서 askGemini 함수 호출을 askChatGPT로 변경하면 ChatGPT API를 활용한 유튜브 챗봇이 완성됩니다. (두 군데를 모두 변경해야 합니다.)
    변경 전 : var reply = askGemini(prompt);
    변경 후 : var reply = askChatGPT(prompt);

    chatgpt-챗봇-만들기-코드

댓글 8
4.3 (6개 평가)
강민준🤗
강민준🤗 2024.08.07 16:31
좋은 강의 감사드립니다.
더블유에이
더블유에이 2024.08.08 11:07
다른 구글 서비스와 연동할 때에는 앱스크립트가 매우 강력한 것 같습니다..^^
인천들국화
인천들국화 2024.10.02 14:46
오후 2:44:43오류TypeError: Cannot read properties of undefined (reading 'includes')
generateReplies@ Code.gs:61답글생성하기@ YoutubeAPI.gs:34

youtubeapi appscript 에서 댓글생성하기 누르면 위 오류가 나는데 왜그런지요??ㅠㅠ
인천들국화
인천들국화 2024.10.02 15:06
if (commentId.includes('.')) { 이부분에서 오류가 나요
오빠두엑셀
오빠두엑셀 작성자 2024.10.07 19:37
안녕하세요.
undefined 오륜느 includes 를 호출한 개체, 'commendId'가 올바르게 선언되지 않아서 발생합니다.
코드를 디버깅하셔서, 오류가 발생하는 전 단계에 Logger.log(commendID); 를 추가해서 commendId가 올바르게 선언되었는지 확인해보시길 바랍니다.
감사합니다.
송서영
송서영 2025.01.28 18:21
안녕하세요 정말 좋은 강의 감사합니다ㅠ
ChatGPT를 통한 생성으로 변경 후 다음과 같은 오류가 발생하는 데 해결하는 방법이 있을까요?

오류 메시지 : API call to youtube.comments.insert failed with error: The comment cannot be created due to insufficient permissions. The request might not be properly authorized.
오빠두엑셀
오빠두엑셀 작성자 2025.01.28 20:27
안녕하세요.
해당 오류는 Youtube API에 댓글 작성에 대한 권한이 올바르게 주어지지 않아 발생하는 것으로 보입니다.
API 설정을 다시 한번 확인해보세요 :)
송서영
송서영 2025.01.29 23:07
안녕하세요, 유튜브의 댓글은 불러와지는데, 답글 업로드가 안됩니다...
화면-캡처-2025-01-29-230717