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.

1784 lines
72 KiB

12 years ago
12 years ago
12 years ago
11 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
11 years ago
11 years ago
12 years ago
12 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
12 years ago
11 years ago
12 years ago
11 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
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. * Currency(irc object, database object, [required options])
  4. *
  5. * example:
  6. * var Currency = require('./lib/plugins/currency.js')(irc, db, {
  7. * currency : 'currency name',
  8. * subscribers : 'google doc spreadsheet until it's available from twitch api'
  9. * });
  10. *
  11. * commands:
  12. * !<currency>
  13. * reply with currency amount
  14. *
  15. * !<currency> on/[off repeat on/off]
  16. * toggle currency request status,
  17. * repeat status only available when
  18. * turning off requests
  19. *
  20. * !<currency> auction open
  21. * open a new auction
  22. *
  23. * !<currency> auction close
  24. * close current auction
  25. *
  26. * !<currency> auction cancel
  27. * cancel current auction
  28. *
  29. * !<currency> auction draw
  30. * draw the next highest bidder
  31. *
  32. * !bid <amount>
  33. * place a bid on an open auction,
  34. * only valid amounts will be accepted
  35. *
  36. * !<currency> raffle open <price> <max>
  37. * open a new raffle
  38. * price and max is optional
  39. * default price: 10
  40. * default max: 10
  41. *
  42. * !<currency> raffle close
  43. * draw the another ticket from raffle
  44. *
  45. * !<currency> raffle cancel
  46. * cancel the open raffle
  47. *
  48. * !<currency> raffle draw
  49. * open a new auction
  50. *
  51. * !<currency> raffle restore
  52. * restores a previous raffle if a new
  53. * one is accidentally opened
  54. *
  55. * !ticket <amount>
  56. * place a bid on an open auction,
  57. * only valid amounts will be accepted
  58. */
  59. var https = require('https'),
  60. http = require('http'),
  61. file = require('fs'),
  62. utils = require('./utils.js');
  63. //-------- Construct ---------
  64. function Currency(irc, db, options) {
  65. var __self = this;
  66. __self.irc = irc;
  67. __self.db = db;
  68. // config
  69. __self.config = options || {};
  70. __self.config.currency = options.currency || 'coins';
  71. __self.config.subscribers_json = options.subscribers || '';
  72. __self.config.website = options.website || '';
  73. __self.config.modpowers = options.modpowers || false;
  74. __self.config.ignorelist = options.ignorelist || ['jtv'];
  75. // general settings
  76. __self.pre_text = '> ' + __self.config.currency + ': ';
  77. __self.max_requests = 10;//response after 10 request
  78. __self.temp = {};
  79. // currency request settings
  80. __self.coin_flood = [];
  81. __self.coin_response = null;
  82. __self.coin_response_timer = 3000;
  83. __self.coin_response_reset = true;
  84. __self.coin_toggle = false;
  85. __self.coin_toggle_msg = null;
  86. __self.coin_toggle_timer = 180000;//milliseconds
  87. __self.top_flood = [];
  88. __self.top_response_reset = true;
  89. __self.rank_flood = [];
  90. __self.rank_response_reset = true;
  91. // auction settings
  92. __self.auction_status = false;
  93. __self.auction_bids = [];
  94. __self.auction_previous_bid = {};
  95. __self.auction_bid_response = null;
  96. __self.auction_bid_response_long = null;
  97. // handout coins settings
  98. __self.viewer_list = [];
  99. __self.streaming = false;
  100. __self.streaming_check = 4;//minutes
  101. __self.give_coins = false;
  102. __self.give_coins_timer = options.payrate || 30;//minutes
  103. __self.subscriber_check = 4;//minutes
  104. __self.subscribers = [];
  105. // raffle settings
  106. __self.raffle_status = false;
  107. __self.raffle_ticket_requests = [];
  108. __self.raffle_tickets = [];
  109. __self.raffle_ticket_cost = 10;//currency cost per ticket
  110. __self.raffle_max_tickets = 10;
  111. // raffle restoration
  112. __self.raffle_restore_ticket_requests = [];
  113. __self.raffle_restore_tickets = [];
  114. // gambling settings
  115. __self.bets_status = false;
  116. __self.bets_board = [];
  117. __self.bets_viewers = [];
  118. __self.bets_payout = false;
  119. __self.bets_total = 0;
  120. __self.bets_flood = [];
  121. __self.bets_response = null;
  122. __self.bets_response_timer = 3000;
  123. __self.bets_response_reset = true;
  124. // high score table
  125. __self.score_bet_pool = 0;
  126. __self.score_bet_single = 0;
  127. }
  128. //-------- Methods ---------
  129. Currency.prototype.start = function () {
  130. var __self = this;
  131. // populate highscore holders
  132. __self.db.execute('SELECT * FROM highscores', function(rows) {
  133. rows.forEach(function(element, index, array){
  134. switch(element.name) {
  135. case 'bet_pool':
  136. __self.score_bet_pool = element.value;
  137. break;
  138. case 'bet_single':
  139. __self.score_bet_single = element.value;
  140. break;
  141. }
  142. });
  143. });
  144. __self.handout_coins();
  145. };
  146. Currency.prototype.commands = function (data) {
  147. var __self = this,
  148. broadcaster_bot_initiated = __self.irc.caller(data[0]).toLowerCase() === __self.irc.config.broadcaster.toLowerCase() || __self.irc.caller(data[0]).toLowerCase() === __self.irc.config.name.toLowerCase(),
  149. moderator_initiated = __self.irc.mods.indexOf(__self.irc.caller(data[0])) >= 0;
  150. // handle !<currency> commands
  151. if (data[3].slice(1) === '!' + __self.config.currency.toLowerCase()) {
  152. // public commands
  153. if (!__self.coin_toggle && data[4] === undefined) {
  154. __self.get_coins(__self.irc.caller(data[0]));
  155. }
  156. // broadcaster only commands
  157. if (broadcaster_bot_initiated || (__self.config.modpowers && moderator_initiated)) {
  158. //open / close auction system
  159. if (data[4] === 'auction') {
  160. switch (data[5]) {
  161. case 'open':
  162. __self.auction(true);
  163. break;
  164. case 'close':
  165. __self.auction(false);
  166. break;
  167. case 'draw':
  168. __self.next_auction_winner();
  169. break;
  170. case 'cancel':
  171. __self.auction('cancel');
  172. break;
  173. case 'help':
  174. __self.irc.emit('message', {message:__self.pre_text + '!' + __self.config.currency.toLowerCase() + ' auction open/close/cancel/draw'});
  175. break;
  176. }
  177. }
  178. // open / close raffle system
  179. if (data[4] === 'raffle') {
  180. switch (data[5]) {
  181. case 'open':
  182. if (data[6] && data[7] && !__self.raffle_status) {
  183. if(parseInt(data[6], 10) > 0 && parseInt(data[7], 10) > 0) {
  184. // save default values
  185. __self.temp.raffle_ticket_cost = __self.raffle_ticket_cost;
  186. __self.temp.raffle_max_tickets = __self.raffle_max_tickets;
  187. // set new raffle cost / amount
  188. __self.raffle_ticket_cost = data[6];
  189. __self.raffle_max_tickets = data[7];
  190. }
  191. } else if (__self.temp.raffle_ticket_cost && __self.temp.raffle_max_tickets && !__self.raffle_status){
  192. __self.raffle_ticket_cost = __self.temp.raffle_ticket_cost;
  193. __self.raffle_max_tickets = __self.temp.raffle_max_tickets;
  194. delete __self.temp.raffle_ticket_cost;
  195. delete __self.temp.raffle_max_tickets;
  196. }
  197. __self.raffle(true);
  198. break;
  199. case 'close':
  200. __self.raffle(false);
  201. break;
  202. case 'draw':
  203. __self.next_raffle_winner();
  204. break;
  205. case 'cancel':
  206. __self.raffle('cancel');
  207. break;
  208. case 'restore':
  209. __self.raffle('restore');
  210. break;
  211. case 'help':
  212. __self.irc.emit('message', {message:__self.pre_text + '!' + __self.config.currency.toLowerCase() + ' raffle open <ticket cost> <tickets per user>/close/cancel/draw/restore'});
  213. break;
  214. }
  215. }
  216. // open / close betting system
  217. if (data[4] === 'bet') {
  218. console.log("bet command found");
  219. switch (data[5]) {
  220. case 'open':
  221. console.log("bet open command found");
  222. if (data[6] && data[7]) {
  223. __self.bets(true, data);
  224. } else {
  225. __self.irc.emit('message', {message:__self.pre_text + 'Unable to open betting, need at least two items to bet against'});
  226. }
  227. break;
  228. case 'close':
  229. __self.bets(false, null);
  230. break;
  231. case 'pool':
  232. __self.bets('pool', null);
  233. break;
  234. case 'winner':
  235. __self.bets('winner', data[6]);
  236. break;
  237. case 'cancel':
  238. __self.bets('cancel');
  239. break;
  240. case 'help':
  241. __self.irc.emit('message', {message:__self.pre_text + '!' + __self.config.currency.toLowerCase() + ' bet open <option A> <option B> <etc>/close/pool/winner <option>/cancel'});
  242. break;
  243. }
  244. }
  245. // TODO: create a log file that monitors just add/remove/push - greedy mods :D
  246. // add currency
  247. if (data[4] === 'add') {
  248. if(parseInt(data[5], 10) > 0 && data[6]) {
  249. __self.adjust_currency('add', data[5], data[6]);
  250. }
  251. }
  252. // remove currency
  253. if (data[4] === 'remove') {
  254. if(parseInt(data[5], 10) > 0 && data[6]) {
  255. __self.adjust_currency('remove', data[5], data[6]);
  256. }
  257. }
  258. // push currency to new viewer
  259. if (data[4] === 'push') {
  260. if(parseInt(data[5], 10) > 0 && data[6]) {
  261. __self.adjust_currency('push', data[5], data[6]);
  262. }
  263. }
  264. }
  265. // moderator commands
  266. if (broadcaster_bot_initiated || moderator_initiated) {
  267. // enable/disable currency requests
  268. switch (data[4]) {
  269. case 'on':
  270. if (!__self.auction_status && !__self.raffle_status) {
  271. __self.coin_toggle = false;
  272. // output currency request status
  273. __self.irc.emit('message', {message:__self.pre_text + 'Currency requests are now enabled. Type !' + __self.config.currency.toLowerCase() + ' to view your total'});
  274. // stop periodic message
  275. clearInterval(__self.coin_toggle_msg);
  276. }
  277. break;
  278. case 'off':
  279. if (!__self.auction_status && !__self.raffle_status) {
  280. var msg;
  281. if (!__self.coin_toggle) {
  282. // output message depending on if an offsite is provided
  283. if (__self.config.website !== '') {
  284. msg = __self.pre_text + 'Currency requests have been disabled. To view your ' + __self.config.currency + ' please visit ' + __self.config.website;
  285. __self.irc.emit('message', {message:msg});
  286. } else {
  287. msg = __self.pre_text + 'Currency requests have been disabled';
  288. __self.irc.emit('message', {message:msg});
  289. }
  290. }
  291. // start the periodic message
  292. if (data[5] !== undefined && data[6] !== undefined) {
  293. // manually enable / disable repeat
  294. if (data[5] === 'repeat') {
  295. switch (data[6]) {
  296. case 'on':
  297. __self.irc.emit('message', {message:'+ Periodic notification enabled'});
  298. __self.coin_toggle_msg = setInterval(function () {
  299. if (__self.coin_toggle) {
  300. msg = __self.pre_text + 'To view your ' + __self.config.currency + ' please visit ' + __self.config.website;
  301. __self.irc.emit('message', {message:msg});
  302. }
  303. }, __self.coin_toggle_timer);
  304. break;
  305. case 'off':
  306. __self.irc.emit('message', {message:'+ Periodic notification disabled'});
  307. clearInterval(__self.coin_toggle_msg);
  308. }
  309. }
  310. }
  311. __self.coin_toggle = true;
  312. }
  313. break;
  314. }
  315. // adjust currency response rate
  316. if (data[4] === 'timer') {
  317. if (isNaN(parseInt(data[5], 10)) === false) {
  318. if (data[5] >= 3 && data[5] % 1 === 0) {
  319. __self.coin_response_timer = data[5] * 1000;
  320. __self.irc.emit('message', {message:__self.pre_text + 'Currency totals will now show ' + data[5] + ' seconds after request'});
  321. if (data[6] && data[7]) {
  322. if (data[6] === 'reset') {
  323. switch(data[7]) {
  324. case 'on':
  325. __self.irc.emit('message', {message:'+ Timer will now reset after each new request'});
  326. __self.coin_response_reset = true;
  327. break;
  328. case 'off':
  329. __self.irc.emit('message', {message:'+ Timer will not reset after each new request'});
  330. __self.coin_response_reset = false;
  331. break;
  332. }
  333. }
  334. }
  335. } else if (data[5] < 3) {
  336. __self.irc.emit('message', {message:__self.pre_text + 'Timer cannot be less than 2 seconds'});
  337. }
  338. }
  339. }
  340. }
  341. }
  342. // public commands related to !<currency>
  343. switch (data[3].slice(1)) {
  344. // submit bid for the auction
  345. case '!bid':
  346. if (isNaN(parseInt(data[4], 10)) === false) {
  347. if (data[4] > 0 && data[4] % 1 === 0) {
  348. __self.bid(__self.irc.caller(data[0]), parseInt(data[4], 10));
  349. }
  350. }
  351. break;
  352. // purchase a ticket for raffle
  353. case '!ticket':
  354. if (isNaN(parseInt(data[4], 10)) === false) {
  355. if (data[4] >= 0 && data[4] % 1 === 0) {
  356. __self.collect_tickets(__self.irc.caller(data[0]), parseInt(data[4], 10));
  357. }
  358. }
  359. break;
  360. // show top currency earners
  361. case '!top':
  362. __self.get_top(data[4]);
  363. break;
  364. // show a users ranking in the ladder
  365. case '!rank':
  366. __self.get_rank(__self.irc.caller(data[0]).toLowerCase());
  367. break;
  368. }
  369. // place a bet
  370. if (__self.bets_status === true) {
  371. for (var i = 0; i < __self.bets_board.length; i++) {
  372. if (data[3].slice(1) === '!' + __self.bets_board[i].name) {
  373. if (isNaN(parseInt(data[4], 10)) === false) {
  374. if (data[4] >= 0 && data[4] % 1 === 0) {
  375. __self.collect_bets(__self.irc.caller(data[0]), __self.bets_board[i], parseInt(data[4], 10));
  376. break;
  377. }
  378. }
  379. }
  380. }
  381. }
  382. };
  383. /**
  384. * ============================================
  385. * CURRENCY REQUESTS
  386. * --------------------------------------------
  387. */
  388. Currency.prototype.get_coins = function (caller) {
  389. var __self = this;
  390. function fill_request(viewer, points) {
  391. var request = '(' + points + ')';
  392. if (__self.raffle_status) {
  393. for (var i = 0; i < __self.raffle_ticket_requests.length; i++){
  394. if (__self.raffle_ticket_requests[i].viewer.toLowerCase() === viewer.toLowerCase() && (__self.raffle_ticket_requests[i].tickets * __self.raffle_ticket_cost) <= points) {
  395. request = '(' + (points - (__self.raffle_ticket_requests[i].tickets * __self.raffle_ticket_cost)) + ') [' + __self.raffle_ticket_requests[i].tickets + ']';
  396. break;
  397. }
  398. }
  399. return request;
  400. } else {
  401. return request;
  402. }
  403. }
  404. function do_work() {
  405. var multi_response = '';
  406. if (__self.coin_flood.length > 1) {// send flood requests
  407. __self.query_coins(__self.coin_flood, function (rows) {
  408. for (var i = 0; i < rows.length; i++) {
  409. var currency_request = fill_request(rows[i].user, rows[i].points);
  410. // setup currency response
  411. if (i !== rows.length - 1) {
  412. multi_response += rows[i].user + ' ' + currency_request + ', ';
  413. } else {
  414. multi_response += rows[i].user + ' ' + currency_request;
  415. }
  416. }
  417. __self.irc.emit('message', {message:__self.pre_text + multi_response, timer: 1});
  418. });
  419. } else if (__self.coin_flood.length === 1) {// send single request
  420. __self.query_coins(caller, function (rows) {
  421. var currency_request = fill_request(rows[0].user, rows[0].points);
  422. __self.irc.emit('message', {message:__self.pre_text + caller + ' ' + currency_request, timer: 1});
  423. });
  424. }
  425. // clear flood requests
  426. __self.coin_flood = [];
  427. }
  428. // add flood users to array
  429. if (__self.coin_flood.indexOf(caller) < 0) {
  430. __self.coin_flood.push(caller);
  431. }
  432. // clear timer on flood
  433. if (__self.coin_response_reset) {
  434. clearTimeout(__self.coin_response);
  435. }
  436. // check if flood has a set amount of requests and output
  437. // if not, set the output timer
  438. if (__self.coin_flood.length === __self.max_requests) {
  439. do_work();
  440. } else {
  441. if (__self.coin_response_reset) {
  442. __self.coin_response = setTimeout(function () {do_work();}, __self.coin_response_timer);
  443. } else {
  444. setTimeout(function () {do_work();}, __self.coin_response_timer);
  445. }
  446. }
  447. };
  448. Currency.prototype.get_rank = function (data) {
  449. var __self = this;
  450. function do_work() {
  451. __self.query_rank(__self.rank_flood, function (ranks, total) {
  452. var response = '> Rank (of '+total+'): ';
  453. for(var i = 0; i < ranks.length; i++) {
  454. response += ranks[i]['user'] + ' ('+ranks[i]['@row_number:=@row_number+1']+') ['+ranks[i]['points']+'], ';
  455. }
  456. response = response.slice(1,-2);
  457. __self.irc.emit('message', {message: response, timer: 1});
  458. });
  459. __self.rank_flood = [];
  460. }
  461. // add flood users to array
  462. if (__self.rank_flood.indexOf(data) < 0) {
  463. __self.rank_flood.push(data);
  464. }
  465. // clear timer on flood
  466. if (__self.rank_response_reset) {
  467. clearTimeout(__self.rank_response);
  468. }
  469. // check if flood has hit max requests or set timer
  470. if (__self.rank_flood.length === __self.max_requests) {
  471. do_work();
  472. } else {
  473. if (__self.rank_response_reset) {
  474. __self.rank_response = setTimeout(function () {do_work();}, __self.coin_response_timer);
  475. } else {
  476. setTimeout(function () {do_work();}, __self.coin_response_timer);
  477. }
  478. }
  479. };
  480. Currency.prototype.get_top = function (data) {
  481. var __self = this;
  482. var limit = 0;
  483. function do_work() {
  484. var response = '';
  485. __self.query_top(__self.top_flood, function (rows) {
  486. for (var i = 0; i < rows.length; i++) {
  487. // setup response
  488. if (i !== rows.length - 1) {
  489. response += '#'+(i+1)+': ' + rows[i].user + '('+rows[i].points+'), ';
  490. } else {
  491. response += '#'+(i+1)+': ' + rows[i].user + '('+rows[i].points+')';
  492. }
  493. }
  494. __self.irc.emit('message', {message:'> Top Farmers: ' + response});
  495. });
  496. // clear flood requests
  497. __self.top_flood = [];
  498. }
  499. // check that we got a number
  500. ( isNaN(data) ) ? limit = 3 : limit = data;
  501. // limit the number
  502. if (limit > 6) limit = 3;
  503. // add flood users to array
  504. if (__self.top_flood.indexOf(limit) < 0) {
  505. __self.top_flood.push(limit);
  506. }
  507. // clear timer on flood
  508. if (__self.top_response_reset) {
  509. clearTimeout(__self.top_response);
  510. }
  511. // check if flood has hit max requests or set timer
  512. if (__self.top_flood.length === __self.max_requests) {
  513. do_work();
  514. } else {
  515. if (__self.top_response_reset) {
  516. __self.top_response = setTimeout(function () {do_work();}, __self.coin_response_timer);
  517. } else {
  518. setTimeout(function () {do_work();}, __self.coin_response_timer);
  519. }
  520. }
  521. };
  522. Currency.prototype.query_rank = function(data, callback) {
  523. var __self = this;
  524. var ranks = {}, total = 0;
  525. var inner_sql = '', sql = 'set @row_number:=0;';
  526. data.forEach(function(user) {
  527. inner_sql += "user='"+user+"' or ";
  528. });
  529. // remove last or
  530. inner_sql=inner_sql.slice(0,-4);
  531. sql += "select * from (select user, points, @row_number:=@row_number+1 from viewers order by points desc) as rank where ("+inner_sql+");select count(1) from viewers where points > 0;";
  532. __self.db.execute(sql, function(rows) {
  533. total = rows[2][0]['count(1)'];
  534. ranks = rows[1];
  535. callback(ranks, total);
  536. });
  537. };
  538. Currency.prototype.query_top = function (data, callback) {
  539. var __self = this, sql = '';
  540. // build sql conditions
  541. sql = 'SELECT * FROM viewers ORDER BY points DESC LIMIT ' + data + ';';
  542. __self.db.execute(sql, function(rows) {
  543. callback(rows);
  544. });
  545. };
  546. Currency.prototype.query_coins = function (data, callback) {
  547. var __self = this, sql = '';
  548. // build sql conditions
  549. if (typeof data === 'string') {
  550. sql = 'SELECT * FROM viewers WHERE ' + 'user = \'' + data.toLowerCase() + '\'';
  551. } else {
  552. for (var i = 0; i < data.length; i++) {
  553. if (i !== data.length - 1) {
  554. sql += 'SELECT * FROM viewers WHERE ' + 'user = \'' + data[i].toLowerCase() + '\'' + ';';
  555. } else {
  556. sql += 'SELECT * FROM viewers WHERE ' + 'user = \'' + data[i].toLowerCase() + '\'';
  557. }
  558. }
  559. }
  560. // execute query
  561. __self.db.execute(sql, function (rows) {
  562. var temp = [], newrows = [];
  563. if (typeof data !== 'string') {
  564. // get rid of the nested arrays
  565. for (var i = 0; i < rows.length; i++) {
  566. if (rows[i].length > 0) {
  567. newrows.push(rows[i][0]);
  568. }
  569. }
  570. // separate users into their own array
  571. for (var i = 0; i < newrows.length; i++) {
  572. temp.push(newrows[i].user.charAt(0).toUpperCase() + newrows[i].user.slice(1));
  573. }
  574. // compare the users in the data array against the temp array
  575. // if not found, push them to rows with 0 points
  576. for (var i = 0; i < data.length; i++) {
  577. if (temp.indexOf(data[i]) < 0) {
  578. newrows.push({'user' : data[i], 'points' : 0});
  579. }
  580. }
  581. // capitalize usernames on rows
  582. for (var key in newrows) {
  583. if (newrows.hasOwnProperty(key)) {
  584. newrows[key].user = newrows[key].user.charAt(0).toUpperCase() + newrows[key].user.slice(1);
  585. }
  586. }
  587. rows = newrows;
  588. } else {
  589. if (rows.length === 0) {
  590. rows = [{'user' : data, 'points' : 0}];
  591. } else {
  592. rows[0].user = rows[0].user.charAt(0).toUpperCase() + rows[0].user.slice(1);
  593. }
  594. }
  595. callback(rows);
  596. });
  597. };
  598. /**
  599. * ============================================
  600. * HANDOUT CURRENCY
  601. * --------------------------------------------
  602. */
  603. Currency.prototype.handout_coins = function () {
  604. var __self = this;
  605. // check stream status
  606. function stream_status() {
  607. var time = utils.make_interval(__self.streaming_check);
  608. if (time === 0) {
  609. // get stream status
  610. https.get('https://api.twitch.tv/kraken/streams/' + __self.irc.config.channel.slice(1), function (response) {
  611. var body = '';
  612. // put together response
  613. response.on('data', function (chunk) {
  614. body += chunk;
  615. });
  616. // start / stop handing out coins based on stream status
  617. response.on('end', function () {
  618. var json = JSON.parse(body);
  619. __self.streaming = json.stream !== null;
  620. if (__self.streaming && __self.give_coins === false) {
  621. insert_coins();
  622. }
  623. __self.irc.emit('data', 'DATA - Online Status Check - Returned: ' + __self.streaming);
  624. setTimeout(stream_status, 1000);
  625. });
  626. });
  627. } else {
  628. setTimeout(stream_status, time);
  629. }
  630. }
  631. // get subscribers
  632. function subscribers() {
  633. var time = utils.make_interval(__self.subscriber_check);
  634. if (time === 0) {
  635. // get stream status
  636. http.get(__self.config.subscribers_json, function (response) {
  637. var body = '';
  638. // put together response
  639. response.on('data', function (chunk) {
  640. body += chunk;
  641. });
  642. // start / stop handing out coins based on stream status
  643. response.on('end', function () {
  644. var json = JSON.parse(body);
  645. var entries = json.feed.entry, subs = '';
  646. __self.subscribers = [];
  647. for (var i = 0; i < entries.length; i++) {
  648. __self.subscribers.push(entries[i].title['$t']);
  649. subs += entries[i].title['$t'] + ' ';
  650. }
  651. __self.irc.emit('data', 'DATA - Subscriber Check - Returned: ' + subs);
  652. setTimeout(subscribers, 1000);
  653. });
  654. });
  655. } else {
  656. setTimeout(subscribers, time);
  657. }
  658. }
  659. // trigger coin handout
  660. function insert_coins() {
  661. __self.give_coins = __self.streaming;
  662. console.log('DATA - Inserting coins');
  663. if (__self.give_coins) {
  664. var time = utils.make_interval(__self.give_coins_timer);
  665. if (time === 0) {
  666. // build sql from the saved viewer list
  667. var sql = '';
  668. for (var i = 0; i < __self.viewer_list.length; i++) {
  669. var currency_amount = __self.subscribers.indexOf(__self.viewer_list[i]) >= 0 ? 2 : 1;
  670. if (__self.viewer_list[i] !== '') {
  671. if (i != __self.viewer_list.length - 1) {
  672. sql += 'INSERT INTO viewers (user, points) ';
  673. sql += 'VALUES (\'' + __self.viewer_list[i] + '\', ' + currency_amount + ') ';
  674. sql += 'ON DUPLICATE KEY UPDATE points = points + ' + currency_amount + '; ';
  675. } else {
  676. sql += 'INSERT INTO viewers (user, points) ';
  677. sql += 'VALUES (\'' + __self.viewer_list[i] + '\', ' + currency_amount + ') ';
  678. sql += 'ON DUPLICATE KEY UPDATE points = points + ' + currency_amount;
  679. }
  680. }
  681. }
  682. // execute query
  683. __self.db.execute(sql, function () {});
  684. setTimeout(insert_coins, 1000);
  685. } else {
  686. setTimeout(insert_coins, time);
  687. }
  688. }
  689. }
  690. // monitor viewers in irc
  691. __self.irc.on('data', function (data) {
  692. var data_split = data.split(' '), viewer = '';
  693. // viewers from \who
  694. // TODO: new twitch chat doesn't allow /who, remove this?
  695. if (data_split[3] == '352') {
  696. if (data_split[6] !== undefined) {
  697. viewer = data_split[6].toLowerCase();
  698. if (__self.viewer_list.indexOf(viewer) < 0 && __self.config.ignorelist.indexOf(viewer) < 0) {
  699. __self.viewer_list.push(viewer);
  700. }
  701. }
  702. }
  703. // Processing below replaces the now defunct who-calls above
  704. // handle /names
  705. if (data_split[3] == '353'){
  706. viewers = data_split.splice(7);
  707. viewers.forEach(function(name) {
  708. if(name.charAt(0) === ':') name = name.slice(1);
  709. if (__self.viewer_list.indexOf(name) < 0 && __self.config.ignorelist.indexOf(name) < 0) {
  710. __self.viewer_list.push(name);
  711. }
  712. });
  713. }
  714. // handle JOIN's
  715. if (data_split[3] == 'JOIN') {
  716. var servernick = data_split[2].toLowerCase().split('!');
  717. viewer = servernick[0];
  718. if (viewer != __self.irc.config.name.toLowerCase()) {
  719. viewer = viewer.slice(1);
  720. }
  721. if (__self.viewer_list.indexOf(viewer) < 0 && __self.config.ignorelist.indexOf(viewer) < 0) {
  722. __self.viewer_list.push(viewer);
  723. } else {
  724. }
  725. }
  726. // handle PART's
  727. if (data_split[3] == 'PART') {
  728. var servernick = data_split[2].toLowerCase().split('!');
  729. viewer = servernick[0];
  730. if (viewer != __self.irc.config.name.toLowerCase()) {
  731. viewer = viewer.slice(1);
  732. }
  733. var viewer_idx = __self.viewer_list.indexOf(viewer);
  734. if (viewer_idx >= 0) {
  735. __self.viewer_list.splice(viewer_idx,1);
  736. } else {
  737. console.log("[*ERROR*] User "+viewer+" PART'ed but was not in viewer_list.");
  738. }
  739. }
  740. // viewers chatting
  741. if (data_split[3] == 'PRIVMSG') {
  742. var servernick = data_split[2].toLowerCase().split('!');
  743. viewer = servernick[0];
  744. if (viewer != __self.irc.config.name.toLowerCase()) {
  745. viewer = viewer.slice(1);
  746. }
  747. // add missing chatters to viewer_list
  748. if (__self.viewer_list.indexOf(viewer) < 0 && __self.config.ignorelist.indexOf(viewer) < 0) {
  749. __self.viewer_list.push(viewer);
  750. console.log("[*ADD NAME*] "+viewer+" from CHAT");
  751. }
  752. // log chat if enabled
  753. if (__self.irc.store_chat && __self.config.ignorelist.indexOf(viewer) < 0) {
  754. file.exists(__self.irc.chat_log, function (exists) {
  755. if (exists) {
  756. // get a timestamp
  757. var date = new Date(),
  758. hours = date.getHours() < 10 ? '0' + date.getHours() : date.getHours(),
  759. min = date.getMinutes() < 10 ? '0' + date.getMinutes() : date.getMinutes(),
  760. sec = date.getSeconds() < 10 ? '0' + date.getSeconds() : date.getSeconds(),
  761. streaming_time = '';
  762. // create start time string
  763. streaming_time += hours.toString() + ':' + min.toString();
  764. // craft message
  765. data_split[5] = data_split[5].slice(1);
  766. var message = data_split.slice(5).join(' ');
  767. // put it all together
  768. var logline = '[' + streaming_time + '] <' + viewer + '> ' + message + '\r\n';
  769. file.appendFile(__self.irc.chat_log, logline, 'utf-8', function (err) {
  770. if (err) {
  771. throw err;
  772. }
  773. });
  774. }
  775. });
  776. }
  777. }
  778. });
  779. stream_status();
  780. // only start subscribers if gdoc is available
  781. if (__self.config.subscribers_json !== '') {
  782. subscribers();
  783. }
  784. };
  785. /**
  786. * ============================================
  787. * Adjust Currency
  788. * --------------------------------------------
  789. */
  790. Currency.prototype.adjust_currency = function (method, amount, viewer) {
  791. var __self = this;
  792. viewer = viewer.toLowerCase();
  793. __self.db.execute('SELECT * FROM viewers WHERE user=\'' + viewer + '\'', function(rows){
  794. if (rows.length > 0 || method === 'push') {
  795. var check = rows.length > 0 ? rows[0].user : rows.push({user: viewer});
  796. if (check === viewer || method === 'push') {
  797. var sql = '', settings = [];
  798. // push settings for message
  799. if (method === 'add' || method === 'push') {
  800. settings.push('+');
  801. settings.push('Added');
  802. settings.push('to');
  803. } else if (method === 'remove') {
  804. settings.push('-');
  805. settings.push('Removed');
  806. settings.push('from');
  807. }
  808. settings.push(rows[0].user.charAt(0).toUpperCase() + rows[0].user.slice(1));
  809. // create sql
  810. if (method === 'add' || method === 'remove') {
  811. sql += 'UPDATE viewers ';
  812. sql += 'SET points = points ' + settings[0] + ' ' + amount + ' ';
  813. sql += 'WHERE user = \'' + rows[0].user + '\'; ';
  814. } else if (method === 'push') {
  815. sql += 'INSERT INTO viewers (user, points) ';
  816. sql += 'VALUES (\'' + viewer + '\', ' + amount + ') ';
  817. sql += 'ON DUPLICATE KEY UPDATE points = points + ' + amount + '; ';
  818. }
  819. //execute adjustment
  820. __self.db.execute(sql, function(){
  821. __self.irc.emit('message', {message:__self.pre_text + settings[1] + ' ' + amount + ' ' + __self.config.currency + ' ' + settings[2] + ' ' + settings[3]});
  822. });
  823. }
  824. } else {
  825. __self.irc.emit('message', {message:__self.pre_text + 'User was not found, use the push command to add a new user'});
  826. }
  827. });
  828. };
  829. /**
  830. * ============================================
  831. * AUCTION SYSTEM
  832. * --------------------------------------------
  833. */
  834. Currency.prototype.auction = function (status) {
  835. var __self = this;
  836. switch (status) {
  837. case true:
  838. if (!__self.bets_status) {
  839. if (!__self.raffle_status) {
  840. if (!__self.auction_status) {
  841. // open up the auction
  842. __self.auction_status = true;
  843. // request toggle
  844. if (__self.temp.raffle_toggle) {
  845. __self.temp.auction_toggle = __self.temp.raffle_toggle;
  846. } else {
  847. __self.temp.auction_toggle = __self.coin_toggle;
  848. }
  849. __self.coin_toggle = false;
  850. // default request timer
  851. if (__self.temp.raffle_timer && __self.temp.raffle_timer_reset) {
  852. __self.temp.auction_timer = __self.temp.raffle_timer;
  853. __self.temp.auction_timer_reset = __self.temp.raffle_timer_reset;
  854. } else {
  855. __self.temp.auction_timer = __self.coin_response_timer;
  856. __self.temp.auction_timer_reset = __self.coin_response_reset;
  857. }
  858. __self.coin_response_timer = 3000;
  859. __self.coin_response_reset = true;
  860. // clear previous bids
  861. __self.auction_bids = [];
  862. __self.auction_previous_bid = {};
  863. // auction open response
  864. __self.irc.emit('message', {message:__self.pre_text + 'Auction opened, accepting bids'})
  865. } else {
  866. // auction is already open response
  867. __self.irc.emit('message', {message:__self.pre_text + 'Auction already in progress'});
  868. }
  869. } else {
  870. // raffle currently running
  871. __self.irc.emit('message', {message:__self.pre_text + 'You must close the raffle before you can open an auction'});
  872. }
  873. } else {
  874. // gambling currently running
  875. __self.irc.emit('message', {message:__self.pre_text + 'Betting must be closed before you can open an auction'});
  876. }
  877. break;
  878. case false:
  879. if (__self.auction_status) {
  880. // close the auction
  881. __self.auction_status = false;
  882. // request toggle
  883. __self.coin_toggle = __self.temp.auction_toggle;
  884. delete __self.temp.auction_toggle;
  885. // default request timer
  886. __self.coin_response_timer = __self.temp.auction_timer;
  887. __self.coin_response_reset = __self.temp.auction_timer_reset;
  888. delete __self.temp.auction_timer;
  889. delete __self.temp.auction_timer_reset;
  890. // clear response timers
  891. clearTimeout(__self.auction_bid_response);
  892. clearInterval(__self.auction_bid_response_long);
  893. if (__self.auction_bids.length > 0) {
  894. // pick a winner response
  895. for (var i = 0; i < __self.auction_bids.length; i++) {
  896. if (__self.auction_bids[i].bid === utils.max(__self.auction_bids)) {
  897. __self.irc.emit('message', {message:__self.pre_text + 'Auction closed, Winner: ' + __self.auction_bids[i].viewer + ' @ ' + __self.auction_bids[i].bid});
  898. // save the winners info for draw refund
  899. __self.auction_previous_bid.viewer = __self.auction_bids[i].viewer;
  900. __self.auction_previous_bid.bid = __self.auction_bids[i].bid;
  901. // remove winners money
  902. var sql = '';
  903. sql += 'UPDATE viewers ';
  904. sql += 'SET points = points - ' + __self.auction_bids[i].bid + ' ';
  905. sql += 'WHERE user = \'' + __self.auction_bids[i].viewer + '\'';
  906. __self.db.execute(sql, function() {});
  907. // remove winner from main list
  908. __self.auction_bids.splice(i, 1);
  909. break;
  910. }
  911. }
  912. } else {
  913. // no bidders to pick from response
  914. __self.irc.emit('message', {message:__self.pre_text + 'Auction closed, no bidders to pick a winner'});
  915. }
  916. } else {
  917. // auction is already open response
  918. __self.irc.emit('message', {message:__self.pre_text + 'Auction is already closed'});
  919. }
  920. break;
  921. case 'cancel':
  922. if (__self.auction_status) {
  923. // close the auction
  924. __self.auction_status = false;
  925. // request toggle
  926. __self.coin_toggle = __self.temp.auction_toggle;
  927. delete __self.temp.auction_toggle;
  928. // default request timer
  929. __self.coin_response_timer = __self.temp.auction_timer;
  930. __self.coin_response_reset = __self.temp.auction_timer_reset;
  931. delete __self.temp.auction_timer;
  932. delete __self.temp.auction_timer_reset;
  933. // clear response timers
  934. clearTimeout(__self.auction_bid_response);
  935. clearInterval(__self.auction_bid_response_long);
  936. // clear previous bids
  937. __self.auction_bids = [];
  938. __self.auction_previous_bid = {};
  939. // auction cancelled notification
  940. __self.irc.emit('message', {message:__self.pre_text + 'Auction has been cancelled'});
  941. } else {
  942. // auction cancelled notification
  943. __self.irc.emit('message', {message:__self.pre_text + 'Auction is not opened'});
  944. }
  945. break;
  946. }
  947. };
  948. Currency.prototype.bid = function (caller, amount) {
  949. var __self = this;
  950. function find_duplicate(amount) {
  951. var duplicate = false;
  952. for (var i = 0; i < __self.auction_bids.length; i++) {
  953. if (__self.auction_bids[i].bid === amount) {
  954. duplicate = true;
  955. break;
  956. }
  957. }
  958. return duplicate;
  959. }
  960. if (__self.auction_status) {
  961. // verify that bidder has the coins for bidding
  962. __self.query_coins(caller, function (rows) {
  963. var has_tickets = false;
  964. // only add bid if they have the enough to pay
  965. if (rows[0].points >= amount) {
  966. if (__self.auction_bids.length > 0) {
  967. // check if an existing bid exists and modify it
  968. for (var i = 0; i < __self.auction_bids.length; i++) {
  969. if (__self.auction_bids[i].viewer === caller) {
  970. has_tickets = true;
  971. // check if bid is higher then original and not a duplicate
  972. if (__self.auction_bids[i].bid < amount && !find_duplicate(amount)) {
  973. __self.auction_bids[i].bid = amount;
  974. }
  975. break;
  976. }
  977. }
  978. // add new bids to list if they are not a duplicate
  979. if (!has_tickets && !find_duplicate(amount)) {
  980. __self.auction_bids.push({viewer: caller, bid: amount});
  981. }
  982. } else {
  983. // push first bid
  984. __self.auction_bids.push({viewer: caller, bid: amount});
  985. }
  986. }
  987. // clear timers on flood
  988. clearTimeout(__self.auction_bid_response);
  989. clearInterval(__self.auction_bid_response_long);
  990. // reply after set amount of bids
  991. if ((__self.auction_bids.length % __self.max_requests) === 0) {
  992. // bulk flood response
  993. for (var i = 0; i < __self.auction_bids.length; i++) {
  994. if (__self.auction_bids[i].bid === utils.max(__self.auction_bids)) {
  995. __self.irc.msg(__self.pre_text + 'Highest bid, ' + __self.auction_bids[i].viewer + ' @ ' + __self.auction_bids[i].bid);
  996. }
  997. }
  998. } else {
  999. // response after time without flood has passed
  1000. var viewer, bid;
  1001. for (var i = 0; i < __self.auction_bids.length; i++) {
  1002. if (__self.auction_bids[i].bid === utils.max(__self.auction_bids)) {
  1003. viewer = __self.auction_bids[i].viewer;
  1004. bid = __self.auction_bids[i].bid;
  1005. }
  1006. }
  1007. if (viewer !== undefined && bid !== undefined && __self.auction_status) {
  1008. var msg = __self.pre_text + 'Highest bid, ' + viewer + ' @ ' + bid;
  1009. __self.auction_bid_response = setTimeout(function () {__self.irc.emit('message', {message:msg, timer: 1});}, 5000);
  1010. __self.auction_bid_response_long = setInterval(function () {__self.irc.emit('message', {message:msg, timer: 1});}, 30000);
  1011. }
  1012. }
  1013. });
  1014. }
  1015. };
  1016. Currency.prototype.next_auction_winner = function () {
  1017. var __self = this, empty_list = [];
  1018. // custom dialog when the bidder list is empty
  1019. empty_list.push('Hey, I just met you and this is crazy, but there\'s no more bidders, so start an new auction maybe?');
  1020. empty_list.push('Are there more bidders? Well, to tell you the truth, in all this excitement I kind of lost track myself.');
  1021. empty_list.push('Heyyyyyy there\'s no more bidders, Op, op, op, op, Open Auction Style.');
  1022. empty_list.push('Da bids! Da bids! Where are all da bids, boss?');
  1023. if (!__self.auction_status) {
  1024. // get next highest bidder or prompt to open new auction
  1025. if (__self.auction_bids.length > 0) {
  1026. for (var i = 0; i < __self.auction_bids.length; i++) {
  1027. if (__self.auction_bids[i].bid === utils.max(__self.auction_bids)) {
  1028. __self.irc.emit('message',{message:__self.pre_text + 'Drawing the next highest bid: ' + __self.auction_bids[i].viewer + ' @ ' + __self.auction_bids[i].bid});
  1029. // refund previous winner's money
  1030. var sql = '';
  1031. sql += 'UPDATE viewers ';
  1032. sql += 'SET points = points + ' + __self.auction_previous_bid.bid + ' ';
  1033. sql += 'WHERE user = \'' + __self.auction_previous_bid.viewer + '\'';
  1034. __self.db.execute(sql, function() {});
  1035. // save the new winner's info for next draw
  1036. __self.auction_previous_bid.viewer = __self.auction_bids[i].viewer;
  1037. __self.auction_previous_bid.bid = __self.auction_bids[i].bid;
  1038. // remove winners money
  1039. sql = '';
  1040. sql += 'UPDATE viewers ';
  1041. sql += 'SET points = points - ' + __self.auction_bids[i].bid + ' ';
  1042. sql += 'WHERE user = \'' + __self.auction_bids[i].viewer + '\'';
  1043. __self.db.execute(sql, function() {});
  1044. // remove winner from main list
  1045. __self.auction_bids.splice(i, 1);
  1046. break;
  1047. }
  1048. }
  1049. } else {
  1050. // check if a previous viewer is saved
  1051. if (__self.auction_previous_bid.viewer !== null) {
  1052. // refund previous winner's money
  1053. var sql = '';
  1054. sql += 'UPDATE viewers ';
  1055. sql += 'SET points = points + ' + __self.auction_previous_bid.bid + ' ';
  1056. sql += 'WHERE user = \'' + __self.auction_previous_bid.viewer + '\'';
  1057. __self.db.execute(sql, function() {});
  1058. // clear previous bid
  1059. __self.auction_previous_bid = {};
  1060. }
  1061. // notify that there's no more bids
  1062. __self.irc.emit('message',{message:__self.pre_text + utils.selectRandomArrayItem(empty_list)});
  1063. }
  1064. }
  1065. };
  1066. /**
  1067. * ============================================
  1068. * RAFFLE SYSTEM
  1069. * --------------------------------------------
  1070. */
  1071. Currency.prototype.raffle = function (status) {
  1072. var __self = this;
  1073. switch (status) {
  1074. case true:
  1075. if (!__self.bets_status) {
  1076. if (!__self.auction_status) {
  1077. if (!__self.raffle_status) {
  1078. // open up a raffle
  1079. __self.raffle_status = true;
  1080. // request toggle
  1081. if (__self.temp.auction_toggle) {
  1082. __self.temp.raffle_toggle = __self.temp.auction_toggle;
  1083. } else {
  1084. __self.temp.raffle_toggle = __self.coin_toggle;
  1085. }
  1086. __self.coin_toggle = false;
  1087. // default request timer
  1088. if (__self.temp.auction_timer && __self.temp.auction_timer_reset) {
  1089. __self.temp.raffle_timer = __self.temp.auction_timer;
  1090. __self.temp.raffle_timer_reset = __self.temp.auction_timer_reset;
  1091. } else {
  1092. __self.temp.raffle_timer = __self.coin_response_timer;
  1093. __self.temp.raffle_timer_reset = __self.coin_response_reset;
  1094. }
  1095. __self.coin_response_timer = 3000;
  1096. __self.coin_response_reset = true;
  1097. // save previous raffle settings in case
  1098. // a new one is opened on accident
  1099. __self.raffle_restore_ticket_requests = __self.raffle_ticket_requests;
  1100. __self.raffle_restore_tickets = __self.raffle_tickets;
  1101. // clear previous tickets
  1102. __self.raffle_ticket_requests = [];
  1103. __self.raffle_tickets = [];
  1104. // raffle open response
  1105. __self.irc.emit('message',{message:__self.pre_text + 'Raffle opened'});
  1106. __self.irc.emit('message',{message:'+ Tickets cost ' + __self.raffle_ticket_cost + ' ' + __self.config.currency.toLowerCase() + ' / Maximum of ' + __self.raffle_max_tickets + ' tickets per viewer'});
  1107. } else {
  1108. // raffle in progress response
  1109. __self.irc.emit('message',{message:__self.pre_text + 'Raffle already in progress'});
  1110. }
  1111. } else {
  1112. // auction in progress
  1113. __self.irc.emit('message', {message:__self.pre_text + 'You must close the auction before you can open an a raffle'});
  1114. }
  1115. } else {
  1116. // gambling currently running
  1117. __self.irc.emit('message', {message:__self.pre_text + 'Betting must be closed before you can open a raffle'});
  1118. }
  1119. break;
  1120. case false:
  1121. if (__self.raffle_status) {
  1122. // close the raffle
  1123. __self.raffle_status = false;
  1124. // request toggle
  1125. __self.coin_toggle = __self.temp.raffle_toggle;
  1126. delete __self.temp.raffle_toggle;
  1127. // default request timer
  1128. __self.coin_response_timer = __self.temp.raffle_timer;
  1129. __self.coin_response_reset = __self.temp.raffle_timer_reset;
  1130. delete __self.temp.raffle_timer;
  1131. delete __self.temp.raffle_timer_reset;
  1132. // validation / winner / deduction
  1133. __self.raffle_winner();
  1134. } else {
  1135. // raffle is already open response
  1136. __self.irc.emit('message',{message:__self.pre_text + 'Raffle is already closed'});
  1137. }
  1138. break;
  1139. case 'cancel':
  1140. if (__self.raffle_status) {
  1141. // close the raffle
  1142. __self.raffle_status = false;
  1143. // request toggle
  1144. __self.coin_toggle = __self.temp.raffle_toggle;
  1145. delete __self.temp.raffle_toggle;
  1146. // default request timer
  1147. __self.coin_response_timer = __self.temp.raffle_timer;
  1148. __self.coin_response_reset = __self.temp.raffle_timer_reset;
  1149. delete __self.temp.raffle_timer;
  1150. delete __self.temp.raffle_timer_reset;
  1151. // clear previous tickets
  1152. __self.raffle_ticket_requests = [];
  1153. __self.raffle_tickets = [];
  1154. // raffle cancelled notification
  1155. __self.irc.emit('message', {message:__self.pre_text + 'Raffle has been cancelled'});
  1156. } else {
  1157. // raffle cancelled notification
  1158. __self.irc.emit('message', {message:__self.pre_text + 'Raffle is not opened'});
  1159. }
  1160. break;
  1161. case 'restore':
  1162. if (__self.raffle_status) {
  1163. // close raffle
  1164. __self.raffle_status = false;
  1165. // restore previous raffle tickets
  1166. __self.raffle_ticket_requests = __self.raffle_restore_ticket_requests;
  1167. __self.raffle_tickets = __self.raffle_restore_tickets;
  1168. __self.irc.emit('message', {message:__self.pre_text + 'Previous raffle has been restored'});
  1169. } else {
  1170. // raffle restore failed notification
  1171. __self.irc.emit('message', {message:__self.pre_text + 'Raffle is closed, unable to restore'});
  1172. }
  1173. break;
  1174. }
  1175. };
  1176. Currency.prototype.collect_tickets = function (caller, amount) {
  1177. var __self = this, has_tickets = false;
  1178. if (__self.raffle_ticket_requests.length > 0) {
  1179. // check if viewer already has tickets
  1180. for (var i = 0; i < __self.raffle_ticket_requests.length; i++) {
  1181. if (__self.raffle_ticket_requests[i].viewer === caller) {
  1182. has_tickets = true;
  1183. if (amount <= __self.raffle_max_tickets && amount >= 1) {
  1184. __self.raffle_ticket_requests[i].tickets = amount;
  1185. } else if (amount === 0) {
  1186. __self.raffle_ticket_requests.splice(i, 1);
  1187. }
  1188. break;
  1189. }
  1190. }
  1191. // if viewer doesn't have tickets and meets > 1 < max req add their request
  1192. if (!has_tickets && amount <= __self.raffle_max_tickets && amount >= 1 && amount !== 0) {
  1193. __self.raffle_ticket_requests.push({viewer: caller, tickets: amount});
  1194. }
  1195. } else {
  1196. // push first ticket if > 1 < max
  1197. if (amount <= __self.raffle_max_tickets && amount >= 1 && amount !== 0) {
  1198. __self.raffle_ticket_requests.push({viewer: caller, tickets: amount});
  1199. }
  1200. }
  1201. };
  1202. Currency.prototype.raffle_winner = function () {
  1203. var __self = this, sql = '';
  1204. if (__self.raffle_ticket_requests.length > 0) {
  1205. // setup sql to grab all viewers that request coins from the database
  1206. sql += 'SELECT * FROM viewers WHERE ';
  1207. for (var i = 0; i < __self.raffle_ticket_requests.length; i++) {
  1208. if (i !== __self.raffle_ticket_requests.length - 1) {
  1209. sql += 'user=\'' + __self.raffle_ticket_requests[i].viewer.toLowerCase() + '\' OR ';
  1210. } else {
  1211. sql += 'user=\'' + __self.raffle_ticket_requests[i].viewer.toLowerCase() + '\'';
  1212. }
  1213. }
  1214. // execute viewer search query
  1215. __self.db.execute(sql, function(rows) {
  1216. // currency validation
  1217. // - this takes the results of the query and uses the names from the database
  1218. // to filter through the viewers that requested tickets (since they have to be in the
  1219. // database in the first place)
  1220. // - during the filtering process the viewers requested tickets are multiplied by the
  1221. // ticket cost and compared against their currency amount
  1222. // - if the viewer has the funds, their tickets are added and the sql is updated to include their
  1223. // deduction
  1224. sql = '';
  1225. for (var i = 0; i < rows.length; i++) {
  1226. for (var j = 0; j < __self.raffle_ticket_requests.length; j++) {
  1227. if (__self.raffle_ticket_requests[j].viewer.toLowerCase() === rows[i].user) {
  1228. var money = __self.raffle_ticket_requests[j].tickets * __self.raffle_ticket_cost;
  1229. if (rows[i].points >= money) {
  1230. for (var k = 1; k <= __self.raffle_ticket_requests[j].tickets; k++) {
  1231. __self.raffle_tickets.push(__self.raffle_ticket_requests[j].viewer);
  1232. }
  1233. if (i !== rows.length - 1) {
  1234. sql += 'UPDATE viewers ';
  1235. sql += 'SET points = points - ' + money + ' ';
  1236. sql += 'WHERE user = \'' + rows[i].user + '\'; ';
  1237. } else {
  1238. sql += 'UPDATE viewers ';
  1239. sql += 'SET points = points - ' + money + ' ';
  1240. sql += 'WHERE user = \'' + rows[i].user + '\'';
  1241. }
  1242. }
  1243. break;
  1244. }
  1245. }
  1246. }
  1247. // randomize array before selecting a random winner
  1248. __self.raffle_tickets.sort(function () {return 0.5 - Math.random();});
  1249. // select random ticket from array
  1250. var winner = utils.selectRandomArrayItem(__self.raffle_tickets);
  1251. // count winner's tickets
  1252. var winning_ticket_amount;
  1253. for (var i = 0; i < __self.raffle_ticket_requests.length; i++) {
  1254. if (__self.raffle_ticket_requests[i].viewer === winner) {
  1255. winning_ticket_amount = __self.raffle_ticket_requests[i].tickets;
  1256. break;
  1257. }
  1258. }
  1259. // output winner to chat
  1260. __self.irc.emit('message', {message:__self.pre_text + 'Raffle closed, ' + __self.raffle_tickets.length + ' tickets purchased!'});
  1261. __self.irc.emit('message', {message:'+ Winner: ' + winner + ' (' + winning_ticket_amount + ' tickets purchased)'});
  1262. // remove one ticket from raffle bowl
  1263. if (__self.raffle_tickets.indexOf(winner) >= 0 ) {
  1264. __self.raffle_tickets.splice(__self.raffle_tickets.indexOf(winner), 1);
  1265. }
  1266. // execute query
  1267. __self.db.execute(sql, function () {});
  1268. });
  1269. } else {
  1270. // no tickets to pick from response
  1271. __self.irc.emit('message', {message:__self.pre_text + 'Raffle closed, no tickets to draw a winner'});
  1272. }
  1273. };
  1274. Currency.prototype.next_raffle_winner = function () {
  1275. var __self = this, empty_list = [];
  1276. // custom dialog when there are no more raffle tickets
  1277. empty_list.push('Hey, I just met you and this is crazy, but there\'s no more tickets, so start an new raffle maybe?');
  1278. empty_list.push('Are there more tickets? Well, to tell you the truth, in all this excitement I kind of lost track myself.');
  1279. empty_list.push('Heyyyyyy there\'s no more tickets, Op, op, op, op, Open Raffle Style.');
  1280. empty_list.push('Da tickets! Da tickets! Where are all da tickets, boss?');
  1281. if (!__self.raffle_status) {
  1282. // draw next ticket or prompt to open new raffle
  1283. if (__self.raffle_tickets.length > 0) {
  1284. // randomize array before selecting a random winner
  1285. __self.raffle_tickets.sort(function () {return 0.5 - Math.random();});
  1286. // select random ticket from array
  1287. var winner = utils.selectRandomArrayItem(__self.raffle_tickets);
  1288. // count next winner's tickets
  1289. var winning_ticket_amount;
  1290. for (var i = 0; i < __self.raffle_ticket_requests.length; i++) {
  1291. if (__self.raffle_ticket_requests[i].viewer === winner) {
  1292. winning_ticket_amount = __self.raffle_ticket_requests[i].tickets;
  1293. break;
  1294. }
  1295. }
  1296. // output winner to chat
  1297. __self.irc.emit('message', {message:__self.pre_text + 'Drawing next ticket'});
  1298. __self.irc.emit('message', {message:'+ Winner: ' + winner + ' (' + winning_ticket_amount + ' tickets purchased)'});
  1299. // remove one ticket from raffle bowl
  1300. if (__self.raffle_tickets.indexOf(winner) >= 0 ) {
  1301. __self.raffle_tickets.splice(__self.raffle_tickets.indexOf(winner), 1);
  1302. }
  1303. } else {
  1304. __self.irc.emit('message', {message:__self.pre_text + utils.selectRandomArrayItem(empty_list)});
  1305. }
  1306. }
  1307. };
  1308. /**
  1309. * ============================================
  1310. * BETTING SYSTEM
  1311. * --------------------------------------------
  1312. */
  1313. Currency.prototype.bets = function(status, data) {
  1314. var __self = this;
  1315. switch(status){
  1316. case true:
  1317. if (!__self.auction_status) {
  1318. if (!__self.raffle_status) {
  1319. if (!__self.bets_status && !__self.bets_payout) {
  1320. var wager_msg = '';
  1321. // open up bets
  1322. __self.bets_status = true;
  1323. __self.bets_payout = true;
  1324. // clear previous board / bets
  1325. __self.bets_board = [];
  1326. __self.bets_viewers = [];
  1327. __self.bets_total = 0;
  1328. // create new betting board
  1329. __self.bets_board = data.join().split(',').filter(function(n){return n}).slice(6).map(function(n){return {name: n, num: 0, total: 0};});
  1330. for (var i = 0; i < __self.bets_board.length; i++) {
  1331. __self.bets_board[i].idx = i;
  1332. }
  1333. // create chat message on how to place a bet
  1334. for (var i = 0; i < __self.bets_board.length; i++) {
  1335. if (i !== __self.bets_board.length - 1) {
  1336. wager_msg += '"!' + __self.bets_board[i].name + '" / ';
  1337. } else {
  1338. wager_msg += '"!' + __self.bets_board[i].name + '"';
  1339. }
  1340. }
  1341. // output to chat
  1342. __self.irc.emit('message', {message:__self.pre_text + 'Betting is now open'});
  1343. __self.irc.emit('message', {message:'+ Type ' + wager_msg + ' and the bet amount to enter'});
  1344. } else {
  1345. if (__self.bets_payout) {
  1346. // payout pending message
  1347. __self.irc.emit('message', {message:__self.pre_text + 'Unable to take new bets until previous have been paid out'});
  1348. } else {
  1349. // gambling is already open response
  1350. __self.irc.emit('message', {message:__self.pre_text + 'Betting already in progress'});
  1351. }
  1352. }
  1353. // FIXME: messages below should be "cant open a bet until X is over/closed" instead? test it
  1354. } else {
  1355. // raffle in progress
  1356. __self.irc.emit('message', {message:__self.pre_text + 'Betting must be closed before you can open a raffle'});
  1357. }
  1358. } else {
  1359. // auction currently running
  1360. __self.irc.emit('message', {message:__self.pre_text + 'Betting must be closed before you can open an auction'});
  1361. }
  1362. break;
  1363. case false:
  1364. if (__self.bets_status && __self.bets_payout) {
  1365. // close out bets
  1366. __self.bets_status = false;
  1367. // deduct bets from viewers amounts
  1368. __self.bets_deduct_bets();
  1369. // output to chat
  1370. if (__self.bets_viewers.length > 0) {
  1371. __self.irc.emit('message', {message:__self.pre_text + 'Betting is now closed'});
  1372. for (var i = 0; i < __self.bets_board.length; i++) {
  1373. __self.irc.emit('message', {message:'+ ' + __self.bets_board[i].name + ' with ' + __self.bets_board[i].num + ' bets totaling ' + __self.bets_board[i].total});
  1374. }
  1375. } else {
  1376. __self.irc.emit('message', {message:__self.pre_text + 'Betting closed, no bets were placed'});
  1377. }
  1378. }
  1379. break;
  1380. case 'pool':
  1381. function do_work() {
  1382. if (__self.bets_board.length > 0) {
  1383. __self.irc.emit('message', {message:__self.pre_text + 'Current betting pool is:'});
  1384. for (var i = 0; i < __self.bets_board.length; i++) {
  1385. __self.irc.emit('message', {message:'+ ' + __self.bets_board[i].name + ' with ' + __self.bets_board[i].num + ' bets totaling ' + __self.bets_board[i].total});
  1386. }
  1387. } else {
  1388. __self.irc.emit('message', {message:__self.pre_text + 'No current bet.'});
  1389. }
  1390. }
  1391. if (__self.bets_response_reset) {
  1392. __self.bets_response = setTimeout(function () {do_work();}, __self.bets_response_timer);
  1393. } else {
  1394. setTimeout(function () {do_work();}, __self.bets_response_timer);
  1395. }
  1396. break;
  1397. case 'winner':
  1398. if (!__self.bets_status) {
  1399. if (__self.bets_payout) {
  1400. for (var i = 0; i < __self.bets_board.length; i++) {
  1401. if (data == __self.bets_board[i].name) {
  1402. __self.bets_award_winner(__self.bets_board[i]);
  1403. if(__self.bets_board[i].total > __self.score_bet_pool) {
  1404. sql = 'UPDATE highscores SET value = ' + __self.bets_board[i].total + ' WHERE name = \'bet_pool\'';
  1405. __self.db.execute(sql, function() {
  1406. __self.score_bet_pool = __self.bets_board[i].total;
  1407. __self.irc.emit('message', {message:__self.pre_text + 'Betting payed out to ' + data + '. New record set!!'});
  1408. });
  1409. } else {
  1410. __self.irc.emit('message', {message:__self.pre_text + 'Betting payed out to ' + data});
  1411. }
  1412. }
  1413. }
  1414. } else {
  1415. __self.irc.emit('message', {message:__self.pre_text + 'Betting has already payed out'});
  1416. }
  1417. } else {
  1418. __self.irc.emit('message', {message:__self.pre_text + 'Betting must be closed before you can have a winner'});
  1419. }
  1420. break;
  1421. case 'cancel':
  1422. if (!__self.bets_status) {
  1423. // Return all the currency back to original owners
  1424. if (__self.bets_payout) {
  1425. sql = '';
  1426. for (var i = 0; i < __self.bets_viewers.length; i++) {
  1427. if (__self.bets_viewers[i].amount > 0) {
  1428. if (sql.length > 0) {
  1429. sql += '; ';
  1430. }
  1431. sql += 'UPDATE viewers ';
  1432. sql += 'SET points = points + ' + __self.bets_viewers[i].amount + ' ';
  1433. sql += 'WHERE user = \'' + __self.bets_viewers[i].viewer + '\'';
  1434. }
  1435. }
  1436. __self.db.execute(sql, function() {
  1437. __self.irc.emit('message', {message:__self.pre_text + 'Bet was canceled, all ' + __self.config.currency.toLowerCase() + ' returned.'});
  1438. });
  1439. __self.bets_payout = false;
  1440. // Or just cancel the bets
  1441. } else {
  1442. __self.irc.emit('message', {message:__self.pre_text + 'No bet to cancel.'});
  1443. }
  1444. } else {
  1445. __self.bets_status = false;
  1446. __self.bets_payout = false;
  1447. __self.irc.emit('message', {message:__self.pre_text + 'Current bet was canceled.'});
  1448. }
  1449. break;
  1450. }
  1451. };
  1452. Currency.prototype.collect_bets = function (caller, bet, amount) {
  1453. var __self = this, has_bet = false;
  1454. function do_work() {
  1455. var multi_response = [];
  1456. for (var i = 0; i < __self.bets_board.length; i++) {
  1457. multi_response.push([]);
  1458. }
  1459. if (__self.bets_flood.length > 0) {// send flood requests
  1460. var bet;
  1461. // setup bet response
  1462. for (var i = 0; i < __self.bets_flood.length; i++) {
  1463. bet = __self.bets_viewers[__self.bets_flood[i]];
  1464. multi_response[bet.bet].push(bet.viewer + ' (' + bet.amount + ')');
  1465. }
  1466. for (var i = 0; i < __self.bets_board.length; i++) {
  1467. if (multi_response[i].length > 0) {
  1468. __self.irc.emit('message', {message:'New bets for ' + __self.bets_board[i].name + ': ' + multi_response[i].join(', '), timer: 1});
  1469. }
  1470. }
  1471. }
  1472. // clear flood requests
  1473. __self.bets_flood = [];
  1474. }
  1475. // Bound amount by positive currency
  1476. __self.query_coins(caller, function (rows) {
  1477. for (var i = 0; i < rows.length; i++) {
  1478. amount = Math.min(amount, rows[i].points);
  1479. }
  1480. // check for hiscore and set db appropriately
  1481. if(amount > __self.score_bet_single) {
  1482. sql = 'UPDATE highscores SET value = ' + amount + ' WHERE name = \'bet_single\'';
  1483. __self.db.execute(sql, function() {
  1484. __self.score_bet_single = amount;
  1485. __self.irc.emit('message', {message:__self.pre_text + ' ' + caller + ' set new record bet at ' + amount + '!'});
  1486. });
  1487. }
  1488. if (__self.bets_viewers.length > 0) {
  1489. for (var i = 0; i < __self.bets_viewers.length; i++) {
  1490. if (__self.bets_viewers[i].viewer === caller) {
  1491. has_bet = true;
  1492. if (amount > __self.bets_viewers[i].amount) {
  1493. __self.bets_board[__self.bets_viewers[i].bet].num -= 1;
  1494. __self.bets_board[__self.bets_viewers[i].bet].total -= __self.bets_viewers[i].amount;
  1495. __self.bets_viewers[i].bet = bet.idx;
  1496. __self.bets_viewers[i].amount = amount;
  1497. __self.bets_board[bet.idx].num += 1;
  1498. __self.bets_board[bet.idx].total += amount;
  1499. // add flood users to array
  1500. if (__self.bets_flood.indexOf(i) < 0) {
  1501. __self.bets_flood.push(i);
  1502. }
  1503. }
  1504. break;
  1505. }
  1506. }
  1507. if (!has_bet && amount >= 1 && amount !== 0) {
  1508. __self.bets_viewers.push({viewer: caller, bet: bet.idx, amount: amount});
  1509. __self.bets_board[bet.idx].num += 1;
  1510. __self.bets_board[bet.idx].total += amount;
  1511. __self.bets_flood.push(__self.bets_viewers.length - 1);
  1512. }
  1513. } else {
  1514. if (amount >= 1 && amount !== 0) {
  1515. __self.bets_viewers.push({viewer: caller, bet: bet.idx, amount: amount});
  1516. __self.bets_board[bet.idx].num += 1;
  1517. __self.bets_board[bet.idx].total += amount;
  1518. __self.bets_flood.push(__self.bets_viewers.length - 1);
  1519. }
  1520. }
  1521. console.log(__self.bets_viewers);
  1522. // check if flood has a set amount of requests and output
  1523. // if not, set the output timer
  1524. if (__self.bets_flood.length === __self.max_requests) {
  1525. do_work();
  1526. } else {
  1527. if (__self.bets_response_reset) {
  1528. __self.bets_response = setTimeout(function () {do_work();}, __self.bets_response_timer);
  1529. } else {
  1530. setTimeout(function () {do_work();}, __self.bets_response_timer);
  1531. }
  1532. }
  1533. });
  1534. };
  1535. Currency.prototype.bets_deduct_bets = function () {
  1536. var __self = this;
  1537. if (__self.bets_viewers.length > 0) {
  1538. // Don't trust the on-the-fly numbers
  1539. for (var i = 0; i < __self.bets_board.length; i++) {
  1540. __self.bets_board[i].num = 0;
  1541. __self.bets_board[i].total = 0;
  1542. }
  1543. __self.bets_total = 0;
  1544. // Remove the points and add them to an escrow
  1545. sql = '';
  1546. for (var i = 0; i < __self.bets_viewers.length; i++) {
  1547. if (__self.bets_viewers[i].amount > 0) {
  1548. if (sql.length > 0) {
  1549. sql += '; ';
  1550. }
  1551. sql += 'UPDATE viewers ';
  1552. sql += 'SET points = points - ' + __self.bets_viewers[i].amount + ' ';
  1553. sql += 'WHERE user = \'' + __self.bets_viewers[i].viewer + '\'';
  1554. __self.bets_board[__self.bets_viewers[i].bet].num += 1;
  1555. __self.bets_board[__self.bets_viewers[i].bet].total += __self.bets_viewers[i].amount;
  1556. __self.bets_total += __self.bets_viewers[i].amount;
  1557. }
  1558. }
  1559. __self.db.execute(sql, function() {});
  1560. }
  1561. };
  1562. Currency.prototype.bets_award_winner = function (winner) {
  1563. var __self = this;
  1564. if (__self.bets_payout) {
  1565. // set payout to complete
  1566. __self.bets_payout = false;
  1567. }
  1568. // Payout the points
  1569. if (__self.bets_viewers.length > 0) {
  1570. sql = '';
  1571. for (var i = 0; i < __self.bets_viewers.length; i++) {
  1572. if (__self.bets_viewers[i].bet == winner.idx) {
  1573. if (sql.length > 0) {
  1574. sql += '; ';
  1575. }
  1576. sql += 'UPDATE viewers ';
  1577. sql += 'SET points = points + ' + Math.ceil(__self.bets_total * __self.bets_viewers[i].amount / __self.bets_board[__self.bets_viewers[i].bet].total) + ' ';
  1578. sql += 'WHERE user = \'' + __self.bets_viewers[i].viewer + '\'';
  1579. }
  1580. }
  1581. __self.db.execute(sql, function() {});
  1582. }
  1583. // Clear the board
  1584. __self.bets_board = [];
  1585. __self.bets_viewers = [];
  1586. __self.bets_total = 0;
  1587. };
  1588. module.exports = function (irc, db, options) {
  1589. return new Currency(irc, db, options);
  1590. };