├── license.md ├── readme.md └── src ├── bot ├── actions │ └── speak.js ├── functions │ ├── bindPage.js │ └── pages │ │ ├── pageActions.js │ │ ├── reddit.js │ │ └── twitter.js └── init.js ├── init.js └── manifest.json /license.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Tim Holman 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ## Buddy! 2 | 3 | ### Foreword 4 | In the winter of 2016, I began creating Buddy. Initially Buddy was built to be a friend when working from home, something or someone to talk back at me every now and then, and inject a little humor into my life. 5 | 6 | After some time, the ***surprise and delight*** of creating my own bot (if you could even call it that) went missing, and I opened Buddy up to some friends, with the promise that whatever code they write, will be running in my browser. 7 | 8 | Its probably best if you watch me talk about him, to really experience what he became all about. 9 | 10 | [![Video of me talking about buddy](https://s3.amazonaws.com/tholman.com/img/buddy-video-image.png)](https://youtu.be/RY8aCIfERHU?t=15m4s) 11 | 12 | He's a nasty piece of work. 13 | 14 | -- 15 | 16 | Understandably, there's some really weird code in his actual source, but due to overwhelming want, I'm open sourcing a really (read: really really) simple shell, of Buddy's existance. So people can create and play with their own "Buddy". 17 | 18 | A few things that the "video" version has that this doesn't are: style injecting, some strange system to give default "popups", something that definitely manages timings, so certain things can happen at certain hours of the day. __But hey, perhaps the fun part is building those pieces yourself!__ 19 | 20 | ### Installing 21 | 1. Clone repo, or download it 22 | 2. Go to `chrome://extensions/` (in chrome) and click "load unpacked extension" 23 | 3. Choose the `src` folder (it has the `manifest.json` file) 24 | 4. You should hear him speak, straight out of the bat. 25 | 26 | ### How it works! 27 | #### tldr: 28 | Buddy injects scripts onto your page. 29 | 30 | #### More weird version that makes little sense to myself 31 | 32 | A few things you need to know... for the most part, pages funcitonality runs out of `pages/pageName.js` ... so reddit will run out of reddit.js. 33 | 34 | The javascript for specific pages is injected by the `bindPage` file, which then requests page information from the `client` ... which is in `bot/init.js` 35 | 36 | A few other files exist. `actions.js` which is the client side functions like "speak", this maps to the `pageActions` speak function. 37 | 38 | ### Licence 39 | The MIT License (MIT) 40 | 41 | Copyright (C) 2016 ~ [Tim Holman](http://tholman.com) ~ timothy.w.holman@gmail.com 42 | 43 | -------------------------------------------------------------------------------- /src/bot/actions/speak.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Buddy Speaks! 3 | */ 4 | 5 | var languageOptions = { 6 | 'lang': 'en-GB', 7 | 'rate': 0.5, 8 | 'pitch': 2.0, 9 | } 10 | 11 | function speak(words) { 12 | chrome.tts.speak(words, languageOptions); 13 | } -------------------------------------------------------------------------------- /src/bot/functions/bindPage.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Binds to the pages that Buddy can take actions on. 3 | */ 4 | 5 | var pagePath = "./bot/functions/pages/" 6 | 7 | var initters = { 8 | "www.reddit.com": { 9 | script: "reddit.js" 10 | }, 11 | "twitter.com": { 12 | script: "twitter.js" 13 | } 14 | } 15 | 16 | function initPage(pageLocation) { 17 | 18 | if(initters[pageLocation]) { 19 | 20 | setSimilarTabs(pageLocation); 21 | 22 | chrome.tabs.executeScript(null, {file: pagePath + "pageActions.js"}); 23 | chrome.tabs.executeScript(null, {file: pagePath + initters[pageLocation].script}); 24 | 25 | // Inject Script 26 | setTimeout(function() { 27 | 28 | // Inject Settings 29 | chrome.tabs.executeScript({ code: "var PAGE = new page();"}); 30 | chrome.tabs.executeScript({ code: "PAGE.init(" + getSettings(pageLocation) + ");"}); 31 | 32 | // Set to be opened 33 | initters[pageLocation].opened = true 34 | }, 10); 35 | } 36 | } 37 | 38 | function getSettings(id) { 39 | return JSON.stringify(initters[id]); 40 | } 41 | 42 | /** 43 | * How many tabs with the same url are open? 44 | */ 45 | function setSimilarTabs(id) { 46 | chrome.tabs.query({}, function(tabs) { 47 | 48 | var similarTabsOpened = 0; 49 | for( var i = 0; i < tabs.length; i++ ) { 50 | if( tabs[i].url.indexOf(id) !== -1 ) { 51 | similarTabsOpened++; 52 | } 53 | } 54 | 55 | initters[id].similar = similarTabsOpened; 56 | }); 57 | } -------------------------------------------------------------------------------- /src/bot/functions/pages/pageActions.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Page Actions, 3 | * -- Match with /actions 4 | */ 5 | 6 | function speak( words ) { 7 | chrome.runtime.sendMessage({type: "speak", words: words}, function() {}); 8 | } -------------------------------------------------------------------------------- /src/bot/functions/pages/reddit.js: -------------------------------------------------------------------------------- 1 | function page() { 2 | 3 | var stats; 4 | 5 | this.init = function( settings ) { 6 | 7 | stats = settings; 8 | 9 | // First time visiting this site? 10 | if( !stats.opened ) { 11 | 12 | speak("REDDIT ISN'T WORK"); 13 | 14 | } else { 15 | 16 | // 2 reddit tabs open? 17 | if( stats.similar === 2 ) { 18 | speak("Twice the reddit, twice the fun!"); 19 | 20 | // 3 reddit tabs open 21 | } else if (stats.similar === 3) { 22 | speak("Seriously, get back to work"); 23 | 24 | // Back on reddit, after being on it once. 25 | } else { 26 | speak("Back at it again, I see!"); 27 | } 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /src/bot/functions/pages/twitter.js: -------------------------------------------------------------------------------- 1 | function page() { 2 | 3 | var stats; 4 | var tweetBox; 5 | var tweetReadbackTimeout; 6 | var tweetReadbackTimeoutLength = 1000; // ms 7 | 8 | this.init = function( settings ) { 9 | stats = settings; 10 | bindPage(); 11 | } 12 | 13 | function bindPage() { 14 | tweetBox = document.querySelector('.tweet-box'); 15 | tweetBox.addEventListener('keyup', debounceReadBackTweet); 16 | } 17 | 18 | function debounceReadBackTweet() { 19 | clearTimeout(tweetReadbackTimeout); 20 | tweetReadbackTimeout = setTimeout(function() { 21 | readBackTweet(); 22 | }, tweetReadbackTimeoutLength); 23 | } 24 | 25 | function readBackTweet() { 26 | var text = document.querySelector('.tweet-box > div').textContent; 27 | speak("Let me read that back to you: " + text + "."); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/bot/init.js: -------------------------------------------------------------------------------- 1 | (function buddy() { 2 | 3 | var pageData = {}; 4 | 5 | // Say something when you're turned on for the first time 6 | function init() { 7 | speak("Hi ho, hi ho, its off to work I go!") 8 | bindOtherFunctions(); 9 | listenToMessages(); 10 | } 11 | 12 | // Listen for commands bubbling up! 13 | function listenToMessages() { 14 | chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) { 15 | 16 | if( request.type ) { 17 | 18 | // A page is loaded! 19 | if(request.type === "pageLoad") { 20 | initPage(request.url); 21 | } 22 | 23 | // Something said speak! 24 | if(request.type === "speak") { 25 | speak(request.words); 26 | } 27 | } 28 | }); 29 | } 30 | 31 | // Do things that you can only do in the outer shell here 32 | function bindOtherFunctions() { 33 | 34 | // When a tab is open? 35 | chrome.tabs.onCreated.addListener(function(tabId, changeInfo, tab) { 36 | 37 | chrome.tabs.query({}, function(tabs) { 38 | 39 | // If there are more than 10 tabs! 40 | if( tabs.length > 10 ) { 41 | // Do something erratic! 42 | } 43 | }) 44 | }) 45 | } 46 | 47 | init(); 48 | })(); 49 | -------------------------------------------------------------------------------- /src/init.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Client side buddy. 3 | */ 4 | 5 | (function clientBuddy() { 6 | 7 | function init() { 8 | sendPage(); 9 | } 10 | 11 | function sendPage() { 12 | sendMessage({type: "pageLoad", url: window.location.host}) 13 | } 14 | 15 | function sendMessage(message) { 16 | chrome.runtime.sendMessage(message, function() {}); 17 | } 18 | 19 | // Self initting 20 | init(); 21 | })(); -------------------------------------------------------------------------------- /src/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "Buddy", 4 | "description": "Because working from home is never fun.", 5 | "version": "1.0", 6 | "icons": { 7 | // "48": "./assets/icons/48.png", 8 | // "128": "./assets/icons/128.png" 9 | }, 10 | "permissions": [ 11 | "", 12 | "activeTab", 13 | "tabs", 14 | "tts" 15 | ], 16 | "tts_engine": { 17 | "voices": [ 18 | { 19 | "voice_name": "Buddy", 20 | "lang": "en-US", 21 | "gender": "male", 22 | "event_types": ["start", "marker", "end"] 23 | } 24 | ] 25 | }, 26 | "content_scripts": [ 27 | { 28 | "matches": [""], 29 | "js": [ 30 | "./init.js" 31 | ] 32 | } 33 | ], 34 | "background": { 35 | "scripts": [ 36 | "./bot/actions/speak.js", 37 | "./bot/functions/bindPage.js", 38 | "./bot/init.js" 39 | ], 40 | "persistent": true 41 | } 42 | } --------------------------------------------------------------------------------