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

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

# 구글시트 # 업무생산성

작성자 :
오빠두엑셀
최종 수정일 : 2024. 08. 14. 18:38
URL 복사
메모 남기기 : (5)

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

유튜브 챗봇 만들기 목차 바로가기
영상 강의

  1. - oAuth 인증 이해하기 -
  2. 구글시트 + 유튜브 연동 원리 이해하기
    01:43
  3. 외부 서비스와 내 계정을 안전하게 연동하는 방법
    03:37
  4. oAuth 인증 방식의 동작 원리 이해하기
    05:15
  5. - 구글시트 ↔ Gemini 연동 -
  6. Gemini 에서 제공하는 무료 API키 발급받기
    07:14
  7. Gemini API 무료 요금제 살펴보기
    09:45
  8. 구글시트 Apps Script 새로운 프로젝트 추가하기
    12:00
  9. Gemini API 키 등록 및 마스터 코드 붙여넣기
    13:29
  10. askGemini 함수 사용해보기
    15:04
  11. - 구글시트 ↔ Youtube 연동 -
  12. 구글 클래우드 플랫폼에서 Youtube API 활성화하기
    17:00
  13. 구글 클래우드 플랫폼의 프로젝트 ID 확인하기
    18:33
  14. Apps Script에서 사용할 라이브러리 추가하기
    19:49
  15. 구글시트 ↔ 클라우드 플랫폼 연동하기
    21:22
  16. GCP에서 발급한 클라이언트 ID 확인하기
    24:28
  17. 구글시트 ↔ 유튜브 연동에 필요한 속성 설정하기
    25:39
  18. - 유튜브 댓글 자동화 함수 테스트 -
  19. 유튜브 자동 댓글 챗봇에 사용할 템플릿 만들기
    29:06
  20. 구글시트 ↔ 유튜브 연동 마스터 코드 실행하기
    29:43
  21. 유튜브 댓글 불러오기 함수 동작 테스트
    31:32
  22. Gemini 답글 생성 & 업로드 함수 동작 테스트
    32:13
  23. 함수를 실행하는 버튼 만들기
    33:47
  24. 답글 초안을 바탕으로 자동 댓글 생성하기
    37:39
  25. 틀 고정으로 행 머리글 영역 고정하기
    39:55
  26. - 유튜브 자동 댓글 챗봇 만들기 -
  27. 매 n 분마다 실행되는 유튜브 자동 댓글 챗봇 설정
    40:16
  28. 유튜브 자동 댓글 챗봇 실행 내역 확인하기
    43:17
  29. 유튜브 채널 분위기에 맞는 챗봇으로 커스텀 하기
    43:42
  30. 유튜브 데이터 API에서 제공하는 기본 할당량 안내
    45:51
큰 화면으로 보기

예제파일 다운로드

오빠두엑셀의 강의 예제파일은 여러분을 위해 자유롭게 제공하고 있습니다.

  • [업무생산성] 유튜브 자동 댓글 챗봇 만들기
    보충파일

.

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

위캔두 회원이 되시면 매주 오빠두엑셀에서 진행하는 라이브강의 풀영상을 확인하실 수 있습니다.


관련 링크 모음

  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키-발급
    OpenAI 개발자센터에서 API키를 발급받습니다.
    오빠두Tip : OpenAI API키를 발급받는 전체 과정 및 엑셀+ChatGPT 추가기능 사용법은 아래 영상 강의를 확인하세요!👇
  2. 앱스크립트 편집기로 이동한 후, 속성에서 새로운 속성값을 추가합니다.
    Property : OPENAI_API_KEY
    Value : OpenAI API키

    구글시트-chatgpt-연동-api
    발급받은 API키를 구글시트 앱스크립트 속성에 추가합니다.
  3. Gemini 연동 마스터 코드 중, askGemini를 askChatGPT로 변경하면 ChatGPT API를 활용한 유튜브 챗봇이 완성됩니다. (두 군데를 모두 바꿔주세요!)
    변경 전 : var reply = askGemini(prompt);
    변경 후 : var reply = askChatGPT(prompt);

    chatgpt-챗봇-만들기-코드
    Gemini 연동 마스터 코드에서, askGemini 함수를 askChatGPT로 수정합니다.
4 4 투표
게시글평점
5 댓글
Inline Feedbacks
모든 댓글 보기
5
0
여러분의 생각을 댓글로 남겨주세요.x