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.

389 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
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 : 'utf-8'
  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. __self.raw('TWITCHCLIENT 1');
  185. });
  186. // handle incoming socket data
  187. __self.socket.on('data', function (data) {
  188. var prepack, lines, line, word;
  189. prepack = data.replace('\r\n', '\n');
  190. __self.buffer += prepack;
  191. lines = __self.buffer.split('\n');
  192. __self.buffer = '';
  193. if (lines[lines.length - 1] !== '') {
  194. __self.buffer = lines[lines.length - 1];
  195. }
  196. lines = lines.splice(0, lines.length - 1);
  197. for (var i = 0; i < lines.length; i++) {
  198. line = lines[i].replace('\r','');
  199. word = line.replace('\r', '').split(' ');
  200. __self.emit('data', 'RECV - ' + line);
  201. __self.join(word);
  202. __self.pingpong(word);
  203. __self.moderators(word);
  204. if (word[3] !== undefined) {
  205. __self.emit('command', word);
  206. }
  207. }
  208. });
  209. __self.socket.on('error', function(){
  210. __self.reconnect();
  211. });
  212. };
  213. // reconnect to socket
  214. IRC.prototype.reconnect = function () {
  215. var __self = this;
  216. // send quit to server, destroy socket connection,
  217. // clear socket variable and then reconnect
  218. __self.socket.end('QUIT\r\n');
  219. __self.socket.destroy();
  220. __self.socket = null;
  221. __self.connect();
  222. };
  223. // join channel
  224. IRC.prototype.join = function (data) {
  225. var __self = this;
  226. if (data[3] === ':>' || data[3] === ':End') {
  227. __self.raw('JOIN ' + __self.config.channel);
  228. }
  229. };
  230. // ping / pong
  231. IRC.prototype.pingpong = function (data) {
  232. var __self = this;
  233. if (data[0] === 'PING') {
  234. __self.raw('PONG ' + data[1]);
  235. }
  236. };
  237. // store / remove mods
  238. IRC.prototype.moderators = function (data) {
  239. var __self = this;
  240. if (data[1] === 'MODE') {
  241. if (data[4] !== undefined) {
  242. var user = data[4].charAt(0).toUpperCase() + data[4].slice(1);
  243. switch (data[3]) {
  244. case '+o':
  245. if (__self.mods.indexOf(user) < 0) {
  246. __self.mods.push(user);
  247. }
  248. break;
  249. case '-o':
  250. if (__self.mods.indexOf(user) >= 0) {
  251. __self.mods.splice(__self.mods.indexOf(user), 1);
  252. }
  253. break;
  254. }
  255. }
  256. }
  257. };
  258. // output to socket / console
  259. IRC.prototype.raw = function (data, hide) {
  260. var __self = this;
  261. __self.socket.write(data + '\r\n', __self.config.encoding, function (){
  262. if (!hide) {
  263. // monitor commands sent by the bot
  264. // and push them to command action
  265. var parse = data.split(' ');
  266. if (parse[0] === 'PRIVMSG') {
  267. parse = __self.options.name + ' ' + data;
  268. __self.emit('command', parse.split(' '));
  269. __self.emit('data', 'SENT - ' + __self.options.name + ' ' + data);
  270. } else {
  271. // output response
  272. __self.emit('data', 'SENT - ' + data);
  273. }
  274. }
  275. });
  276. };
  277. // who sent message
  278. IRC.prototype.caller = function (data) {
  279. var caller = data.split('!');
  280. return caller[0].charAt(1).toUpperCase() + caller[0].slice(2);
  281. };
  282. // send message to twitch chat
  283. IRC.prototype.msg = function (msg, options) {
  284. var __self = this, opts = options || {caller:null, auth:0};
  285. switch (opts.auth) {
  286. case 0:
  287. __self.raw('PRIVMSG ' + __self.config.channel + ' :' + msg);
  288. break;
  289. case 1:
  290. if (__self.mods.indexOf(opts.caller) >= 0) {
  291. __self.raw('PRIVMSG ' + __self.config.channel + ' :' + msg);
  292. }
  293. break;
  294. }
  295. };
  296. // message queue
  297. IRC.prototype.queue = function(msg) {
  298. var __self = this;
  299. __self.queue_messages.push(msg);
  300. };
  301. IRC.prototype.monitor_queue = function() {
  302. var __self = this, prepend_text = ['>', '+'];
  303. // handle messages in queue
  304. function handle_queue() {
  305. if (__self.queue_messages.length > 0) {
  306. var message = __self.queue_messages[0].message,
  307. options = __self.queue_messages[0].options,
  308. timer = __self.queue_messages[0].timer || __self.queue_timer;
  309. // change message if it's the same as the previous message
  310. if (message === __self.previous_message) {
  311. for (var i = 0; i < prepend_text.length; i++) {
  312. if (prepend_text[i] !== message.charAt(0)) {
  313. message = prepend_text[i] + message.slice(1);
  314. __self.previous_message = message;
  315. break;
  316. }
  317. }
  318. } else {
  319. __self.previous_message = __self.queue_messages[0].message;
  320. }
  321. // remove message from queue
  322. __self.queue_messages.splice(0, 1);
  323. // output message to chat
  324. setTimeout(function() {
  325. if (options === null) {
  326. __self.msg(message);
  327. } else {
  328. __self.msg(message, options);
  329. }
  330. // recheck the queue
  331. setTimeout(handle_queue, 500);
  332. }, timer);
  333. } else {
  334. setTimeout(handle_queue, 500);
  335. }
  336. }
  337. handle_queue();
  338. };
  339. module.exports = function (options) {
  340. return new IRC(options);
  341. };