PageRenderTime 49ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/src/program.js

https://gitlab.com/guy.spronck/node-telegram-bot
JavaScript | 490 lines | 389 code | 50 blank | 51 comment | 75 complexity | e9019e0cab3082e99d4d38566f40fcda MD5 | raw file
  1. // Require modules
  2. var botgram = require('botgram');
  3. var Forecast = require('forecast');
  4. var cool = require('cool-ascii-faces');
  5. var Pokedex = require('pokedex');
  6. var isUp = require('is-up');
  7. var leet = require('l33tsp34k');
  8. var predict = require('eightball');
  9. var Datastore = require('nedb');
  10. var request = require("request-promise");
  11. var clor = require("clor");
  12. // Load variables
  13. var config = require('./config');
  14. var pokedex = new Pokedex();
  15. // Initialise db
  16. var db = new Datastore({ filename: 'datastore.js', autoload: true });
  17. // Initialise the forecast.io component
  18. var forecast = new Forecast({
  19. service: 'forecast.io',
  20. key: config.forecast,
  21. units: 'celcius', // Only the first letter is parsed
  22. cache: true, // Cache API requests?
  23. ttl: { // How long to cache requests. Uses syntax from moment.js: http://momentjs.com/docs/#/durations/creating/
  24. minutes: 27,
  25. seconds: 45
  26. }
  27. });
  28. // Initialise geocder
  29. var geocoder = require('node-geocoder')(config.geocode.provider, 'https', {apiKey: config.geocode.apiKey});
  30. // Setup the bot settings
  31. var options = {
  32. polling: true
  33. };
  34. // Initialise the bot.
  35. var bot = botgram(config.token);
  36. console.log("The bot is up and running!");
  37. // ###################
  38. // # COMMAND SECTION #
  39. // ###################
  40. // Spam protection, only for commands in regex
  41. bot.command(/(forecast|joke|pokedex|isup)/, function (msg, reply, next) {
  42. spamProtect(msg, function(blocked, cmdcount){
  43. if(!blocked){
  44. if(cmdcount >= 4){
  45. reply.text(`Take a break ${(msg.from).name}, or you will be blocked!`);
  46. }else{
  47. next();
  48. }
  49. }else if(cmdcount == 5){
  50. reply.text(`${(msg.from).name}, you have been blocked!`);
  51. }
  52. });
  53. });
  54. // /start & /help - Displays help information
  55. bot.command("start", "help", function (msg, reply, next) {
  56. logFunc(msg);
  57. var helpConf = config.help;
  58. if(helpConf != ""){
  59. reply.text(config.help);
  60. }
  61. });
  62. // /echo - Echos the given text to the user
  63. bot.command("echo", function (msg, reply, next) {
  64. logFunc(msg);
  65. var args = msg.args();
  66. if (args.trim().length == 0) {
  67. reply.text("Usage: /echo [text]");
  68. return;
  69. }
  70. reply.text(args);
  71. });
  72. // Catch locations in chat and give forecast for that location
  73. bot.location(function(msg, reply, next){
  74. logFunc(msg);
  75. getForecast(msg.latitude,msg.longitude, undefined, undefined, function(str){
  76. reply.text(str);
  77. });
  78. });
  79. // /forecast [place] - Gives forecast for location
  80. bot.command("forecast", function (msg, reply, next) {
  81. logFunc(msg);
  82. var args = msg.args();
  83. if (args.trim().length == 0) {
  84. reply.text("Usage: /forecast [location]");
  85. return;
  86. }
  87. geocoder.geocode(args, function(err, res){
  88. if(err){
  89. // something went wrong
  90. return;
  91. }
  92. if(res.length == 0){
  93. reply.text("Cannot find the place you are looking for.");
  94. return;
  95. }
  96. getForecast(res[0].latitude,res[0].longitude, res[0].city, res[0].country, function(str){
  97. reply.text(str);
  98. });
  99. });
  100. });
  101. function getForecast(lat,long, city, country, callback){
  102. forecast.get([lat,long], function(err, weather){
  103. if (err){
  104. callback("Could not find weather data for this location.");
  105. }
  106. if(country === undefined){
  107. var respString = "The weather at the given location is " + weather.currently.summary
  108. + " with a temperature of " + weather.currently.temperature + "°C";
  109. }else{
  110. var respString = "The weather in "+ city + ", " + country+" is " + weather.currently.summary
  111. + " with a temperature of " + weather.currently.temperature + "°C";
  112. };
  113. callback(respString);
  114. });
  115. }
  116. // /pokedex - Pokedex, find pokemon by name or id
  117. bot.command("pokedex", function(msg,reply,next){
  118. logFunc(msg);
  119. var args = msg.args();
  120. if (args.trim().length == 0) {
  121. reply.text("Usage: /pokedex [id / name]");
  122. return;
  123. }
  124. var result = pokedex.pokemon(args);
  125. // Check if found name
  126. if(result.id === undefined){
  127. result = pokedex.pokemon(parseInt(args));
  128. }
  129. // Check if found ID
  130. if(result.id === undefined){
  131. reply.text("No pokemon found with that ID/name.");
  132. return;
  133. }
  134. var respString = "ID: " + result.id
  135. + "\r\nName: " + result.name
  136. + "\r\nHeigth: " + result.height
  137. + "\r\nWeight: " + result.weight;
  138. if(result.sprites.animated){
  139. reply.text(result.sprites.animated);
  140. }else if(result.sprites.normal){
  141. reply.text(result.sprites.normal);
  142. }
  143. reply.text(respString);
  144. });
  145. // /ishetaltijdvoorbier
  146. bot.command("ishetaltijdvoorbier", function(msg,reply,next){
  147. logFunc(msg);
  148. var strings = ["Het is altijd tijd voor bier!",
  149. "Nu al? Tuurlijk, waarom niet!",
  150. "Biertje moet kunnen.",
  151. "Het is ergens 12 uur geweest, toch?"];
  152. reply.text(strings[Math.floor(Math.random()*strings.length)]);
  153. });
  154. // /isup - Checks if given hostname is up
  155. bot.command("isup", function(msg,reply,next){
  156. logFunc(msg);
  157. var args = msg.args();
  158. if (args.trim().length == 0) {
  159. reply.text("Usage: /isup [hostname]");
  160. return;
  161. }
  162. isUp(args).then(up => {
  163. if(up){
  164. reply.html("It looks like <strong>" + args + "</strong> is <strong>up</strong> from here!");
  165. }else{
  166. reply.html("It looks like <strong>" + args + "</strong> is <strong>down</strong> from here.");
  167. }
  168. });
  169. });
  170. // /cool - Displays a cool face
  171. bot.command("cool", function(msg, reply, next){
  172. logFunc(msg);
  173. reply.text(cool());
  174. });
  175. // /doekoe
  176. bot.command("doekoe", "moneyz", "stacks", function(msg,reply,next){
  177. logFunc(msg);
  178. var respString = `Het aantal dagen kan licht afwijken!\r\n`;
  179. var sah = getDaysTillNextDay(8);
  180. var zorgToeslag = getDaysTillNextDay(20);
  181. var stufi = getDaysTillNextDay(24);
  182. if(sah == 0){
  183. respString += `\r\n*Salaris van studentaanhuis komt vandaag!*`;
  184. }else{
  185. respString += `\r\nSalaris van studentaanhuis komt over ${sah} dagen.`;
  186. }
  187. if(zorgToeslag == 0){
  188. respString += `\r\n*Zorgtoeslag komt vandaag!*`;
  189. }else{
  190. respString += `\r\nZorgtoeslag komt over ${zorgToeslag} dagen.`;
  191. }
  192. if(stufi == 0){
  193. respString += `\r\n*Stufi komt vandaag!*`;
  194. }else{
  195. respString += `\r\nStufi komt over ${stufi} dagen.`;
  196. }
  197. reply.markdown(respString);
  198. });
  199. // Gives the days until the next time this date comes (0 for today)
  200. function getDaysTillNextDay(day) {
  201. var today, nextDay, diff, days;
  202. today = new Date();
  203. nextDay = new Date(today.getFullYear(), today.getMonth(), day, today.getHours(), today.getHours(), today.getMinutes()+1);
  204. if(today.getDate() == day){
  205. return 0;
  206. }
  207. if(today.getTime() > nextDay.getTime()){
  208. nextDay.setMonth(nextDay.getMonth()+1);
  209. }
  210. diff = nextDay.getTime() - today.getTime();
  211. days = Math.round(Math.abs(diff/(1000*60*60*24)));
  212. return days;
  213. }
  214. // /leet
  215. bot.command("leet", function(msg,reply,next){
  216. logFunc(msg);
  217. var args = msg.args();
  218. if (args.trim().length == 0) {
  219. reply.text("Usage: /leet [string]");
  220. return;
  221. }
  222. reply.text(leet(args));
  223. });
  224. // /8ball
  225. bot.command("8ball", "magic8ball", function(msg,reply,next){
  226. logFunc(msg);
  227. var args = msg.args();
  228. if (args.trim().length == 0) {
  229. reply.text("Usage: /8ball [yes/no question]");
  230. return;
  231. }
  232. reply.text(predict());
  233. });
  234. // /joke
  235. bot.command("joke", function(msg,reply,next){
  236. logFunc(msg);
  237. var url = 'https://www.reddit.com/r/jokes/.json';
  238. request(url)
  239. .then( data => {
  240. var parsed = JSON.parse(data).data.children,
  241. l = parsed.length,
  242. n = Math.floor(Math.random() * (l - 1)) + 1,
  243. title = parsed[n].data.title,
  244. selftext = parsed[n].data.selftext;
  245. console.log("\n"+clor.blue.underline(title));
  246. console.log(clor.yellow(selftext)+"\n\n\n");
  247. reply.markdown("*" + title + "*\r\n" + selftext);
  248. })
  249. .catch( err => {
  250. console.warn(err);
  251. });
  252. });
  253. // /info
  254. bot.command("info", function(msg, reply, next){
  255. logFunc(msg);
  256. reply.markdown("*Source is available here:*\r\nhttps://git.sprofix.com/guyspronck/node-telegram-bot\r\n\r\n"
  257. + "Have an idea for a cool command? Check the [issue tracker](https://git.sprofix.com/guyspronck/node-telegram-bot/issues) if it has already been requested, or request is yourself!"
  258. + "\r\n_Please use labels to mark your issue/request!_");
  259. });
  260. // /stats
  261. bot.command("stats", function(msg, reply, next){
  262. getStats(msg, function(response){
  263. reply.html(response);
  264. });
  265. });
  266. // #############
  267. // # SUPERUSER #
  268. // #############
  269. // /su - Simple superuser check
  270. bot.command("su", function (msg, reply, next) {
  271. logFunc(msg);
  272. if((config.superusers.indexOf((msg.from).id) == -1)){
  273. reply.text("You do not deserve my greetings, pleb.");
  274. }else{
  275. reply.text("I greet you master.");
  276. }
  277. });
  278. // /block - Toggles the user block
  279. bot.command("block", function (msg, reply, next) {
  280. logFunc(msg);
  281. // Check for access
  282. if((config.superusers.indexOf((msg.from).id) == -1)){
  283. return;
  284. }
  285. // Check args
  286. var args = msg.args();
  287. if (args.trim().length == 0) {
  288. reply.text("Usage: /block [userid]");
  289. return;
  290. }
  291. db.findOne({ userid: parseInt(args)}, function(err, doc){
  292. if(doc == null){
  293. reply.text("Userid not found.");
  294. }else{
  295. doc.blocked = !doc.blocked;
  296. db.update({ userid: parseInt(args)}, doc, {}, function(err, numReplaced){
  297. });
  298. reply.text("The block for the given user has been toggled.");
  299. }
  300. });
  301. });
  302. // ###########
  303. // # GENERIC #
  304. // ###########
  305. // Command not found...
  306. bot.command(function (msg, reply, next) {
  307. reply.text("Invalid command, oops!");
  308. });
  309. // Used for stats tracking
  310. bot.message(function (msg, reply, next) {
  311. updateStats(msg);
  312. });
  313. // ####################
  314. // # FUNCTION SECTION #
  315. // ####################
  316. function getStats(msg, callback){
  317. if((msg.chat).type == 'group' || (msg.chat).type == 'supergroup'){
  318. db.findOne({ chatid: (msg.chat).id}, function(err, doc){
  319. if(doc == null){
  320. callback("No stats for this chat yet, start talking!");
  321. return;
  322. }
  323. var response = `Stats for <strong>${(msg.chat).title}</strong>:\r\n`;
  324. var totalmsgs = 0;
  325. doc.users.sort(function(a,b){
  326. return b.msgcount - a.msgcount;
  327. });
  328. for(var i = 0; i < doc.users.length; i++){
  329. var firstname = doc.users[i].firstname !== undefined ? doc.users[i].firstname + " " : "";
  330. var lastname = doc.users[i].lastname !== undefined ? doc.users[i].lastname + " " : "";
  331. var username = doc.users[i].username !== undefined ? doc.users[i].username : "";
  332. response += `\r\n${firstname}${lastname}(${username}): <strong>${doc.users[i].msgcount}</strong>`
  333. totalmsgs += doc.users[i].msgcount;
  334. }
  335. response += `\r\n\r\nTotal messages: <strong>${totalmsgs}</strong>`;
  336. callback(response);
  337. });
  338. }else{
  339. callback("Stats are only available in (super)groups, get some friends!");
  340. }
  341. }
  342. function updateStats(msg){
  343. if((msg.chat).type == 'group' || (msg.chat).type == 'supergroup'){
  344. db.findOne({ chatid: (msg.chat).id}, function(err, doc){
  345. if(doc == null){
  346. // Need to create it now!
  347. db.insert({chatid: (msg.chat).id, users:[]}, function(err){
  348. if(err){
  349. console.log("Error saving stats.");
  350. }
  351. });
  352. updateStats(msg);
  353. }else{
  354. // It exists, add stuff to it!
  355. var foundUser = false;
  356. for (var i = 0; i < doc.users.length; i++) {
  357. if(doc.users[i].id == (msg.from).id){
  358. foundUser = true;
  359. doc.users[i].msgcount++;
  360. doc.users[i].username = (msg.from).username;
  361. doc.users[i].firstname = (msg.from).firstname;
  362. doc.users[i].lastname = (msg.from).lastname;
  363. }
  364. }
  365. // User does not exist yet, save him
  366. if(foundUser == false){
  367. doc.users.push({
  368. id: (msg.from).id,
  369. msgcount: 1,
  370. username: (msg.from).username,
  371. firstname: (msg.from).firstname,
  372. lastname: (msg.from).lastname
  373. });
  374. }
  375. // Updated doc needs to be saved
  376. var docid = doc._id;
  377. delete doc['_id'];
  378. db.update({chatid: doc.chatid}, doc, { }, function(err, numReplaced){
  379. });
  380. }
  381. });
  382. }
  383. }
  384. function spamProtect(msg, callback){
  385. console.log("spamProtect");
  386. if((config.superusers.indexOf((msg.from).id) != -1)){
  387. callback(false, 1);
  388. return;
  389. }
  390. db.findOne({ userid: (msg.from).id}, function(err, doc){
  391. if(doc == null){
  392. db.insert({userid: (msg.from).id,
  393. blocked: false,
  394. cmdcount: 0,
  395. lastcmd: msg.date}, function(err){
  396. if(err){
  397. console.log("Error saving spamProtect msg.");
  398. }
  399. callback(false, 1);
  400. });
  401. }else{
  402. if(doc.blocked){
  403. // User is blocked
  404. callback(true, 0);
  405. return;
  406. }
  407. // if it's been 10 sec since last command, reset counter
  408. if((new Date().getTime() - doc.lastcmd.getTime()) > 10000){
  409. doc.lastcmd = msg.date;
  410. doc.cmdcount = 0;
  411. db.update({ userid: (msg.from).id}, doc, {}, function(err, numReplaced){
  412. });
  413. callback(false, 0);
  414. }else{
  415. if(doc.cmdcount >= 5){
  416. doc.blocked = true;
  417. db.update({ userid: (msg.from).id}, doc, {}, function(err, numReplaced){
  418. });
  419. callback(true, 5);
  420. }else{
  421. doc.cmdcount++;
  422. db.update({ userid: (msg.from).id}, doc, {}, function(err, numReplaced){
  423. });
  424. callback(false, doc.cmdcount++);
  425. }
  426. }
  427. }
  428. });
  429. }
  430. function logFunc(msg){
  431. if(msg.type == 'location'){
  432. console.log(`[${msg.date.toLocaleString('en-GB')}]: Got a location, returning forecast (From: ${(msg.from).name})`);
  433. return;
  434. }
  435. console.log(`[${msg.date.toLocaleString('en-GB')}]: ${msg.text} (From: ${(msg.from).name} ${(msg.from).id})`);
  436. }
  437. process.on('uncaughtException', function(err) {
  438. // handle the error safely
  439. console.log(err)
  440. })