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.

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