├── .gitignore ├── LICENSE.md ├── README.md ├── bot.js ├── health.js ├── jerkeygit └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | /settings.js 2 | /sudobot.log 3 | 4 | ### BELOW THIS IS BOILERPLATE STUFF 5 | 6 | # Logs 7 | logs 8 | *.log 9 | npm-debug.log* 10 | yarn-debug.log* 11 | yarn-error.log* 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # Typescript v1 declaration files 45 | typings/ 46 | 47 | # Optional npm cache directory 48 | .npm 49 | 50 | # Optional eslint cache 51 | .eslintcache 52 | 53 | # Optional REPL history 54 | .node_repl_history 55 | 56 | # Output of 'npm pack' 57 | sudobot-*.tgz 58 | 59 | # Yarn Integrity file 60 | .yarn-integrity 61 | 62 | # dotenv environment variables file 63 | .env 64 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015-2017 by the authors. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # sudobot 2 | 3 | [Sudo Room](https://sudoroom.org)'s IRC robot, the canonical instance of 4 | which lives in [our IRC channel](https://sudoroom.org/chat/). 5 | 6 | ## Getting Started 7 | 8 | These instructions should get you a copy of the project up and running 9 | on your local machine for development and testing purposes. See deployment 10 | for notes on how to deploy the project on a live system. 11 | 12 | ### Prerequisites 13 | 14 | sudoboot is known to run in production on the nodejs provided by [chris 15 | lea](https://launchpad.net/~chris-lea)'s 16 | [node.js PPA](https://launchpad.net/~chris-lea/+archive/ubuntu/node.js) 17 | (specifically version `0.10.37-1chl1~trusty1`). However, you might not be 18 | running Ubuntu, so an alternative way to get the right node.js runtime 19 | is suggested: using [Node Version Manager](http://nvm.sh). 20 | 21 | First, install nvm as described in its documentation. Then, install node 22 | version 0.10.37 with the following command: 23 | 24 | ``` 25 | nvm install 0.10.37 26 | ``` 27 | 28 | Once this is done, start using this newly installed node: 29 | 30 | ``` 31 | nvm use 0.10.37 32 | ``` 33 | 34 | Next, install the bot's dependencies. Be sure you are in the package's 35 | root directory (wherever this readme file is located) and do the following: 36 | 37 | ``` 38 | npm install 39 | ``` 40 | 41 | At this point, you should probably be able to run the bot, like so: 42 | 43 | ``` 44 | node bot.js 45 | ``` 46 | 47 | If this doesn't work, you may have found a bug. In that case, please 48 | [see our issues](https://github.com/sudoroom/sudobot/issues) and open 49 | a new one if the bug you've found is not already documented. 50 | 51 | ## Deployment 52 | 53 | It seems to be deployed using something called psy. 54 | 55 | ## Contributing 56 | 57 | Please read [CONTRIBUTING.md](CONTRIBUTING.md) for details on our code 58 | of conduct, and the process for submitting pull requests to us. 59 | 60 | ## Versioning 61 | 62 | ¯\\\_(ツ)\_/¯ 63 | 64 | ## Authors 65 | 66 | * **substack** - *most of the commits through 2015* 67 | * **jerkey** - *some door and speech related code* 68 | * **deilann**, **morganrallen**, and **Juul** - *various contributions* 69 | * **karissa** - *almost all maintenance in 2017* 70 | * **rcsheets** - *docs, etc* 71 | 72 | ## License 73 | 74 | This project is licensed under the MIT license - see the 75 | [LICENSE.md](LICENSE.md) file for details. 76 | 77 | -------------------------------------------------------------------------------- /bot.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const console = require('console'); 4 | var set = require(__dirname + '/settings.js'); 5 | var Client = require('irc').Client; 6 | var client = new Client('irc.libera.chat', 'sudobot', { 7 | channels: [ '#sudoroom' ], 8 | nick: 'sudobot', 9 | userName: 'sudobot', 10 | realName: 'sudobot', 11 | password: set.password, 12 | port: 7000, 13 | debug: true, 14 | autoConnect: false, 15 | secure: true, 16 | selfSigned: true, 17 | certExpired: true, 18 | sasl: true, 19 | stripColors: true, 20 | }); 21 | var minimist = require('minimist'); 22 | 23 | var split = require('split2'); 24 | var through = require('through2'); 25 | var spawn = require('child_process').spawn; 26 | 27 | var failing = {}; 28 | var timeout = null; 29 | var last = {}; 30 | 31 | function say (msg) { 32 | client.say('#sudoroom', msg); 33 | } 34 | 35 | client.addListener('message#sudoroom', function (from, message) { 36 | //if (/^....ATTN:.This.channel.has.moved\s+/.test(message)) { 37 | //if (/^.fter.the.acquisition.by.Private\s+/.test(message)) { 38 | if (/^.hristel.just.posted.this..denial..on.the.freenode\s+/.test(message)) { 39 | // this does not work :) say('/kick ' + from); 40 | client.send('KICK', '#sudoroom', from); 41 | } 42 | if (/^!say\s+/.test(message)) { 43 | var argv = minimist(message.split(/\s+/).slice(1)); 44 | var args = []; 45 | // amplitude 46 | if (argv.a) { 47 | var amp = parseInt(argv.a); 48 | if(amp) args.push('-a', amp); 49 | } 50 | 51 | // Indicate capital letters with: 1=sound, 2=the word "capitals", higher values = a pitch increase (try -k20). 52 | var k = 20; 53 | if (argv.k) { 54 | k = parseInt(argv.k) || k; 55 | } 56 | 57 | args.push('-k', k); 58 | 59 | var voice = "en+f3"; 60 | if (argv.v) { 61 | var m = args.v.match(/[\w\+_-]+/); 62 | if(m) 63 | voice = m.pop(); 64 | } 65 | 66 | var s = 87; 67 | if (argv.s) { 68 | s = parseInt(args.s) || s; 69 | } 70 | 71 | args.push('-s', s); 72 | 73 | if (argv.p) { 74 | var p = parseInt(args.p); 75 | if(p) 76 | args.push('-p', p); 77 | } 78 | 79 | args.unshift('pi@100.64.64.27', 'bin/mainscreenturnon; espeak '); 80 | args.push('-w /tmp/out.wav --stdin && aplay /tmp/out.wav'); 81 | var ps = spawn('ssh', args); 82 | ps.stdin.end(argv._.join(' ')); 83 | ps.stderr.pipe(process.stdout); ps.stdout.pipe(process.stdout) 84 | } else if(/^!ssay\s+/.test(message)) { 85 | var argv = minimist(message.split(/\s+/).slice(1)); 86 | 87 | var ps = spawn('aoss', ['flite', '-voice', '/opt/voices/cmu_us_awb.flitevox']); 88 | ps.stdin.end(argv._.join(' ')); 89 | } else if(/^!fsay\s+/.test(message)) { 90 | var argv = minimist(message.split(/\s+/).slice(1)); 91 | 92 | var ps = spawn('aoss', ['flite', '-voice', '/opt/voices/cmu_us_slt.flitevox']); 93 | ps.stdin.end(argv._.join(' ')); 94 | } 95 | if (/sudoroom_BigTV/.test(message)) { 96 | spawn('ssh', ['pi@100.64.64.27', 'bin/mainscreenturnon']); 97 | } 98 | }); 99 | 100 | function currentHour() { 101 | var timeNow = new Date(); 102 | var hour = timeNow.getHours(); 103 | return hour; 104 | } 105 | 106 | var prev = { ssh: null, health: null }; 107 | 108 | client.connect(5, function () { ssh(); }) 109 | 110 | function ssh () { 111 | var ps = spawn('ssh', [ 'root@100.64.64.11', 'psy log doorjam' ]); 112 | ps.on('exit', function () { 113 | clearTimeout(timeout); 114 | timeout = null; 115 | setTimeout(ssh, 300000); 116 | failing.ssh = true; 117 | }); 118 | ps.stderr.pipe(process.stderr); 119 | ps.stdout.pipe(process.stdout); 120 | ps.stdout.pipe(split()).pipe(through(write)); 121 | ps.stderr.pipe(split()).pipe(through(write)); 122 | 123 | function write (buf, enc, next) { 124 | if (failing.ssh && !timeout) { 125 | timeout = setTimeout(function () { 126 | say('DOOR EVENT: omnidoor ssh connection established'); 127 | failing.ssh = false; 128 | timeout = null; 129 | }, 5000) 130 | } 131 | 132 | var line = buf.toString(); 133 | if (line.indexOf('Could not resolve') > -1) { 134 | say('DOOR EVENT: omnidoor ssh connection FAILED!!!!'); 135 | } 136 | var m; 137 | if (/^#ANNOUNCE/.test(line) && (m = /"([^"]+)"/.exec(line))) { 138 | failing.logs = false; 139 | if (!last.swipe || Date.now() - last.swipe > 1000*15) { 140 | say('DOOR EVENT: ' + m[1] + ' swiped into the building'); 141 | last.swipe = Date.now(); 142 | } 143 | } 144 | else if (/^Access granted/i.test(line) && !/^#ANNOUNCE/.test(prev)) { 145 | failing.logs = false; 146 | if (!last.swipe || Date.now() - last.swipe > 1000*15) { 147 | if (currentHour() < 11) { 148 | say('!say DOOR EVENT: somebody swiped into the building'); 149 | } 150 | else { 151 | say('DOOR EVENT: somebody swiped into the building'); 152 | } 153 | last.swipe = Date.now(); 154 | } 155 | } 156 | else if (/^Everything initialized and ready/i.test(line)) { 157 | failing.logs = false; 158 | failing.serial = false; 159 | failing.magstripe = false; 160 | say('DOOR EVENT: READY'); 161 | } 162 | else if (/^(Error:|SERIAL ERROR)/i.test(line)) { 163 | failing.logs = false; 164 | if (!failing.serial) { 165 | say('DOOR EVENT: ' + line); 166 | } 167 | failing.serial = true; 168 | } 169 | else if (/^(Error:|MAGSTRIPE ERROR)/i.test(line)) { 170 | failing.logs = false; 171 | if (!failing.magstripe) { 172 | say('DOOR EVENT: ' + line); 173 | } 174 | failing.magstripe = true; 175 | } 176 | else if (/^Can not find log files,/.test(line)) { 177 | if (!failing.logs) { 178 | say('DOOR EVENT: log read failure'); 179 | } 180 | failing.logs = true; 181 | } 182 | else if (/^health/.test(line)) { 183 | try { var health = JSON.parse(line.replace(/^health\s+/, '')) } 184 | catch (err) { return next() } 185 | if ((isNaN(health.voltage) || health.voltage < 13.0) 186 | && health.sinceMotor > 1000*10 187 | && health.voltage >= 0 188 | && (!last.criticalVoltage 189 | || Date.now() - last.criticalVoltage >= 1000*60*10)) { 190 | say('CRITICAL ARDUINO VOLTAGE: ' + health.voltage); 191 | last.criticalVoltage = Date.now(); 192 | } 193 | if (health.sinceVoltage > 1000 * 60 * 2 194 | && Date.now() - last.sinceVoltage >= 1000*60*10) { 195 | var mins = Math.floor(sinceVoltage / 1000 / 60); 196 | say('NO ARDUINO VOLTAGE REPORTED IN ' + mins + ' MINUTES'); 197 | last.sinceVoltage = Date.now(); 198 | } 199 | } 200 | next(); 201 | } 202 | } 203 | -------------------------------------------------------------------------------- /health.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var exec = require('child_process').exec; 3 | 4 | setInterval(check, 1000*60); 5 | check(); 6 | 7 | function check () { 8 | exec('acpi', function (err, stdout, stderr) { 9 | var m = /^Battery \d+: (Charging|Discharging|Full), (\d+)%/.exec(stdout); 10 | var msg = {}; 11 | if (m) { 12 | msg.charging = m[1] !== 'Discharging'; 13 | msg.percent = Number(m[2]); 14 | } 15 | console.log(JSON.stringify(msg)); 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /jerkeygit: -------------------------------------------------------------------------------- 1 | git -c "user.name=jerkey" -c "user.email=jerkey@g-cipher.net" "$@" 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sudobot", 3 | "version": "1.0.0", 4 | "description": "sudoroom status bot", 5 | "main": "bot.js", 6 | "dependencies": { 7 | "irc": "^0.3.12", 8 | "minimist": "^1.1.1", 9 | "split2": "^0.2.1", 10 | "through2": "^0.6.5" 11 | }, 12 | "devDependencies": {}, 13 | "scripts": { 14 | "start": "node bot.js" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git://github.com/sudoroom/sudobot.git" 19 | }, 20 | "keywords": [ 21 | "irc", 22 | "bot", 23 | "sudoroom" 24 | ], 25 | "author": "substack", 26 | "license": "MIT", 27 | "bugs": { 28 | "url": "https://github.com/sudoroom/sudobot/issues" 29 | }, 30 | "homepage": "https://github.com/sudoroom/sudobot#readme" 31 | } 32 | --------------------------------------------------------------------------------