├── README.markdown ├── index.js └── package.json /README.markdown: -------------------------------------------------------------------------------- 1 | A node.js API for creating bots on Dubtrack.fm. 2 | 3 | This API is just beginning, and since the Dubtrack Javascript API is not documented yet, features are somewhat limited. 4 | 5 | `phantomjs` and `phantomjs-node` are required. Right now, the API requires phantomjs version 2.0 or greater. Version 1.x hasn't been tested. Please let me know if you get it to work with 1.x. 6 | 7 | ``` 8 | npm install phantomjs 9 | ``` 10 | If you created the Dubtrack account for the bot by connecting through Facebook or Twitter, you will have to first login as that user, and view the cookies in your browser and get the value of the "connect.sid" cookie. It will look something like `s%Bp46VM86r0gns4w2krwtQ86JiROoNorcapnylGMTdG9qvs3rfs9YOCacdhFl2MlRCr4e8uM8LuP905RP`. 11 | 12 | Otherwise, you can use the Username and Password if you created your account with one. There is currently no way to get a password for an account created through Facebook or Twitter. 13 | 14 | Then, create a .js file similar to below, and run it with node: 15 | ``` 16 | node bot.js 17 | ``` 18 | 19 | ``` 20 | var DubtrackAPI = require('./dubtrackapi'); 21 | 22 | // For the cookie route, just set creds to the cookie value 23 | var creds = '...'; // Put the value of the connect.sid cookie here 24 | 25 | // For username and password 26 | creds = { 27 | username: '...', 28 | password: '...' 29 | }; 30 | 31 | var bot = new DubtrackAPI(creds); 32 | 33 | bot.connect('chillout-mixer'); // specify the room name part of the url for the room 34 | 35 | bot.on('ready', function(data) { 36 | console.log("Ready to go: ", data); 37 | // data contains the currentDJ (by name) and currentTrack (artist and track), and the list of users in the room (does not update on join/depart) 38 | 39 | bot.getEvents(function(events) { 40 | console.log("These are the Dubtrack events I respond to: ", events); 41 | }); 42 | }); 43 | 44 | bot.on('chat-message', function(data) { 45 | console.log("got chat: ", data); 46 | }); 47 | 48 | bot.on('room_playlist-update', function(data) { 49 | console.log("new song playing: ", data); 50 | }); 51 | 52 | bot.on('error', function(msg, trace) { 53 | console.log("Got an error from the virtual browser: ", msg, trace); 54 | }); 55 | ``` -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | (function() { 2 | var __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }; 3 | var phantom = require('phantom'); 4 | var EventEmitter = require('events').EventEmitter; 5 | var __hasProp = {}.hasOwnProperty; 6 | var http = require('http'); 7 | var net = require('net'); 8 | var util = require('util'); 9 | var request = require('request'); 10 | 11 | process.on('uncaughtException', function(err) { }); 12 | 13 | DubtrackAPI = (function(_super) { 14 | __extends(DubtrackAPI, _super); 15 | 16 | function DubtrackAPI(creds) { 17 | this.creds = creds; 18 | this.authCookie = typeof creds === 'string' ? creds : false; 19 | this.page = false; 20 | this.pageReady = false; 21 | this.loggedin = false; 22 | this.cookies = {}; 23 | this.ph = false; 24 | this.room = false; 25 | 26 | this.status = { 27 | 'INVALID_LOGIN': 0, 28 | 'LOGGED_IN': 1 29 | }; 30 | this.loginChecks = 0; 31 | this.maxLoginChecks = 10; 32 | 33 | this.userPermissions = { 34 | USER: 0, 35 | MOD: 1, 36 | CREATOR: 2, 37 | ADMIN: 3 38 | }; 39 | 40 | this.phantomPort = 12300; // default phantom port 41 | }; 42 | 43 | DubtrackAPI.setPhantomPort = function(port) { 44 | this.phantomPort = port; 45 | }; 46 | 47 | DubtrackAPI.prototype.login = function() { 48 | var self = this; 49 | this.createPage(function(ph) { 50 | self.ph = ph; 51 | self.ph.createPage(function (page) { 52 | page.set('viewportSize', { 53 | width: 1280, 54 | height: 720 55 | }); 56 | self.page = page; 57 | page.open("https://www.dubtrack.fm/login", function(status) { 58 | setTimeout(function() { 59 | page.evaluate(function(creds) { 60 | $('#login-input-username').val(creds.username); 61 | $('#login-input-password').val(creds.password); 62 | $('#login-window form').submit(); 63 | }, function() { 64 | self.checkLogin(); 65 | }, self.creds); 66 | }, 3000); 67 | }); 68 | }); 69 | }); 70 | }; 71 | 72 | DubtrackAPI.prototype.checkLogin = function() { 73 | var self = this; 74 | setTimeout(function() { 75 | self.page.evaluate(function(selfstatus) { 76 | var status = {}; 77 | if($('.err-message').is(':visible')) { 78 | status.state = selfstatus.INVALID_LOGIN; 79 | } else if($('#login-model-window').length === 0) { 80 | status.state = selfstatus.LOGGED_IN; 81 | } 82 | return status; 83 | }, function(status) { 84 | if(status.state === self.status.INVALID_LOGIN) { 85 | console.log("Invalid login."); 86 | self.ph.exit(); 87 | } else if(status.state === self.status.LOGGED_IN) { 88 | self.page.getCookies(function(cookies) { 89 | var foundCookie = false; 90 | for(var i in cookies) { 91 | var cookie = cookies[i]; 92 | if(cookie.name == 'connect.sid') { 93 | foundCookie = cookie.value; 94 | } 95 | } 96 | if(foundCookie !== false) { 97 | self.authCookie = foundCookie; 98 | self.openPage(self.room); 99 | } else { 100 | console.log("Login failed for some reason."); 101 | self.ph.exit(); 102 | } 103 | }); 104 | } else { 105 | self.loginChecks++; 106 | if(self.loginChecks >= self.maxLoginChecks) { 107 | console.log("Tried to login but was not successful. Sorry."); 108 | self.ph.exit(); 109 | } else 110 | self.checkLogin(); 111 | } 112 | }, self.status); 113 | }, 2000); 114 | }; 115 | 116 | DubtrackAPI.prototype.connect = function(room) { 117 | this.room = room; 118 | if(this.authCookie === false) { 119 | this.login(); 120 | } else { 121 | var self = this; 122 | if (this.ph === false) { 123 | // Need to create page 124 | this.createPage(function (ph) { 125 | self.ph = ph; 126 | self.connect(room); 127 | }); 128 | } else { 129 | console.log("connecting to " + room); 130 | this.openPage(room); 131 | } 132 | } 133 | }; 134 | 135 | DubtrackAPI.prototype.chat = function(msg) { 136 | this.page.evaluate(function(msg) { 137 | Dubtrack.room.chat._messageInputEl.val(msg); 138 | Dubtrack.room.chat.sendMessage(); 139 | }, function() { 140 | 141 | }, msg); 142 | }; 143 | 144 | DubtrackAPI.prototype.openPage = function(room) { 145 | var self = this; 146 | 147 | this.ph.createPage(function (page) { 148 | page.set('viewportSize', { 149 | width: 1280, 150 | height: 720 151 | }); 152 | if(this.authCookie !== false) { 153 | var cookie = { 154 | domain: '.dubtrack.fm', 155 | name: 'connect.sid', 156 | value: self.authCookie 157 | }; 158 | self.ph.addCookie(cookie); 159 | } 160 | page.set('onError', function(msg, trace) { 161 | self.emit('error', msg, trace); 162 | }); 163 | 164 | page.open('https://www.dubtrack.fm/join/' + room, function(status) { 165 | self.page = page; 166 | 167 | page.evaluate(function(data) { 168 | function debug(msg) { 169 | console.log("DubtrackAPI: " + JSON.stringify({ 170 | event: 'debug', 171 | data: msg 172 | })); 173 | } 174 | 175 | var interval = setInterval(function() { 176 | 177 | if($('ul.avatar-list li').length === 0) { 178 | return; 179 | } else { 180 | clearInterval(interval); 181 | } 182 | 183 | var currentTrack = $('li.infoContainer span.currentSong').html(); 184 | var username = $('li.imgEl img').attr('alt'); 185 | var currentDJ = currentTrack.split(" - "); 186 | var users = {}; 187 | $('ul.avatar-list li').each(function() { 188 | var user = {}; 189 | user.username = $(this).find('p.username').html(); 190 | if($(this).hasClass('admin')) 191 | user.permission = data.userPermissions.CREATOR; 192 | else if($(this).hasClass('creator')) 193 | user.permission = data.userPermissions.CREATOR; 194 | else if($(this).hasClass('mod')) 195 | user.permission = data.userPermissions.MOD; 196 | else 197 | user.permission = data.userPermissions.USER; 198 | users[user.username] = user; 199 | }); 200 | 201 | console.log("DubtrackAPI: " + JSON.stringify({ 202 | event: 'ready', 203 | data: { 204 | currentDJ: username, 205 | currentTrack: { 206 | artist: currentDJ[0], 207 | track: currentDJ[1] 208 | }, 209 | users: users 210 | } 211 | })); 212 | 213 | //Emit all Dubtrack JS API events 214 | var events = []; 215 | for(var event in Dubtrack.Events._events) { 216 | if(!event.match(/user[-_]update[_-]/)) { 217 | events.push(event); 218 | } 219 | } 220 | debug("events: " + JSON.stringify(events)); 221 | 222 | events.forEach(function(event) { 223 | debug("binding event for " + event.replace("realtime:", "")); 224 | Dubtrack.Events.bind(event, function (data) { 225 | console.log("DubtrackAPI: " + JSON.stringify({ 226 | event: event.replace("realtime:", ""), 227 | data: data 228 | })); 229 | }); 230 | }); 231 | }, 2000); 232 | 233 | }, function() { 234 | 235 | }, { 236 | userPermissions: self.userPermissions 237 | }); 238 | 239 | page.set('onConsoleMessage', function(msg) { 240 | if(!msg.match(/^The page at/)) { 241 | //console.log("console message: ", msg); 242 | } 243 | var re = new RegExp("^DubtrackAPI: (.+)"); 244 | if(msg.match(re)) { 245 | var obj = JSON.parse(RegExp.$1); 246 | if(obj.event === 'ready') 247 | self.pageReady = true; 248 | self.emit(obj.event, obj.data); 249 | } 250 | }); 251 | 252 | 253 | }); 254 | 255 | 256 | }); 257 | }; 258 | 259 | DubtrackAPI.prototype.createPage = function(callback) { 260 | var self = this; 261 | phantom.create(function(ph) { 262 | ph.get('version', function(result) { 263 | if(result.major < 2) { 264 | var version = result.major + "." + result.minor + "." + result.patch; 265 | console.log("Sorry, but PlugBotAPI requires phantomjs version >= 2.0.0. You are running version " + version + "."); 266 | ph.exit(); 267 | process.exit(1); 268 | } 269 | }); 270 | 271 | if(typeof callback == 'function') 272 | callback(ph); 273 | }); 274 | }; 275 | 276 | DubtrackAPI.prototype.getEvents = function(callback) { 277 | var self = this; 278 | if(self.pageReady === false) 279 | return; 280 | this.page.evaluate(function() { 281 | var events = []; 282 | for(var event in Dubtrack.Events._events) { 283 | //var event = Dubtrack.Events._events[i]; 284 | if(!event.match(/[a-f0-9]{24}/)) 285 | events.push(event.replace("realtime:", "")); 286 | } 287 | return events; 288 | }, function(events) { 289 | if(typeof callback === 'function') { 290 | callback(events); 291 | } 292 | }); 293 | }; 294 | 295 | DubtrackAPI.prototype.getUsers = function(callback) { 296 | if(this.pageReady === false) 297 | return; 298 | request.get("https://api.dubtrack.fm/room/" + this.room, 299 | function(error, response, body) { 300 | var room = JSON.parse(body); 301 | request.get("https://api.dubtrack.fm/room/" + room.data._id + "/users", 302 | function(error, response, body) { 303 | var usersObj = JSON.parse(body); 304 | var users = usersObj.data; 305 | if(typeof callback === 'function') { 306 | callback(users); 307 | } 308 | }); 309 | }); 310 | }; 311 | 312 | 313 | 314 | return DubtrackAPI; 315 | })(EventEmitter); 316 | 317 | module.exports = DubtrackAPI; 318 | }).call(this); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dubtrackapi", 3 | "description": "An API for creating bots on plug.dj", 4 | "version": "0.1.7", 5 | "keywords": ["plug.dj", "plug"], 6 | "repository": { 7 | "type": "git", 8 | "url": "git://github.com/atomjack/plugbotapi.git" 9 | }, 10 | "author": "Chris Bellew ", 11 | "main": "index.js", 12 | "directories": { 13 | "lib": "lib" 14 | }, 15 | "engines": { 16 | "node": "*" 17 | }, 18 | "dependencies" : { 19 | "phantom": "0.8.0", 20 | "net": "*" 21 | }, 22 | "devDependencies": {}, 23 | "optionalDependencies": { 24 | } 25 | } 26 | --------------------------------------------------------------------------------