A Twitch.tv viewer reward and games system.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

388 lines
12 KiB

12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
12 years ago
  1. /**
  2. * api:
  3. * IRC([required options])
  4. * required options - {name, pass, channel}
  5. *
  6. * IRC.connect()
  7. * connects to the twitch irc server
  8. *
  9. * IRC.on('command', callback)
  10. * allows custom commands
  11. *
  12. * IRC.on('data', callback)
  13. * event when data is recieved / sent
  14. *
  15. * IRC.msg(message, [options])
  16. * options - {caller, auth[0/1]}
  17. *
  18. * IRC.caller(data[0])
  19. * parse out user name from socket data,
  20. * mainly for plugin use when working with commands
  21. *
  22. * example:
  23. * require:
  24. * var irc = require('./lib/core/irc.js')({
  25. * name : 'TwitchBot',
  26. * pass : 'twitch!twitch!twitch!',
  27. * channel : '#awesomebroadcaster'
  28. * });
  29. *
  30. * connect:
  31. * irc.connect();
  32. *
  33. * custom commands:
  34. * irc.on('command' function (data) {
  35. * if (data[3] == ':!command') {
  36. * // do something
  37. * }
  38. * });
  39. *
  40. * irc data:
  41. * irc.on('data', function (data) {
  42. * //do something with data
  43. * });
  44. *
  45. * irc logging:
  46. * irc.on('data', function (data) {
  47. * irc.realtime(data);
  48. * }
  49. *
  50. * send a message to chat:
  51. * irc.msg('Hi chat!');
  52. * irc.msg('Hi chat!', {caller: 'SupremoRTD', auth: 1});
  53. *
  54. * get user name:
  55. * irc.caller(data[0]);
  56. */
  57. var net = require('net'),
  58. events = require('events'),
  59. file = require('fs'),
  60. https = require('https'),
  61. utils = require('./utils.js');
  62. //-------- Construct ---------
  63. function IRC(options) {
  64. var __self = this;
  65. __self.options = options || {pass: 'irc_bot', name: 'irc_bot', channel: 'irc_bot'};
  66. __self.config = {
  67. // twitch bot info
  68. pass : __self.options.pass,
  69. name : __self.options.name,
  70. nick : 'irc_bot',
  71. broadcaster : __self.options.channel.slice(1).charAt(0).toUpperCase() + __self.options.channel.slice(2).toLowerCase(),
  72. // twitch server
  73. addr : 'irc.twitch.tv', //__self.options.name.toLowerCase() + '.jtvirc.com',
  74. port : 6667,
  75. channel : __self.options.channel.toLowerCase(),
  76. encoding : 'ascii'
  77. };
  78. __self.mods = [];
  79. __self.buffer = [];
  80. // message queue
  81. __self.queue_timer = 2000;
  82. __self.queue_messages = [];
  83. __self.previous_message = '';
  84. // irc logging
  85. __self.check_streaming = 4;//minutes
  86. __self.streaming = false;
  87. __self.new_file = false;
  88. __self.log = '';
  89. // chat logging
  90. __self.first_check = true;
  91. __self.store_chat = __self.options.chatlog || false;
  92. __self.chat_log = '';
  93. }
  94. IRC.prototype = new events.EventEmitter();
  95. //-------- Methods --------
  96. IRC.prototype.start = function () {
  97. var __self = this;
  98. // check stream status
  99. function stream_status() {
  100. var time = utils.make_interval(__self.check_streaming);
  101. if (time === 0 || __self.first_check) {
  102. // Important to log chat right away, so lets skip the first 4 minute wait
  103. if (__self.first_check) __self.first_check = false;
  104. https.get('https://api.twitch.tv/kraken/streams/' + __self.config.channel.slice(1), function (response) {
  105. var body = '';
  106. // put together response
  107. response.on('data', function (chunk) {
  108. body += chunk;
  109. });
  110. // log file creation
  111. response.on('end', function () {
  112. var json = null;
  113. try {
  114. json = JSON.parse(body);
  115. __self.streaming = json.stream !== null;
  116. } catch (err) {
  117. __self.streaming = false;
  118. }
  119. if (__self.streaming && !__self.new_file) {
  120. // prevent another file from being created while streaming
  121. __self.new_file = true;
  122. // set stream time for file
  123. var date = new Date(),
  124. hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours(),
  125. min = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes(),
  126. sec = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds(),
  127. streaming_time = '';
  128. // create start time string
  129. streaming_time += (date.getMonth().toString() + 1) + date.getDate().toString() + date.getFullYear().toString();
  130. streaming_time += hours.toString() + min.toString() + sec.toString();
  131. // create new log file
  132. __self.log = './../logs/' + __self.config.channel.slice(1) + '_' + streaming_time.toString() + '.txt';
  133. file.open(__self.log, 'w');
  134. // create chat log
  135. if (__self.store_chat) {
  136. __self.chat_log = './../logs/chat/' + __self.config.channel.slice(1) + '_' + streaming_time.toString() + '.txt';
  137. file.open(__self.chat_log, 'w');
  138. }
  139. } else if (!__self.streaming) {
  140. __self.new_file = false;
  141. __self.log = '';
  142. }
  143. setTimeout(stream_status, 1000);
  144. });
  145. });
  146. } else {
  147. setTimeout(stream_status, time);
  148. }
  149. }
  150. stream_status();
  151. __self.connect();
  152. debugger;
  153. __self.monitor_queue();
  154. };
  155. IRC.prototype.realtime = function (data){
  156. var __self = this;
  157. // only log irc data if streaming
  158. if (__self.streaming) {
  159. // check if file exists, if it does append
  160. file.exists(__self.log, function (exists) {
  161. if (exists) {
  162. file.appendFile(__self.log, data + '\r\n', 'utf-8', function (err) {
  163. if (err) {
  164. throw err;
  165. }
  166. });
  167. }
  168. });
  169. }
  170. };
  171. IRC.prototype.connect = function () {
  172. var __self = this;
  173. // create new socket
  174. __self.socket = new net.Socket();
  175. __self.socket.setEncoding(__self.config.encoding);
  176. __self.socket.setNoDelay();
  177. __self.socket.connect(__self.config.port, __self.config.addr);
  178. // connect to twitch irc via socket
  179. __self.socket.on('connect', function () {
  180. __self.emit('data', 'RECV - Established connection to Twitch IRC, registering user...');
  181. __self.raw('PASS ' + __self.config.pass, true);
  182. __self.raw('NICK ' + __self.config.name);
  183. __self.raw('USER ' + __self.config.nick + ' ' + __self.config.nick + '.com ' + __self.config.nick + ' :' + __self.config.name);
  184. });
  185. // handle incoming socket data
  186. __self.socket.on('data', function (data) {
  187. var prepack, lines, line, word;
  188. prepack = data.replace('\r\n', '\n');
  189. __self.buffer += prepack;
  190. lines = __self.buffer.split('\n');
  191. __self.buffer = '';
  192. if (lines[lines.length - 1] !== '') {
  193. __self.buffer = lines[lines.length - 1];
  194. }
  195. lines = lines.splice(0, lines.length - 1);
  196. for (var i = 0; i < lines.length; i++) {
  197. line = lines[i].replace('\r','');
  198. word = line.replace('\r', '').split(' ');
  199. __self.emit('data', 'RECV - ' + line);
  200. __self.join(word);
  201. __self.pingpong(word);
  202. __self.moderators(word);
  203. if (word[3] !== undefined) {
  204. __self.emit('command', word);
  205. }
  206. }
  207. });
  208. __self.socket.on('error', function(){
  209. __self.reconnect();
  210. });
  211. };
  212. // reconnect to socket
  213. IRC.prototype.reconnect = function () {
  214. var __self = this;
  215. // send quit to server, destroy socket connection,
  216. // clear socket variable and then reconnect
  217. __self.socket.end('QUIT\r\n');
  218. __self.socket.destroy();
  219. __self.socket = null;
  220. __self.connect();
  221. };
  222. // join channel
  223. IRC.prototype.join = function (data) {
  224. var __self = this;
  225. if (data[3] === ':>' || data[3] === ':End') {
  226. __self.raw('JOIN ' + __self.config.channel);
  227. }
  228. };
  229. // ping / pong
  230. IRC.prototype.pingpong = function (data) {
  231. var __self = this;
  232. if (data[0] === 'PING') {
  233. __self.raw('PONG ' + data[1]);
  234. }
  235. };
  236. // store / remove mods
  237. IRC.prototype.moderators = function (data) {
  238. var __self = this;
  239. if (data[1] === 'MODE') {
  240. if (data[4] !== undefined) {
  241. var user = data[4].charAt(0).toUpperCase() + data[4].slice(1);
  242. switch (data[3]) {
  243. case '+o':
  244. if (__self.mods.indexOf(user) < 0) {
  245. __self.mods.push(user);
  246. }
  247. break;
  248. case '-o':
  249. if (__self.mods.indexOf(user) >= 0) {
  250. __self.mods.splice(__self.mods.indexOf(user), 1);
  251. }
  252. break;
  253. }
  254. }
  255. }
  256. };
  257. // output to socket / console
  258. IRC.prototype.raw = function (data, hide) {
  259. var __self = this;
  260. __self.socket.write(data + '\r\n', __self.config.encoding, function (){
  261. if (!hide) {
  262. // monitor commands sent by the bot
  263. // and push them to command action
  264. var parse = data.split(' ');
  265. if (parse[0] === 'PRIVMSG') {
  266. parse = __self.options.name + ' ' + data;
  267. __self.emit('command', parse.split(' '));
  268. __self.emit('data', 'SENT - ' + __self.options.name + ' ' + data);
  269. } else {
  270. // output response
  271. __self.emit('data', 'SENT - ' + data);
  272. }
  273. }
  274. });
  275. };
  276. // who sent message
  277. IRC.prototype.caller = function (data) {
  278. var caller = data.split('!');
  279. return caller[0].charAt(1).toUpperCase() + caller[0].slice(2);
  280. };
  281. // send message to twitch chat
  282. IRC.prototype.msg = function (msg, options) {
  283. var __self = this, opts = options || {caller:null, auth:0};
  284. switch (opts.auth) {
  285. case 0:
  286. __self.raw('PRIVMSG ' + __self.config.channel + ' :' + msg);
  287. break;
  288. case 1:
  289. if (__self.mods.indexOf(opts.caller) >= 0) {
  290. __self.raw('PRIVMSG ' + __self.config.channel + ' :' + msg);
  291. }
  292. break;
  293. }
  294. };
  295. // message queue
  296. IRC.prototype.queue = function(msg) {
  297. var __self = this;
  298. __self.queue_messages.push(msg);
  299. };
  300. IRC.prototype.monitor_queue = function() {
  301. var __self = this, prepend_text = ['>', '+'];
  302. // handle messages in queue
  303. function handle_queue() {
  304. if (__self.queue_messages.length > 0) {
  305. var message = __self.queue_messages[0].message,
  306. options = __self.queue_messages[0].options,
  307. timer = __self.queue_messages[0].timer || __self.queue_timer;
  308. // change message if it's the same as the previous message
  309. if (message === __self.previous_message) {
  310. for (var i = 0; i < prepend_text.length; i++) {
  311. if (prepend_text[i] !== message.charAt(0)) {
  312. message = prepend_text[i] + message.slice(1);
  313. __self.previous_message = message;
  314. break;
  315. }
  316. }
  317. } else {
  318. __self.previous_message = __self.queue_messages[0].message;
  319. }
  320. // remove message from queue
  321. __self.queue_messages.splice(0, 1);
  322. // output message to chat
  323. setTimeout(function() {
  324. if (options === null) {
  325. __self.msg(message);
  326. } else {
  327. __self.msg(message, options);
  328. }
  329. // recheck the queue
  330. setTimeout(handle_queue, 500);
  331. }, timer);
  332. } else {
  333. setTimeout(handle_queue, 500);
  334. }
  335. }
  336. handle_queue();
  337. };
  338. module.exports = function (options) {
  339. return new IRC(options);
  340. };