├── test_sender.js ├── lib ├── launch.sh ├── comm.js └── urxvt-config ├── send.js ├── client_console.js ├── client_status.js ├── init.sh ├── .gitignore ├── package.json ├── test_map.js ├── qemu.js ├── map.json └── twitch_master.js /test_sender.js: -------------------------------------------------------------------------------- 1 | var pub = require('./lib/comm').sender(); 2 | 3 | process.stdin.resume(); 4 | 5 | process.stdin.on('data', function(data) { 6 | pub.send(['qemu-manager', data.toString()]); 7 | }); 8 | -------------------------------------------------------------------------------- /lib/launch.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd ~/qemu/x86_64-softmmu 3 | while true; do 4 | ./qemu-system-x86_64 -cdrom ~/archlinux-2015.10.01-dual-patched.iso -vnc :0 -monitor stdio -m 1G -smp 2 -drive file=~/Arch.img,index=0,media=disk,format=raw 5 | done 6 | -------------------------------------------------------------------------------- /send.js: -------------------------------------------------------------------------------- 1 | var zmq = require('zmq') 2 | var control = zmq.socket('req'); 3 | 4 | control.connect('tcp://localhost:1301', function(err) { 5 | if (err) 6 | console.log(err); 7 | }); 8 | 9 | control.on('message', function(msg) { 10 | console.log(msg); 11 | }); 12 | 13 | control.send("TEST"); 14 | 15 | process.stdin.resume(); 16 | process.stdin.on('data', function(data) { 17 | process.stdout.write('local: ' + data); 18 | control.send(data.toString()); 19 | }); 20 | -------------------------------------------------------------------------------- /client_console.js: -------------------------------------------------------------------------------- 1 | var sub = require('./lib/comm').receiver('client-console', true); 2 | console.log('Connecting to master...'); 3 | 4 | sub.on('connect', function() { 5 | console.log('Connected to master'); 6 | }); 7 | 8 | sub.on('disconnect', function() { 9 | console.log('Disconnected from master'); 10 | console.log('Connecting to master...'); 11 | }); 12 | 13 | sub.on('message', function() { 14 | var msg = arguments[1].toString(); 15 | console.log(msg.trim()); 16 | }); 17 | -------------------------------------------------------------------------------- /client_status.js: -------------------------------------------------------------------------------- 1 | var sub = require('./lib/comm').receiver('client-status', true); 2 | console.log('Connecting to master...'); 3 | 4 | sub.on('connect', function() { 5 | console.log('Connected to master'); 6 | }); 7 | 8 | sub.on('disconnect', function() { 9 | console.log('Disconnected from master'); 10 | console.log('Connecting to master...'); 11 | }); 12 | 13 | sub.on('message', function() { 14 | var msg = arguments[1].toString(); 15 | console.log(msg.trim()); 16 | }); 17 | -------------------------------------------------------------------------------- /lib/comm.js: -------------------------------------------------------------------------------- 1 | var zmq = require('zmq'); 2 | 3 | module.exports.receiver = function(topic, monitor) { 4 | monitor = monitor == null ? false : monitor; 5 | var sub = zmq.socket('sub'); 6 | if (monitor) 7 | sub.monitor(); 8 | sub.connect('tcp://localhost:1300'); 9 | sub.subscribe(topic); 10 | return sub; 11 | }; 12 | 13 | module.exports.sender = function() { 14 | var pub = zmq.socket('pub'); 15 | pub.bind('tcp://*:1300', function(err) { 16 | if(err) 17 | console.log(err); 18 | else 19 | console.log('Listening on 1300...'); 20 | }); 21 | return pub; 22 | }; 23 | 24 | -------------------------------------------------------------------------------- /init.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | case "$1" in 3 | "client_console") 4 | xrdb lib/urxvt-config 5 | urxvt256c -geometry 84x48 -e bash -c 'echo -ne "\033]0;client_console\007"; node client_console.js' 6 | ;; 7 | "client_status") 8 | xrdb lib/urxvt-config 9 | urxvt256c -geometry 104x10 -e bash -c 'echo -ne "\033]0;client_status\007"; node client_status.js' 10 | ;; 11 | "client_vnc") 12 | vncviewer -Shared -geometry=640x480 localhost:0 13 | ;; 14 | "twitch_master") 15 | node twitch_master.js 16 | ;; 17 | "qemu") 18 | node qemu.js 19 | ;; 20 | *) 21 | echo "No such command" 22 | exit 1 23 | ;; 24 | esac 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | 6 | # Runtime data 7 | pids 8 | *.pid 9 | *.seed 10 | 11 | # Directory for instrumented libs generated by jscoverage/JSCover 12 | lib-cov 13 | 14 | # Coverage directory used by tools like istanbul 15 | coverage 16 | 17 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 18 | .grunt 19 | 20 | # node-waf configuration 21 | .lock-wscript 22 | 23 | # Compiled binary addons (http://nodejs.org/api/addons.html) 24 | build/Release 25 | 26 | # Dependency directory 27 | # https://docs.npmjs.com/misc/faq#should-i-check-my-node-modules-folder-into-git 28 | node_modules 29 | 30 | # Vim swap files 31 | .*.swp 32 | -------------------------------------------------------------------------------- /lib/urxvt-config: -------------------------------------------------------------------------------- 1 | URxvt*termName: screen-256color 2 | URxvt*geometry: 240x84 3 | URxvt*loginShell: true 4 | URxvt*scrollColor: #777777 5 | URxvt*scrollstyle: plain 6 | URxvt*scrollTtyKeypress: true 7 | URxvt*scrollTtyOutput: false 8 | URxvt*scrollWithBuffer: false 9 | URxvt*secondaryScreen: true 10 | URxvt*secondaryScroll: true 11 | URxvt*skipScroll: true 12 | URxvt*scrollBar: false 13 | URxvt*scrollBar_right: false 14 | URxvt*scrollBar_floating: false 15 | URxvt*fading: 30 16 | URxvt*utmpInhibit: false 17 | URxvt*urgentOnBell: false 18 | URxvt*visualBell: true 19 | URxvt*mapAlert: true 20 | URxvt*mouseWheelScrollPage: false 21 | URxvt*background: Black 22 | URxvt*foreground: White 23 | URxvt*colorUL: yellow 24 | URxvt*underlineColor: yellow 25 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "twitch-master", 3 | "version": "1.0.0", 4 | "description": "Read input from Twitch, and send keys to other programs", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/twitchinstallsarchlinux/twitch-master.git" 12 | }, 13 | "author": "John Ott", 14 | "license": "Apache-2.0", 15 | "bugs": { 16 | "url": "https://github.com/twitchinstallsarchlinux/twitch-master/issues" 17 | }, 18 | "homepage": "https://github.com/twitchinstallsarchlinux/twitch-master#readme", 19 | "dependencies": { 20 | "irc": "^0.4.0", 21 | "zmq": "^2.13.0" 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /test_map.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var pub = require('./lib/comm').sender(); 3 | 4 | var TIMEOUT = 500; 5 | 6 | // Load json command mapping 7 | var map = {} 8 | 9 | function map_load() { 10 | fs.exists('map.json', function() { 11 | try { 12 | var map_new = JSON.parse(fs.readFileSync('map.json', 'utf8')); 13 | map = map_new; 14 | console.log('(Re)loaded map.json'); 15 | } catch (ex) { 16 | console.log('Could not load map.json'); 17 | console.log(ex); 18 | } 19 | }); 20 | } 21 | 22 | map_load(); 23 | 24 | var length = 0; 25 | 26 | function iter() { 27 | var next = Object.keys(map)[length]; 28 | if (next != null) { 29 | console.log('Sending: ' + next + ' -> ' + map[next]); 30 | pub.send(['qemu-manager', map[next] + '\n']); 31 | length++; 32 | setTimeout(iter, TIMEOUT); 33 | } else { 34 | console.log('Done!'); 35 | } 36 | } 37 | 38 | setTimeout(iter, TIMEOUT); 39 | -------------------------------------------------------------------------------- /qemu.js: -------------------------------------------------------------------------------- 1 | var sub = require('./lib/comm').receiver('qemu-manager'); 2 | console.log('Connecting to master...'); 3 | sub.monitor(); 4 | 5 | var child = null; 6 | var spawn = require('child_process').spawn; 7 | 8 | function spawn_process(child_process) { 9 | child_process = spawn('bash', ['lib/launch.sh']); 10 | 11 | child_process.stdout.on('data', function(data) { 12 | console.log('QEMU: ' + data); 13 | }); 14 | 15 | child_process.stderr.on('data', function(data) { 16 | console.log('ERR QEMU: ' + data); 17 | }); 18 | 19 | // respawn process on error? Not sure if this will trigger 20 | // an exit event, spawning two processes 21 | //child_process.on('error', function(data) { 22 | // console.log('PROCERR QEMU: ' + data); 23 | // child_process.kill('SIGKILL'); 24 | // spawn_process(child); 25 | //}); 26 | 27 | //respawn process if it exits 28 | child_process.on('exit', function(data) { 29 | console.log('EXIT QEMU: ' + data); 30 | spawn_process(child); 31 | }); 32 | 33 | child = child_process; 34 | } 35 | spawn_process(child); 36 | 37 | sub.on('connect', function() { 38 | console.log('Connected to master'); 39 | }); 40 | 41 | sub.on('disconnect', function() { 42 | console.log('Disconnected from master'); 43 | }); 44 | 45 | sub.on('message', function() { 46 | var msg = arguments[1].toString(); 47 | process.stdout.write('> ' + msg); 48 | child.stdin.write(msg); 49 | }); 50 | 51 | process.stdin.resume(); 52 | 53 | process.stdin.on('data', function(data) { 54 | child.stdin.write(data.toString()); 55 | }); 56 | -------------------------------------------------------------------------------- /map.json: -------------------------------------------------------------------------------- 1 | { 2 | "a": "sendkey a", 3 | "A": "sendkey shift-a", 4 | "b": "sendkey b", 5 | "B": "sendkey shift-b", 6 | "c": "sendkey c", 7 | "C": "sendkey shift-c", 8 | "d": "sendkey d", 9 | "D": "sendkey shift-d", 10 | "e": "sendkey e", 11 | "E": "sendkey shift-e", 12 | "f": "sendkey f", 13 | "F": "sendkey shift-f", 14 | "g": "sendkey g", 15 | "G": "sendkey shift-g", 16 | "h": "sendkey h", 17 | "H": "sendkey shift-h", 18 | "i": "sendkey i", 19 | "I": "sendkey shift-i", 20 | "j": "sendkey j", 21 | "J": "sendkey shift-j", 22 | "k": "sendkey k", 23 | "K": "sendkey shift-k", 24 | "l": "sendkey l", 25 | "L": "sendkey shift-l", 26 | "m": "sendkey m", 27 | "M": "sendkey shift-m", 28 | "n": "sendkey n", 29 | "N": "sendkey shift-n", 30 | "o": "sendkey o", 31 | "O": "sendkey shift-o", 32 | "p": "sendkey p", 33 | "P": "sendkey shift-p", 34 | "q": "sendkey q", 35 | "Q": "sendkey shift-q", 36 | "r": "sendkey r", 37 | "R": "sendkey shift-r", 38 | "s": "sendkey s", 39 | "S": "sendkey shift-s", 40 | "t": "sendkey t", 41 | "T": "sendkey shift-t", 42 | "u": "sendkey u", 43 | "U": "sendkey shift-u", 44 | "v": "sendkey v", 45 | "V": "sendkey shift-v", 46 | "w": "sendkey w", 47 | "W": "sendkey shift-w", 48 | "x": "sendkey x", 49 | "X": "sendkey shift-x", 50 | "y": "sendkey y", 51 | "Y": "sendkey shift-y", 52 | "z": "sendkey z", 53 | "Z": "sendkey shift-z", 54 | "0": "sendkey 0", 55 | "1": "sendkey 1", 56 | "2": "sendkey 2", 57 | "3": "sendkey 3", 58 | "4": "sendkey 4", 59 | "5": "sendkey 5", 60 | "6": "sendkey 6", 61 | "7": "sendkey 7", 62 | "8": "sendkey 8", 63 | "9": "sendkey 9", 64 | "!": "sendkey shift-1", 65 | "at": "sendkey shift-2", 66 | "#": "sendkey shift-3", 67 | "$": "sendkey shift-4", 68 | "%": "sendkey shift-5", 69 | "^": "sendkey shift-6", 70 | "&": "sendkey shift-7", 71 | "*": "sendkey shift-8", 72 | "(": "sendkey shift-9", 73 | ")": "sendkey shift-0", 74 | "-": "sendkey minus", 75 | "_": "sendkey shift-minus", 76 | "'": "sendkey apostrophe", 77 | "\"": "sendkey shift-apostrophe", 78 | "dot": "sendkey dot", 79 | ":": "sendkey shift-semicolon", 80 | ";": "sendkey semicolon", 81 | "=": "sendkey equal", 82 | ">": "sendkey shift-dot", 83 | "<": "sendkey shift-comma", 84 | "`": "sendkey grave_accent", 85 | "~": "sendkey shift-grave_accent", 86 | "slash": "sendkey slash", 87 | "?": "sendkey shift-slash", 88 | "backslash": "sendkey backslash", 89 | "|": "sendkey shift-backslash", 90 | "[": "sendkey bracket_left", 91 | "]": "sendkey bracket_right", 92 | "{": "sendkey shift-bracket_left", 93 | "}": "sendkey shift-bracket_right", 94 | "escape": "sendkey esc", 95 | "backspace": "sendkey backspace", 96 | "enter": "sendkey ret", 97 | "space": "sendkey spc", 98 | "tab": "sendkey tab", 99 | "up": "sendkey up", 100 | "down": "sendkey down", 101 | "left": "sendkey left", 102 | "right": "sendkey right", 103 | "f1": "sendkey f1", 104 | "f2": "sendkey f2", 105 | "f3": "sendkey f3", 106 | "f4": "sendkey f4", 107 | "f5": "sendkey f5", 108 | "f6": "sendkey f6", 109 | "f7": "sendkey f7", 110 | "f8": "sendkey f8", 111 | "f9": "sendkey f9", 112 | "alt-f1": "sendkey alt-f1", 113 | "alt-f2": "sendkey alt-f2", 114 | "alt-f3": "sendkey alt-f3", 115 | "alt-f4": "sendkey alt-f4", 116 | "alt-f5": "sendkey alt-f5", 117 | "alt-f6": "sendkey alt-f6", 118 | "alt-f7": "sendkey alt-f7", 119 | "alt-f8": "sendkey alt-f8", 120 | "alt-f9": "sendkey alt-f9", 121 | "ctrl-a": "sendkey ctrl-a", 122 | "ctrl-b": "sendkey ctrl-b", 123 | "ctrl-c": "VOTE sendkey ctrl-c", 124 | "ctrl-d": "sendkey ctrl-d", 125 | "ctrl-e": "sendkey ctrl-e", 126 | "ctrl-f": "sendkey ctrl-f", 127 | "ctrl-g": "sendkey ctrl-g", 128 | "ctrl-h": "sendkey ctrl-h", 129 | "ctrl-i": "sendkey ctrl-i", 130 | "ctrl-j": "sendkey ctrl-j", 131 | "ctrl-k": "sendkey ctrl-k", 132 | "ctrl-l": "sendkey ctrl-l", 133 | "ctrl-m": "sendkey ctrl-m", 134 | "ctrl-n": "sendkey ctrl-n", 135 | "ctrl-o": "sendkey ctrl-o", 136 | "ctrl-p": "sendkey ctrl-p", 137 | "ctrl-q": "sendkey ctrl-q", 138 | "ctrl-r": "sendkey ctrl-r", 139 | "ctrl-s": "sendkey ctrl-s", 140 | "ctrl-t": "sendkey ctrl-t", 141 | "ctrl-u": "sendkey ctrl-u", 142 | "ctrl-v": "sendkey ctrl-v", 143 | "ctrl-w": "sendkey ctrl-w", 144 | "ctrl-x": "sendkey ctrl-x", 145 | "ctrl-y": "sendkey ctrl-y", 146 | "ctrl-z": "sendkey ctrl-z", 147 | "nop": "", 148 | "system_reset": "VOTE system_reset", 149 | "boot_cd": "VOTE boot_set dc", 150 | "boot_drive": "VOTE boot_set cd", 151 | "democracy": "VOTE democracy", 152 | "anarchy": "VOTE anarchy", 153 | "yes": "" 154 | } 155 | -------------------------------------------------------------------------------- /twitch_master.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var irc = require('irc'); 3 | var pub = require('./lib/comm').sender(); 4 | 5 | var command_interval = 15; 6 | 7 | // possible values: democracy, anarchy 8 | var command_mode = 'anarchy'; 9 | 10 | //smudge values when in anarchy mode. 0.3 = 30% 11 | var smudge_factor = 0.3; 12 | 13 | function reportStatus(message) { 14 | pub.send(['client-status', message]); 15 | 16 | if (twitch_chat && config) { 17 | twitch_chat.say('#' + config['nick'], message); 18 | } 19 | } 20 | 21 | process.stdin.resume(); 22 | process.stdin.on('data', function(data) { 23 | process.stdout.write('Control: ' + data); 24 | var args = data.toString().split(' '); 25 | switch(args[0].trim()) { 26 | case 'map_load': 27 | map_load(); 28 | break; 29 | 30 | case 'reset_voting': 31 | // For when this inevitably breaks 32 | voting_command = null; 33 | break; 34 | 35 | case 'anarchy': 36 | setCommandMode('anarchy'); 37 | break; 38 | 39 | case 'democracy': 40 | setCommandMode('democracy'); 41 | break; 42 | 43 | case 'set_interval': 44 | var new_interval = +args[1]; 45 | if (new_interval >= 1) { 46 | command_interval = new_interval; 47 | reportStatus('VOTING INTERVAL is now ' + new_interval + ' seconds'); 48 | } else { 49 | console.log('Trouble parsing command interval seconds, try again'); 50 | } 51 | break; 52 | 53 | default: 54 | console.log("Sending...", args); 55 | pub.send(args); 56 | break; 57 | } 58 | }); 59 | 60 | // Load json command mapping 61 | var map = {} 62 | 63 | function map_load() { 64 | fs.exists('map.json', function() { 65 | try { 66 | var map_new = JSON.parse(fs.readFileSync('map.json', 'utf8')); 67 | map = map_new; 68 | console.log('(Re)loaded map.json'); 69 | } catch (ex) { 70 | console.log('Could not load map.json'); 71 | console.log(ex); 72 | } 73 | }); 74 | } 75 | 76 | map_load(); 77 | 78 | // Load json config 79 | var config = JSON.parse(fs.readFileSync('config.json', 'utf8')); 80 | 81 | var twitch_chat = new irc.Client('irc.twitch.tv', config['nick'], { 82 | channels: ['#' + config['nick']], 83 | userName: config['nick'], 84 | password: config['password'], 85 | autoConnect: false 86 | }); 87 | 88 | twitch_chat.connect(0, function() { 89 | console.log("Twitch connected!"); 90 | }); 91 | 92 | var last_tally = {}; 93 | var last_command; 94 | 95 | twitch_chat.addListener('message#' + config['nick'], function(from, msg) { 96 | msg = msg.trim(); 97 | if (map[msg] != null) { 98 | console.log(from + ': ' + msg + ' -> ' + map[msg]); 99 | pub.send(['client-console', '> ' + from + ': ' + msg]); 100 | last_tally[from.trim()] = msg; 101 | last_command = msg; 102 | } 103 | }); 104 | 105 | var voting_command = null; 106 | 107 | function democracy() { 108 | var command_count = {}; 109 | for (var user in last_tally) { 110 | if (command_count[last_tally[user]] == null) 111 | command_count[last_tally[user]] = 0; 112 | command_count[last_tally[user]] += 1; 113 | } 114 | 115 | var top_array = []; 116 | var top_count = 0; 117 | var second_array = []; 118 | var second_count = 0; 119 | for (var command in command_count) { 120 | if (command_count[command] > top_count) { 121 | second_array = top_array.slice(); 122 | second_count = top_count; 123 | top_array = []; 124 | top_array.push(command); 125 | top_count = command_count[command]; 126 | } else if (command_count[command] == top_count) { 127 | top_array.push(command); 128 | } else if (command_count[command] > second_count) { 129 | second_array = []; 130 | second_array.push(command); 131 | second_count = command_count[command] 132 | } else if (command_count[command] == second_count) { 133 | second_array.push(command); 134 | } 135 | } 136 | 137 | var counts = ''; 138 | var commands = top_array.concat(second_array); 139 | for (var index in commands) { 140 | var command = commands[index]; 141 | counts += '\'' + command + '\' = ' + Math.round(command_count[command]/Object.keys(last_tally).length * 100, 2) + '%'; 142 | if (index != Object.keys(commands).length - 1) 143 | counts += ', ' 144 | } 145 | 146 | // Clear out tally info for next time 147 | last_tally = {}; 148 | 149 | if (top_array.length > 0) { 150 | var selected_command = top_array[Math.floor(Math.random()*top_array.length)]; 151 | 152 | console.log('Selected: ' + selected_command); 153 | reportStatus('Winning command: ' + selected_command); 154 | 155 | console.log('Votes: ' + counts); 156 | reportStatus('VOTES: ' + counts); 157 | 158 | return selected_command; 159 | } 160 | } 161 | 162 | function anarchy() { 163 | if (last_command) { 164 | var selected_command = last_command; 165 | last_command = null; 166 | 167 | reportStatus('Winning command: ' + selected_command); 168 | return selected_command; 169 | } 170 | } 171 | 172 | function setCommandMode(mode) { 173 | 174 | switch(mode) { 175 | case 'anarchy': 176 | command_mode = 'anarchy'; 177 | last_command = null; 178 | reportStatus('ANARCHY is now in effect'); 179 | break; 180 | case 'democracy': 181 | command_mode = 'democracy'; 182 | last_tally = {}; 183 | reportStatus('DEMOCRACY is now in effect'); 184 | break; 185 | default: 186 | break; 187 | } 188 | } 189 | 190 | function processCommand() { 191 | var next_ms = command_interval * 1000; 192 | if (command_mode == 'anarchy' && !voting_command) { 193 | // Smudges timer by random amounts. "smudge_factor" is skew percentage. 194 | next_ms += ((Math.random() - 0.5)*2) * next_ms * smudge_factor; 195 | } 196 | setTimeout(processCommand, next_ms); 197 | 198 | var selected_command; 199 | 200 | if (command_mode == 'democracy' || voting_command != null) { 201 | selected_command = democracy(); 202 | } else if (command_mode == 'anarchy') { 203 | selected_command = anarchy(); 204 | } 205 | 206 | if (selected_command) { 207 | if (voting_command != null) { 208 | // We are voting to run a dangerous command 209 | if (selected_command == 'yes') { 210 | console.log('Vote succeeded: ' + voting_command); 211 | twitch_chat.say('#' + config['nick'], 'Vote succeeded: ' + voting_command) 212 | pub.send(['client-status', 'VOTING SUCCEEDED: ' + voting_command]); 213 | 214 | // Replace VOTE 215 | var cmd = map[voting_command].replace(/^VOTE /, ''); 216 | 217 | // Set mode 218 | if ((cmd == 'democracy' || cmd == 'anarchy')) { 219 | setCommandMode(cmd); 220 | } else { 221 | 222 | // Send Command 223 | var command_qemu = cmd; 224 | console.log('Sending to qemu: ' + command_qemu); 225 | pub.send(['qemu-master', command_qemu]); 226 | } 227 | } else { 228 | console.log('Vote failed: ' + voting_command); 229 | twitch_chat.say('#' + config['nick'], 'Vote failed: ' + voting_command) 230 | pub.send(['client-status', 'VOTING FAILED: ' + voting_command]); 231 | } 232 | voting_command = null; 233 | 234 | } else if (map[selected_command].indexOf("VOTE") == 0) { 235 | // This command requires a vote 236 | console.log('Voting on command: ' + selected_command); 237 | twitch_chat.say('#' + config['nick'], 'Vote \'yes\' to run this command: ' + selected_command); 238 | pub.send(['client-status', 'VOTING ON COMMAND (yes to run this command): ' + selected_command]); 239 | voting_command = selected_command; 240 | last_tally = {}; // in case we are in anarchy 241 | 242 | } else if (map[selected_command] != "") { 243 | // Normal command, not NOOP 244 | console.log('Sending to qemu: ' + map[selected_command]); 245 | pub.send(['qemu-master', map[selected_command]]); 246 | } 247 | 248 | } else { 249 | console.log('Not enough votes'); 250 | twitch_chat.say('#' + config['nick'], 'Not enough votes'); 251 | pub.send(['client-status', 'NOT ENOUGH VOTES PLACED!']); 252 | } 253 | } 254 | 255 | // Set the first voting timer 256 | setTimeout(processCommand, command_interval * 1000); 257 | --------------------------------------------------------------------------------