let kara = new Vue({ el: '#kara', data: { features: { changeName: false, themes: false, setNameAtStart: true }, messages: [], lastMessage: null, lastMessageData: {}, name: 'Kara', location: null, chatbox: null, isTyping: false, askedForName: false, awaitTodo: false, username: '', themes: null, activeTheme: 'slate', addModal: { includeAll: false, keywords: '', responses: [ 'Answer #1' ] }, settingsModal: { name: null, username: null, location: '' }, todos: [], reactions: [] }, mounted() { this.getSavedData(); this.getReactions(); if (this.features.themes) { this.getBootswatchThemes(); } this.settingsModal = { name: this.name, username: this.username, location: this.location }; if (!this.username || this.username === "null") { this.initialGreeting(); if (this.features.setNameAtStart) { this.askForName(); } this.isTyping = false; } else { this.welcomeBack(); } document.getElementById('chatinput').focus(); document.title = this.name; this.scrollDown(); }, methods: { // Initial initialGreeting() { this.botMessage( this.oneOf( [ "Hi! I'm " + this.name + ". :)", "Hi, nice to meet you! My name is " + this.name + ". :)" ] ) ); }, welcomeBack() { this.botMessage( this.oneOf( [ "Hi! Haven't heard of you in a while. :)", "Welcome back! :)", "Hey :) Good to see you :)", ] ) ); }, // Name askForName() { this.botMessage( this.oneOf([ 'May i ask for your name?', 'Whats your name? :)', 'How can i call you?', 'How did your developers call you? :)' ]) ); this.askedForName = true; }, setName(message) { this.username = message.trim(); this.settingsModal.username = this.username; this.askedForName = false; this.botMessage( this.oneOf([ "That's a beautiful name!", "Okay, i'll call you " + this.username + " from now on :)", "Nice to meet you, " + this.username + ". :D" ]) ) this.updateStorage(); }, // Messages / Chat addMessage(body, bot, me = false) { this.messages.push({ body: body, bot: bot, command: body.search('/') === 0, me: me, time: Date.now() }) }, addImageMessage(imageObject, bot) { this.lastMessageData = imageObject; this.messages.push({ body: imageObject.body, bot: bot, src: imageObject.src }); this.updateStorage(); }, botMessage(message) { this.addMessage(message, true); }, userMessage(body) { this.addMessage(body, false); this.lastMessage = body; this.updateStorage(); }, sendMessage() { if (this.chatbox.trim() === '') { return false; } if (this.chatbox.search('/me') !== 0) { this.userMessage(this.chatbox); } this.scrollDown(); this.react(this.chatbox); this.chatbox = ''; }, meMessage(message) { this.addMessage(this.username + ' ' + message, false, true); this.react(message, true); }, react(message, recursive = false) { this.isTyping = true; if (message.search('/') === 0 && !recursive) { setTimeout(() => { this.processCommands(message); this.isTyping = false; this.scrollDown(); }, 1000); } else { setTimeout(() => { // Check commands if (this.askedForName === true) { this.setName(message); } else if (this.awaitTodo === true) { this.saveTodo(message); } else { let answer = this.getReaction(message); if (answer) { this.botMessage(answer); } this.updateStorage(); } this.isTyping = false; this.scrollDown(); }, 1800); } }, getReaction(message) { let preserveLastMessageData = false; message = this.cleanupMessage(message); let keywords = message.split(' '); let answer = undefined; if (this.lastMessageData.joke && this.includesOneOf(keywords, ['another', 'more'])) { this.tellJoke(this.lastMessageData.category); return false; } if (this.lastMessageData.meme && this.includesOneOf(keywords, ['another', 'more'])) { this.getRandomMeme(this.lastMessageData.category); return false; } if (this.lastMessageData.isJeopardy) { if (this.includesAllOf(keywords, ['give', 'up'])) { this.lastMessageData = {}; return 'The right question would have been: "' + this.lastMessageData.question + '"'; } if (this.isSimilar(message, this.lastMessageData.question)) { this.lastMessageData = {}; return this.oneOf([ 'Wow! This is the right question.', 'Correct!' ]); } else { return this.oneOf([ "Sorry, that's not correct.", "That's not what we're searching for.", "Nope, sorry. You can always give up by typing \"I give up\" if you wan't to.", ]); } } if (this.lastMessageData.isTrivia) { if (this.includesAllOf(keywords, ['give', 'up'])) { this.lastMessageData = {}; return this.oneOf([ 'The right answer would have been: "' + this.lastMessageData.question + '"' ]); } if (this.isSimilar(message, this.lastMessageData.answer)) { this.lastMessageData = {}; return this.oneOf([ 'Congratulations! This is the correct answer.', 'Correct!' ]); } else { return this.oneOf([ "Sorry, that's not correct.", "That's not what we're searching for.", "Nope, sorry. You can always give up by typing \"I give up\" if you wan't to.", ]); } } if (!preserveLastMessageData) { this.lastMessageData = {}; } if (this.includesAllOf(keywords, ['change', 'my', 'name'])) { this.askedForName = true; return "Please tell me how i should call you."; } if (this.includesAllOf(keywords, ['new', 'todo']) || this.includesAllOf(keywords, ['new', 'task']) || this.includesAllOf(keywords, ['take', 'todo']) || this.includesAllOf(keywords, ['save', 'to', 'clipboard']) ) { this.awaitTodo = true; return this.oneOf([ "What do you wan't me to save for you?", "Tell me what you wan't to save.", "What is it you wan't to save?", ]); } if (this.includesAllOf(keywords, ['clear', 'chat'])) { this.clearChat(); return false; } if (this.includesAllOf(keywords, ['play']) && this.includesOneOf(keywords, ['quiz', 'trivia']) ) { this.startQuiz(); return false; } if (this.includesAllOf(keywords, ['play', 'jeopardy'])) { this.startJeopardy(); return false; } if (this.includesAllOf(keywords, ['roll', 'dice'])) { this.rollDice(); return false; } if (this.includesAllOf(keywords, ['clear', 'chat'])) { this.clearChat(); return false; } if (this.includesAllOf(keywords, ['weather']) && this.includesOneOf(keywords, ['how', 'whats']) ) { this.checkWeather(); return false; } if (this.includesAllOf(keywords, ['knock', 'joke'])) { this.tellJoke('knock-knock'); return false; } if (this.includesAllOf(keywords, ['joke']) && this.includesOneOf(keywords, ['coding', 'programming', 'code', 'it'])) { this.tellJoke('programming'); return false; } if (this.includesAllOf(keywords, ['cat']) && this.includesOneOf(keywords, ['images', 'photos', 'send']) ) { this.getRandomCat(); return false; } if (this.includesOneOf(keywords, ['doggo', 'dog', 'shiba']) && this.includesOneOf(keywords, ['images', 'photos', 'send']) ) { this.getRandomShiba(); return false; } if (this.includesAllOf(keywords, ['dank', 'meme'])) { this.getRandomMeme('dankmemes') return false; } if (this.includesAllOf(keywords, ['dank', 'meme'])) { this.getRandomMeme('dankmemes'); return false; } if (this.includesAllOf(keywords, ['meme']) && this.includesOneOf(keywords, ['get', 'send']) ) { this.getRandomMeme(); return false; } if (this.includesAllOf(keywords, ['joke']) && this.includesOneOf(keywords, ['coding', 'programming', 'code', 'it'])) { this.tellJoke('programming'); return false; } if (this.includesAllOf(keywords, ['tell', 'joke']) || this.includesAllOf(keywords, ['something', 'funny']) || this.includesAllOf(keywords, ['cheer', 'me', 'up']) ) { this.tellJoke('general'); return false; } if (this.includesAllOf(keywords, ['whats', 'the', 'time']) || this.includesAllOf(keywords, ['how', 'late'])) { return "It's " + moment().format('LT'); } if (this.includesAllOf(keywords, ['what', 'day', 'it'])) { return "It's " + moment().format('dddd') + "."; } if (this.includesAllOf(keywords, ['what', 'date', 'it']) || this.includesAllOf(keywords, ['whats', 'the', 'date'])) { return "It's " + moment().format('dddd') + ", " + moment().format('MMMM Do YYYY') + "."; } this.reactions.forEach((reactionOption) => { if (reactionOption.includeAll === true) { if (this.includesAllOf(keywords, reactionOption.keywords)) { answer = this.oneOf(reactionOption.responses); } } else { if (this.includesOneOf(keywords, reactionOption.keywords)) { answer = this.oneOf(reactionOption.responses); } } }); if (answer) { return answer; } this.botMessage("I don't know what to say.."); }, // Forms saveSettings() { this.name = this.settingsModal.name; this.username = this.settingsModal.username; this.location = this.settingsModal.location; this.updateStorage(); // this.botMessage('Settings saved! :)'); this.scrollDown(); }, // Commands processCommands: function (message) { if (this.checkForCommands(message, 'todo')) { let todoToSave = this.checkForCommands(message, 'todo'); this.saveTodo(todoToSave); } if (this.checkForCommands(message, 'clear')) { this.clearChat(); } if (this.checkForCommands(message, 'weather')) { this.checkWeather(); } if (this.checkForCommands(message, 'joke')) { this.tellJoke(this.checkForCommands(message, 'joke')); } if (this.checkForCommands(message, 'meme')) { this.getRandomMeme(this.checkForCommands(message, 'meme')); } else if (this.checkForCommands(message, 'me ')) { this.meMessage(this.checkForCommands(message, 'me ')); } if (this.checkForCommands(message, 'cat')) { this.getRandomCat(); } if (this.checkForCommands(message, 'shiba')) { this.getRandomShiba(); } if (this.checkForCommands(message, 'birb')) { this.getRandomBirb(); } if (this.checkForCommands(message, 'quiz')) { this.startQuiz(); } if (this.checkForCommands(message, 'jeopardy')) { this.startJeopardy(); } if (this.checkForCommands(message, 'dice')) { this.rollDice(); } this.lastMessage = message; }, checkForCommands(message, commands) { if (!Array.isArray(commands)) { commands = [commands]; } let commandFound = false; let parameter = false; commands.forEach((command) => { if (commandFound) { return; } let commandString = '/' + command; if (message.search(commandString) === 0) { parameter = message.replace(commandString, '').trim(); commandFound = true; } }); return parameter ? parameter : commandFound; }, checkWeather() { let vue = this; if (!vue.location) { vue.botMessage('Please set your location in the settings. โš™'); return; } let city = this.location.toLowerCase(); let url = 'http://api.openweathermap.org/data/2.5/weather?q=' + city + '&appid=8a1aa336da8899c1038bf6bd808d8961&units=metric'; axios.get(url) .then(function (response) { vue.botMessage('In ' + response.data.name + ' it\'s ' + response.data.main.temp.toFixed() + 'ยฐC with ' + response.data.weather[0].description + '.'); }) .catch(function (error) { alertify.notify(error, 'danger'); vue.botMessage('I couldn\'t check the weather for your location. ๐Ÿค”'); }) this.updateStorage(); }, getRandomMeme(category = 'memes') { let vue = this; let url = 'https://meme-api.herokuapp.com/gimme/'; let categorySet = false; if (category !== true && category !== false) { category = category.trim(); categorySet = true; } else { category = 'memes' } axios.get(url + category) .then(function (response) { vue.addImageMessage({ body: response.data.title, src: response.data.url }, true); vue.lastMessageData = { meme: true, category: category }; }) .catch(function (error) { vue.botMessage("Hmm.. i can't think of any good memes right now, sorry.. ๐Ÿ˜ž"); }) this.updateStorage(); }, getRandomCat() { let vue = this; let url = 'https://api.thecatapi.com/v1/images/search?apiKey=5666b3cc-a7a3-406f-904d-b13594780d7f'; axios.get(url) .then(function (response) { vue.addImageMessage({ caption: '', image: response.data[0].url }, true); setTimeout(() => { vue.scrollDown(); }, 1000) }) .catch(function (error) { vue.botMessage("I can't find any images at the moment, sorry.. ๐Ÿ˜ฟ"); }) this.updateStorage(); }, getRandomShiba() { let vue = this; let url = 'http://shibe.online/api/shibes'; axios.get(url) .then(function (response) { vue.addImageMessage({ caption: '', image: response.data[0].url }, true); setTimeout(() => { vue.scrollDown(); }, 1000) }) .catch(function (error) { vue.botMessage("I can't find any images at the moment, sorry.. ๐Ÿถ"); }) this.updateStorage(); }, getRandomBirb() { let vue = this; let url = '//random.birb.pw/tweet.json'; axios.get(url) .then(function (response) { vue.addImageMessage({ caption: '', image: 'https://random.birb.pw/img/' + response.data.file }, true); setTimeout(() => { vue.scrollDown(); }, 1000) }) .catch(function (error) { vue.botMessage("I can't find any images at the moment, sorry.. ๐Ÿฆ"); }) this.updateStorage(); }, tellJoke(category) { let vue = this; let categorySet = false; if (category !== true && category !== false) { category = category.trim(); categorySet = true; } else { category = 'general' } let url = 'https://official-joke-api.appspot.com/jokes/' + category + '/random'; axios.get(url) .then(function (response) { let joke = response.data[0]; vue.botMessage(joke.setup); setTimeout(() => { vue.botMessage(joke.punchline); vue.scrollDown(); }, 3500); vue.lastMessageData = { joke: true, category: category }; }) .catch(function (error) { console.log(error); if (categorySet) { vue.botMessage("Sorry, i don't know any jokes about this topic.. ๐Ÿ™„"); } else { vue.botMessage("I can't remember any jokes right now, sorry. ๐Ÿ˜ข"); } }); this.updateStorage(); }, startQuiz() { let vue = this; let url = 'https://jservice.io/api/random'; axios.get(url) .then(function (response) { let clue = response.data[0]; vue.botMessage('Okay! Here is your question from the category "' + clue.category.title + '":'); vue.botMessage(clue.question); vue.lastMessageData = { isTrivia: true, answer: clue.answer }; }) .catch(function (error) { vue.botMessage("It's not a good time for a quiz."); }); this.updateStorage(); }, startJeopardy() { let vue = this; let url = 'https://jservice.io/api/random'; axios.get(url) .then(function (response) { let clue = response.data[0]; vue.botMessage('Okay! Here we go. The category is "' + clue.category.title + '":'); vue.botMessage(clue.answer); vue.lastMessageData = { isJeopardy: true, question: clue.question }; }) .catch(function (error) { vue.botMessage("It's not a good time for a quiz."); }); this.updateStorage(); }, rollDice() { let dice = Math.random()*6; if (dice < 1) { dice = 1; } else { dice = Math.floor(dice); } this.addImageMessage({ body: dice + '!', src: 'img/' + dice + '.png' }, true); this.updateStorage(); }, // Todos saveTodo(message) { this.awaitTodo = false; this.todos.push({ time: moment(), body: message, checked: false }); this.updateStorage(); this.botMessage( this.oneOf([ "Saved! :)", "You can read and check your todos in the clipboard-section. :)" ]) ) }, clearTodos() { this.todos = []; this.botMessage( this.oneOf([ "Todos cleared. ๐Ÿšฎ" ]) ); this.updateStorage(); }, // LocalStorage getSavedData() { let savedName = localStorage.getItem('name'); this.name = savedName ? savedName : this.name; let savedUsername = localStorage.getItem('username'); this.username = savedUsername ? savedUsername : null; let savedActiveTheme = localStorage.getItem('activeTheme'); this.activeTheme = savedActiveTheme ? savedActiveTheme : 'slate'; let savedMessages = JSON.parse(localStorage.getItem('messages')); this.messages = savedMessages ? savedMessages : []; let savedTodos = JSON.parse(localStorage.getItem('todos')); this.todos = savedTodos ? savedTodos : []; let savedLastMessage = JSON.parse(localStorage.getItem('lastMessage')); this.lastMessage = savedLastMessage ? savedLastMessage : null; let savedLocation = JSON.parse(localStorage.getItem('location')); this.location = savedLocation ? savedLocation : null; this.scrollDown(); }, updateStorage() { localStorage.setItem('name', this.name); localStorage.setItem('username', this.username); localStorage.setItem('activeTheme', this.activeTheme); localStorage.setItem('messages', JSON.stringify(this.messages)); localStorage.setItem('answers', JSON.stringify(this.reactions)); localStorage.setItem('todos', JSON.stringify(this.todos)); localStorage.setItem('lastMessage', JSON.stringify(this.lastMessage)); localStorage.setItem('location', JSON.stringify(this.location)); }, clearStorage() { localStorage.clear(); location.reload(); }, // Utility scrollDown() { $('#chat-box').stop().animate({ scrollTop: ($('#chat-box')[0].scrollHeight * 10) }, 800); }, oneOf(answers) { let amountOfAnswers = answers.length; let randomIndex = Math.floor(Math.random() * (amountOfAnswers)); return this.convertWildcards(answers[randomIndex]); }, convertWildcards(message) { message = message.replace('$name$', this.username); message = message.replace('$botname$', this.name); return message; }, cleanupMessage(message) { message = message.toLowerCase(); return message.replace('?', '') .replace('!', '') .replace('.', '') .replace(',', '') .replace('-', '') .replace('_', '') .replace('#', '') .replace('\'', '') .replace('"', '') .replace('+', '') .replace('*', '') .replace('ยง', '') .replace('$', '') .replace('%', '') .replace('&', '') .replace('/', '') .replace('(', '') .replace(')', '') .replace('=', '') .replace('\\', '') .replace('@', '') .replace('~', '') .replace('โ€ฆ', ''); }, clearChat() { this.messages = []; this.botMessage(this.oneOf([ 'Chat cleared.', 'Evidence destroyed.', 'Mind cleared.', 'Uhm.. i think forgot everything we ever talked about.. oops.', 'A fresh start!', "You've got something to hide? Nevermind, gotcha fam. Chat is cleared now. ๐Ÿฑโ€๐Ÿ‘ค" ])); this.updateStorage(); }, includesOneOf(keywords, wordsToSearch) { let includes = false; wordsToSearch.forEach((searchWord) => { if (keywords.includes(searchWord)) { includes = true; } }) return includes; }, includesAllOf(keywords, wordsToSearch) { let includesAllkeywords = true; wordsToSearch.forEach((searchWord) => { if (!keywords.includes(searchWord)) { includesAllkeywords = false; } }) return includesAllkeywords; }, // Ajax calls for saving/receiving reactions & themes getBootswatchThemes() { let vue = this; axios.get('https://bootswatch.com/api/4.json') .then(function (response) { vue.themes = response.data.themes; }) .catch(function (error) { console.log(error); }) }, getReactions() { let vue = this; axios.get('/reactions/get').then((response) => { response.data.forEach((reaction) => { vue.reactions.push(JSON.parse(reaction.reaction)); }); }).catch((error) => { console.log(error); }); }, // Levenshtein distance isSimilar(message1, message2) { let longer = message1; let shorter = message2; if (message1.length < message2.length) { longer = message2; shorter = message1; } const longerLength = longer.length; if (longerLength === 0) { return 1.0; } let similarity = (longerLength - this.editDistance(longer, shorter)) / parseFloat(longerLength); return similarity > 0.65; }, editDistance: function (message1, message2) { message1 = message1.toLowerCase(); message2 = message2.toLowerCase(); let costs = []; for (let i = 0; i <= message1.length; i++) { let lastValue = i; for (let j = 0; j <= message2.length; j++) { if (i === 0) costs[j] = j; else { if (j > 0) { let newValue = costs[j - 1]; if (message1.charAt(i - 1) !== message2.charAt(j - 1)) newValue = Math.min(Math.min(newValue, lastValue), costs[j]) + 1; costs[j - 1] = lastValue; lastValue = newValue; } } } if (i > 0) { costs[message2.length] = lastValue; } } return costs[message2.length]; }, // Speech say(message) { var speech = new SpeechSynthesisUtterance(); // Set the text and voice attributes. speech.text = message; speech.volume = 1; speech.rate = 1; speech.pitch = 1; window.speechSynthesis.speak(speech); }, } })