|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | const MEBO_API_KEY = "5c276d9b-f99b-4e3f-b9ed-2653e7267cec19103d7a0b7358"; | 
					
						
						|  | const MEBO_AGENT_ID = "e63015b0-32b4-484b-b103-b91815f309f519100f6394b202"; | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | const VOICE_VOX_API_URL = "https://kenken999-voicebox.hf.space"; | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | const YOUTUBE_VIDEO_ID = ''; | 
					
						
						|  |  | 
					
						
						|  | const YOUTUBE_DATA_API_KEY = 'AIzaSyC1ALJ9naZQXZs-FwrxrPz9D4gkE1OOkLo'; | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | const INTERVAL_MILL_SECONDS_RETRIEVING_COMMENTS = 10000; | 
					
						
						|  |  | 
					
						
						|  | const INTERVAL_MILL_SECONDS_HANDLING_COMMENTS = 3000; | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | const VOICEVOX_SPEAKER_ID = '10'; | 
					
						
						|  |  | 
					
						
						|  | var audio = new Audio(); | 
					
						
						|  |  | 
					
						
						|  | var liveCommentQueues = []; | 
					
						
						|  |  | 
					
						
						|  | var responsedLiveComments = []; | 
					
						
						|  |  | 
					
						
						|  | var isThinking = false; | 
					
						
						|  |  | 
					
						
						|  | var LIVE_OWNER_ID = createUuid(); | 
					
						
						|  |  | 
					
						
						|  | var ngwords = [] | 
					
						
						|  |  | 
					
						
						|  | var nextPageToken = ""; | 
					
						
						|  |  | 
					
						
						|  | var isLiveCommentsRetrieveStarted = true; | 
					
						
						|  |  | 
					
						
						|  | const getLiveChatId = async () => { | 
					
						
						|  | const response = await fetch('https://youtube.googleapis.com/youtube/v3/videos?part=liveStreamingDetails&id=' + YOUTUBE_VIDEO_ID + '&key=' + YOUTUBE_DATA_API_KEY, { | 
					
						
						|  | method: 'get', | 
					
						
						|  | headers: { | 
					
						
						|  | 'Content-Type': 'application/json' | 
					
						
						|  | } | 
					
						
						|  | }) | 
					
						
						|  | const json = await response.json(); | 
					
						
						|  | if (json.items.length == 0) { | 
					
						
						|  | return ""; | 
					
						
						|  | } | 
					
						
						|  | return json.items[0].liveStreamingDetails.activeLiveChatId | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | const getLiveComments = async (activeLiveChatId) => { | 
					
						
						|  | const response = await fetch('https://youtube.googleapis.com/youtube/v3/liveChat/messages?liveChatId=' + activeLiveChatId + '&part=authorDetails%2Csnippet&key=' + YOUTUBE_DATA_API_KEY, { | 
					
						
						|  | method: 'get', | 
					
						
						|  | headers: { | 
					
						
						|  | 'Content-Type': 'application/json' | 
					
						
						|  | } | 
					
						
						|  | }) | 
					
						
						|  | const json = await response.json(); | 
					
						
						|  | const items = json.items; | 
					
						
						|  | return json.items[0].liveStreamingDetails.activeLiveChatId | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | const startTyping = (param) => { | 
					
						
						|  | let el = document.querySelector(param.el); | 
					
						
						|  | el.textContent = ""; | 
					
						
						|  | let speed = param.speed; | 
					
						
						|  | let string = param.string.split(""); | 
					
						
						|  | string.forEach((char, index) => { | 
					
						
						|  | setTimeout(() => { | 
					
						
						|  | el.textContent += char; | 
					
						
						|  | }, speed * index); | 
					
						
						|  | }); | 
					
						
						|  | }; | 
					
						
						|  |  | 
					
						
						|  | async function getMeboResponse(utterance, username, uid, apikey, agentId) { | 
					
						
						|  | var requestBody = { | 
					
						
						|  | 'api_key': apikey, | 
					
						
						|  | 'agent_id': agentId, | 
					
						
						|  | 'utterance': utterance, | 
					
						
						|  | 'username': username, | 
					
						
						|  | 'uid': uid, | 
					
						
						|  | } | 
					
						
						|  | const response = await fetch('https://api-mebo.dev/api', { | 
					
						
						|  | method: 'post', | 
					
						
						|  | headers: { | 
					
						
						|  | 'Content-Type': 'application/json' | 
					
						
						|  | }, | 
					
						
						|  | body: JSON.stringify(requestBody) | 
					
						
						|  | }) | 
					
						
						|  | const content = await response.json(); | 
					
						
						|  | return content.bestResponse.utterance; | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | const playVoice = async (inputText) => { | 
					
						
						|  | audio.pause(); | 
					
						
						|  | audio.currentTime = 0; | 
					
						
						|  | const ttsQuery = await fetch(VOICE_VOX_API_URL + '/audio_query?speaker=' + VOICEVOX_SPEAKER_ID + '&text=' + encodeURI(inputText), { | 
					
						
						|  | method: 'post', | 
					
						
						|  | headers: { | 
					
						
						|  | 'Content-Type': 'application/json' | 
					
						
						|  | } | 
					
						
						|  | }) | 
					
						
						|  | if (!ttsQuery) return; | 
					
						
						|  | const queryJson = await ttsQuery.json(); | 
					
						
						|  | const response = await fetch(VOICE_VOX_API_URL + '/synthesis?speaker=' + VOICEVOX_SPEAKER_ID + '&speedScale=2', { | 
					
						
						|  | method: 'post', | 
					
						
						|  | headers: { | 
					
						
						|  | 'Content-Type': 'application/json' | 
					
						
						|  | }, | 
					
						
						|  | body: JSON.stringify(queryJson) | 
					
						
						|  | }) | 
					
						
						|  | if (!response) return; | 
					
						
						|  | const blob = await response.blob(); | 
					
						
						|  | const audioSourceURL = window.URL || window.webkitURL | 
					
						
						|  | audio = new Audio(audioSourceURL.createObjectURL(blob)); | 
					
						
						|  | audio.onended = function () { | 
					
						
						|  | setTimeout(handleNewLiveCommentIfNeeded, 1000); | 
					
						
						|  | } | 
					
						
						|  | audio.play(); | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | const visibleAIResponse = () => { | 
					
						
						|  | let target = document.getElementById('aiResponse'); | 
					
						
						|  | target.style.display = "" | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | const invisibleAIResponse = () => { | 
					
						
						|  | let target = document.getElementById('aiResponse'); | 
					
						
						|  | target.style.display = "none" | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | const handleLiveComment = async (comment, username) => { | 
					
						
						|  | isThinking = true; | 
					
						
						|  | visibleAIResponse(); | 
					
						
						|  | startTyping({ | 
					
						
						|  | el: "#aiResponseUtterance", | 
					
						
						|  | string: "Thinking................", | 
					
						
						|  | speed: 50 | 
					
						
						|  | }); | 
					
						
						|  | let userCommentElement = document.querySelector("#userComment"); | 
					
						
						|  | userCommentElement.textContent = username + ": " + comment; | 
					
						
						|  | const response = await getMeboResponse(comment, username, LIVE_OWNER_ID, MEBO_API_KEY, MEBO_AGENT_ID); | 
					
						
						|  | isThinking = false; | 
					
						
						|  | if (username == "") { | 
					
						
						|  | await playVoice(response, true, response, false); | 
					
						
						|  | } else { | 
					
						
						|  | await playVoice(username + "さん、" + response, true, response, false); | 
					
						
						|  | } | 
					
						
						|  | startTyping({ | 
					
						
						|  | el: "#aiResponseUtterance", | 
					
						
						|  | string: response, | 
					
						
						|  | speed: 50 | 
					
						
						|  | }); | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | const retrieveYouTubeLiveComments = (activeLiveChatId) => { | 
					
						
						|  | var url = "https://youtube.googleapis.com/youtube/v3/liveChat/messages?liveChatId=" + activeLiveChatId + '&part=authorDetails%2Csnippet&key=' + YOUTUBE_DATA_API_KEY | 
					
						
						|  | if (nextPageToken !== "") { | 
					
						
						|  | url = url + "&pageToken=" + nextPageToken | 
					
						
						|  | } | 
					
						
						|  | fetch(url, { | 
					
						
						|  | method: 'get', | 
					
						
						|  | headers: { | 
					
						
						|  | 'Content-Type': 'application/json' | 
					
						
						|  | } | 
					
						
						|  | }).then( | 
					
						
						|  | (response) => { | 
					
						
						|  | return response.json(); | 
					
						
						|  | } | 
					
						
						|  | ).then( | 
					
						
						|  | (json) => { | 
					
						
						|  | const items = json.items; | 
					
						
						|  | let index = 0 | 
					
						
						|  | nextPageToken = json.nextPageToken; | 
					
						
						|  | items?.forEach( | 
					
						
						|  | (item) => { | 
					
						
						|  | try { | 
					
						
						|  | const username = item.authorDetails.displayName; | 
					
						
						|  | let message = "" | 
					
						
						|  | if (item.snippet.textMessageDetails != undefined) { | 
					
						
						|  |  | 
					
						
						|  | message = item.snippet.textMessageDetails.messageText; | 
					
						
						|  | } | 
					
						
						|  | if (item.snippet.superChatDetails != undefined) { | 
					
						
						|  |  | 
					
						
						|  | message = item.snippet.superChatDetails.userComment; | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | const additionalComment = username + ":::" + message; | 
					
						
						|  | if (!liveCommentQueues.includes(additionalComment) && message != "") { | 
					
						
						|  | let isNg = false | 
					
						
						|  | ngwords.forEach( | 
					
						
						|  | (ngWord) => { | 
					
						
						|  | if (additionalComment.includes(ngWord)) { | 
					
						
						|  | isNg = true | 
					
						
						|  | } | 
					
						
						|  | } | 
					
						
						|  | ) | 
					
						
						|  | if (!isNg) { | 
					
						
						|  | if (isLiveCommentsRetrieveStarted) { | 
					
						
						|  | liveCommentQueues.push(additionalComment) | 
					
						
						|  | } else { | 
					
						
						|  | responsedLiveComments.push(additionalComment); | 
					
						
						|  | } | 
					
						
						|  | } | 
					
						
						|  | } | 
					
						
						|  | } catch { | 
					
						
						|  |  | 
					
						
						|  | } | 
					
						
						|  | index = index + 1 | 
					
						
						|  | } | 
					
						
						|  | ) | 
					
						
						|  | } | 
					
						
						|  | ).finally( | 
					
						
						|  | () => { | 
					
						
						|  | setTimeout(retrieveYouTubeLiveComments, INTERVAL_MILL_SECONDS_RETRIEVING_COMMENTS, activeLiveChatId); | 
					
						
						|  | } | 
					
						
						|  | ) | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | const getNextComment = () => { | 
					
						
						|  | let nextComment = "" | 
					
						
						|  | let nextRaw = "" | 
					
						
						|  | for (let index in liveCommentQueues) { | 
					
						
						|  | if (!responsedLiveComments.includes(liveCommentQueues[index])) { | 
					
						
						|  | const arr = liveCommentQueues[index].split(":::") | 
					
						
						|  | if (arr.length > 1) { | 
					
						
						|  | nextComment = arr[0] + "さんから、「" + arr[1] + "」というコメントが届いているよ。" | 
					
						
						|  | nextRaw = arr[1] | 
					
						
						|  | break; | 
					
						
						|  | } | 
					
						
						|  | } | 
					
						
						|  | } | 
					
						
						|  | return [nextComment, nextRaw]; | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | const handleNewLiveCommentIfNeeded = async () => { | 
					
						
						|  |  | 
					
						
						|  | if (liveCommentQueues.length == 0) { | 
					
						
						|  |  | 
					
						
						|  | setTimeout(handleNewLiveCommentIfNeeded, INTERVAL_MILL_SECONDS_HANDLING_COMMENTS); | 
					
						
						|  | return; | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | if (isThinking) { | 
					
						
						|  |  | 
					
						
						|  | setTimeout(handleNewLiveCommentIfNeeded, INTERVAL_MILL_SECONDS_HANDLING_COMMENTS); | 
					
						
						|  | return; | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | if (!audio.ended) { | 
					
						
						|  |  | 
					
						
						|  | setTimeout(handleNewLiveCommentIfNeeded, INTERVAL_MILL_SECONDS_HANDLING_COMMENTS); | 
					
						
						|  | return; | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | for (let index in liveCommentQueues) { | 
					
						
						|  | if (!responsedLiveComments.includes(liveCommentQueues[index])) { | 
					
						
						|  | const arr = liveCommentQueues[index].split(":::") | 
					
						
						|  | if (arr.length > 1) { | 
					
						
						|  | responsedLiveComments.push(liveCommentQueues[index]); | 
					
						
						|  | isThinking = true; | 
					
						
						|  | await handleLiveComment(arr[1], arr[0]); | 
					
						
						|  | break; | 
					
						
						|  | } | 
					
						
						|  | } | 
					
						
						|  | } | 
					
						
						|  | setTimeout(handleNewLiveCommentIfNeeded, 5000); | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | const onClickSend = () => { | 
					
						
						|  |  | 
					
						
						|  | let utterance = document.querySelector("#utterance"); | 
					
						
						|  |  | 
					
						
						|  | miiboAvatar.autoRecognizeMessage(utterance.value) | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | utterance.value = ""; | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  |  | 
					
						
						|  | const startLive = () => { | 
					
						
						|  |  | 
					
						
						|  | let startLiveButton = document.querySelector("#startLiveButton"); | 
					
						
						|  | startLiveButton.style.display = "none"; | 
					
						
						|  | let submitForm = document.querySelector("#submit_form"); | 
					
						
						|  | submitForm.style.display = "none"; | 
					
						
						|  | getLiveChatId().then( | 
					
						
						|  | (id) => { | 
					
						
						|  | retrieveYouTubeLiveComments(id); | 
					
						
						|  | } | 
					
						
						|  | ) | 
					
						
						|  |  | 
					
						
						|  | handleLiveComment('', ''); | 
					
						
						|  | blink(); | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | const img = ["charas.png", "chara_blinkings.png"]; | 
					
						
						|  | var isBlinking = false; | 
					
						
						|  |  | 
					
						
						|  | function blink() { | 
					
						
						|  | if (isBlinking) { | 
					
						
						|  | isBlinking = false; | 
					
						
						|  | document.getElementById("charaImg").src = "img[1]"; | 
					
						
						|  | setTimeout(blink, 100); | 
					
						
						|  | } else { | 
					
						
						|  | isBlinking = true; | 
					
						
						|  | document.getElementById("charaImg").src = img[0]; | 
					
						
						|  | setTimeout(blink, 3500); | 
					
						
						|  | } | 
					
						
						|  | } | 
					
						
						|  |  | 
					
						
						|  | function createUuid() { | 
					
						
						|  | return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (a) { | 
					
						
						|  | let r = (new Date().getTime() + Math.random() * 16) % 16 | 0, v = a == 'x' ? r : (r & 0x3 | 0x8); | 
					
						
						|  | return v.toString(16); | 
					
						
						|  | }); | 
					
						
						|  | } |